4.2.1 网络请求之HTTP

Posted qkeyar

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了4.2.1 网络请求之HTTP相关的知识,希望对你有一定的参考价值。

HTTP请求&响应:(常用的只有Post与Get,还有Head/put/delete/connect/options/trace)

Get&Post建议用post规范参数传递方式,并没有什么更优秀,只是大家都这样社会更和谐。

网络请求中我们常用键值对来传输参数(少部分API用json来传递,毕竟不是主流)。

通过上面的介绍,可以看出虽然Post与Get本意一个是表单提交一个是请求页面,但本质并没有什么区别。

  • Get方式:在url中填写参数:  http://xxxx.xx.com/xx.php?params1=value1&params2=value2
  • Post方式:参数是经过编码放在请求体中的。编码包括x-www-form-urlencoded 与 form-data。

因为url是存在于请求行中的,所以Get与Post区别本质就是参数是放在请求行中还是放在请求体

当然无论用哪种都能放在请求头中。一般在请求头中放一些发送端的常量。

表单提交中get post方式的区别有4点:

1) get是从服务器上获取数据,post是向服务器传送数据。

2) get是把参数数据队列加到提交表单的 ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTPpost机制,将表单内各个字段与其内容放置在html HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。所以,get安全性非常低,post安全性较高。

3) get,服务器端用 Request.QueryString获取变量的值;post,服务器端用Request.Form获取提交的数据。

4) get 传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。

常见问题:

1. Get是明文,Post隐藏:  错,不用https全都是明文。

2. Get传递数据上限XXX有限制的是url长度,不是Http。Http服务器部分有限制的设置一下即可。

3. Get中文需要编码:      是真的...要注意:URLEncoder.encode(params, "gbk");

  请求是键值对,但返回数据我们常用Json。对于内存中的结构数据,肯定要用数据描述语言将对象序列化成文本,再用Http传递,接收端并从文本还原成结构数据。对象(服务器)<-->文本(Http传输)<-->对象(移动端) 。

  服务器返回的数据大部分都是复杂的结构数据,所以Json最适合。另:要求传输性能的话用FlatBuffers。

HttpURLConnection( HttpClient 被废弃了)

1. 入门级

public class NetUtils {
        public static String post(String url, String content) {
            HttpURLConnection conn = null;
            try {
                URL mURL = new URL(url);                       // 创建一个URL对象
                conn = (HttpURLConnection) mURL.openConnection(); .// 获取HttpURLConnection对象
                conn.setRequestMethod("POST");                   // 设置请求方法为post
                conn.setReadTimeout(5000);                       // 设置读取超时为5秒
                conn.setConnectTimeout(10000);                   .// 设置连接网络超时为10秒
                conn.setDoOutput(true);                          .// 设置此方法,允许向服务器输出内容
                String data = content;                            .//  post请求的参数
                OutputStream out = conn.getOutputStream();        // 获得一个输出流,向服务器写数据
                out.write(data.getBytes());                         // GET方式不需要
                out.flush();  out.close();
                int responseCode = conn.getResponseCode();        // 调用此方法就不必再使用.connect()方法
                if (responseCode == 200) {
                    InputStream is = conn.getInputStream();
                    String response = getStringFromInputStream(is);
                    return response;
                } else 
                    throw new NetworkErrorException("response status is "+responseCode);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null)  conn.disconnect();               // 关闭连接
            }
            return null;
        }
        public static String get(String url) {
            HttpURLConnection conn = null;
            try {
                URL mURL = new URL(url); // 利用string url构建URL对象
                conn = (HttpURLConnection) mURL.openConnection();
                conn.setRequestMethod("GET");
                conn.setReadTimeout(5000);
                conn.setConnectTimeout(10000);
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    InputStream is = conn.getInputStream();
                    String response = getStringFromInputStream(is);
                    return response;
                } else 
                    throw new NetworkErrorException("response status is "+responseCode);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null)  conn.disconnect();
            }
            return null;
        }
    // 模板代码 必须熟练
        private static String getStringFromInputStream(InputStream is) throws IOException {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = is.read(buffer)) != -1) 
                os.write(buffer, 0, len);
            is.close();
            String state = os.toString();  // 把流中的数据转换成字符串,采用的编码是utf-8(模拟器默认编码)
            os.close();
            return state;
        }
    }

  注意网络权限:  <uses-permission android:name="android.permission.INTERNET"/>

