Đầu tiên từ chối trách nhiệm trước: đoạn mã được đăng là tất cả các ví dụ cơ bản. Bạn sẽ cần phải xử lý những thứ tầm thường IOExceptionvà RuntimeExceptionthích NullPointerException, ArrayIndexOutOfBoundsExceptionvà tự phối ngẫu.
Chuẩn bị
Trước tiên chúng ta cần biết ít nhất là URL và bộ ký tự. Các tham số là tùy chọn và phụ thuộc vào các yêu cầu chức năng.
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));
Các tham số truy vấn phải ở name=valueđịnh dạng và được nối bởi &. Thông thường bạn cũng sẽ mã hóa URL các tham số truy vấn bằng bộ ký tự được chỉ định bằng cách sử dụng URLEncoder#encode().
Chỉ String#format()là cho thuận tiện. Tôi thích nó khi tôi cần toán tử nối chuỗi +hơn hai lần.
Thực hiện yêu cầu HTTP GET với các tham số truy vấn (tùy chọn)
Đó là một nhiệm vụ tầm thường. Đây là phương thức yêu cầu mặc định.
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
Bất kỳ chuỗi truy vấn nào cũng cần được nối với URL bằng cách sử dụng ?. Các Accept-Charsettiêu đề có thể gợi ý các máy chủ gì mã hóa các thông số. Nếu bạn không gửi bất kỳ chuỗi truy vấn, sau đó bạn có thể để Accept-Charsettiêu đề ra. Nếu bạn không cần đặt bất kỳ tiêu đề nào, thì bạn thậm chí có thể sử dụng URL#openStream()phương pháp phím tắt.
InputStream response = new URL(url).openStream();
// ...
Dù bằng cách nào, nếu phía bên kia là a HttpServlet, thì doGet()phương thức của nó sẽ được gọi và các tham số sẽ có sẵn bởi HttpServletRequest#getParameter().
Đối với mục đích thử nghiệm, bạn có thể in phần thân phản hồi ra thiết bị xuất chuẩn như dưới đây:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
Bắn một yêu cầu POST HTTP với các tham số truy vấn
Đặt URLConnection#setDoOutput()thành truengầm định đặt phương thức yêu cầu thành POST. POST HTTP tiêu chuẩn như các biểu mẫu web thực hiện là loại application/x-www-form-urlencodedtrong đó chuỗi truy vấn được ghi vào phần thân yêu cầu.
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();
// ...
Lưu ý: bất cứ khi nào bạn muốn gửi biểu mẫu HTML theo chương trình, đừng quên đưa các name=valuecặp <input type="hidden">phần tử bất kỳ vào chuỗi truy vấn và tất nhiên cũng là name=valuecặp của<input type="submit"> phần tử mà bạn muốn "nhấn" theo chương trình (vì thường được sử dụng ở phía máy chủ để phân biệt nếu nhấn nút và nếu có thì nút nào).
Bạn cũng có thể cast được URLConnectionđến HttpURLConnectionvà sử dụng nó HttpURLConnection#setRequestMethod()để thay thế. Nhưng nếu bạn đang cố gắng sử dụng kết nối cho đầu ra, bạn vẫn cần phải đặt URLConnection#setDoOutput()thành true.
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
Dù bằng cách nào, nếu phía bên kia là a HttpServlet, thì doPost()phương thức của nó sẽ được gọi và các tham số sẽ có sẵn bởi HttpServletRequest#getParameter().
Thực tế bắn yêu cầu HTTP
Bạn có thể thực hiện yêu cầu HTTP một cách rõ ràng URLConnection#connect(), nhưng yêu cầu sẽ tự động được thực hiện theo yêu cầu khi bạn muốn nhận bất kỳ thông tin nào về phản hồi HTTP, chẳng hạn như cơ quan phản hồi sử dụng URLConnection#getInputStream(), v.v. Các ví dụ trên thực hiện chính xác điều đó, vì vậy connect()cuộc gọi thực tế là không cần thiết.
Thu thập thông tin phản hồi HTTP
Trạng thái phản hồi HTTP :
Bạn cần một HttpURLConnectionở đây. Đúc nó trước nếu cần thiết.
int status = httpConnection.getResponseCode();
Tiêu đề phản hồi HTTP :
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
Mã hóa phản hồi HTTP :
Khi Content-Typechứa charsettham số, thì phần thân phản hồi có khả năng dựa trên văn bản và chúng tôi muốn xử lý phần thân phản hồi bằng mã hóa ký tự được chỉ định ở phía máy chủ.
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.
}
Duy trì phiên
Phiên phía máy chủ thường được hỗ trợ bởi một cookie. Một số biểu mẫu web yêu cầu bạn đăng nhập và / hoặc được theo dõi bởi một phiên. Bạn có thể sử dụng CookieHandlerAPI để duy trì cookie. Bạn cần phải chuẩn bị một CookieManagervới một CookiePolicysố ACCEPT_ALLtrước khi gửi tất cả các yêu cầu 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();
// ...
Lưu ý rằng điều này được biết là không phải lúc nào cũng hoạt động đúng trong mọi trường hợp. Nếu nó không thành công với bạn, thì tốt nhất là thu thập thủ công và đặt các tiêu đề cookie. Về cơ bản bạn cần phải lấy tất cảSet-Cookie tiêu đề từ phản hồi của đăng nhập hoặc GETyêu cầu đầu tiên và sau đó chuyển thông qua các yêu cầu tiếp theo.
// 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]);
}
// ...
Có split(";", 2)[0]để loại bỏ các thuộc tính cookie không liên quan đến phía máy chủ như expires,path vv Ngoài ra, bạn cũng có thể sử dụng cookie.substring(0, cookie.indexOf(';'))thay vì split().
Chế độ truyền phát
Ý HttpURLConnectionchí theo mặc định sẽ đệm toàn bộ cơ thể yêu cầu trước khi thực sự gửi nó, bất kể bạn có tự đặt thời lượng nội dung cố định hay không connection.setRequestProperty("Content-Length", contentLength);. Điều này có thể gây ra OutOfMemoryExceptionbất cứ khi nào bạn gửi đồng thời các yêu cầu POST lớn (ví dụ: tải lên tệp). Để tránh điều này, bạn muốn đặt HttpURLConnection#setFixedLengthStreamingMode().
httpConnection.setFixedLengthStreamingMode(contentLength);
Nhưng nếu độ dài nội dung thực sự không được biết trước, thì bạn có thể sử dụng chế độ phát trực tuyến chunk bằng cách đặt HttpURLConnection#setChunkedStreamingMode()tương ứng. Điều này sẽ đặt Transfer-Encodingtiêu đề HTTP chunkedsẽ buộc phần thân yêu cầu được gửi theo khối. Ví dụ dưới đây sẽ gửi phần thân trong khối 1KB.
httpConnection.setChunkedStreamingMode(1024);
Đại lý người dùng
Nó có thể xảy ra rằng một yêu cầu trả về một phản hồi không mong muốn, trong khi nó hoạt động tốt với một trình duyệt web thực sự . Phía máy chủ có thể đang chặn các yêu cầu dựa trên User-Agenttiêu đề yêu cầu. Ý URLConnectionchí theo mặc định sẽ đặt nó Java/1.6.0_19ở nơi phần cuối rõ ràng là phiên bản JRE. Bạn có thể ghi đè lên điều này như sau:
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.
Sử dụng chuỗi User-Agent từ một trình duyệt gần đây .
Xử lý lỗi
Nếu mã phản hồi HTTP là 4nn(Lỗi máy khách) hoặc 5nn(Lỗi máy chủ), thì bạn có thể muốn đọc HttpURLConnection#getErrorStream()để xem máy chủ có gửi bất kỳ thông tin lỗi hữu ích nào không.
InputStream error = ((HttpURLConnection) connection).getErrorStream();
Nếu mã phản hồi HTTP là -1, thì có vấn đề với việc xử lý kết nối và phản hồi. Việc HttpURLConnectiontriển khai là trong các JRE cũ có phần lỗi với việc giữ kết nối còn sống. Bạn có thể muốn tắt nó bằng cách đặt thuộc tính http.keepAlivehệ thống thành false. Bạn có thể thực hiện việc này theo chương trình khi bắt đầu ứng dụng của mình bằng cách:
System.setProperty("http.keepAlive", "false");
Đang tải lên tập tin
Bạn thường sử dụng multipart/form-datamã hóa cho nội dung POST hỗn hợp (dữ liệu nhị phân và ký tự). Mã hóa chi tiết hơn được mô tả trong 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();
}
Nếu phía bên kia là một HttpServlet, thì doPost()phương thức của nó sẽ được gọi và các phần sẽ có sẵn bằng cách HttpServletRequest#getPart()(lưu ý, do đó, không phải getParameter() như vậy!). Các getPart()phương pháp được tuy nhiên tương đối mới, nó được giới thiệu trong Servlet 3.0 (Glassfish 3, Tomcat 7, vv). Trước Servlet 3.0, lựa chọn tốt nhất của bạn là sử dụng Apache Commons FileUpload để phân tích một multipart/form-datayêu cầu. Cũng xem câu trả lời này để biết ví dụ về cả hai cách tiếp cận FileUpload và Servelt 3.0.
Xử lý các trang web HTTPS không đáng tin cậy hoặc bị định cấu hình sai
Đôi khi bạn cần kết nối URL HTTPS, có lẽ vì bạn đang viết một trình quét web. Trong trường hợp đó, bạn có thể phải đối mặt với một javax.net.ssl.SSLException: Not trusted server certificatesố trang web HTTPS không cập nhật chứng chỉ SSL hoặc một java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] foundhoặc javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_nametrên một số trang web HTTPS bị định cấu hình sai.
Trình statickhởi tạo chạy một lần sau đây trong lớp trình quét web của bạn sẽ giúp dễ HttpsURLConnectiondàng hơn đối với các trang web HTTPS đó và do đó không ném các ngoại lệ đó nữa.
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);
}
}
Những từ cuối
Các Apache HttpClient HttpComponents là nhiều thuận tiện hơn trong tất cả điều này :)
Phân tích cú pháp và trích xuất HTML
Nếu tất cả những gì bạn muốn là phân tích cú pháp và trích xuất dữ liệu từ HTML, thì tốt hơn nên sử dụng trình phân tích cú pháp HTML như Jsoup