1. 程式人生 > >HttpClient原始碼解析系列:第二篇:極簡版實現

HttpClient原始碼解析系列:第二篇:極簡版實現

極簡版的實現,核心架構的原初模型     從MinimalHttpClient從名字可以看出,是一個極簡可以用的版本,是核心設計的原初模型。所以我們就從最精簡的開始分析。          核心元素只有三個,一個引數 params,一個執行器 requestExecutor,一個連線管理器 connManager。
HttpClientConnectionManager(@since 4.3):  連線池元件,管理連線的整個生命週期。連線在連線池中建立、複用以及移除。Connection manager封裝了對連線池的具體操作,比如向連線池租用和歸還連線。 Connection被創建出來後處於閒置狀態,由連線池管理,呼叫時會校驗是否是open狀態,不是的話會進行connect。connect的過程就是 基於不同schema(主要是http和https)建立不同的socket連線(ssl和plain)並且將http請求(連線)繫結到socket。同時連線也會因為心跳或者過期等原因被close變成stale狀態,直至被下一次get到時或者連線滿時被清理出去。 同時連線池還能對連線進行限流–全域性和單route連線數。 ClientExecChain:
代表一次完整的呼叫執行過程,它是一個包裝類,類似於java io類,每個包裝類完成一個特定的功能,多層巢狀完成一個系統性的功能,比如處理協議範疇的例如cookie、報文解析等,又比如處理自動重試的,等等。
再來看核心的執行方法
    @Override     protected CloseableHttpResponse doExecute(             final HttpHost target,             final HttpRequest request,             final HttpContext context) throws IOException, ClientProtocolException {         Args.notNull(target, "Target host");         Args.notNull(request, "HTTP request");         HttpExecutionAware execAware = null;         if (request instanceof HttpExecutionAware) {             execAware = (HttpExecutionAware) request;         }         try {             final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request);             final HttpClientContext
localcontext = HttpClientContext.adapt(                 context != null ? context : new BasicHttpContext());             final HttpRoute route = new HttpRoute(target);             RequestConfig config = null;             if (request instanceof Configurable) {                 config = ((Configurable) request).getConfig();             }             if (config != null) {                 localcontext.setRequestConfig(config);             }             return this.requestExecutor.execute(route, wrapper, localcontext, execAware);
        } catch (final HttpException httpException) {             throw new ClientProtocolException(httpException);         }     }
HttpExecutionAware:一個類可能發生blocking I/O可以繼承這個介面,這樣當blocking I/O被取消的時候,繼承這個介面的類會收到通知。 HttpRequestWrapper:HttpRequest的包裝類,可以保證在修改Request的時候原始請求物件不會被改變。 HttpRoute:指示對方伺服器地址。HttpRoutePlanner用來建立HttpRoute。後者代表客戶端request的對端伺服器,主要包含rout的host以及proxy資訊。 RequestConfig:一些與HTTP請求相關的基礎設定。
最終,落實到 requestExecutor 去執行。 public MinimalHttpClient(             final HttpClientConnectionManager connManager) {         super();         this.connManager = Args.notNull(connManager, "HTTP connection manager");         this.requestExecutor = newMinimalClientExec( new HttpRequestExecutor(),                 connManager,                 DefaultConnectionReuseStrategy.INSTANCE,                 DefaultConnectionKeepAliveStrategy.INSTANCE);         this.params = new BasicHttpParams();     }
    MinimalClientExec:是ClientExecChain的一個實現,只是封裝了最基本的HTTP過程,提供最直接的客戶端伺服器互動,不支援代理,不支援在各種情況下的重試(重定向,許可權校驗,IO異常等)。     所以,我們在往下一層,看 MinimalClientExec 是如何執行的,由於程式碼很長,略過非核心的程式碼用省略代替。
    private final HttpRequestExecutor requestExecutor;     private final HttpClientConnectionManager connManager;     private final ConnectionReuseStrategy reuseStrategy;     private final ConnectionKeepAliveStrategy keepAliveStrategy;     private final HttpProcessor httpProcessor;     public MinimalClientExec(             final HttpRequestExecutor requestExecutor,             final HttpClientConnectionManager connManager,             final ConnectionReuseStrategy reuseStrategy,             final ConnectionKeepAliveStrategy keepAliveStrategy) {         Args.notNull(requestExecutor, "HTTP request executor");         Args.notNull(connManager, "Client connection manager");         Args.notNull(reuseStrategy, "Connection reuse strategy");         Args.notNull(keepAliveStrategy, "Connection keep alive strategy");         this.httpProcessor = new ImmutableHttpProcessor(                 new RequestContent(),                 new RequestTargetHost(),                 new RequestClientConnControl(),                 new RequestUserAgent(VersionInfo.getUserAgent(                         "Apache-HttpClient", "org.apache.http.client", getClass())));         this.requestExecutor    = requestExecutor;         this.connManager        = connManager;         this.reuseStrategy      = reuseStrategy;         this.keepAliveStrategy  = keepAliveStrategy;     }
    public CloseableHttpResponse execute(){ 。。。。         final ConnectionRequest connRequest = connManager.requestConnection(route, null); 。。。。         final HttpClientConnection managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS); 。。。。             context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);             context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);             context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);             context.setAttribute(HttpClientContext.HTTP_ROUTE, route); 。。。。             httpProcessor.process(request, context);             final HttpResponse response = requestExecutor.execute(request, managedConn, context);             httpProcessor.process(response, context); 。。。。             final HttpEntity entity = response.getEntity(); 。。。。 }
