Zunächst ein Haftungsausschluss: Die veröffentlichten Code-Schnipsel sind grundlegende Beispiele. Sie werden trivial behandeln müssen IOException
s und RuntimeException
s wie NullPointerException
, ArrayIndexOutOfBoundsException
und Konsorten selbst.
Vorbereiten
Wir müssen zuerst mindestens die URL und den Zeichensatz kennen. Die Parameter sind optional und hängen von den funktionalen Anforderungen ab.
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));
Die Abfrageparameter müssen name=value
formatiert und von verkettet sein &
. Normalerweise würden Sie die Abfrageparameter auch mit dem angegebenen Zeichensatz per URL codierenURLEncoder#encode()
.
Das String#format()
ist nur zur Vereinfachung. Ich bevorzuge es, wenn ich den String-Verkettungsoperator +
mehr als zweimal benötigen würde .
Auslösen einer HTTP-GET- Anforderung mit (optional) Abfrageparametern
Es ist eine triviale Aufgabe. Dies ist die Standardanforderungsmethode.
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
Jede Abfragezeichenfolge sollte mit der URL verkettet werden ?
. Der Accept-Charset
Header weist den Server möglicherweise darauf hin, in welcher Codierung sich die Parameter befinden. Wenn Sie keine Abfragezeichenfolge senden, können Sie den Accept-Charset
Header weglassen. Wenn Sie keine Überschriften festlegen müssen, können Sie sogar die URL#openStream()
Verknüpfungsmethode verwenden.
InputStream response = new URL(url).openStream();
// ...
In beiden Fällen wird die Methode aufgerufen , wenn die andere Seite a ist HttpServlet
, doGet()
und die Parameter stehen zur Verfügung HttpServletRequest#getParameter()
.
Zu Testzwecken können Sie den Antworttext wie folgt auf stdout drucken:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
Auslösen einer HTTP-POST- Anforderung mit Abfrageparametern
Durch Setzen von URLConnection#setDoOutput()
to wird true
die Anforderungsmethode implizit auf POST gesetzt. Der Standard-HTTP-POST wie bei Webformularen ist vom Typ, application/x-www-form-urlencoded
bei dem die Abfragezeichenfolge in den Anforderungshauptteil geschrieben wird.
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();
// ...
Hinweis: Wenn Sie möchten , dass ein HTML - Formular programmatisch unterbreiten, nicht zu vergessen die nehmen name=value
Paare von irgendwelchen <input type="hidden">
Elementen in die Query - String und natürlich auch das name=value
Paar aus dem <input type="submit">
Element , das Sie zu „drücken“ programmatisch möchten (weil Dies wird normalerweise auf der Serverseite verwendet, um zu unterscheiden, ob und welche Taste gedrückt wurde.
Sie können auch die erhaltenen Guss URLConnection
zu HttpURLConnection
verwenden , das HttpURLConnection#setRequestMethod()
statt. Aber wenn Sie versuchen , die Verbindung für die Ausgabe zu verwenden , müssen Sie noch Satz URLConnection#setDoOutput()
zu true
.
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
In beiden Fällen wird die Methode aufgerufen , wenn die andere Seite a ist HttpServlet
, doPost()
und die Parameter stehen zur Verfügung HttpServletRequest#getParameter()
.
Die HTTP-Anfrage wird tatsächlich ausgelöst
Sie können die HTTP-Anforderung explizit mit URLConnection#connect()
auslösen, die Anforderung wird jedoch automatisch bei Bedarf ausgelöst, wenn Sie Informationen zur HTTP-Antwort abrufen möchten, z. B. den verwendeten Antworttext URLConnection#getInputStream()
usw. Die obigen Beispiele machen genau das, so dass der connect()
Aufruf tatsächlich überflüssig ist.
Sammeln von HTTP-Antwortinformationen
HTTP-Antwortstatus :
Du brauchst ein HttpURLConnection
hier. Wirf es zuerst, wenn nötig.
int status = httpConnection.getResponseCode();
HTTP-Antwortheader :
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
HTTP-Antwortcodierung :
Wenn das Content-Type
einen charset
Parameter enthält , ist der Antworttext wahrscheinlich textbasiert und wir möchten den Antworttext dann mit der serverseitig angegebenen Zeichencodierung verarbeiten.
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.
}
Sitzung pflegen
Die serverseitige Sitzung wird normalerweise durch ein Cookie unterstützt. Einige Webformulare erfordern, dass Sie angemeldet sind und / oder von einer Sitzung verfolgt werden. Sie können die CookieHandler
API verwenden, um Cookies zu verwalten. Sie müssen ein CookieManager
mit einem CookiePolicy
von vorbereiten, ACCEPT_ALL
bevor Sie alle HTTP-Anforderungen senden.
// 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();
// ...
Beachten Sie, dass dies bekanntermaßen nicht immer unter allen Umständen ordnungsgemäß funktioniert. Wenn dies für Sie fehlschlägt, sollten Sie die Cookie-Header am besten manuell erfassen und festlegen. Grundsätzlich müssen Sie alle Set-Cookie
Header aus der Antwort des Logins oder der ersten GET
Anforderung abrufen und diese dann durch die nachfolgenden Anforderungen weiterleiten.
// 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]);
}
// ...
Das split(";", 2)[0]
wird bekommen Cookie Attribute befreien , die wie für die Server - Seite nicht relevant sind expires
, path
usw. Alternativ können Sie auch nutzen könnten cookie.substring(0, cookie.indexOf(';'))
statt split()
.
Streaming-Modus
Das HttpURLConnection
wird standardmäßig puffer den gesamten Körper Anforderung bevor es tatsächlich zu senden, unabhängig davon , ob Sie eine feste Inhaltslänge festgelegt haben , sie mit connection.setRequestProperty("Content-Length", contentLength);
. Dies kann zu OutOfMemoryException
s führen, wenn Sie gleichzeitig große POST-Anforderungen senden (z. B. Dateien hochladen). Um dies zu vermeiden, möchten Sie die einstellen HttpURLConnection#setFixedLengthStreamingMode()
.
httpConnection.setFixedLengthStreamingMode(contentLength);
Wenn die Länge des Inhalts jedoch vorher nicht wirklich bekannt ist, können Sie den Chunked-Streaming-Modus verwenden, indem Sie den HttpURLConnection#setChunkedStreamingMode()
entsprechenden Wert einstellen . Dadurch wird der HTTP- Transfer-Encoding
Header festgelegt, an den chunked
das Senden des Anforderungshauptteils in Blöcken erzwungen wird. Im folgenden Beispiel wird der Body in Blöcken von 1 KB gesendet.
httpConnection.setChunkedStreamingMode(1024);
User-Agent
Es kann vorkommen, dass eine Anfrage eine unerwartete Antwort zurückgibt, während dies mit einem echten Webbrowser problemlos funktioniert . Die Serverseite blockiert wahrscheinlich Anforderungen basierend auf dem User-Agent
Anforderungsheader. Das URLConnection
Testament legt standardmäßig fest, Java/1.6.0_19
wo der letzte Teil offensichtlich die JRE-Version ist. Sie können dies wie folgt überschreiben:
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.
Verwenden Sie die User-Agent-Zeichenfolge eines aktuellen Browsers .
Fehlerbehandlung
Wenn der HTTP-Antwortcode 4nn
(Clientfehler) oder 5nn
(Serverfehler) lautet , möchten Sie möglicherweise den lesen, HttpURLConnection#getErrorStream()
um festzustellen, ob der Server nützliche Fehlerinformationen gesendet hat.
InputStream error = ((HttpURLConnection) connection).getErrorStream();
Wenn der HTTP-Antwortcode -1 ist, ist bei der Verbindung und der Antwortbehandlung ein Fehler aufgetreten. Die HttpURLConnection
Implementierung in älteren JREs ist etwas fehlerhaft, da die Verbindungen am Leben bleiben. Sie können es deaktivieren, indem Sie die http.keepAlive
Systemeigenschaft auf setzen false
. Sie können dies zu Beginn Ihrer Bewerbung programmgesteuert tun, indem Sie:
System.setProperty("http.keepAlive", "false");
Dateien hochladen
Normalerweise verwenden Sie die multipart/form-data
Codierung für gemischte POST-Inhalte (Binär- und Zeichendaten). Die Codierung wird ausführlicher in RFC2388 beschrieben .
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();
}
Wenn die andere Seite a ist HttpServlet
, wird ihre doPost()
Methode aufgerufen und die Teile sind verfügbar von HttpServletRequest#getPart()
(beachten Sie, also nicht getParameter()
und so weiter!). Die getPart()
Methode ist jedoch relativ neu und wurde in Servlet 3.0 (Glassfish 3, Tomcat 7 usw.) eingeführt. Vor Servlet 3.0 verwenden Sie am besten Apache Commons FileUpload , um eine multipart/form-data
Anforderung zu analysieren . In dieser Antwort finden Sie auch Beispiele für die Ansätze FileUpload und Servelt 3.0.
Umgang mit nicht vertrauenswürdigen oder falsch konfigurierten HTTPS-Sites
Manchmal müssen Sie eine HTTPS-URL verbinden, möglicherweise weil Sie einen Web-Scraper schreiben. In diesem Fall kann es javax.net.ssl.SSLException: Not trusted server certificate
auf einigen HTTPS-Sites zu Problemen kommen, die ihre SSL-Zertifikate nicht auf dem neuesten Stand halten, java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
oder javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
auf einigen oder auf einigen falsch konfigurierten HTTPS-Sites.
Der folgende einmalige static
Initialisierer in Ihrer Web-Scraper-Klasse sollte HttpsURLConnection
die HTTPS-Sites milder gestalten und daher diese Ausnahmen nicht mehr auslösen.
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);
}
}
Letzte Worte
Der Apache HttpComponents HttpClient ist in dieser Hinsicht viel praktischer :)
HTML analysieren und extrahieren
Wenn Sie nur Daten aus HTML analysieren und extrahieren möchten, verwenden Sie besser einen HTML-Parser wie Jsoup