2. 初级

同步&异步

这2个概念仅存在于多线程编程中。

android中默认只有一个主线程,也叫UI线程。因为View绘制只能在这个线程内进行。所以如果你阻塞了(某些操作使这个线程在此处运行了N秒)这个线程,这期间View绘制将不能进行,UI就会卡。所以要极力避免在UI线程进行耗时操作。网络请求是一个典型耗时操作

通过上面的Utils类进行网络请求只有一行代码:NetUtils.get("http://www.baidu.com");    //这行代码将执行几百毫秒。

如果你这样写:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String response = Utils.get("http://www.baidu.com");
    }

就会死!!!

这就是同步方式。直接耗时操作阻塞线程直到数据接收完毕然后返回。Android不允许的。

异步方式:(在子线程进行耗时操作,完成后通过Handler将更新UI的操作发送到主线程执行。)

//在主线程new的Handler,就会在主线程进行后续处理。
    private Handler handler = new Handler();
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.text);
        new Thread(new Runnable() {
            @Override
            public void run() {
                final String response = NetUtils.get("http://www.baidu.com");  //从网络获取数据
                handler.post(new Runnable() {                          //向Handler发送处理操作
                    @Override
                    public void run() {
                        textView.setText(response);   //在UI线程更新UI
                    }
                });
            }
        }).start();
    }

但这样写好难看。异步通常伴随者他的好基友回调

这是通过回调封装的AsynNetUtils类

public class AsynNetUtils {
        public interface Callback{
            void onResponse(String response);
        }
        public static void get(final String url, final Callback callback){
            final Handler handler = new Handler();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    final String response = NetUtils.get(url);
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.onResponse(response);
                        }
                    });
                }
            }).start();
        }
        public static void post(final String url, final String content, final Callback callback){
            final Handler handler = new Handler();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    final String response = NetUtils.post(url,content);
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.onResponse(response);
                        }
                    });
                }
            }).start();
        }
    }

然后使用方法:

private TextView textView;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.webview);
        AsynNetUtils.get("http://www.baidu.com", new AsynNetUtils.Callback() {
            @Override
            public void onResponse(String response) {
                textView.setText(response);
            }
        });

但是,愚蠢的地方有很多:

  1. 每次都new Thread,new Handler消耗过大
  2. 没有异常处理机制
  3. 没有缓存机制
  4. 没有完善的API(请求头,参数,编码,拦截器等)与调试模式
  5. 没有Https

3. 高级 OKHttp

加入HTTP缓存机制

缓存对于移动端是非常重要的存在。

  • 减少请求次数,减小服务器压力.
  • 本地数据读取速度更快,让页面不会空白几百毫秒。
  • 在无网络的情况下提供数据。
  1. 1.      高级 OKHttp

加入HTTP缓存机制

缓存对于移动端是非常重要的存在。

  • 减少请求次数,减小服务器压力.
  • 本地数据读取速度更快,让页面不会空白几百毫秒。
  • 在无网络的情况下提供数据。

缓存一般由服务器控制(通过某些方式可以本地控制缓存,比如向过滤器添加缓存控制信息)。通过在请求头添加几个字段,正式使用时按需求也许只包含其中部分字段

 客户端要根据这些信息储存这次请求信息。然后在客户端发起请求的时候要检查缓存。遵循下面步骤:

注意 服务器返回304意思是数据没有变动滚去读缓存信息。

   现在Android网络方面的第三方库很多,volley,Retrofit,OKHttp等,各有各自的特点。不过再怎么封装Volley在功能拓展性上始终无法与OkHttp相比。Volley停止了更新,而OkHttp得到了官方的认可,并在不断优化。

