Pertama, penafian sebelumnya: cuplikan kode yang diposting adalah semua contoh dasar. Anda harus menangani hal-hal sepele IOException
dan RuntimeException
seperti itu NullPointerException
, ArrayIndexOutOfBoundsException
dan mendampingi diri Anda sendiri.
Mempersiapkan
Pertama-tama kita perlu tahu setidaknya URL dan rangkaian karakter. Parameter bersifat opsional dan tergantung pada persyaratan fungsional.
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));
Parameter kueri harus dalam name=value
format dan disatukan oleh &
. Anda biasanya juga akan menyandi URL parameter kueri dengan menggunakan rangkaian karakter yang ditentukan URLEncoder#encode()
.
The String#format()
hanya untuk kenyamanan. Saya lebih suka ketika saya akan membutuhkan operator penggabungan String +
lebih dari dua kali.
Menembak a permintaan HTTP GET dengan parameter kueri (opsional)
Ini tugas yang sepele. Ini metode permintaan default.
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
Setiap string kueri harus digabungkan ke URL menggunakan ?
. The Accept-Charset
sundulan mungkin petunjuk server apa pengkodean parameter dalam. Jika Anda tidak mengirim string kueri, maka Anda dapat meninggalkan Accept-Charset
sundulan jauh. Jika Anda tidak perlu mengatur header apa pun, maka Anda bahkan dapat menggunakan URL#openStream()
metode pintas.
InputStream response = new URL(url).openStream();
// ...
Either way, jika pihak lain adalah HttpServlet
, maka doGet()
metodenya akan dipanggil dan parameter akan tersedia oleh HttpServletRequest#getParameter()
.
Untuk tujuan pengujian, Anda dapat mencetak isi tanggapan ke stdout seperti di bawah ini:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
Memecat permintaan HTTP POST dengan parameter kueri
Mengatur URLConnection#setDoOutput()
agar true
secara implisit menetapkan metode permintaan ke POST. HTTP POST standar seperti yang dilakukan formulir web adalah jenis di application/x-www-form-urlencoded
mana string kueri ditulis ke badan permintaan.
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();
// ...
Catatan: setiap kali Anda ingin mengirimkan formulir HTML secara terprogram, jangan lupa untuk membawa name=value
pasangan <input type="hidden">
elemen apa pun ke dalam string kueri dan tentu saja juga name=value
pasangan dari<input type="submit">
elemen yang ingin Anda "tekan" secara terprogram (karena yang biasanya digunakan di sisi server untuk membedakan jika tombol ditekan dan jika demikian, yang mana).
Anda juga dapat melemparkan yang diperoleh URLConnection
ke HttpURLConnection
dan menggunakannya HttpURLConnection#setRequestMethod()
. Tapi jika Anda mencoba untuk menggunakan koneksi untuk output Anda masih perlu untuk set URLConnection#setDoOutput()
ke true
.
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
Either way, jika pihak lain adalah HttpServlet
, maka doPost()
metodenya akan dipanggil dan parameter akan tersedia olehHttpServletRequest#getParameter()
.
Sebenarnya mengaktifkan permintaan HTTP
Anda dapat memecat permintaan HTTP secara eksplisit dengan URLConnection#connect()
, tetapi permintaan akan secara otomatis dipecat saat diminta ketika Anda ingin mendapatkan informasi tentang respons HTTP, seperti badan respons yang menggunakan URLConnection#getInputStream()
dan sebagainya. Contoh di atas tidak persis seperti itu, jadi connect()
panggilan itu sebenarnya berlebihan.
Mengumpulkan informasi respons HTTP
Status respons HTTP :
Anda butuh di HttpURLConnection
sini. Keluarkan terlebih dahulu jika perlu.
int status = httpConnection.getResponseCode();
Header respons HTTP :
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
Pengodean respons HTTP :
Ketika Content-Type
berisi charset
parameter, maka badan respons kemungkinan berbasis teks dan kami ingin memproses badan respons dengan pengkodean karakter yang ditentukan sisi server.
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.
}
Mempertahankan sesi
Sesi sisi server biasanya didukung oleh cookie. Beberapa formulir web mengharuskan Anda masuk dan / atau dilacak oleh suatu sesi. Anda dapat menggunakan CookieHandler
API untuk mengelola cookie. Anda perlu menyiapkan CookieManager
dengan CookiePolicy
dari ACCEPT_ALL
sebelum mengirim semua permintaan 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();
// ...
Perhatikan bahwa ini diketahui tidak selalu berfungsi dengan baik dalam semua keadaan. Jika gagal untuk Anda, maka yang terbaik adalah mengumpulkan dan mengatur header cookie secara manual. Anda pada dasarnya perlu mengambil semua Set-Cookie
header dari respons login atau GET
permintaan pertama dan kemudian meneruskannya melalui permintaan berikutnya.
// 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]);
}
// ...
The split(";", 2)[0]
apakah ada untuk menyingkirkan atribut cookies yang tidak relevan untuk sisi server seperti expires
, path
, dll Atau, Anda bisa juga menggunakan cookie.substring(0, cookie.indexOf(';'))
bukan split()
.
Mode streaming
Secara HttpURLConnection
default, buffer akan meminta seluruh badan permintaan sebelum mengirimkannya, terlepas dari apakah Anda telah menetapkan sendiri panjang konten yang tetap digunakan connection.setRequestProperty("Content-Length", contentLength);
. Ini dapat menyebabkan OutOfMemoryException
setiap kali Anda secara bersamaan mengirim permintaan POST besar (misalnya mengunggah file). Untuk menghindari ini, Anda ingin mengatur HttpURLConnection#setFixedLengthStreamingMode()
.
httpConnection.setFixedLengthStreamingMode(contentLength);
Tetapi jika panjang konten benar-benar tidak diketahui sebelumnya, maka Anda dapat menggunakan mode streaming chunked dengan mengatur yang HttpURLConnection#setChunkedStreamingMode()
sesuai. Ini akan mengatur Transfer-Encoding
tajuk HTTP chunked
yang akan memaksa badan permintaan dikirim dalam bentuk potongan. Contoh di bawah ini akan mengirim tubuh dalam potongan 1KB.
httpConnection.setChunkedStreamingMode(1024);
Agen pengguna
Itu bisa terjadi bahwa permintaan mengembalikan respons yang tidak terduga, sementara itu berfungsi baik dengan browser web nyata . Sisi server mungkin memblokir permintaan berdasarkan User-Agent
header permintaan. Secara URLConnection
default akan mengaturnya ke Java/1.6.0_19
mana bagian terakhir jelas merupakan versi JRE. Anda dapat menimpa ini sebagai berikut:
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.
Gunakan string User-Agent dari browser terbaru .
Menangani kesalahan
Jika kode respons HTTP adalah 4nn
(Kesalahan Klien) atau 5nn
(Kesalahan Server), maka Anda mungkin ingin membaca HttpURLConnection#getErrorStream()
untuk melihat apakah server telah mengirim informasi kesalahan yang berguna.
InputStream error = ((HttpURLConnection) connection).getErrorStream();
Jika kode respons HTTP -1, maka ada yang salah dengan koneksi dan penanganan respons. The HttpURLConnection
implementasi di JRE yang lebih tua agak buggy dengan menjaga koneksi hidup. Anda mungkin ingin mematikannya dengan menyetel http.keepAlive
properti sistem ke false
. Anda dapat melakukan ini secara terprogram di awal aplikasi Anda dengan:
System.setProperty("http.keepAlive", "false");
Mengunggah file
Anda biasanya menggunakan multipart/form-data
pengodean untuk konten POST campuran (data biner dan karakter). Pengkodean lebih rinci dijelaskan dalam 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();
}
Jika sisi lain adalah a HttpServlet
, maka doPost()
metodenya akan dipanggil dan bagian-bagian akan tersedia oleh HttpServletRequest#getPart()
(perhatikan, maka tidak getParameter()
dan seterusnya!). Namun getPart()
metode ini relatif baru, diperkenalkan di Servlet 3.0 (Glassfish 3, Tomcat 7, dll). Sebelum ke Servlet 3.0, pilihan terbaik Anda menggunakan Apache Commons FileUpload untuk mengurai multipart/form-data
permintaan. Lihat juga jawaban ini untuk contoh pendekatan FileUpload dan Servelt 3.0.
Berurusan dengan situs HTTPS yang tidak terpercaya atau salah konfigurasi
Terkadang Anda perlu menghubungkan URL HTTPS, mungkin karena Anda sedang menulis scraper web. Dalam hal itu, Anda mungkin menghadapi javax.net.ssl.SSLException: Not trusted server certificate
beberapa situs HTTPS yang tidak memperbarui sertifikat SSL mereka, atau a java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
ataujavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
pada beberapa situs HTTPS yang tidak terkonfigurasi.
Berikut-satu kali menjalankan static
initializer di web kelas scraper Anda harus membuat HttpsURLConnection
lebih ringan untuk orang-orang HTTPS situs dan dengan demikian tidak membuang mereka pengecualian lagi.
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);
}
}
Kata-kata terakhir
The Apache HttpComponents HttpClient adalah jauh lebih nyaman dalam ini semua :)
Parsing dan mengekstraksi HTML
Jika semua yang Anda inginkan adalah parsing dan mengekstraksi data dari HTML, maka lebih baik gunakan parser HTML seperti Jsoup