美文网首页
okHttp框架分析--GET和POST请求

okHttp框架分析--GET和POST请求

作者: AntCoding | 来源:发表于2019-05-22 00:16 被阅读0次

    概要

    我们平时使用最多的请求方式莫过于GETPOST请求,它们几乎完成我们平时所需的基本任务,下面我们分析一下它们的区别以及在OkHttp中的调用流程

    GET和POST区别

      1. GETPOST请求都是的HTTP协议都是基于TCP/IP的应用层协议,它们使用的是同一个传输层协议,所以在传输上相同
      1. GET请求有字符长度限制POST请求没有,浏览器通常都会限制URL长度在2K个字节,而(大多数)服务器最多处理64K大小的URL;主要原因来自于浏览器服务器,由于GET请求是明文传输,在URL中可在?后携带多个参数并以&分割,为避免URL消耗资源过多,同时也为了[性能]和[安全]考虑所以为URL长度进行了限制;而POST请求会将敏感数据封装进Request body中,所以不会造成URL过长现象
      1. GET只接受ASCII字符,而POST没有限制
      1. GET请求只能进行URL编码,而POST支持多种编码方式
      1. GETPOST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息,若想保证传输安全,可通过HTTPS进行加密
      1. GET请求产生一个TCP数据包;POST产生两个TCP数据包;对于GET方式的请求,浏览器会把HeaderData一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送Header,服务器响应100 continue,浏览器再发送Data,服务器响应200(返回数据)

    GET源码解析

    GET同步流程解析

    GET流程图.jpg

    [ 1 ]外部操作

    public class GetExample {
      //声明OkHttpClick实例​
      OkHttpClient client = new OkHttpClient();
      String run(String url) throws IOException {
         //声明Request实例,组织所需元素数据​
        Request request = new Request.Builder()
            .url(url)
            .build();
        //通过​newCall得到一个RealCall实例,同时也声明了一个 Transmitter 发射器的实例
         //​调取execute()进行执行
        try (Response response = client.newCall(request).execute()) {
          return response.body().string();
        }
      }
    

    [ 2 ] 进入RelCall.kt ,执行 @方法 execute()

    override fun execute(): Response {
      synchronized(this) {
        check(!executed) { "Already Executed" }
        executed = true
      }
    //设置超时​
      transmitter.timeoutEnter()
      //此方法是个‘钩子’,若在外部没有具体实现,则不执行任何操作​
      transmitter.callStart()
      try {
        //将当前的realCall存入runningSyncCalls链表​
        client.dispatcher().executed(this)
        //调取拦截链,实现数据访问,返回一个Response对象​ 
        return getResponseWithInterceptorChain()
      } finally {
        client.dispatcher().finished(this)
      }
    }
    

    [3] 调取拦截链的实现我们不在此处过多赘述,详情请见我的另一篇文章okHttp框架分析--拦截链

    GET异步请求

    [1]外部操作

    public final class AsynchronousGet {
     //OkHttp客户端实例​
      private final OkHttpClient client = new OkHttpClient();
      public void run() throws Exception {
        //请求实例​
        Request request = new Request.Builder()
            .url("https://raw.github.com/square/okhttp/master/README.md") //url网址配置
            .build();
       //[ 2 ] client new一个RealCall,调用enqueue​函数进入队列,并实现回调操作
        client.newCall(request).enqueue(new Callback() {
         //失败回调实现​
          @Override public void onFailure(Call call, IOException e) {
            e.printStackTrace();  //打印访问服务失败的异常信息
          }
         //成功回调实现​
          @Override public void onResponse(Call call, Response response) throws IOException {
            ...
            //todo 访问服务器成果后的具体操作​
        });
      }
    
    

    [ 2 ] client声明一个RealCall类型的对象,调用enqueue(...)​函数进入队列,并实现回调操作

    override fun enqueue(responseCallback: Callback) {
      synchronized(this) {
        check(!executed) { "Already Executed" }
        executed = true
      }
     //发射器开始调用,此处为一个空操作,可在外部进行操作重写​
      transmitter.callStart()
    //[ 3 ]client事件分发处理,获取一个Dispatcher对象,
    //继续调用enqueue函数,传入回调Callback包装成AsyncCall类型
      client.dispatcher().enqueue(AsyncCall(responseCallback))
    }
    

    [ 3 ]client事件分发处理,获取一个Dispatcher类型对象,继续调用enqueue(...)函数,传入回调Callback包装成AsyncCall类型

    internal fun enqueue(call: AsyncCall) {
      synchronized(this) {
         //将我们的callback回调添加进准备异步调用的队列​
        readyAsyncCalls.add(call)
        //修改AsyncCall,使其共享现有运行调用的AtomicInteger 相同的主机
        if (!call.get().forWebSocket) {
          val existingCall = findExistingCallWithHost(call.host())
          if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
        }
      }
     //[ 4  ] 将符合条件的调用从[readyAsyncCalls]提升到[runningAsyncCalls],并在线程池执行。
    //​必须不同步调用,因为执行调用可以调用在用户代码​
      promoteAndExecute()
    }
    

    [ 4 ] 将符合条件的调用从readyAsyncCalls链表提升到runningAsyncCalls链表,并在线程池执行。执行方法 promoteAndExecute()

    private fun promoteAndExecute(): Boolean {
     //断言 当先线程是否持有Lock锁
    ​  assert(!Thread.holdsLock(this)) 
      //声明私有的一个执行回调队列​
      val executableCalls = ArrayList<AsyncCall>()
      val isRunning: Boolean
      synchronized(this) {
        val i = readyAsyncCalls.iterator()
        while (i.hasNext()) {
          val asyncCall = i.next()
          if (runningAsyncCalls.size >= this.maxRequests) break // 运行异步调用最大容量
          if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // 主机调用最大容量
          i.remove() 
          //此处为原子操作,将主机的调用个数增加 1 ​
          asyncCall.callsPerHost().incrementAndGet()  
         //将我们的Callback添加到执行​回调队列
          executableCalls.add(asyncCall) 
           //将我们的Callback添加到异步运行回调队列​
          runningAsyncCalls.add(asyncCall)
        }
         //正在运行的异步回调和同步回调个数相加​是否大于 0
        isRunning = runningCallsCount() > 0
      }
     //循环​私有执行回调队列
      for (i in 0 until executableCalls.size) {
        val asyncCall = executableCalls[i]
         //[ 5 ]​ 尝试在“executorService”上注册此异步调用,获取一个线程池作为参数
        asyncCall.executeOn(executorService())
      }
      return isRunning
    }
    

    [ 5 ]​ 尝试在executorService上注册此异步调用,获取一个线程池作为参数,执行方法asyncCall.executeOn(executorService())

    fun executeOn(executorService: ExecutorService) {
       //判定当前线程是否持有Lock锁​
      assert(!Thread.holdsLock(client.dispatcher()))
      var success = false
      try {
        //[ 6 ] 执行线程​
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        transmitter.noMoreExchanges(ioException)
        //执行发生异常则回调 onFailure函数​
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher().finished(this) // This call is no longer running!
        }
      }
    }
    

    [ 6 ]最终还是进入方法execute(), 执行线程操作发起对服务器的请求操作

      override fun execute() {
        var signalledCallback = false
        ​//发射器判断超时设置
        transmitter.timeoutEnter()
        try {
         //​/调取拦截链,实现数据访问,返回一个Response对象​ 
          val response = getResponseWithInterceptorChain()​
          signalledCallback = true
          //访问成功则调取回调接口 onResponse​函数
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log(INFO, "Callback failure for ${toLoggableString()}", e)
          } else {
            //​访问发生异常则 回调接口 onFailure函数
            responseCallback.onFailure(this@RealCall, e)
          }
        } finally {
          //最终结束本次调用​
          client.dispatcher().finished(this)
        }
      }
    }
    

    POST源码解析

    POST同步流程解析

    POST流程图.jpg

    [ 1 ]外部操作

     //请求体类型
    ​public static final MediaType MEDIA_TYPE_MARKDOWN
        = MediaType.get("text/x-markdown; charset=utf-8");
    //声明OkHttpClient实例​
    private final OkHttpClient client = new OkHttpClient();
    public void run() throws Exception {
     //请求体文件​
      File file = new File("README.md");
    //声明一个 请求实例​
      Request request = new Request.Builder()
          .url("https://api.github.com/markdown/raw")    //url连接
          .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))  //post请求方法,传入一个请求体参数
          .build();
      //new 一个 RealCall实例,调用 @方法​[ execute() ]函数,执行数据访问
      try (Response response = client.newCall(request).execute()) {​
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
        System.out.println(response.body().string());
      }
    }
    

    [ 2 ] 此处说明一下,POST同步请求流程与GET同步请求流程一般无二,最终会在真正发起服务的拦截器CallServerInterceptor.kt做具体的操作或者在自定拦截器中对不同的请求方法做特殊处理;进入 CallServerInterceptor.kt 类,执行方法 intercept(chain: Interceptor.Chain)

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
      val realChain = chain as RealInterceptorChain
      val exchange = realChain.exchange()
      //获取请求实例​
      val request = realChain.request()    
     //获取请求体​
      val requestBody = request.body()
      ...
    ​  //@重点 做判定,若当前请求方法不是 GET或 HEAD 并且 请求体不为 Null 则判定成立
      if (HttpMethod.permitsRequestBody(request.method()) && requestBody != null) {
        //如果请求上有一个“Expect: 100-continue”报头,则等待一个“HTTP/1.1 100”报头 
      //在发送请求体之前继续“响应”。如果我们没有收到,返回
    ​  //在没有传输请求体的情况下,我们确实得到了(例如4xx响应)
    ​
      //在[预用知识说明]标签  ==>  { “Expect: 100-continue”的来龙去脉 }  中做详细说明​
        if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
          exchange.flushRequest()
          responseHeadersStarted = true
          //开始调用响应头​
          exchange.responseHeadersStart()
           // 读取响应头​
          responseBuilder = exchange.readResponseHeaders(true)
        }
       ​
        if (responseBuilder == null) {
          if (requestBody.isDuplex()) {
            // 准备一个双重主体,以便应用程序稍后可以发送请求主体。
            exchange.flushRequest()
            //创建一个buffer​
            val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
             //请求体写入buff中​
            requestBody.writeTo(bufferedRequestBody)
          } else {
            // 如果满足“Expect: 100-continue”期望,则编写请求主体
            val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
            requestBody.writeTo(bufferedRequestBody)
            bufferedRequestBody.close()
          }
        } else {
          exchange.noRequestBody()
          if (!exchange.connection().isMultiplexed) {
            //如果没有满足“Expect: 100-continue”期望,请阻止HTTP/1连接
          ​ //避免重复使用。否则,我们仍然有义务将请求体传输到 
           //让连接保持一致的状态。
            exchange.noNewExchangesOnConnection()
          }
        }
      } else {
         //没有请求体​
        exchange.noRequestBody()
      }
    ​​
    ​
      if (requestBody == null || !requestBody.isDuplex()) {
        //将所有缓冲数据写入底层接收器(如果存在的话)。然后这个sink是递归不断刷新
       //​将数据尽可能地推送到最终目的地。通常, 目标是一个网络套接字或文件。​
        exchange.finishRequest()
      }
      if (!responseHeadersStarted) {
        //仅在接收响应头之前调用。 
      //​ 连接是隐式的,通常与最后一个[connectionAcquired]事件相关。 
      //​这可以被调用超过1次为一个[Call]。​
        exchange.responseHeadersStart()
      }
      if (responseBuilder == null) {
        //从HTTP传输解析响应头的字节。​
        responseBuilder = exchange.readResponseHeaders(false)!!
      }
      //一个HTTP响应。
     //​该类的实例不是不可变的:响应体是一次性的
     //​只使用一次,然后关闭的值。所有其他属性都是不可变的。​
      var response = responseBuilder
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
      var code = response.code()  //获取响应 Code
      if (code == 100) {
        // 服务器发送了100-continue,即使我们没有请求。
       ​ //再读一遍实际的响应
        response = exchange.readResponseHeaders(false)!!
            .request(request)
            .handshake(exchange.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build()
        code = response.code()
      }
       //在接收响应标头后立即调用​
      exchange.responseHeadersEnd(response)
      response = if (forWebSocket && code == 101) {
        //连接叠积,但我们需要确保拦截器看到非空响应体。
        response.newBuilder()
            .body(Util.EMPTY_RESPONSE)
            .build()
      } else {
        response.newBuilder()
            .body(exchange.openResponseBody(response))
            .build()
      }
     //关闭连接​
      if ("close".equals(response.request().header("Connection"), ignoreCase = true) ||
          "close".equals(response.header("Connection"), ignoreCase = true)) {
        exchange.noNewExchangesOnConnection()
      }
     //抛出连接异常​
      if ((code == 204 || code == 205) && response.body()?.contentLength() ?: -1 > 0) {
        throw ProtocolException(
            "HTTP $code had non-zero Content-Length: ${response.body()?.contentLength()}")
      }
      //返回响应数据​
      return response
    }
    

    POST 异步请求

    与GET异步操作一般无二,同样是在真正发起服务的拦截器CallServerInterceptor.kt做具体的操作,这里不在赘述

    预用知识说明

    • 1)“Expect: 100-continue”的来龙去脉:
      HTTP/1.1 协议 里设计 100 (Continue) HTTP 状态码的的目的是,在客户端发送 Request Message 之前,HTTP/1.1 协议允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于 Request Headers)即, ClientServerPost (较大)数据之前,允许双方“握手”,如果匹配上了,Client 才开始发送(较大)数据。这么做的原因是,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。

    协议对 HTTP/1.1 clients 的要求是:
    如果 client预期等待100-continue的应答,那么它发的请求必须包含一个 Expect: 100-continue的头域!

    2)libcurl 发送大于1024字节 数据时启用Expect:100-continue特性:

    这也就是 Laruence在 2011 年撰文所写的:
    在使用 curlPOST 的时候,当要 POST 的数据大于 1024 字节的时候,curl 并不会直接就发起 POST 请求,而是会分为两步:

      1. 发送一个请求,包含一个 Expect: 100-continue 头域,询问 Server 是否愿意接收数据;
      1. 接收到 Server 返回的 100-continue 应答以后,才把数据 POSTServer;这是 libcurl 的行为。
        This ALL! Thanks EveryBody!

    相关文章

      网友评论

          本文标题:okHttp框架分析--GET和POST请求

          本文链接:https://www.haomeiwen.com/subject/jooszqtx.html