java.net.HttpURLConnection 的设计引起的事件

23 篇文章 0 订阅

以下的内容,我抽空会翻译程中文的,先大家将就着看代码吧!

JavaHTTPクライアントを作ろうと思ったときには、おそらく二つくらいのアプローチがあって、ひとつApache JakartaプロジェクトのHTTPClientを使う方法、もうひとつjava.net.HttpURLConnection を使う方法だ。まあもちろん、自前でTCPクライアントの上に乗せてもいいのだが、そんな車輪の再発明するくらいならもっと別のことに労力を使うべきだと思う。

というわけで、そのうちひとつjava.net.HttpURLConnectionの件。HTTPは最初だけ大文字、後小文字の癖に、URLは全部大文字というよくわからんネーミングセンスについてはとりあえず置いておくとして、設計的によくわからないというか、何がしたいのか意味が不明なところがあるのでそれについて書く。

http://java.sun.com/j2se/1.5.0/docs/api/java/net/HttpURLConnection.html

HttpURLConnectionは、URLオブジェクトからコネクションを張ることで以下のように接続を行う。

 URL url = new URL("http://example.com/");
 HttpURLConnection conn = (HttpURLConnection)url.openConnection();
 conn.setRequestMethod("GET");
 conn.connect();

で、その後データを取得する。この際使用するのがBufferedReaderなのはバイナリが帰ってくるかもしれないから仕方ない。

 BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));

問題なのは、この

 conn.getInputStream()

が、throw IOExceptionとなっていることなのだ。これはリファレンスによれば、

if an I/O error occurs while creating the input stream.

と書いてある。これ、実際にどういうときに起きるかといえば、他にもあるかもしれないがサーバエラーを返したときなのだ。つまり、サーバが4xxまたは5xxのエラーレスポンスを返したときには、なぜかInputStreamは生成されない。nullが返るのではなくてExceptionが飛んでくる。そしてその場合にはgetErrorStream()で取得したStreamHTTP ResponseBodyが返ってくる。

あるサンプルソースコードはこのため、こんなことをやっている(http://java.sun.com/j2se/1.5.0/docs/guide/net/http-keepalive.html)。

try {
	URL a = new URL(args[0]);
	URLConnection urlc = a.openConnection();
	is = conn.getInputStream();
	int ret = 0;
	while ((ret = is.read(buf)) > 0) {
	  processBuf(buf);
	}
	// close the inputstream
	is.close();
} catch (IOException e) {
	try {
		respCode = ((HttpURLConnection)conn).getResponseCode();
		es = ((HttpURLConnection)conn).getErrorStream();
		int ret = 0;
		// read the response body
		while ((ret = es.read(buf)) > 0) {
			processBuf(buf);
		}
		// close the errorstream
		es.close();
	} catch(IOException ex) {
		// deal with the exception
	}
}

このソースコードは、まあ目的が違って永続セッションを張ろうとしているので(サーバエラーを返した時点で終了するのがクライアントとして正しい動作なので)これでいいのだろうけども、「とりあえずInputStreamを取得して、Exceptionが出たらErrorStream使おう」とかいう態度はどうかと思う。例外は、正常形で投げられてはいけないのだ。

HTTPClientを書こうとした人間が、サーバの4xxエラーまたは5xxエラーを正常系とみなすか異常系とみなすかは不明である。サーバにとって異常系なのは明らかだが、例えばサーバエラーリンク切れ発見するテストクライアントであれば、異常系ではない。

4xxや5xxがクライアントにとっても異常系だと主張するにしても、getInputStream()で例外を投げるのはタイミングがおかしい。エラーbodyを取得する前にわかっているのだから、その前に例外を投げるべきだ。

あるいは、以下のような実装も考えられる。

 conn.connect();
 InputStream stream;
 if(conn.isSuccess()){
   stream = conn.getInputStream();
 }else{
   stream = conn.getErrorStream();
 }

しかし、isSuccessのようなメソッドは実装されていない。また、本質的な話としてこのHttpURLConnection#getInputStreamが例外を投げるケースはResponse Codeが何番なのかということはドキュメントのどこにも書いていないことが挙げられる。したがって、このようなisSuccessを自前で作ることすらできないのだ。

実際に正しくやろうと思ったら現状では以下のようにするしかないが、これで正しいかどうかは誰も保障してくれないのだ。

 InputStream stream;
 int responseCode = conn.getResponseCode();
 if(responseCode / 100 == 4 || responseCode / 100 == 5){
  stream = conn.getErrorStream();
 }else{
  stream = conn.getInputStream();
 }

明らかに設計ミスだと思うんですが。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值