Un client HTTP à la main
- Création d'une socket cliente vers le serveur
- Envoi d'une requête (en-têtes ASCII, éventuel corps encodé correctement pour un formulaire)
- Réception des données du serveur avec analyse préalable des en-têtes
- Éventuelle gestion des connexions persistantes et du pipelining
- Éventuel support de proxys
- Plus complexe avec HTTP/2.0
La classe HttpURLConnection
- Dérive de URLConnection
- Permet d'envoyer une requête vers un serveur
- Support possible de piscine de connexions persistantes
-
Existe en version https pour les connexions TLS :
- HttpsUrlConnection (avec personnalisation possible du HostnameVerifier)
-
Client HTTP simple instanciable depuis une URL :
- URL url = new URL(address) ;
- HttpURLConnection conn = (HttpURLConnection)url.openConnection() ;
- InputStream is = new BufferedInputStream(conn.getInputStream()) ;;
- Client HTTP bloquant : à employer possiblement dans une nouvelle thread.
- Timeout en lecture avec setReadTimeout(int millis) ; lève SocketTimeoutException
-
Pour envoyer un fichier
- conn.setDoOutput(true) ;
- conn.setChunkedStreamingMode(0) ; // Use the chunked mode
- OutputStream out = new BufferedOutputStream(conn.getOutputStream()) ;
- Ne pas oublier de fermer la connexion : conn.disconnect()
- Gestion automatique d'un pool de connexions ouvertes (réutilisation de connexions) ; désactivation avec System.setProperty("http.keepAlive", "false")
Concernant la version Android :
- android.net.http.HttpResponseCache fournit un cache depuis ICS
Propriétés système réseau
- Pour les proxys HTTP : http.proxyHost, http.proxyPort, http.nonProxyHosts (hôtes séparés par des | pour lesquels on ne doit pas passer par un proxy) ; propriétés existantes en version https
- Pour les proxys SOCKS : socksProxyHost, socksProxyPort, socksProxyVersion, java.net.socks.username, java.net.socks.password
- Pour utiliser les propriétés proxy du système : java.net.useSystemProxies (par défaut : false)
- Pour fixer le nom d'agent HTTP : http.agent
- Pour les connexions persistantes : http.keepalive (par défaut : true), http.maxConnections pour paramétrer le nombre de connexions persistantes à conserver pour chaque hôte
- Pour les autres propriétés : http://docs.oracle.com/javase/7/docs/api/java/net/doc-files/net-properties.html
Initialisation d'une propriété système :
- Sur la ligne de commande : -DpropertyName=propertyValue
- ou dynamiquement avec System.setProperty(String propertyName, String propertyValue
Utilisation d'URI et d'URL
- URI : Uniform Resource Identifier
- URL : Uniform Resource Locator
Des classes pour URI et URL existent.
- À la différence d'URI, URL réalise une résolution de nom et ne fonctionne que si le schéma est supporté.
- Initialisation de URL et URI avec un constructeur à un argument (String) ou plusieurs.
- Possibilité de convertir une URI en URL (ou vice-versa) avec toURL() ou toURI().
- Intérêt de URL : ouverture directe d'une connexion vers le serveur avec URLConnection openConnection(); récupération directe de l'InputStream avec InputStream openStream().
Une méthode pour télécharger un fichier
public void downloadFile(String url, String destination) throws IOException { try ( InputStream is = new URL(url).openStream(); OutputStream out = new FileOutputStream(destination); ) { transfer(is, out); } }
Configuration de HttpURLConnection
HttpURLConnection conn = (HttpURLConnection)new URL(url).openConnection(); // We can add a new header field conn.addRequestProperty("headerName", "headerValue"); // We may specify a conditional retrieval conn.setIfModifiedSince(time); // We don't want to send data to the server conn.setDoOutput(false); // Now we connect to the server // (it could raise an IOException) conn.connect() // We retrieve the status code int code = conn.getResponseCode(); // We can fetch a map of the response headers Map<String, List<String>> map = conn.getHeaderFields(); // Some methods are predefined to fetch the common headers String mime = conn.getContentType(); // We can work on the stream returned by the server InputStream stream = conn.getInputStream();
Gestion des cookies (sous Android)
-
On initialise le CookieHandler de la VM :
- CookieManager cm = new CookieManager()
- CookieHandler.setDefault(cm) ;
-
On ajoute des cookies :
-
Manuellement
- HttpCookie cookie = new HttpCookie("key", "value") ;
- cookie.setDomain("univ-mlv.fr") ;
- cm.getCookieStore().add(new URI("http://www.univ-mlv.fr"), cookie) ;
- Automatiquement par HttpURLConnection
-
Manuellement
- Les cookies résident dans la mémoire du processus : pour un stockage persistent il faut fournir un CookieStore personnalisé à CookieManager
Exemples de code pour poster du contenu (méthode POST)
POST en urlencoded
public static void postURLEncoder(HttpURLConnection conn, Map<String, ?> data) throws IOException { // Encode data conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setChunkedStreamingMode(0); // We don't already know the total size, does not work with HTTP/1.0 conn.connect(); Writer w = new BufferedWriter( new OutputStreamWriter(conn.getOutputStream(), "ASCII")); boolean first = true; for (Map.Entry<String, ?> entry: data.entrySet()) { if (! first) w.write("&"); // Add a separator else first = false; w.write(URLEncoder.encode(entry.getKey(), "UTF-8")); w.write("="); w.write(URLEncoder.encode(entry.getValue().toString(), "UTF-8")); } w.close(); }
POST en form-data (supporte les fichiers)
public static void postFormData(HttpURLConnection conn, Map<String, Object> data) throws IOException { conn.setDoOutput(true); conn.setRequestMethod("POST"); String boundary = Long.toHexString(new Random().nextLong()); // Random string for boundary conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); conn.setChunkedStreamingMode(0); OutputStream os = new BufferedOutputStream(conn.getOutputStream()); for (Map.Entry<String, Object> entry: data.entrySet()) { if (entry.getValue() == null) continue; String header = "--" + boundary + "\r\n" + "Content-Disposition: form-data; field=\"" + entry.getKey() + "\"\r\n" + "Content-Type: " + ((entry.getValue() instanceof String)? "text/plain; charset=UTF-8":"application/octet-stream") + "\r\n\r\n"; os.write(header.getBytes("ASCII")); if (entry.getValue() instanceof String) os.write(entry.getValue().toString().getBytes("UTF-8")); else if (entry.getValue() instanceof InputStream) { InputStream is = (InputStream)entry.getValue(); byte[] buffer = new byte[BUFFER_LEN]; for (int r = is.read(buffer); r >= 0; r = is.read(buffer)) os.write(buffer, 0, r); } else throw new IOException( "Object of type " + entry.getValue().getClass() + " is not supported"); os.write("\r\n".getBytes("ASCII")); } os.write(("--" + boundary + "--\r\n").getBytes("ASCII")); os.close(); }