ConnectionRequest:是由ConnectionManager來管理的Request。 HttpClientConnection:Http連線,用於做請求傳送和響應收取。 HttpContext:儲存KV型的資料。主要用於HTTP請求過程中,多個邏輯過程之間的資料共享。 ImmutableHttpProcessor:是HttpProcessor的一個實現,從這個介面的名字就可以看出來,為的是處理Http協議。其中包含了多個協議攔截器,分開處理HTTP協議中的不同部分,這是責任鏈模式的一個典範實現。單純到ImmutableHttpProcessor,就是一系列HttpRequestInterceptor 和 HttpResponseInterceptor。然後按照次序呼叫這些攔截器,處理Request或者Response這兩個過程。
    除了上面說說的,外圍的管理和處理,最核心的就在於Http請求是如何發出的,如何拿到最原始的資料返回。這就是 HttpRequestExecutor 做的事情。其核心流程是基於 blocking (classic) I/O 模型的。同樣,我們略去非核心的部分,只留下核心程式碼。
public HttpResponse execute(){ 。。。             HttpResponse response = doSendRequest(request, conn, context);             if (response == null) {                 response = doReceiveResponse(request, conn, context);             } 。。。 }
protected HttpResponse doSendRequest(             final HttpRequest request,             final HttpClientConnection conn,             final HttpContext context) throws IOException, HttpException { 。。。。         conn.sendRequestHeader(request);         if (request instanceof HttpEntityEnclosingRequest) {             // Check for expect-continue handshake. We have to flush the             // headers and wait for an 100-continue response to handle it.             // If we get a different response, we must not send the entity.             boolean sendentity = true;             final ProtocolVersion ver =                 request.getRequestLine().getProtocolVersion();             if (((HttpEntityEnclosingRequest) request).expectContinue() &&                 !ver.lessEquals(HttpVersion.HTTP_1_0)) {                 conn.flush();                 // As suggested by RFC 2616 section 8.2.3, we don't wait for a                 // 100-continue response forever. On timeout, send the entity.                 if (conn.isResponseAvailable(this.waitForContinue)) {                     response = conn.receiveResponseHeader();                     if (canResponseHaveBody(request, response)) {                         conn.receiveResponseEntity(response);                     }                     final int status = response.getStatusLine().getStatusCode();                     if (status < 200) {                         if (status != HttpStatus.SC_CONTINUE) {                             throw new ProtocolException(                                     "Unexpected response: " + response.getStatusLine());                         }                         // discard 100-continue                         response = null;                     } else {                         sendentity = false;                     }                 }             }             if (sendentity) {                 conn.sendRequestEntity((HttpEntityEnclosingRequest) request);             }         }         conn.flush();         context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);         return response;     }
protected HttpResponse doReceiveResponse(             final HttpRequest request,             final HttpClientConnection conn,             final HttpContext context) throws HttpException, IOException { 。。。。。         while (response == null || statusCode < HttpStatus.SC_OK) {             response = conn.receiveResponseHeader();             if (canResponseHaveBody(request, response)) {                 conn.receiveResponseEntity(response);             }             statusCode = response.getStatusLine().getStatusCode();         } // while intermediate response         return response;     }
通過上面的程式碼,可以發現,最終發出請求和收取請求內容,是 HttpClientConnection 來完成的。Executor只是封裝了這個過程,並完成了外圍處理。至於 HttpClientConnection 的功能設計和底層Socket實現,之後再討論。     到這裡,其實Http請求的發出收取處理管理等工作的最基礎版本(Minimal)就已經非常清楚了。最後理一下MinimalHttpClient最相關的核心類:     可以看到,一個簡單的 傳送-收取 過程被分成了眾多的類來分別處理。看上去很多,實際上也就三個步驟:管理請求(ConnectionManager),執行請求(ReqeustExecutor),處理請求(HttpProcessor)。     而每個步驟都有不少相關的東西,但是主線邏輯是非常清晰的。這裡基本把最核心的執行鏈講完了,下面是分開討論其他問題。