Prima una dichiarazione di non responsabilità: gli snippet di codice pubblicati sono tutti esempi di base. Avrai bisogno di gestire banali IOException
s e RuntimeException
s come NullPointerException
, ArrayIndexOutOfBoundsException
e consorti te.
Preparazione
Dobbiamo prima conoscere almeno l'URL e il set di caratteri. I parametri sono opzionali e dipendono dai requisiti funzionali.
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));
I parametri della query devono essere in name=value
formato e devono essere concatenati da &
. Normalmente dovresti anche codificare URL i parametri della query con il set di caratteri specificato usando URLEncoder#encode()
.
Il String#format()
è solo per convenienza. Lo preferisco quando avrei bisogno dell'operatore di concatenazione di stringhe +
più di due volte.
Attivazione di una richiesta GET HTTP con (facoltativamente) parametri di query
È un compito banale. È il metodo di richiesta predefinito.
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
Qualsiasi stringa di query deve essere concatenata all'URL utilizzando ?
. L' Accept-Charset
intestazione può suggerire al server la codifica dei parametri. Se non si invia alcuna stringa di query, è possibile lasciare l' Accept-Charset
intestazione. Se non è necessario impostare alcuna intestazione, è anche possibile utilizzare il URL#openStream()
metodo di scelta rapida.
InputStream response = new URL(url).openStream();
// ...
In entrambi i casi, se l'altro lato è a HttpServlet
, doGet()
verrà chiamato il suo metodo e i parametri saranno disponibili per HttpServletRequest#getParameter()
.
A scopo di test, è possibile stampare il corpo della risposta su stdout come di seguito:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
Attivazione di una richiesta POST HTTP con parametri di query
L'impostazione di URLConnection#setDoOutput()
su true
imposta implicitamente il metodo di richiesta su POST. Il POST HTTP standard come fanno i moduli Web è di tipo in application/x-www-form-urlencoded
cui la stringa di query è scritta nel corpo della richiesta.
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();
// ...
Nota: ogni volta che si desidera inviare un modulo HTML a livello di codice, non dimenticare di inserire le name=value
coppie di tutti gli <input type="hidden">
elementi nella stringa di query e, naturalmente, anche la name=value
coppia <input type="submit">
dell'elemento che si desidera "premere" a livello di codice (perché che di solito è stato utilizzato sul lato server per distinguere se un pulsante è stato premuto e, in caso affermativo, quale).
È anche possibile lanciare l'ottenuto URLConnection
per HttpURLConnection
e usare il suo HttpURLConnection#setRequestMethod()
posto. Ma se stai provando a utilizzare la connessione per l'output devi comunque impostare URLConnection#setDoOutput()
su true
.
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
In entrambi i casi, se l'altro lato è a HttpServlet
, doPost()
verrà chiamato il suo metodo e i parametri saranno disponibili per HttpServletRequest#getParameter()
.
Attivazione effettiva della richiesta HTTP
È possibile URLConnection#connect()
attivare esplicitamente la richiesta HTTP con , ma la richiesta verrà automaticamente attivata su richiesta quando si desidera ottenere informazioni sulla risposta HTTP, ad esempio il corpo della risposta URLConnection#getInputStream()
e così via. Gli esempi sopra fanno esattamente questo, quindi la connect()
chiamata è in effetti superflua.
Raccolta di informazioni sulla risposta HTTP
Stato della risposta HTTP :
Hai bisogno di un HttpURLConnection
qui. Lancia prima se necessario.
int status = httpConnection.getResponseCode();
Intestazioni di risposta HTTP :
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
Codifica risposta HTTP :
Quando Content-Type
contiene un charset
parametro, è probabile che il corpo della risposta sia basato sul testo e vorremmo quindi elaborare il corpo della risposta con la codifica dei caratteri specificata sul lato 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.
}
Mantenere la sessione
La sessione lato server è generalmente supportata da un cookie. Alcuni moduli web richiedono che tu abbia effettuato l'accesso e / o che sia tracciato da una sessione. Puoi utilizzare l' CookieHandler
API per conservare i cookie. È necessario preparare un CookieManager
con un CookiePolicy
di ACCEPT_ALL
prima di inviare tutte le richieste 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();
// ...
Si noti che questo non funziona sempre correttamente in tutte le circostanze. Se non riesce, è meglio raccogliere e impostare manualmente le intestazioni dei cookie. Fondamentalmente è necessario afferrare tutte le Set-Cookie
intestazioni dalla risposta dell'accesso o della prima GET
richiesta e quindi passare attraverso le richieste successive.
// 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]);
}
// ...
L' split(";", 2)[0]
è lì per sbarazzarsi di attributi di cookie, che sono irrilevanti per il lato server come expires
, path
ecc In alternativa, si potrebbe anche usare cookie.substring(0, cookie.indexOf(';'))
al posto di split()
.
Modalità streaming
Per HttpURLConnection
impostazione predefinita, bufferizza l' intero corpo della richiesta prima di inviarlo effettivamente, indipendentemente dal fatto che tu abbia impostato tu stesso una lunghezza del contenuto fissa connection.setRequestProperty("Content-Length", contentLength);
. Ciò può causare OutOfMemoryException
s ogni volta che si inviano contemporaneamente richieste POST di grandi dimensioni (ad esempio, il caricamento di file). Per evitare ciò, si desidera impostare il HttpURLConnection#setFixedLengthStreamingMode()
.
httpConnection.setFixedLengthStreamingMode(contentLength);
Ma se la lunghezza del contenuto non è davvero nota in anticipo, è possibile utilizzare la modalità di streaming suddivisa impostando di HttpURLConnection#setChunkedStreamingMode()
conseguenza. Ciò imposterà l' Transfer-Encoding
intestazione HTTP su chunked
cui forzerà l'invio del corpo della richiesta in blocchi. L'esempio seguente invierà il corpo in blocchi di 1 KB.
httpConnection.setChunkedStreamingMode(1024);
User-Agent
Può succedere che una richiesta restituisca una risposta inaspettata, mentre funziona bene con un browser web reale . Il lato server sta probabilmente bloccando le richieste in base all'intestazione della User-Agent
richiesta. Per URLConnection
impostazione predefinita, la volontà lo imposterà Java/1.6.0_19
dove l'ultima parte è ovviamente la versione JRE. Puoi sovrascriverlo come segue:
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.
Utilizzare la stringa User-Agent da un browser recente .
Gestione degli errori
Se il codice di risposta HTTP è 4nn
(Errore client) o 5nn
(Errore server), potresti voler leggere HttpURLConnection#getErrorStream()
per vedere se il server ha inviato utili informazioni di errore.
InputStream error = ((HttpURLConnection) connection).getErrorStream();
Se il codice di risposta HTTP è -1, qualcosa non ha funzionato con la connessione e la gestione della risposta. L' HttpURLConnection
implementazione nei vecchi JRE è in qualche modo difettosa nel mantenere vive le connessioni. È possibile disattivarlo impostando la http.keepAlive
proprietà di sistema su false
. Puoi farlo a livello di codice all'inizio della tua applicazione:
System.setProperty("http.keepAlive", "false");
Caricamento file
Normalmente useresti la multipart/form-data
codifica per contenuti POST misti (dati binari e di caratteri). La codifica è descritta più dettagliatamente in 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();
}
Se l'altro lato è a HttpServlet
, doPost()
verrà chiamato il suo metodo e le parti saranno disponibili da HttpServletRequest#getPart()
(nota, quindi no getParameter()
e così via!). Il getPart()
metodo è tuttavia relativamente nuovo, è stato introdotto in Servlet 3.0 (Glassfish 3, Tomcat 7, ecc.). Prima di Servlet 3.0, la scelta migliore è utilizzare FileUpload di Apache Commons per analizzare una multipart/form-data
richiesta. Vedi anche questa risposta per esempi di approccio FileUpload e Servelt 3.0.
Gestione di siti HTTPS non attendibili o non configurati correttamente
A volte è necessario collegare un URL HTTPS, forse perché si sta scrivendo un raschietto web. In tal caso, è possibile che si verifichino problemi javax.net.ssl.SSLException: Not trusted server certificate
con alcuni siti HTTPS che non mantengono aggiornati i loro certificati SSL java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
o javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
con alcuni siti HTTPS non configurati correttamente.
Il seguente static
inizializzatore di una sola volta nella classe del web scraper dovrebbe rendere HttpsURLConnection
più indulgente su tali siti HTTPS e quindi non generare più tali eccezioni.
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);
}
}
Ultime parole
L' Apache HttpComponents HttpClient è molto più conveniente in tutto questo :)
Analisi ed estrazione di HTML
Se tutto ciò che vuoi è analizzare ed estrarre dati dall'HTML, allora meglio usare un parser HTML come Jsoup