[JAVA] [Mise à jour] Il était difficile de passer de la série httpclient 3.x à la version 4.5, je vais donc rédiger un résumé des modifications

Je ne sais pas ce que j'ai fait, alors j'écrirai ce que j'ai changé.

J'ai été autorisé à faire référence.

** Prend en charge les changements d'interface majeurs de Java apache HttpClient 4.3 (@mychaelstyle) ** https://qiita.com/mychaelstyle/items/e02b3011d1e71bfa26c5 ** Demande avec authentification BASIC (@sh_kawakami) au site HTTPS avec CloseableHttpClient d'Apache httpclient ** https://qiita.com/sh_kawakami/items/bf6d1397851cccd134b2 ** Il semble que l'interface de httpclient a considérablement changé depuis la version 4.3 (@sakito) ** https://qiita.com/sakito/items/6366015dbbc4a88d56fc What does setDefaultMaxPerRoute and setMaxTotal mean in HttpClient? Colonne de réponse de https://stackoverflow.com/questions/30689995/what-does-setdefaultmaxperroute-and-setmaxtotal-mean-in-httpclient

environnement

JDK 1.8.162 httpclient 4.5.4

pom.xml

pom.xml


<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.4</version>
</dependency>

Belle liste obsolète

J'ai regardé autour de moi comme si je n'avais jamais été aidé par la liste obsolète

―― Série 3.x https://hc.apache.org/httpclient-3.x/apidocs/deprecated-list.html ―― Série 4.x http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/deprecated-list.html

Beau tutoriel

http://hc.apache.org/httpcomponents-client-ga/tutorial/html/index.html

Beau guide rapide

Puisqu'il s'agit d'un guide pour passer de 3.x à 4.0, il peut changer à nouveau lorsqu'il atteint 4,5. http://debuguide.blogspot.jp/2013/01/quick-guide-for-migration-of-commons.html

Liste des constantes utiles

―― Série 3.x https://hc.apache.org/httpclient-3.x/apidocs/constant-values.html ―― Série 4.x http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/constant-values.html

Débarrassez-vous des erreurs de compilation

Changement de package

Passer à l'API standard

Un système complètement différent

La manière de créer une instance a changé en premier lieu

before.java


HttpClient client = new HttpClient();

Utilisons Builder! Il paraît que. Si vous n'avez à penser à rien, ci-dessous.

after.java


HttpClient client = HttpClients.createDefault();

Cependant, cela peut ne pas être très pratique car vous devez passer les paramètres de connexion au moment de la génération.

changement de classe de méthode

GetMethod→HttpGet PostMethod→HttpPost Classe parent HttpMethod → HttpRequestBase Changer pour

Exécution et obtenir une réponse

C'était comme ça avant

before.java


PostMethod post = new PostMethod(url);
post.setRequestEntity(Définir quelque chose);
int responseCode = client.executeMethod(post);
if (responseCode == 200) {
	Reader reader = new InputStreamReader(
			httpMethod.getResponseBodyAsStream(), httpMethod.getResponseCharSet());
	//Traitement ultérieur
} else {
  //La gestion des erreurs
}

Ça a beaucoup changé et ça ressemble à ça. Mais maintenant que c'est clair, cela peut être plus facile à comprendre.

after.java


HttpPost post = new HttpPost(url);
//Définissez celui qui correspond au corps de la requête
post.setEntity(Définir quelque chose);
HttpResponse response = client.execute(post);
StatusLine statusLine = response.getStatusLine();
int responseCode = statusLine.getStatusCode();
if (responseCode == HttpStatus.SC_OK) {
	Reader reader = new InputStreamReader(
			response.getEntity().getContent(), Charset.forName("UTF-8"));
	//Traitement ultérieur
} else {
	//La gestion des erreurs
}

Autour de la connexion d'authentification de base

J'avais l'habitude de le faire comme ça.

before.java


Credentials credentials = new UsernamePasswordCredentials(this.username, this.password);
AuthScope scope = new AuthScope(host, port, this.realm);
client.getState().setCredentials(scope, credentials);

Il semble que vous devriez utiliser CredentialsProvider. (Merci à @sh_kawakami!)

after.java


CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(new AuthScope(baseUrl.getHost(),baseUrl.getPort()),
        new UsernamePasswordCredentials(username, password));
//Je le définirai lors de la création d'une instance
HttpClient client = HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).build();

Interface NameValuePair

Il semble que la classe d'entité soit maintenant BasicNameValuePair. Il semble qu'il soit simplement divisé par "=", donc je prie pour que le comportement ne change pas comme avant. Mais le passeur est parti! !! !! Comme je l'ai vu, il semble qu'il ne puisse être défini qu'avec le constructeur. Si vous souhaitez toujours utiliser setter, vous devez hériter de NameValuePair et créer votre propre classe.

Ceci et cela qui a causé une erreur lors de l'exécution

Le HttpClient par défaut n'envoie plus l'en-tête Host

Ajoutons-le nous-mêmes

//en-tête d'hôte
httpMethod.addHeader("Host", uri.getHost());

ConnectionClosedException se produit lorsque l'en-tête Content-Length et la taille de ResponseBody ne correspondent pas