OkHttp是一个高效的HTTP:

  • 支持 SPDY ,共享同一个 Socket 来处理同一个服务器的所有请求
  • 如果 SPDY 不可用,则通过连接池来减少请求延时
  • 无缝的支持GZIP来减少数据流量
  • 缓存响应数据来减少重复的网络请求

  SPDY(读作“SPeeDY”)是Google开发的基于TCP的应用层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。谷歌表示,引入SPDY协议后,在实验室测试中页面加载速度比原先快64%。

  OKHttp(com.squareup.okhttp)是Android版Http客户端,非常高效,会自动处理常见的网络问题,像二次连接、SSL的握手问题。如果你的应用程序中集成了OKHttp,Retrofit默认会使用OKHttp处理其他网络层请求。OkHttp支持Android 2.3及其以上版本。对于Java, JDK1.7以上。Android4.4开始HttpURLConnection的底层实现采用的是okHttp

  在OKHttp,每次网络请求就是一个Request,我们在Request里填写我们需要的url,header等其他参数,再通过Request构造出Call,Call内部去请求参数,得到回复,并将结果告诉调用者。

详细使用步骤如下:

  • 同步请求 excute()

HTTP GET

OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder() .url(url) .build();
Response response = client.newCall(request).execute();    
if (response.isSuccessful())      
  return response.body().string();
else       
  throw new IOException("Unexpected code " + response);
}  // Request是OkHttp中访问的请求,Builder是辅助类。Response即OkHttp中的响应。

HTTP POST

1) POST提交Json数据

public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
     RequestBody body = RequestBody.create(JSON, json);
     Request request = new Request.Builder() .url(url) .post(body) .build();   // 对比GET,放入post数据
     Response response = client.newCall(request).execute();
     if (response.isSuccessful()) 
        return response.body().string();
    else 
        throw new IOException("Unexpected code " + response);
}  //使用Request的post方法来提交请求体RequestBody

2) POST提交键值对

OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
     RequestBody formBody = new FormEncodingBuilder()
                                  .add("platform", "android")
                                  .add("name", "bug")
                                  .build();
    Request request = new Request.Builder() .url(url) .post(formBody) .build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else 
        throw new IOException("Unexpected code " + response);
}

  OkHttp官方文档并不建议我们创建多个OkHttpClient。如果有需要,可以使用clone方法,再进行自定义。

  • 异步请求 enqueue()

  我们通过Request.Builder传入url,然后直接execute执行得到Response,通过Response可以得到code,message等信息。这是通过同步的方式去操作网络请求,而android是不允许在UI线程做网络请求操作的,因此我们需要自己开启一个线程。当然,OKHttp也支持异步线程并且有回调返回,有了上面同步的基础,异步只要稍加改动即可

private void enqueue(){
        Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt") .build();
        client.newCall(request).enqueue(new Callback() {   //就是在同步的基础上讲execute改成enqueue
            public void onFailure(Request request, IOException e) {       }
            public void onResponse(Response response) throws IOException {
                //NOT UI Thread
                if(response.isSuccessful()){
                    System.out.println(response.code());
                    System.out.println(response.body().string());
                }
            }
        });
}// execute改成enqueue,接口回调的代码是在非UI线程的,有更新UI的操作要用Handler或者其他方式。

响应缓存

  为了缓存响应,你需要一个你可以读写的缓存目录,和缓存大小的限制。这个缓存目录应该是私有的,不信任的程序应不能读取缓存内容。

  一个缓存目录同时拥有多个缓存访问是错误的。大多数程序只需要调用一次new OkHttp(),在第一次调用时配置好缓存,然后其他地方只需要调用这个实例就可以了。否则两个缓存示例互相干扰,破坏响应缓存,而且有可能会导致程序崩溃。

