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();
}