Se produit lorsque la taille de la réponse est différente de celle du Body s'il existe un en-tête Content-Length. Cela ne se produit pas sans l'en-tête Content-Length (bien que cela ressemble à une violation RFC) Par exemple, cela se produit lorsque la réponse est comme ça. Eh bien, aujourd'hui, je pense que Content-Length est automatiquement calculé et renvoyé par le serveur sans autorisation, donc je pense que cela ne se produira probablement pas, mais pour diverses raisons, je ne fais pas confiance à l'en-tête de la réponse, donc: suer:

HTTP/1.1 200 OK
Content-Type: text/html; charset=Shift_JIS
Content-Length: 3000
<html>
<head><title>Hoge</title>
</head>
<body>test
</body>
</html>
org.apache.http.ConnectionClosedException: Premature end of Content-Length delimited message body (expected: 3000; received: 124
	at org.apache.http.impl.io.ContentLengthInputStream.read(ContentLengthInputStream.java:178)
	at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:135)
	at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:148)

La cause est ici

java:org.apache.http.impl.io.ContentLengthInputStream.java


    /**
     * Does standard {@link InputStream#read(byte[], int, int)} behavior, but
     * also notifies the watcher when the contents have been consumed.
     *
     * @param b     The byte array to fill.
     * @param off   Start filling at this position.
     * @param len   The number of bytes to attempt to read.
     * @return The number of bytes read, or -1 if the end of content has been
     *  reached.
     *
     * @throws java.io.IOException Should an error occur on the wrapped stream.
     */
    @Override
    public int read (final byte[] b, final int off, final int len) throws java.io.IOException {
        if (closed) {
            throw new IOException("Attempted read from closed stream.");
        }

        if (pos >= contentLength) {
            return -1;
        }

        int chunk = len;
        if (pos + len > contentLength) {
            chunk = (int) (contentLength - pos);
        }
        final int count = this.in.read(b, off, chunk);
        if (count == -1 && pos < contentLength) {
            throw new ConnectionClosedException(
                    "Premature end of Content-Length delimited message body (expected: "
                    + contentLength + "; received: " + pos);
        }
        if (count > 0) {
            pos += count;
        }
        return count;
    }

Je n'ai pas encore trouvé de solution.

Erreur selon laquelle le certificat et le nom d'hôte ne correspondent pas lors de la communication SSL (y compris l'auto-certificat)

javax.net.ssl.SSLPeerUnverifiedException: Certificate for <hostname> doesn't match any of the subject alternative names:

Il semble que le nom d'hôte soit vérifié par défaut. La partie pertinente est ici

java:org.apache.http.conn.ssl.SSLConnectionSocketFactory.java#verifyHostname


            if (!this.hostnameVerifier.verify(hostname, session)) {
                final Certificate[] certs = session.getPeerCertificates();
                final X509Certificate x509 = (X509Certificate) certs[0];
                final List<SubjectName> subjectAlts = DefaultHostnameVerifier.getSubjectAltNames(x509);
                throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
                        "of the subject alternative names: " + subjectAlts);
            }

Étant donné que vérifier ne doit renvoyer que true, il semble qu'une instance de la classe NoopHostnameVerifier devrait être utilisée. Désactiver la vérification du certificat SSL et la vérification du nom d'hôte dans Apache HttpComponents / Client Chapter 2. Connection management 2.7.4. Hostname verification Dans mon cas, je ne configure pas SSLContext ou SSLConnectionSocketFactory directement dans httpclient, mais je l'implémente en le définissant dans ConnectionManager. À ce point. Cela ressemble donc à ce qui suit.

public static PoolingHttpClientConnectionManager createConnectionManager() {
    SSLContext sslContext = null;
	try {
		sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy(){
			@Override
			public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
				return true;
			}
		}
		).build();
	} catch (Exception e) {
		e.printStackTrace();
	}
	SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
	Registry<ConnectionSocketFactory> registry =
			RegistryBuilder.<ConnectionSocketFactory>create()
					.register("http", PlainConnectionSocketFactory.getSocketFactory())
					.register("https", sslsf)
					.build();
	return new PoolingHttpClientConnectionManager(registry);
}

Autres sites auxquels j'ai fait référence

DefaultHttpRequestRetryHandler d'Apache HttpClient ne réessaye pas sur ConnectTimeoutException http://kntmr.hatenablog.com/entry/2016/12/09/150615 Use of non-ascii credentials not working in httpclient 4.3.x https://stackoverflow.com/questions/27955067/use-of-non-ascii-credentials-not-working-in-httpclient-4-3-x

Recommended Posts

[Mise à jour] Il était difficile de passer de la série httpclient 3.x à la version 4.5, je vais donc rédiger un résumé des modifications
Les rails étaient difficiles, alors j'ai fait quelque chose comme un contrôleur Spring Framework pour faire une pause
J'ai pu obtenir OCJP Silver SE 11 en toute sécurité, donc un résumé
Résumé des points qui m'inquiétaient lors de la migration de Java vers Kotlin
Résumé de la mousse lors de la mise à jour de JMockit 1.4 vers 1.30
clone de kintone? J'étais assez accro au lancement de l'extension OSS WebDB avec Lightsail + Docker, alors prenez-en note.