[解決済み] java.net.URLConnectionを使用してHTTPリクエストを発生させ処理する方法
質問
使用方法
java.net.URLConnection
は、ここで結構頻繁に質問されるのですが、その際に
オラクルチュートリアル
は
も
を簡潔に説明します。
そのチュートリアルでは、基本的に GET をリクエストし、そのレスポンスを読みます。このメソッドを使って、特に POST リクエスト、リクエストヘッダの設定、レスポンスヘッダの読み込み、クッキーの処理、HTMLフォームの送信、ファイルのアップロードなどです。
では、どうすれば
java.net.URLConnection
を使用して、高度な HTTP リクエストを処理できますか?
どのように解決するのですか?
まず最初に、掲載されているコード例はすべて基本的な例であることをあらかじめお断りしておきます。些細な
IOException
と
RuntimeException
のように
NullPointerException
,
ArrayIndexOutOfBoundsException
と自分を慰める。
JavaではなくAndroidで開発している場合、APIレベル28の導入以降、平文のHTTPリクエストは、以下のようになることにも注意してください。
デフォルトで無効
. を使用することが推奨されます。
HttpsURLConnection
しかし、本当に必要であれば、Application ManifestでClearTextを有効にすることができます。
準備中
まず、最低でもURLと文字コードを知る必要があります。パラメータはオプションで、機能要件に依存します。
String url = "http://example.com";
String charset = "UTF-8"; // Or in Java 7 and later, use the constant: java.nio.charset.StandardCharsets.UTF_8.name()
String param1 = "value1";
String param2 = "value2";
// ...
String query = String.format("param1=%s¶m2=%s",
URLEncoder.encode(param1, charset),
URLEncoder.encode(param2, charset));
クエリパラメータは
name=value
形式で連結され
&
. また、通常であれば
URLエンコード
を使うと、クエリパラメータを指定した文字セットで表示することができます。
URLEncoder#encode()
.
は
String#format()
は単なる便宜的なものです。私は、文字列連結演算子
+
を2回以上使用します。
を発射する HTTP GET リクエストにクエリパラメータ(オプション)を指定します。
些細なことですが これはデフォルトのリクエストメソッドです。
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
任意のクエリ文字列は、URLに
?
. また
Accept-Charset
ヘッダは、パラメータがどのようなエンコーディングであるかをサーバーに知らせることができます。もしクエリ文字列を送らないのであれば
Accept-Charset
ヘッダを削除します。ヘッダを設定する必要がなければ
URL#openStream()
のショートカットメソッドです。
InputStream response = new URL(url).openStream();
// ...
いずれにせよ、相手先が
HttpServlet
であれば、その
doGet()
メソッドが呼び出され、パラメータは
HttpServletRequest#getParameter()
.
テスト用に、レスポンス・ボディを出力して 標準出力 を以下のように設定します。
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
を発射する HTTP POST クエリパラメータ付きリクエスト
を設定します。
URLConnection#setDoOutput()
から
true
は暗黙のうちにリクエストメソッドをPOSTに設定します。ウェブフォームが行う標準的な HTTP POST は
application/x-www-form-urlencoded
ここで、クエリ文字列はリクエストボディに書き込まれます。
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true); // Triggers POST.
connection.setRequestProperty("Accept-Charset", charset);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + charset);
try (OutputStream output = connection.getOutputStream()) {
output.write(query.getBytes(charset));
}
InputStream response = connection.getInputStream();
// ...
注意: プログラムで HTML フォームを送信したい場合は、常に
name=value
のペアは
<input type="hidden">
要素をクエリ文字列に追加し、もちろん
name=value
というペアの
<input type="submit">
というのは、通常、サーバー側ではボタンが押されたかどうか、押された場合はどのボタンかを区別するために使用されるからです。
また、取得した
URLConnection
を
HttpURLConnection
を使用し、その
HttpURLConnection#setRequestMethod()
の代わりに しかし、接続を出力に使おうとしているのであれば、まだ
URLConnection#setDoOutput()
から
true
.
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
いずれにせよ、相手先が
HttpServlet
であれば、その
doPost()
メソッドが呼び出され、パラメータは
HttpServletRequest#getParameter()
.
実際に HTTP リクエストを発行する
HTTP リクエストを明示的に起動するには
URLConnection#connect()
を使用してレスポンスボディなどの HTTP レスポンスに関する情報を取得したい場合、 リクエストは自動的にオンデマンドで発行されます。
URLConnection#getInputStream()
といった具合です。上の例ではまさにそうなっているので
connect()
の呼び出しは、実は余計なことなのです。
HTTPレスポンス情報の収集
が必要です。
HttpURLConnection
ここで 必要であれば、最初にキャストしてください。
int status = httpConnection.getResponseCode();
-
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) { System.out.println(header.getKey() + "=" + header.getValue()); }
というときに
Content-Type
が含まれています。
charset
パラメータがある場合、レスポンスボディはテキストベースである可能性が高いので、サーバ側で指定された文字エンコーディングで処理したいと思います。
String contentType = connection.getHeaderField("Content-Type");
String charset = null;
for (String param : contentType.replace(" ", "").split(";")) {
if (param.startsWith("charset=")) {
charset = param.split("=", 2)[1];
break;
}
}
if (charset != null) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(response, charset))) {
for (String line; (line = reader.readLine()) != null;) {
// ... System.out.println(line)?
}
}
} else {
// It's likely binary content, use InputStream/OutputStream.
}
セッションの維持
サーバー側のセッションは、通常、クッキーによってバックアップされています。いくつかのウェブフォームでは、ログインしていること、および/または、セッションによって追跡されることが要求されます。そのような場合は
CookieHandler
APIを使用してCookieを保持することができます。を用意する必要があります。
CookieManager
と共に
CookiePolicy
の
ACCEPT_ALL
は、すべてのHTTPリクエストを送信する前に
// First set the default cookie manager.
CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
// All the following subsequent URLConnections will use the same cookie manager.
URLConnection connection = new URL(url).openConnection();
// ...
connection = new URL(url).openConnection();
// ...
connection = new URL(url).openConnection();
// ...
この方法は、すべての状況下で常に正しく動作するわけではないことが知られていることに注意してください。もしこれがうまくいかない場合は、手動でクッキーヘッダを収集し、設定するのが一番です。基本的には、すべての
Set-Cookie
ヘッダは、ログイン時のレスポンス、あるいは最初の
GET
を作成し、これを後続のリクエストに渡します。
// Gather all cookies on the first request.
URLConnection connection = new URL(url).openConnection();
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
// ...
// Then use the same cookies on all subsequent requests.
connection = new URL(url).openConnection();
for (String cookie : cookies) {
connection.addRequestProperty("Cookie", cookie.split(";", 2)[0]);
}
// ...
は
split(";", 2)[0]
のように、サーバサイドに無関係なクッキーの属性を取り除くために存在します。
expires
,
path
など。あるいは
cookie.substring(0, cookie.indexOf(';'))
の代わりに
split()
.
ストリーミングモード
は
HttpURLConnection
はデフォルトでバッファリングされます。
全体
リクエストのボディを実際に送信する前に、コンテンツの長さを
connection.setRequestProperty("Content-Length", contentLength);
. このため
OutOfMemoryException
は、大きな POST リクエスト (ファイルのアップロードなど) を同時に送信するたびに発生します。これを避けるには、POST リクエストの際に
HttpURLConnection#setFixedLengthStreamingMode()
.
httpConnection.setFixedLengthStreamingMode(contentLength);
しかし、コンテンツの長さが本当に事前にわからない場合は、チャンクドストリーミングモードを利用するために
HttpURLConnection#setChunkedStreamingMode()
に従ってください。これによってHTTPの
Transfer-Encoding
ヘッダーを
chunked
これは、リクエストボディをチャンクで送信することを強制します。以下の例では、1KBのチャンクでボディを送信します。
httpConnection.setChunkedStreamingMode(1024);
ユーザーエージェント
以下のようなことが起こり得ます。
を実行すると、実際のウェブブラウザでは正常に動作するものの、予期しないレスポンスが返されます。
. サーバー側は、おそらく
User-Agent
リクエストヘッダがあります。は
URLConnection
は、デフォルトでこれを
Java/1.6.0_19
ここで、最後の部分は明らかにJREのバージョンです。以下のようにオーバーライドすることができます。
connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"); // Do as if you're using Chrome 41 on Windows 7.
のUser-Agent文字列を使用します。 最近のブラウザ .
エラー処理
HTTPレスポンスコードが
4nn
(クライアントエラー) または
5nn
(サーバーエラー)の場合、その後に
HttpURLConnection#getErrorStream()
を使用して、サーバーが有用なエラー情報を送信したかどうかを確認します。
InputStream error = ((HttpURLConnection) connection).getErrorStream();
HTTPレスポンスコードが-1であれば、接続とレスポンスの処理に何か問題があったということです。その
HttpURLConnection
の実装は、古い JRE では接続を維持するのに多少バグがあります。を設定することで、これをオフにすることができます。
http.keepAlive
システムプロパティを
false
. アプリケーションの冒頭で、次のようにしてプログラム的に行うことができます。
System.setProperty("http.keepAlive", "false");
ファイルのアップロード
通常は
multipart/form-data
エンコーディングは、POST コンテンツ (バイナリおよび文字データ) が混在している場合に使用します。このエンコーディングの詳細については
RFC2388
.
String param = "value";
File textFile = new File("/path/to/file.txt");
File binaryFile = new File("/path/to/file.bin");
String boundary = Long.toHexString(System.currentTimeMillis()); // Just generate some unique random value.
String CRLF = "\r\n"; // Line separator required by multipart/form-data.
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
try (
OutputStream output = connection.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, charset), true);
) {
// Send normal param.
writer.append("--" + boundary).append(CRLF);
writer.append("Content-Disposition: form-data; name=\"param\"").append(CRLF);
writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF);
writer.append(CRLF).append(param).append(CRLF).flush();
// Send text file.
writer.append("--" + boundary).append(CRLF);
writer.append("Content-Disposition: form-data; name=\"textFile\"; filename=\"" + textFile.getName() + "\"").append(CRLF);
writer.append("Content-Type: text/plain; charset=" + charset).append(CRLF); // Text file itself must be saved in this charset!
writer.append(CRLF).flush();
Files.copy(textFile.toPath(), output);
output.flush(); // Important before continuing with writer!
writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.
// Send binary file.
writer.append("--" + boundary).append(CRLF);
writer.append("Content-Disposition: form-data; name=\"binaryFile\"; filename=\"" + binaryFile.getName() + "\"").append(CRLF);
writer.append("Content-Type: " + URLConnection.guessContentTypeFromName(binaryFile.getName())).append(CRLF);
writer.append("Content-Transfer-Encoding: binary").append(CRLF);
writer.append(CRLF).flush();
Files.copy(binaryFile.toPath(), output);
output.flush(); // Important before continuing with writer!
writer.append(CRLF).flush(); // CRLF is important! It indicates end of boundary.
// End of multipart/form-data.
writer.append("--" + boundary + "--").append(CRLF).flush();
}
もし相手が
HttpServlet
であれば、その
doPost()
メソッドが呼び出され、部品が
HttpServletRequest#getPart()
(注、このように
ではなく
getParameter()
などなど!)。は
getPart()
しかし、このメソッドは比較的新しく、Servlet 3.0 (Glassfish 3, Tomcat 7 など) で導入されました。Servlet 3.0 より前のバージョンでは、最も適した方法は
アパッチコモンズファイルアップロード
をパースして
multipart/form-data
リクエストに対応します。また
この回答
は、FileUploadとServelt 3.0の両方のアプローチの例を示しています。
信頼されていないサイトや設定ミスのあるHTTPSサイトへの対応
Javaではなく、Android向けに開発する場合。 気をつける 開発中に正しい証明書が配備されない場合、以下の回避策で救われるかもしれません。しかし、本番環境では使用しないでください。最近(2021年4月)、Googleは安全でないホスト名検証器を検出した場合、アプリをPlay Storeで配布することを許可しないようになっています。 https://support.google.com/faqs/answer/7188426。
ウェブスクレイパーを作成している場合など、HTTPSのURLを接続する必要がある場合があります。そのような場合、おそらく
javax.net.ssl.SSLException: Not trusted server certificate
は、SSL証明書を最新にしていないHTTPSサイト、あるいは
java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
または
javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
を使用すると、一部の設定ミスのあるHTTPSサイトでも使用できます。
以下のワンタイムラン
static
ウェブスクレイパークラスのイニシャライザーは
HttpsURLConnection
HTTPSサイトに対してより寛容になり、例外をスローしなくなりました。
static {
TrustManager[] trustAllCertificates = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null; // Not relevant.
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
// Do nothing. Just allow them all.
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
// Do nothing. Just allow them all.
}
}
};
HostnameVerifier trustAllHostnames = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true; // Just allow them all.
}
};
try {
System.setProperty("jsse.enableSNIExtension", "false");
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCertificates, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier(trustAllHostnames);
}
catch (GeneralSecurityException e) {
throw new ExceptionInInitializerError(e);
}
}
最後の言葉
は Apache HttpComponents HttpClient は だいぶ より便利になっています。)
HTMLのパースと抽出
もし、HTMLを解析してデータを取り出すだけなら、以下のようなHTMLパーサーを使うと良いでしょう。 Jsoup .
関連
-
[解決済み] serialVersionUIDとは何ですか、またなぜそれを使用する必要がありますか?
-
[解決済み] Javaで配列を宣言し、初期化する方法は?
-
[解決済み] なぜゲッターとセッター/アクセッサーを使うのですか?
-
[解決済み] HTTP POSTリクエストでは、どのようにパラメータが送信されるのですか?
-
[解決済み] updateとdeleteのHTTPステータスコード?
-
[解決済み] HTTP POST Web リクエストの作成方法
-
[解決済み] node.jsでHTTP POSTリクエストはどのように行われるのですか?
-
[解決済み] HTTPでファイルをダウンロードするには?
-
[解決済み] ローカルファイルの読み込み時に "Cross origin requests are only supported for HTTP." というエラーが発生する。
-
[解決済み】HTTPのPOSTとPUTの違いは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Java エラー報告 スレッド "main" での例外 java.util.NoSuchElementException
-
アクセス制限について アプリケーションの種類がAPIでない(必要なライブラリの制限)。
-
this()の呼び出しはコンストラクタ本体の最初の文でなければならない 例外解決と原因分析
-
Eclipseプロンプトを実行する java仮想マシンを使用しない
-
spring-boot 401 このリソースにアクセスするには完全な認証が必要です エラー解決
-
java 例外。Javaツールの初期化
-
テストが見つかりませんでした
-
コミットには何も追加されないが、未追跡のファイルが存在し、gitで未追跡のファイルに対する完璧な解決策
-
maven プラグイン エラー プラグインの実行は、ライフサイクル構成ソリューションの対象外です。
-
[解決済み】javaでHTTPリクエストを送信するには?[重複している]。