HttpClient 源碼解讀

前面寫了兩篇HttpClient和HtmlUnit和文章,然後就很久沒有更新了,真的是有事,現在閒下來,把N久沒動的博客也更新一下吧,因爲上次的HttpClient方面講的比較少嘛,這篇文章也正好補一下。


寫博客之前也看了下網上其他人寫的文章,也有很多解讀HttpClient源碼的,但是都是版本HttpClient4.1之前的,這裏我先把我看到的一篇比較好的解讀HttpClient源碼的文章和大家一起分享一下:http://www.educity.cn/wenda/147389.html。


今天主要是講解HttpClient4.3版本的源碼,4.3改動還是有一點的,主要是對於“鏈”的概念更加濃重了。


一、HttpClient 執行步驟


先來看一下,HttpClient4.0大致的時序圖。


(看的清楚麼,我畫的時候已經很注意字體大小以及截圖的時候的清晰度了,如果實在看不清只能自己純看文字了。)

你也可以參考下我上面分享的那篇文章裏面的時序圖,對比下HttpClient4.3版本做了哪些改變。


已請求一個正常的POST流爲例,我們來看下HttpClient是如何執行的:


1、首先你需要建立一個HttpClient,設置這個HttpClient的一些基本屬性,這其中包括HTTPS處理策略,連接超時時間,Cookie策略,連接池等等,這些都可以通過搜索HttpClient4.0+ 的教程學習。


2、執行HttpClient的execute方法,這個時候你會發現實際走向是 HttpClient ->CloseableHttpClient ->InternalHttpClient ,然後在doExecute()方法中進行了一些基本判斷,初始化值之後,就進入了ClientExecChain(MainClientExec)的execute()方法。


3、然後我們可以簡單看下MainClientExec的execute方法。