private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024;   // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);
    client = new OkHttpClient();
    client.setCache(cache);
}
public void run() throws Exception {
    Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();
    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    String response1Body = response1.body().string();
    System.out.println("Response 1 response:          " + response1);
    System.out.println("Response 1 cache response:    " + response1.cacheResponse());
    System.out.println("Response 1 network response:  " + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    String response2Body = response2.body().string();
    System.out.println("Response 2 response:         " + response2);
    System.out.println("Response 2 cache response:    " + response2.cacheResponse());
    System.out.println("Response 2 network response:  " + response2.networkResponse());
    System.out.println("Response 2 equals Response 1 ? " + response1Body.equals(response2Body));
}

  response1 的结果在networkresponse,代表是从网络请求加载过来的;response2的networkresponse 就为null,而cacheresponse有数据。因为设置了缓存因此第二次请求时发现缓存里有就不再去走网络请求了。

  但有时候,即使在有缓存的情况下我们依然需要去后台请求最新的资源(比如资源更新了)这个时候可以使用强制走网络来要求必须请求网络数据

public void execute() throws Exception {
    Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt").build();
        Response response1 = client.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
        String response1Body = response1.body().string();
        System.out.println("Response 1 response:          "  +  response1);
        System.out.println("Response 1 cache response:     "  +  response1.cacheResponse());
        System.out.println("Response 1 network response:   "  +  response1.networkResponse());
        
  request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();
        Response response2 = client.newCall(request).execute();
        if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
        String response2Body = response2.body().string();
        System.out.println("Response 2 response:          "  +  response2);
        System.out.println("Response 2 cache response:     "  +  response2.cacheResponse());
        System.out.println("Response 2 network response:   "  +  response2.networkResponse());
        System.out.println("Response 2 equals Response 1?  "  +  response1Body.equals(response2Body));
}
// response2的cache response为null,network response依然有数据。

  同样的我们可以使用 FORCE_CACHE 强制只要使用缓存的数据,但如果请求必须从网络获取才有数据,但又使用了FORCE_CACHE 策略就会返回504错误

HTTPHTTPS

  1. HTTP是一个属于应用层的面向对象的协议,使用80端口。HTTP协议的主要特点可概括如下:

  • 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得服务器的程序规模小,因而通信速度很快。
  • 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
  • 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
  • 无状态:HTTP协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

  2. HTTPS(基于SSL的HTTP协议)使用了HTTP协议,但使用不同于HTTP协议的默认端口,使用443端口,以及一个加密、身份验证层(HTTP与TCP之间),即HTTP下加入SSL层

使用HTTPS方式与Web服务器通信时有以下几个步骤,如图所示:

  1. 客户使用https的URL访问Web服务器,要求与Web服务器建立SSL连接。
  2. Web服务器收到客户端请求后,会将网站的证书信息(证书中包含公钥)传送一份给客户端。
  3. 客户端的浏览器与Web服务器开始协商SSL连接的安全等级,也就是信息加密的等级。
  4. 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站。
  5. Web服务器利用自己的私钥解密出会话密钥。
  6. Web服务器利用会话密钥加密与客户端之间的通信。

 

SSL介绍:

  安全套接字(Secure Socket Layer,SSL)协议是Web浏览器与Web服务器之间安全交换信息的协议,提供两个基本的安全服务:鉴别与保密。

  SSL介于应用层和TCP层之间。应用层数据不再直接传递给传输层,而是传递给SSL层,SSL层对从应用层收到的数据进行加密,并增加自己的SSL头。

  SSL协议的三个特性

  ① 保密:在握手协议中定义了会话密钥后,所有的消息都被加密。

  ② 鉴别:可选的客户端认证,和强制的服务器端认证。

  ③ 完整性:传送的消息包括消息完整性检查(使用MAC)  

 

以上是关于4.2.1 网络请求之HTTP的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

计算机网络HTTP协议详解

简单的 Javascript http 请求片段但不起作用

OKHttp源码解析之网络请求