BSDソケットAPIは、タイムアウト・オプション(SO_TIMEOUT)をサポートしており、それは、java.net.socketでサポートされています。 しかしながら残念なことに、java.net.URLConnectionは、基底のソケットクラスを公開していません。 そのため、死んでいる(すなわち、正しい形式のURLで存在しているが サイトがダウンしている)URLに接続しようとする場合には、ソケットは OSのデフォルトタイムアウトが結局使用されるでしょう(Windows NTの場合には 420秒です)。 例えばリンクを張るプログラムやURLチェックのためには、このタイムアウトは非常に長い時間です。
以下のファイルは、実際のjavaソースコードを元にURL接続にソケットタイムアウトを導入するための テクニックを説明します。 (オープンソースコミュニティライセンスを参照してください JavaSoft)。
Javaのネットワークの実装は、オブジェクト指向と同様にプロトコルとは独立しています。 したがって、実装は人が想像するかもしれないほど、単純ではありません。
URLConnection は、「ファクトリー」デザイン・パターンと同様にクライアント/サーバー・モデルを 使用している内部のいくつかのクラスに依存しています。 クライアントの基底クラスは sun.net.www.http.HttpClient です。 このクラスは、ソケットを公開する目的のために継承されています。
デフォルト・ファクトリーはURLStreamHandlerFactoryです。そして、それは HTTPプロトコルに特有であるクラス(sun.net.www.protocol.http.Handler)を インスタンス化することによって間接的にHTTPクライアントの作成を「取り扱い」ます。
実際問題として、ファクトリーは、模倣の(mimic)javaの実装に必要なだけですが、 本当に必要なのはハンドラーだけです。
我々は、javaソースコードで対称性を維持するために4つのクラスを導出します。
HttpURLConnectionTimeout は sun.net.www.protocol.http.HttpURLConnection を継承する
HttpTimeoutHandler は sun.net.www.protocol.http.Handler を継承する
HttpTimeoutFactory は java.net.URLStreamHandlerFactory を実装する
HttpClientTimeout は sun.net.www.http.HttpClient を継承する
ソースコードでは
// whatever package you want
import sun.net.www.http.HttpClient;
import java.net.*;
import java.io.*;
public class HttpClientTimeout extends HttpClient
{
public HttpClientTimeout(URL url, String proxy, int proxyPort) throws IOException
{
super(url, proxy, proxyPort);
}
public HttpClientTimeout(URL url) throws IOException
{
super(url, null, -1);
}
public void SetTimeout(int i) throws SocketException {
serverSocket.setSoTimeout(i);
}
/* このクラスは、HTTP用に公開コンストラクタはありません。
* このメソッドは、指定したURLのHttpClientを得るために使用されます。
* もし現在アクティブなHttpClinetが存在している場合には、その
* サーバ/ポートを取得します。
*
* no longer syncrhonized -- it slows things down too much
* synchronize at a higher level
*/
public static HttpClientTimeout GetNew(URL url)
throws IOException {
/* see if one's already around */
HttpClientTimeout ret = (HttpClientTimeout) kac.get(url);
if (ret == null) {
ret = new HttpClientTimeout (url); // CTOR called openServer()
} else {
ret.url = url;
}
// don't know if we're keeping alive until we parse the headers
// for now, keepingAlive is false
return ret;
}
public void Close() throws IOException
{
serverSocket.close();
}
public Socket GetSocket()
{
return serverSocket;
}
}
import java.net.*;
public class HttpTimeoutFactory implements URLStreamHandlerFactory
{
int fiTimeoutVal;
public HttpTimeoutFactory(int iT) { fiTimeoutVal = iT; }
public URLStreamHandler createURLStreamHandler(String str)
{
return new HttpTimeoutHandler(fiTimeoutVal);
}
}
import java.net.*;
import java.io.IOException;
public class HttpTimeoutHandler extends sun.net.www.protocol.http.Handler
{
int fiTimeoutVal;
HttpURLConnectionTimeout fHUCT;
public HttpTimeoutHandler(int iT) { fiTimeoutVal = iT; }
protected java.net.URLConnection openConnection(URL u) throws IOException {
return fHUCT = new HttpURLConnectionTimeout(u, this, fiTimeoutVal);
}
String GetProxy() { return proxy; } // breaking encapsulation
int GetProxyPort() { return proxyPort; } // breaking encapsulation
public void Close() throws Exception
{
fHUCT.Close();
}
public Socket GetSocket()
{
return fHUCT.GetSocket();
}
}
import java.net.*;
import java.io.*;
import sun.net.www.http.HttpClient;
public class HttpURLConnectionTimeout extends sun.net.www.protocol.http.HttpURLConnection
{
int fiTimeoutVal;
HttpTimeoutHandler fHandler;
HttpClientTimeout fClient;
public HttpURLConnectionTimeout(URL u, HttpTimeoutHandler handler, int iTimeout) throws IOException
{
super(u, handler);
fiTimeoutVal = iTimeout;
}
public HttpURLConnectionTimeout(URL u, String host, int port) throws IOException
{
super(u, host, port);
}
public void connect() throws IOException {
if (connected) {
return;
}
try {
if ("http".equals(url.getProtocol()) /* && !failedOnce <- PRIVATE */ ) {
// for safety's sake, as reported by KLGroup
synchronized (url)
{
http = HttpClientTimeout.GetNew(url);
}
fClient = (HttpClientTimeout)http;
((HttpClientTimeout)http).SetTimeout(fiTimeoutVal);
} else {
// make sure to construct new connection if first
// attempt failed
http = new HttpClientTimeout(url, fHandler.GetProxy(), fHandler.GetProxyPort());
}
ps = (PrintStream)http.getOutputStream();
} catch (IOException e) {
throw e;
}
}
/**
* 新たに HttpClient オブジェクトを作成し、HTTPクライアント
* オブジェクト/コネクションのキャッシュを迂回する.
*
* @param url the URL being accessed
*/
protected HttpClient getNewClient (URL url)
throws IOException {
HttpClientTimeout client = new HttpClientTimeout (url, (String)null, -1);
try {
client.SetTimeout(fiTimeoutVal);
} catch (Exception e) {
System.out.println("Unable to set timeout value");
}
return (HttpClient)client;
}
/**
* 同一ホストへのリダイレクトだけストリームのオープンを許可する.
*/
public static InputStream openConnectionCheckRedirects(URLConnection c)
throws IOException
{
boolean redir;
int redirects = 0;
InputStream in = null;
do {
if (c instanceof HttpURLConnectionTimeout) {
((HttpURLConnectionTimeout) c).setInstanceFollowRedirects(false);
}
// getHeaderField()他がIOExceptionを飲み込むので、
// ヘッダを得る前に入力ストリームをオープンしたい。
in = c.getInputStream();
redir = false;
if (c instanceof HttpURLConnectionTimeout) {
HttpURLConnectionTimeout http = (HttpURLConnectionTimeout) c;
int stat = http.getResponseCode();
if (stat >= 300 && stat <= 305 &&
stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
URL base = http.getURL();
String loc = http.getHeaderField("Location");
URL target = null;
if (loc != null) {
target = new URL(base, loc);
}
http.disconnect();
if (target == null
|| !base.getProtocol().equals(target.getProtocol())
|| base.getPort() != target.getPort()
|| !HostsEquals(base, target)
|| redirects >= 5)
{
throw new SecurityException("illegal URL redirect");
}
redir = true;
c = target.openConnection();
redirects++;
}
}
} while (redir);
return in;
}
// java.net.URL.hostsEqual と同じ
static boolean HostsEquals(URL u1, URL u2)
{
final String h1 = u1.getHost();
final String h2 = u2.getHost();
if (h1 == null) {
return h2 == null;
} else if (h2 == null) {
return false;
} else if (h1.equalsIgnoreCase(h2)) {
return true;
}
// 比較する前にアドレスの解決をしなければならない。そうでないと
// tachyon と tachyon.eng という名前の比較で異なってしまう。
final boolean result[] = {false};
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
InetAddress a1 = InetAddress.getByName(h1);
InetAddress a2 = InetAddress.getByName(h2);
result[0] = a1.equals(a2);
} catch(UnknownHostException e) {
} catch(SecurityException e) {
}
return null;
}
}
);
return result[0];
}
void Close() throws Exception
{
fClient.Close();
}
Socket GetSocket()
{
return fClient.GetSocket();
}
}
import java.net.*;
public class MainTest
{
public static void main(String args[])
{
int i = 0;
try {
URL theURL = new URL((URL)null, "http://www.snowball.com", new HttpTimeoutHandler(150)); // タイムアウト値をミリ秒で指定
// 次のステップはオプション
theURL.setURLStreamHandlerFactory(new HttpTimeoutFactory(150));
URLConnection theURLconn = theURL.openConnection();
theURLconn.connect();
i = theURLconn.getContentLength();
} catch (InterruptedIOException e) {
System.out.println("timeout on socket");
}
System.out.println("Done, Length:" + i);
}
}
try {
HttpTimeoutHandler xHTH = new HttpTimeoutHandler(10); // タイムアウト値をミリ秒で指定
URL theURL = new URL((URL)null, "http://www.javasoft.com", xHTH);
HttpURLConnection theUC = theURL.openConnection();
.
.
.
} catch (InterruptedIOException e) {
// socket timed out
}
備考: このコードは、スレッドセーフ。
More to come