// @CaiBo
	public CloseableHttpResponse execute(final HttpRoute route, final HttpRequestWrapper request, final HttpClientContext context,
			final HttpExecutionAware execAware) throws IOException, HttpException {
		// 判斷是否爲空(Args這個工具類將經常出現)
		Args.notNull(route, "HTTP route");
		Args.notNull(request, "HTTP request");
		Args.notNull(context, "HTTP context");

		// 看到Auth相關的直接跳過,這和認證(HTTPS等)相關,我們暫時不理會,直接往下看
		AuthState targetAuthState = context.getTargetAuthState();
		if (targetAuthState == null) {
			targetAuthState = new AuthState();
			context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, targetAuthState);
		}
		AuthState proxyAuthState = context.getProxyAuthState();
		if (proxyAuthState == null) {
			proxyAuthState = new AuthState();
			context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, proxyAuthState);
		}

		// 如果是環式實體,則將其請求進行加強(返回代理)
		// 按我說,意思就是如果這個HTTP請求實體是有entity的,那麼這個entity寫完之後,執行關閉,這些是通過代理模式實現的。
		if (request instanceof HttpEntityEnclosingRequest) {
			Proxies.enhanceEntity((HttpEntityEnclosingRequest) request);
		}

		/*
		 * 這是HttpClient中一個標識,它可以用來保證某些連接一段時間內只爲某個用戶所使用,默認爲null
		 * 連接池的連接都有state,其實就是這裏的userToken(準確來說不全是),所有的連接的state默認都是null
		 * 舉個例子,當向連接池請求userToken爲“zstu”的連接時,它會返回一個連接,並將連接狀態標記爲zstu,
		 * 這樣同一個線程下次再來請求連接的時候,就會優先拿到這個連接
		 */
		Object userToken = context.getUserToken();

		// 向連接池請求一個連接,這裏用到了JUC編程的相關知識,返回的是一個future
		final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);

		/*
		 * execAware是什麼呢,其實就是這次HTTP請求的本體,HttpRequest。
		 * 那你一定好奇,HttpRequestWrapper是什麼呢?看名字像是HttpRequest的包裝,爲什麼要搞兩個呢?
		 * 是這樣:HttpRequestWrapper是HttpRequest的一份拷貝,execAware則是真正的請求。
		 * 這是由於HttpRequestWrapper在後面的操作中都會發生一些變化, 而HttpClient又不想這些變化被使用者知道或者捕獲,所以拷貝了一份
		 * 然而使用者可以隨時取消網絡執行或者釋放連接等等,所以又不得不用到原生的對象,以便響應使用者的請求。
		 */
		if (execAware != null) {
			if (execAware.isAborted()) {
				connRequest.cancel();
				throw new RequestAbortedException("Request aborted");
			} else {
				execAware.setCancellable(connRequest);
			}
		}

		// 就是你前面建立HttpClient時可以設置的屬性之一,有長連接時間,超時時間等可以設置。
		final RequestConfig config = context.getRequestConfig();

		// 如果你使用的是同步線程池(PoolingHttpClientConnectionManager)的話,這裏真正跑的是LoggingManagedHttpClientConnection
		// 當然說的是大部分情況下是這個。
		final HttpClientConnection managedConn;
		try {
			final int timeout = config.getConnectionRequestTimeout();
			// 這裏通過future特性來獲取值
			managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
		} catch (final InterruptedException interrupted) {
			Thread.currentThread().interrupt();
			throw new RequestAbortedException("Request aborted", interrupted);
		} catch (final ExecutionException ex) {
			Throwable cause = ex.getCause();
			if (cause == null) {
				cause = ex;
			}
			throw new RequestAbortedException("Request execution failed", cause);
		}
		// 這個地方將這個連接設置到上下文中,暴露連接
		// 這樣的話,使用者可以方便的在調用處獲得本次執行所使用的連接
		// 只需要自己創建一個context,然後讓HttpClient使用你的上下文即可
		// 在執行過程中,HttpClient會將諸多和本次執行相關的引用都設置到你的上下文中,包括執行用的連接
		context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);

		if (config.isStaleConnectionCheckEnabled()) {
			// validate connection
			if (managedConn.isOpen()) {
				this.log.debug("Stale connection check");
				// 這個函數比較重要,如果你想看的話,它的實現在BHttpConnectionBase類中
				// 它的作用是檢查連接是否有效(但是它也不能完全的保證連接有效),可以查看這裏,我就不復制了。
				// http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/connmgmt.html#d5e652
				if (managedConn.isStale()) {
					this.log.debug("Stale connection detected");
					managedConn.close();
				}
			}
		}
		// 新建一個連接持有類,主要就是操作連接的N多代碼都是重複的,而且操作連接比較頻繁
		// 所以將對連接的操作都封裝到這個類中
		final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
		try {
			if (execAware != null) {
				// 大家平時用完HttpClient.execute(post)之後,一般都會調用類似post.releaseConnection()方法吧
				// 這個方法之所以有效,就是在這裏設置了connHolder,你調用的最終方法其實是ConnectionHolder提供的。
				execAware.setCancellable(connHolder);
			}

			HttpResponse response;
			// 真的開始執行了,可以看到,這裏是個循環,有可能會執行N次
			for (int execCount = 1;; execCount++) {

				// 如果發現已經執行過了並且這個請求是不可重複的,那麼就報錯了。
				// 那麼什麼叫請求不可重複呢?這主要是和流有關,大家知道InputStream有很多種,有些流可以反覆讀(PushbackInputStream),但很多流只能讀一次
				// 類似的HttpEntity也存在這樣的情況,平常的StringEntity(就是純字符串)是可以隨便讀多少次的,因爲就是個字符串嘛,讀了不會消失。
				// 然後上傳文件等操作就沒這麼簡單了,如果你是使用InputStreamEntity,那麼就不能重複請求了。
				// 所有建議大家在上傳文件時使用FileEntity,如果想做到通用(所有流)則使用ByteArrayEntity。
				if (execCount > 1 && !Proxies.isRepeatable(request)) {
					throw new NonRepeatableRequestException("Cannot retry request " + "with a non-repeatable request entity.");
				}

				if (execAware != null && execAware.isAborted()) {
					throw new RequestAbortedException("Request aborted");
				}

				// 這個連接如果並沒有打開,則要建立網絡連接(sockt連接)。
				// 這裏你可能好奇,這個連接明明是連接池給我的,爲什麼還會存在沒有打開的情況呢?
				// 這個就涉及到連接池是怎麼給你連接的,我們以後會分析。
				// 這裏只要知道,連接池給的連接有可能是沒建立“連接”的連接。
				if (!managedConn.isOpen()) {
					this.log.debug("Opening connection " + route);
					try {
						establishRoute(proxyAuthState, managedConn, route, request, context);
					} catch (final TunnelRefusedException ex) {
						if (this.log.isDebugEnabled()) {
							this.log.debug(ex.getMessage());
						}
						response = ex.getResponse();
						break;
					}
				}
				final int timeout = config.getSocketTimeout();
				if (timeout >= 0) {
					managedConn.setSocketTimeout(timeout);
				}
				// 是不是總是看到這句話,所以我上面說我們可以“隨時”取消請求嘛。
				if (execAware != null && execAware.isAborted()) {
					throw new RequestAbortedException("Request aborted");
				}

				if (this.log.isDebugEnabled()) {
					this.log.debug("Executing request " + request.getRequestLine());
				}

				// 這下面兩個也是認證相關的,沒什麼用,暫時跳過。
				if (!request.containsHeader(AUTH.WWW_AUTH_RESP)) {
					if (this.log.isDebugEnabled()) {
						this.log.debug("Target auth state: " + targetAuthState.getState());
					}
					this.authenticator.generateAuthResponse(request, targetAuthState, context);
				}
				if (!request.containsHeader(AUTH.PROXY_AUTH_RESP) && !route.isTunnelled()) {
					if (this.log.isDebugEnabled()) {
						this.log.debug("Proxy auth state: " + proxyAuthState.getState());
					}
					this.authenticator.generateAuthResponse(request, proxyAuthState, context);
				}
				// 調用執行,其實這個requestExecutor沒幹太多事,模擬一個HTTP請求該有的材料都有了。
				// 這個類只不過是拿到這些材料,然後將他們組裝組裝,用起來,並返回一個結果
				// 說白了就是個加工工廠
				response = requestExecutor.execute(request, managedConn, context);

				// 執行完了,判斷這個連接是不是要保持長連接的
				// 主要是通過返回的消息頭判斷
				// 如果是需要保持長連接的,則將其標記爲可重用
				// The connection is in or can be brought to a re-usable state.
				if (reuseStrategy.keepAlive(response, context)) {
					// Set the idle duration of this connection
					final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
					if (this.log.isDebugEnabled()) {
						final String s;
						if (duration > 0) {
							s = "for " + duration + " " + TimeUnit.MILLISECONDS;
						} else {
							s = "indefinitely";
						}
						this.log.debug("Connection can be kept alive " + s);
					}
					connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
					connHolder.markReusable();
				} else {
					connHolder.markNonReusable();
				}

				// 認證相關,跳過
				if (needAuthentication(targetAuthState, proxyAuthState, route, response, context)) {
					// Make sure the response body is fully consumed, if present
					final HttpEntity entity = response.getEntity();
					if (connHolder.isReusable()) {
						EntityUtils.consume(entity);
					} else {
						managedConn.close();
						if (proxyAuthState.getState() == AuthProtocolState.SUCCESS && proxyAuthState.getAuthScheme() != null
								&& proxyAuthState.getAuthScheme().isConnectionBased()) {
							this.log.debug("Resetting proxy auth state");
							proxyAuthState.reset();
						}
						if (targetAuthState.getState() == AuthProtocolState.SUCCESS && targetAuthState.getAuthScheme() != null
								&& targetAuthState.getAuthScheme().isConnectionBased()) {
							this.log.debug("Resetting target auth state");
							targetAuthState.reset();
						}
					}
					// discard previous auth headers
					final HttpRequest original = request.getOriginal();
					if (!original.containsHeader(AUTH.WWW_AUTH_RESP)) {
						request.removeHeaders(AUTH.WWW_AUTH_RESP);
					}
					if (!original.containsHeader(AUTH.PROXY_AUTH_RESP)) {
						request.removeHeaders(AUTH.PROXY_AUTH_RESP);
					}
				} else {
					break;
				}
			}

			if (userToken == null) {
				userToken = userTokenHandler.getUserToken(context);
				context.setAttribute(HttpClientContext.USER_TOKEN, userToken);
			}
			if (userToken != null) {
				connHolder.setState(userToken);
			}

			// check for entity, release connection if possible
			final HttpEntity entity = response.getEntity();
			// 如果返回響應是流則釋放掉這個連接,其他則要abort連接
			// 這裏又使用到了代理模式,這個在網絡編程中特別常見。
			// 這裏代理的主要作用就是:如果你讀取完了響應,那麼這個響應會關閉
			if (entity == null || !entity.isStreaming()) {
				// connection not needed and (assumed to be) in re-usable state
				connHolder.releaseConnection();
				return Proxies.enhanceResponse(response, null);
			} else {
				return Proxies.enhanceResponse(response, connHolder);
			}
			// 任何異常發生,都要保證連接被關閉,不然會滯留在服務器上,可能會引起服務器端口被佔用完
		} catch (final ConnectionShutdownException ex) {
			final InterruptedIOException ioex = new InterruptedIOException("Connection has been shut down");
			ioex.initCause(ex);
			throw ioex;
		} catch (final HttpException ex) {
			connHolder.abortConnection();
			throw ex;
		} catch (final IOException ex) {
			connHolder.abortConnection();
			throw ex;
		} catch (final RuntimeException ex) {
			connHolder.abortConnection();
			throw ex;
		}
	}

*我把註釋都寫在代碼裏了,大家看起來方便一點,可能講的不是特別的詳細,後面還會一一補充,同時也鑑於第一張時序圖的效果(幾乎看不清),也就不畫圖了,都用文字描述吧。

4、到這裏基本上就完了,本來還想把請求的封裝,響應的返回,連接的處理分開說的,結果看看這個代碼裏面基本都包含了,那也就不囉嗦了。這個MainClientExec#execute是HttpClient執行HTTP請求的主要方法,以它爲核心,擴散着去看整個HttpClient,應該就能看個大概了。



---------先把文章發一下,以後再來更新。

看了這一章,你應該對HttpClient整個執行的過程有一定的瞭解,接下來具體講講經過的每一個類都幹了哪些事情,以及它們是如何做這些事情的,用了哪些技巧和特性。








版權聲明:本文爲博主原創文章,未經博主允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章