java.net.URLを使ってHTTP接続してファイルをダウンロードするプログラムを作っているときにはまったことのメモです。
getFile()メソッドは名前で安易に振る舞いを想像してはならない
以下のサンプルコードを実行すると、
public void test() throws IOException { URL url = new URL("http://www.example.com/foo/bar.zip?a=b"); System.out.println(url.getFile()); System.out.println(url.getPath()); }
次のように表示されます。
/foo/bar.zip?a=b /foo/bar.zip
getFile()は、メソッド名から想像するとbar.zipを返すように思ってしまいましたが、実際は違います。Javadocで確認すると「getPath()にgetQuery()を連結したもの」とあります。
やりたかったことは、URLからファイル名部分(この例ではbar.zip)を抜き出すことです。URLクラスにはずばりのメソッドがないので文字列の切り出しを行う必要があります。
String[] pathElements = url.getPath().split("/"); String fileName = pathElements[pathElements.length - 1];
そんなとき、Java SE 7のNIO.2で追加されたjava.nio.file.Pathsを使うと
Path path = Paths.get(url.getPath()); String fileName = path.getFileName();
あるいは一行で
String fileName = Paths.get(url.getPath()).getFileName();
と書くことができます。
URLからInputStreamの取り出し方
URLからInputStreamを取得するには、次の方法があります。
- URLクラスのopenStream()メソッドでInputStreamを取得する
- URLクラスのopenConnection()メソッドでURLConnectionを取得し、URLConnectionのgetInputStream()メソッドでInputStreamを取得する
前者が簡単ですが、タイムアウト設定やProxy設定をすることができないようです。また、HTTPのPOSTで取得する場合に対応できません。
URLConnectionは、AutoCloseableでない
URLConnectionは、使用を終了するときはdisconnect()を呼ぶのがよさそうですが(ステータスに応じて呼ぶ/呼ばないを使い分けるべきかも)、AutoCloseableインタフェースを実装していないので、Java SE 7で追加されたtry with resourceに対応していません。
URLのデータをローカルファイルに保存
URLから取り出したInputStreamを、ローカルファイルのOutputStreamにループでreadしてwriteするのかなと思ってましたが、NIO.2のFiles.copyメソッドを使うと
try (BufferedInputStream in = new BufferedInputStream(con.getInputStream())) { Path savePath = FileSystems.getDefault().getPath("C:/tmp/bar.zip"); Files.copy(in, savePath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { ... }
といった感じで、InputStreamからPathへつなげることができるようです。