D'abord un avertissement préalable: les extraits de code publiés sont tous des exemples de base. Vous devrez gérer les éléments triviaux IOException
et RuntimeException
similaires NullPointerException
, ArrayIndexOutOfBoundsException
et vous épouser.
En train de préparer
Nous devons d'abord connaître au moins l'URL et le jeu de caractères. Les paramètres sont facultatifs et dépendent des exigences fonctionnelles.
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));
Les paramètres de requête doivent être au name=value
format et être concaténés par &
. Normalement, vous devez également encoder par URL les paramètres de requête avec le jeu de caractères spécifié à l'aide de URLEncoder#encode()
.
C'est String#format()
juste pour plus de commodité. Je le préfère lorsque j'aurais besoin de l'opérateur de concaténation de chaînes +
plus de deux fois.
Lancer une requête HTTP GET avec (facultativement) des paramètres de requête
C'est une tâche insignifiante. C'est la méthode de demande par défaut.
URLConnection connection = new URL(url + "?" + query).openConnection();
connection.setRequestProperty("Accept-Charset", charset);
InputStream response = connection.getInputStream();
// ...
Toute chaîne de requête doit être concaténée avec l'URL à l'aide de ?
. L'en- Accept-Charset
tête peut indiquer au serveur le codage des paramètres. Si vous n'envoyez aucune chaîne de requête, vous pouvez laisser l'en- Accept-Charset
tête à l'écart. Si vous n'avez pas besoin de définir d'en-têtes, vous pouvez même utiliser la URL#openStream()
méthode de raccourci.
InputStream response = new URL(url).openStream();
// ...
Dans les deux cas, si l'autre côté est un HttpServlet
, sa doGet()
méthode sera appelée et les paramètres seront disponibles par HttpServletRequest#getParameter()
.
À des fins de test, vous pouvez imprimer le corps de la réponse sur stdout comme ci-dessous:
try (Scanner scanner = new Scanner(response)) {
String responseBody = scanner.useDelimiter("\\A").next();
System.out.println(responseBody);
}
Lancer une requête HTTP POST avec des paramètres de requête
Réglage de la URLConnection#setDoOutput()
à true
définit implicitement la méthode de requête POST à. Le HTTP POST standard comme le font les formulaires Web est de type application/x-www-form-urlencoded
dans lequel la chaîne de requête est écrite dans le corps de la demande.
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();
// ...
Remarque: chaque fois que vous souhaitez soumettre un formulaire HTML par programme, n'oubliez pas de prendre les name=value
paires d' <input type="hidden">
éléments dans la chaîne de requête et bien sûr aussi la name=value
paire de l' <input type="submit">
élément sur lequel vous souhaitez "appuyer" par programme (car qui a généralement été utilisé côté serveur pour distinguer si un bouton a été enfoncé et si oui, lequel).
Vous pouvez également jeter l'obtenu URLConnection
à HttpURLConnection
et utiliser sa HttpURLConnection#setRequestMethod()
place. Mais si vous essayez d'utiliser la connexion pour la sortie, vous devez toujours la régler URLConnection#setDoOutput()
sur true
.
HttpURLConnection httpConnection = (HttpURLConnection) new URL(url).openConnection();
httpConnection.setRequestMethod("POST");
// ...
Dans les deux cas, si l'autre côté est un HttpServlet
, sa doPost()
méthode sera appelée et les paramètres seront disponibles par HttpServletRequest#getParameter()
.
Déclencher réellement la requête HTTP
Vous pouvez déclencher explicitement la demande HTTP avec URLConnection#connect()
, mais la demande sera automatiquement déclenchée à la demande lorsque vous souhaitez obtenir des informations sur la réponse HTTP, telles que le corps de la réponse à l'aide de URLConnection#getInputStream()
etc. Les exemples ci-dessus font exactement cela, donc l' connect()
appel est en fait superflu.
Collecte des informations de réponse HTTP
Statut de réponse HTTP :
Vous en avez besoin HttpURLConnection
ici. Jetez-le d'abord si nécessaire.
int status = httpConnection.getResponseCode();
En-têtes de réponse HTTP :
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
System.out.println(header.getKey() + "=" + header.getValue());
}
Encodage de réponse HTTP :
Lorsque le Content-Type
contient un charset
paramètre, le corps de la réponse est probablement basé sur du texte et nous aimerions alors traiter le corps de la réponse avec le codage de caractères spécifié côté serveur.
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.
}
Maintenir la session
La session côté serveur est généralement soutenue par un cookie. Certains formulaires Web nécessitent que vous soyez connecté et / ou que vous soyez suivi par une session. Vous pouvez utiliser l' CookieHandler
API pour gérer les cookies. Vous devez préparer un CookieManager
avec CookiePolicy
des ACCEPT_ALL
avant d' envoyer toutes les requêtes 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();
// ...
Notez que cela est connu pour ne pas toujours fonctionner correctement en toutes circonstances. Si cela échoue pour vous, le mieux est de rassembler et de définir manuellement les en-têtes des cookies. Vous devez essentiellement récupérer tous les en- Set-Cookie
têtes de la réponse de la connexion ou de la première GET
demande, puis les transmettre aux demandes suivantes.
// 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]);
}
// ...
Le split(";", 2)[0]
est là pour se débarrasser des attributs de cookie qui ne sont pas pertinents pour le côté serveur comme expires
, path
, etc. Sinon, vous pouvez également utiliser au cookie.substring(0, cookie.indexOf(';'))
lieu de split()
.
Mode streaming
Le HttpURLConnection
testament par défaut met en mémoire tampon tout le corps de la requête avant de l'envoyer, que vous ayez défini vous-même une longueur de contenu fixe connection.setRequestProperty("Content-Length", contentLength);
. Cela peut entraîner des OutOfMemoryException
s lorsque vous envoyez simultanément des demandes POST volumineuses (par exemple, le téléchargement de fichiers). Pour éviter cela, vous souhaitez définir le HttpURLConnection#setFixedLengthStreamingMode()
.
httpConnection.setFixedLengthStreamingMode(contentLength);
Mais si la longueur du contenu n'est vraiment pas connue à l'avance, vous pouvez utiliser le mode de diffusion en morceaux en définissant le en HttpURLConnection#setChunkedStreamingMode()
conséquence. Cela définira l'en- Transfer-Encoding
tête HTTP sur chunked
lequel forcera l'envoi du corps de la demande par blocs. L'exemple ci-dessous enverra le corps en morceaux de 1 Ko.
httpConnection.setChunkedStreamingMode(1024);
Agent utilisateur
Il peut arriver qu'une demande renvoie une réponse inattendue, alors qu'elle fonctionne correctement avec un véritable navigateur Web . Le côté serveur bloque probablement les demandes sur la base de l'en- User-Agent
tête de demande. La URLConnection
volonté par défaut le placera là Java/1.6.0_19
où la dernière partie est évidemment la version JRE. Vous pouvez remplacer cela comme suit:
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.
Utilisez la chaîne User-Agent d'un navigateur récent .
La gestion des erreurs
Si le code de réponse HTTP est 4nn
(Erreur client) ou 5nn
(Erreur serveur), vous pouvez lire le HttpURLConnection#getErrorStream()
pour voir si le serveur a envoyé des informations d'erreur utiles.
InputStream error = ((HttpURLConnection) connection).getErrorStream();
Si le code de réponse HTTP est -1, quelque chose s'est mal passé avec la connexion et la gestion des réponses. L' HttpURLConnection
implémentation est dans les anciens JRE quelque peu boguée avec le maintien des connexions en vie. Vous pouvez le désactiver en définissant la http.keepAlive
propriété système sur false
. Vous pouvez le faire par programme au début de votre application en:
System.setProperty("http.keepAlive", "false");
Téléchargement de fichiers
Vous utiliseriez normalement l' multipart/form-data
encodage pour le contenu POST mixte (données binaires et caractères). Le codage est décrit plus en détail dans la 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();
}
Si l'autre côté est un HttpServlet
, alors sa doPost()
méthode sera appelée et les pièces seront disponibles par HttpServletRequest#getPart()
(notez donc pas getParameter()
et ainsi de suite!). La getPart()
méthode est cependant relativement nouvelle, elle est introduite dans Servlet 3.0 (Glassfish 3, Tomcat 7, etc.). Avant Servlet 3.0, votre meilleur choix est d'utiliser Apache Commons FileUpload pour analyser une multipart/form-data
demande. Consultez également cette réponse pour des exemples des approches FileUpload et Servelt 3.0.
Gestion des sites HTTPS non fiables ou mal configurés
Parfois, vous devez connecter une URL HTTPS, peut-être parce que vous écrivez un grattoir Web. Dans ce cas, vous pouvez probablement rencontrer un javax.net.ssl.SSLException: Not trusted server certificate
sur certains sites HTTPS qui ne tiennent pas à jour leurs certificats SSL, ou un java.security.cert.CertificateException: No subject alternative DNS name matching [hostname] found
ou javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name
sur certains sites HTTPS mal configurés.
L' static
initialiseur à exécution unique suivant dans votre classe Web Scraper devrait rendre HttpsURLConnection
plus indulgents ces sites HTTPS et donc ne plus lever ces exceptions.
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);
}
}
Derniers mots
L' Apache HttpComponents HttpClient est beaucoup plus pratique dans tout cela :)
Analyse et extraction de HTML
Si tout ce que vous voulez c'est analyser et extraire des données de HTML, alors mieux vaut utiliser un analyseur HTML comme Jsoup