Commons HttpClient を使用してネットワーク転送量をキャプチャする

# 動機

ネットワーク転送量をキャプチャしたいときってあるよね。
そんなとき、Commons HttpClient なら可能です。

# 概要

ネットワーク転送量は、[HttpConnection](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpConnection.html) インスタンスの[getMetrics()](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpConnection.html#getMetrics()) メソッドで取得できる[HttpConnectionMetrics](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpConnectionMetrics.html) インスタンスに記録されています。

[HttpConnection](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpConnection.html) インスタンスは、[HttpContext](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/protocol/HttpContext.html) インスタンスの[getAttribute](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/protocol/HttpContext.html#getAttribute(java.lang.String))([ExecutionContext](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/protocol/ExecutionContext.html).[HTTP_CONNECTION](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/protocol/ExecutionContext.html#HTTP_CONNECTION))で取得できます。

[HttpContext](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/protocol/HttpContext.html) インスタンスを取得するには、[HttpRequestInterceptor](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpRequestInterceptor.html?is-external=true) インターフェースと[HttpResponseInterceptor](http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpResponseInterceptor.html?is-external=true) インターフェースを実装したクラスを、[addRequestInterceptor()](http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/AbstractHttpClient.html#addRequestInterceptor(org.apache.http.HttpRequestInterceptor))  メソッドと[addResponseInterceptor()](http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/AbstractHttpClient.html#addResponseInterceptor(org.apache.http.HttpResponseInterceptor)) メソッドを使用して[HttpClient(正確には AbstractHttpClient)](http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/AbstractHttpClient.html) インスタンスに登録してやります。

# 実装例

下にHttpRequestInterceptor インターフェースとHttpResponseInterceptorインターフェースの実装を示します。

```java:HttpRequestInterceptorとHttpResponseInterceptorの実装例
class InterceptorImpl implements HttpRequestInterceptor, HttpResponseInterceptor {
	private static final Logger LOG = LoggerFactory.getLogger(InterceptorImpl.class);
	private ThreadLocal<HttpConnectionMetrics> threadLocalMetrics = new ThreadLocal<HttpConnectionMetrics>();

	@Override
	public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
		// context インスタンスから、HttpConnection インスタンスを取得
		final HttpConnection connection = (HttpConnection)context.getAttribute(ExecutionContext.HTTP_CONNECTION);
		// HttpConnection インスタンスからHttpConnectionMetricsを取得
		final HttpConnectionMetrics metrics = connection.getMetrics();
		// サーバにアクセスする前のメトリックをスレッドローカルに保存
		threadLocalMetrics.set(new HttpConnectionMetrics() {
			private final long requestCount = metrics.getRequestCount();
			private final long responseCount = metrics.getResponseCount();
			private final long sentBytesCount = metrics.getSentBytesCount();
			private final long receivedBytesCount = metrics.getReceivedBytesCount();

			@Override
			public long getRequestCount() {
				return this.requestCount;
			}

			@Override
			public long getResponseCount() {
				return responseCount;
			}

			@Override
			public long getSentBytesCount() {
				return sentBytesCount;
			}

			@Override
			public long getReceivedBytesCount() {
				return receivedBytesCount;
			}

			@Override
			public Object getMetric(String metricName) {
				return null;
			}

			@Override
			public void reset() {
			}
		});
	}

	@Override
	public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
		// context インスタンスから、HttpConnection インスタンスを取得
		final HttpConnection connection = (HttpConnection)context.getAttribute(ExecutionContext.HTTP_CONNECTION);
		// HttpConnection インスタンスからHttpConnectionMetricsを取得
		final HttpConnectionMetrics postMetrics = connection.getMetrics();
		// サーバにアクセスする前のメトリックを取得
		final HttpConnectionMetrics prevMetrics = threadLocalMetrics.get();
		threadLocalMetrics.remove();

		// リクエスト回数
		final long requestCount = postMetrics.getRequestCount() - prevMetrics.getRequestCount();
		// レスポンス回数
		final long responseCount = postMetrics.getResponseCount() - prevMetrics.getResponseCount();
		// 送信バイト数
		final long sentBytesCount = postMetrics.getSentBytesCount() - prevMetrics.getSentBytesCount();
		// 受信バイト数
		final long receivedBytesCount = postMetrics.getReceivedBytesCount() - prevMetrics.getReceivedBytesCount();

		// ログに出力
		LOG.info("requestCount={}, responseCount={}, sentBytesCount={}, receivedBytesCount={}",
				totalRequestCount.get(), totalResponseCount.get(), totalSentBytesCount.get(),
				totalReceivedBytesCount.get());
	}
}
```

`InterceptorImpl` クラスは次のようにして使用します。

```java:使用例
public static void main() {
	// タイムアウトを設定してやる
	HttpParams httpParams = new BasicHttpParams();
	HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
	HttpConnectionParams.setSoTimeout(httpParams, 30000);

	// HttpClient インスタンス作成
	SystemDefaultHttpClient httpClient = new SystemDefaultHttpClient(httpParams);
	// Interceptor の登録
	Interceptor interceptor = new Interceptor();
	httpClient.addRequestInterceptor(interceptor);
	httpClient.addResponseInterceptor(interceptor);

	// サーバにアクセス
	HttpGet request = new HttpGet("http://www.yahoo.co.jp/");
	httpClient.exeucte(request);
}
```


プログラム実行後、ログに出力された値を Excel で集計すれば総量や平均が求まります。
`InterceptorImpl` から出力されるログを別ファイルにしておけば集計しやすいでしょう。


本実装例は Commons HttpClient 4.2 で確認しています。4.3 でも Deprecation の警告がでますがいけるはず。