本稿では、URLからファイルを非同期でダウンロードする方法について解説します。
- ファイルのダウンロードを自動化したい
- 初期処理等のため、プログラム上でファイルをダウンロードする必要がある
- 複数ファイルを並列でダウンロードしたい
Javaの標準ライブラリによる実装方法やApache HttpComponents、AsyncHttpClient などの外部ライブラリを用いた実装方法についていくつかサンプルを掲載します。
非同期を考慮しないシンプルな実装方法についてはこちらを参照ください。

Java標準ライブラリで行う方法
Executorフレームワークは、スレッドプールを利用した並列処理実行を行えるようJava 1.5 で導入されたもので、WorkerThreadパターンをシンプルに実装することができます。
以下のサンプルではファイルをダウンロードするタスクをExecutorServiceに送信して、スレッドプール内で実行しています。ここでは戻り値を気にしていませんが、Futureクラスを得るとタスクを追跡することができます。サンプルでは完了までの時間を考慮していませんが、ダウンロードにある程度時間がかかる見込みがあれば、CompletableFutureを使用してすべてのタスクの完了を待ってから終了しましょう。
Java
//取得対象データ(サンプルはロト7の抽選結果)
String[] urlStrings = {
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030001.CSV",
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030002.CSV",
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030003.CSV",
...
};
//最大nスレッド(サンプルでは5)のスレッドプールを作成する
ExecutorService executor = Executors.newFixedThreadPool(5);
//複数のダウンロードを並列に実行する例
for(String s : urlStrings) {
URL url = URI.create(s).toURL();
String path = url.getPath();
String name = path.substring(path.lastIndexOf("/") + 1);
//スレッドプール内でタスクを実行する
executor.execute(() -> {
try {
System.out.println("start. " + url);
//NIO2を使ってファイル保存する例
long size = Files.copy(url.openStream(),
Paths.get(name),
REPLACE_EXISTING);
System.out.println("complete. " + name + " - " + size + " bytes");
} catch (IOException e) {
//error handling
}
});
}
//シャットダウンを開始して、これ以降のタスクは受け付けない
executor.shutdown();
//全タスクが完了するかタイムアウトするまでブロックする
executor.awaitTermination(10, TimeUnit.SECONDS);
Apache HttpComponents を使う方法
org.apache.http パッケージを使用してファイルをダウンロードします。
GETやPOSTなどHTTPメソッドを簡単に使い分けできます。また、FutureCallbackではデータ取得完了時、失敗時、キャンセル時の挙動を制御できます。サンプルではリクエストが完了した時にレスポンスを読み取ってファイルに保存しています。
セットアップ
Mavenを使用する場合は、pom.xmlに以下の依存性を追加してください。
pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/fluent-hc -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
<version>4.5.10</version>
</dependency>
特にプロジェクト管理ツールを使わない場合はより公式よりzipをダウンロードして、プロジェクトにjarを追加してください。
ソースコード
Java
//取得対象データ(サンプルはロト7の抽選結果)
String[] inUrl = {
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030001.CSV",
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030002.CSV",
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030003.CSV",
...
};
//最大nスレッド(サンプルでは5)のスレッドプールを作成する
ExecutorService executor = Executors.newFixedThreadPool(5);
//複数のダウンロードを並列に実行する例
for (String url : inUrl) {
String path = URI.create(url).getPath();
String name = path.substring(path.lastIndexOf("/") + 1);
Async async = Async.newInstance().use(executor);
//対象のURLに接続するタスクを送信する
Future future = async.execute(Request.Get(url), new FutureCallback() {
//対象のURLからのデータ取得が完了した場合の挙動
@Override
public void completed(Content content) {
try {
//NIO2を使ってファイル保存する例
long size = Files.copy(content.asStream(),
Paths.get(name),
StandardCopyOption.REPLACE_EXISTING);
System.out.println("completed. " + name + " - " + size + " bytes");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void failed(Exception e) {
System.out.println("failed.");
}
@Override
public void cancelled() {
System.out.println("cancelled.");
}
});
System.out.println("started. " + url);
}
//シャットダウンを開始して、これ以降のタスクは受け付けない
executor.shutdown();
//全タスクが完了するかタイムアウトするまでブロックする
executor.awaitTermination(10, TimeUnit.SECONDS);
補足
ホストが見つからない場合もファイルが見つからない場合も、FutureCallback#failedが呼ばれます。
AsyncHttpClient を使う方法
AsyncHttpClientライブラリにより、HTTP要求を簡単に実行しHTTP応答を非同期的に処理できます。コールバックを定義することで完了時にファイルを保存するよう制御できます。
以下のサンプルでは、CompletableFutureを使用して、すべてのタスクが完了してからクライアントをクローズするようにしています。
セットアップ
Mavenを使用する場合は、pom.xmlに以下の依存性を追加してください。
pom.xml
<!-- https://mvnrepository.com/artifact/org.asynchttpclient/async-http-client -->
<dependency>
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.10.4</version>
</dependency>
特にプロジェクト管理ツールを使わない場合はMavenのCentralリポジトリよりjarをダウンロードしてください。
async-http-client-2.10.4.jar
ソースコード
Java
//取得対象データ(サンプルはロト7の抽選結果)
String[] inUrl = {
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030001.CSV",
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030002.CSV",
"https://www.mizuhobank.co.jp/retail/takarakuji/loto/loto7/csv/A1030003.CSV",
...
};
AsyncHttpClient client = Dsl.asyncHttpClient();
List futures = new ArrayList();
for (String url : inUrl) {
String path = URI.create(url).getPath();
String name = path.substring(path.lastIndexOf("/") + 1);
//対象URLへ接続するリクエスト
Request request = Dsl.request(HttpConstants.Methods.GET, url).build();
//リクエストが完了した場合のハンドラを指定して非同期で実行
ListenableFuture f = client.executeRequest(request,
new AsyncCompletionHandler() {
@Override
public Long onCompleted(Response response) throws Exception {
//リクエストが完了した時はレスポンスをファイルに保存
long size = Files.copy(response.getResponseBodyAsStream(),
Paths.get(name),
StandardCopyOption.REPLACE_EXISTING);
System.out.println("completed. " + name + " - " + size + " bytes");
return size;
}
});
System.out.println("started. " + url);
//後述のCompletableFutureで使用
futures.add(f.toCompletableFuture());
}
//すべてのタスクが完了してからClientを閉じる
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.whenComplete((r, e) -> {
try {
client.close();
} catch (IOException ex) {
//error handling
}
});
注意
ファイルが存在しない場合でも、接続先のサーバがステータスコード404を返してくれるような場合はonCompleteが呼ばれます。処理の中で適当に扱う必要があります。
if (response.getStatusCode() != 200) {
//Not HTTP_OK
}
ホスト自体が見つからない場合や応答がない場合はonCompleteは呼ばれません。
また、応答なしを考慮してクライアント生成時にタイムアウトを設定したほうがよいでしょう。
AsyncHttpClientConfig config = Dsl.config()
.setConnectTimeout(10)
.setReadTimeout(15)
.build();
AsyncHttpClient client = Dsl.asyncHttpClient(config);