Đầ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 IOException
và RuntimeException
thích NullPointerException
, ArrayIndexOutOfBoundsException
và 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-Charset
tiê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-Charset
tiê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 true
ngầ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-urlencoded
trong đó 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=value
cặ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=value
cặ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 HttpURLConnection
và 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-Type
chứa charset
tham 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 CookieHandler
API để duy trì cookie. Bạn cần phải chuẩn bị một CookieManager
với một CookiePolicy
số ACCEPT_ALL
trướ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 GET
yê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
Ý HttpURLConnection
chí 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 OutOfMemoryException
bấ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-Encoding
tiêu đề HTTP chunked
sẽ 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-Agent
tiêu đề yêu cầu. Ý URLConnection
chí 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 HttpURLConnection
triể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.keepAlive
hệ 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-data
mã 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-data
yê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 certificate
số 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] found
hoặc javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
trên một số trang web HTTPS bị định cấu hình sai.
Trình static
khở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ễ HttpsURLConnection
dà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