ก่อนอื่นขอสงวนสิทธิ์ก่อน: ตัวอย่างรหัสที่โพสต์เป็นตัวอย่างพื้นฐานทั้งหมด คุณจะต้องจัดการกับจิ๊บจ๊อยIOException
และRuntimeException
เหมือนNullPointerException
, ArrayIndexOutOfBoundsException
และภริยาตัวเอง
เตรียมความพร้อม
ก่อนอื่นเราต้องรู้อย่างน้อยที่สุด 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พารามิเตอร์การค้นหาที่มี charset URLEncoder#encode()
ระบุโดยใช้
String#format()
เป็นเพียงเพื่อความสะดวกสบาย ฉันชอบเมื่อฉันต้องการตัวดำเนินการเรียงต่อกันสตริง+
มากกว่าสองครั้ง
การยิง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()
สำหรับวัตถุประสงค์ในการทดสอบคุณสามารถพิมพ์เนื้อหาการตอบสนองไปยัง stdout ได้ดังต่อไปนี้:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
การส่งคำร้องขอHTTP POSTด้วยพารามิเตอร์เคียวรี
การตั้งค่าURLConnection#setDoOutput()
ไปtrue
โดยปริยายกำหนดวิธีการร้องขอไปยังโพสต์ 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
สถานะการตอบกลับ HTTP :
คุณต้องการHttpURLConnection
ที่นี่ โยนก่อนถ้าจำเป็น
int status = httpConnection.getResponseCode();
ส่วนหัวการตอบสนอง HTTP :
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
การเข้ารหัสการตอบสนอง HTTP :
เมื่อ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 เพื่อดูแลคุกกี้ คุณจำเป็นต้องเตรียมความพร้อม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 ขนาดใหญ่ไปพร้อม ๆ กัน (เช่นการอัปโหลดไฟล์) HttpURLConnection#setFixedLengthStreamingMode()
เพื่อหลีกเลี่ยงนี้คุณต้องการที่จะตั้งค่า
httpConnection.setFixedLengthStreamingMode(contentLength);
แต่หากไม่ทราบความยาวของเนื้อหาล่วงหน้าจริงๆคุณสามารถใช้ประโยชน์จากโหมดสตรีมมิ่งแบบหนา ๆ ได้โดยตั้งค่าHttpURLConnection#setChunkedStreamingMode()
ตามความเหมาะสม นี้จะตั้งค่าTransfer-Encoding
ส่วนหัวHTTP chunked
ซึ่งจะบังคับให้ร่างกายร้องขอถูกส่งเป็นชิ้น ตัวอย่างด้านล่างจะส่งเนื้อหาเป็นหน่วยจำนวน 1KB
httpConnection.setChunkedStreamingMode(1024);
User-Agent
อาจเกิดขึ้นได้ว่าคำขอส่งคืนการตอบสนองที่ไม่คาดคิดในขณะที่ทำงานได้ดีกับเว็บเบราว์เซอร์จริง ฝั่งเซิร์ฟเวอร์อาจปิดกั้นคำขอตาม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
ใช้งานอยู่ใน JREs ที่ค่อนข้างเก่าด้วยการเชื่อมต่ออย่างต่อเนื่อง คุณอาจต้องการที่จะปิดโดยการตั้งค่าสถานที่ให้บริการระบบ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();
}
หากอีกด้านหนึ่งเป็น a วิธีการHttpServlet
ของมันdoPost()
จะถูกเรียกใช้และชิ้นส่วนจะพร้อมใช้งานโดยHttpServletRequest#getPart()
(หมายเหตุไม่ใช่ getParameter()
เช่นนั้นเป็นต้น!) อย่างไรก็ตามgetPart()
วิธีการนี้ค่อนข้างใหม่มันถูกนำมาใช้ใน Servlet 3.0 (Glassfish 3, Tomcat 7, ฯลฯ ) ก่อน Servlet 3.0 ทางเลือกที่ดีที่สุดของคุณคือใช้Apache Commons FileUploadเพื่อแยกวิเคราะห์multipart/form-data
คำขอ ดูคำตอบนี้สำหรับตัวอย่างของทั้งวิธี FileUpload และ Servelt 3.0
การจัดการกับเว็บไซต์ HTTPS ที่ไม่น่าเชื่อถือหรือกำหนดค่าผิดพลาด
บางครั้งคุณจำเป็นต้องเชื่อมต่อ HTTPS URL อาจเป็นเพราะคุณกำลังเขียน web scraper ในกรณีดังกล่าวคุณอาจเผชิญjavax.net.ssl.SSLException: Not trusted server certificate
กับไซต์ HTTPS บางแห่งที่ไม่ได้ทำการรับรอง SSL เป็นปัจจุบันหรือjava.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
หรือjavax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
ในบางเว็บไซต์ HTTPS ที่กำหนดค่าไม่ถูกต้อง
static
initializer ที่เรียกใช้ครั้งเดียวต่อไปนี้ในคลาส scraper เว็บของคุณควร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