Je ne comprends pas si ʻIOException se produit lors de l'appel de la méthode
getInputStream` de HttpUrlConnection Les traces de pile d'exceptions peuvent être renvoyées.
TL;DR
Si HttpUrlConnection # getInputStream
déclenche une ʻIOException, l 'ʻIOException
qui s'est produite dans cette instance dans le passé est imbriquée.
Supposons que vous ayez un code comme celui-ci:
--Préparez un serveur HTTP simple qui renvoie 400 demandes incorrectes de manière fixe --Envoyer une requête à un serveur HTTP simple en utilisant HttpUrLConnection --Appelez HttpUrLConnection # getInputStream pour lire la réponse
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.URL;
public class HttpURLConnectionTest {
public static void main(String[] args) throws IOException {
// HTTP Server
HttpServer server = HttpServer.create(new InetSocketAddress(8087), 0);
server.createContext("/", exchange -> {
Headers headers = exchange.getResponseHeaders();
headers.add("Content-type", "text/plain");
String body = "error!";
byte[] bytes = body.getBytes();
exchange.sendResponseHeaders(400, bytes.length);
try (OutputStream out = exchange.getResponseBody()) {
out.write(bytes);
}
});
server.start();
System.out.println("start server.");
// HTTP Client
HttpURLConnection conn = null;
try {
URL url = new URL("http://localhost:8087/");
conn = (HttpURLConnection) url.openConnection();
conn.connect();
int responseCode = conn.getResponseCode();
System.out.println("responseCode = " + responseCode); // 400
try (InputStream in = conn.getInputStream()) { //Une exception se produit ici
throw new AssertionError("Ne devrait pas venir ici");
}
} catch (IOException e) {
e.printStackTrace(); //J'obtiens une trace de pile que je ne comprends pas
} finally {
if (conn != null) conn.disconnect();
server.stop(0);
}
}
}
Une fois exécuté, la trace de pile suivante sera affichée.
Ce qui suit est affiché sur la console.
start server.
responseCode = 400
java.io.IOException: Server returned HTTP response code: 400 for URL: http://localhost:8087/
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1950)
at sun.net.www.protocol.http.HttpURLConnection$10.run(HttpURLConnection.java:1945)
at java.security.AccessController.doPrivileged(Native Method)
at sun.net.www.protocol.http.HttpURLConnection.getChainedException(HttpURLConnection.java:1944)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1514)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
at HttpURLConnectionTest.main(HttpURLConnectionTest.java:38)
Caused by: java.io.IOException: Server returned HTTP response code: 400 for URL: http://localhost:8087/
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1900)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1498)
at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
at HttpURLConnectionTest.main(HttpURLConnectionTest.java:35)
Process finished with exit code 0
HttpURLConnection lance ʻIOException ou
FileNotFoundExceptionsi
getInputStream` est appelé lorsque le code d'état de la réponse est 400 ou supérieur.
if (respCode >= 400) {
if (respCode == 404 || respCode == 410) {
throw new FileNotFoundException(url.toString());
} else {
throw new java.io.IOException("Server returned HTTP" +
" response code: " + respCode + " for URL: " +
url.toString());
}
}
Dans ce cas, le corps de la réponse ne peut pas être lu, sauf si vous utilisez la méthode getErrorStream
au lieu de getInputStream
.
C'est une API incroyable, mais cela fonctionne en fait de cette façon.
En regardant le sujet principal, la trace de pile, il y a des exceptions imbriquées.
En regardant la première exception, l'appel à getInputStream
a déclenché une ʻIOException`, qui est une trace de pile qui est cohérente avec les hypothèses ci-dessus.
Le problème est l'exception imbriquée.
En regardant les exceptions imbriquées, il semble que l'exception soit déclenchée dans l'appel précédent à getResponseCode
au lieu de getInputStream
. Cependant, en réalité, getResponseCode
ne lève pas d'exception et renvoie le code d'état 400 comme valeur de retour.
La trace de pile de l'appel de méthode exécuté deux lignes avant que getInputStream
ne soit imbriquée dans getInputStream
, qui est une trace de pile qui n'est pas vue dans la programmation Java normale.
Pourquoi une telle trace de pile se produit-elle?
--getResponseCode
appelle en interne getInputStream
(pour lire le code d'état)
--getInputStream
lance ʻIOException lorsque le code d'état est supérieur ou égal à 400 --À ce moment-là, enregistrez l'exception qui s'est produite dans le champ d'instance. --
getResponseCode ne lance pas l''IOException
générée et renvoie l'état comme valeur de retour.
--Lorsque ʻIOException se produit dans
getInputStream`, s'il y a une exception qui s'est produite auparavant, imbriquez-la dans cette exception.
Il est décrit comme ceci dans le code source.
/* Remembered Exception, we will throw it again if somebody
calls getInputStream after disconnect */
private Exception rememberedException = null;
Il dit: "Si getInputStream
est appelé après disconnect
, souvenez-vous de renvoyer l'exception."
Si une exception précédente s'est produite, imbriquez l'exception précédente dans la nouvelle exception (utilisez Throwable # initCause
).
private synchronized InputStream getInputStream0() throws IOException {
//Omission
if (rememberedException != null) {
if (rememberedException instanceof RuntimeException)
throw new RuntimeException(rememberedException);
else {
throw getChainedException((IOException)rememberedException);
}
}
try {
final Object[] args = { rememberedException.getMessage() };
IOException chainedException = //...
//Omission
//Imbriquez les exceptions précédentes ici
chainedException.initCause(rememberedException);
return chainedException;
} catch (Exception ignored) {
return rememberedException;
}
}
Pour cette raison, l 'ʻIOException qui s'est produite en interne lorsque le
getResponseCodea été démarré avant est sauvegardé, puis apparaît comme une exception imbriquée lorsque le
getInputStream` est démarré.
Du point de vue de l'utilisateur de l'API, getResponseCode
semble réussir, il ne semble donc pas pertinent que les exceptions imbriquées suivantes dans getInputStream
incluent la trace de pile du dernier getResponseCode
réussi.
Parce que je ne savais pas que je devais utiliser getInputStream
et getErrorStream
correctement
Quand j'ai regardé cette trace de pile, cela n'avait aucun sens.
Si HttpUrlConnection # getInputStream
déclenche une ʻIOException, l 'ʻIOException
qui s'est produite dans cette instance dans le passé est imbriquée.
Grâce à cette recherche, j'ai pensé que HttpUrlConnection avait les problèmes suivants.
getInputStream
et getErrorStream
.De nombreuses personnes ont remis en question la conception de l'API de 1.
De plus, si vous constatez que le comportement est le même dans Java 11 et 13, il est déduit que le comportement ne peut pas être modifié pour maintenir la compatibilité. C'est inévitable, mais au moins j'aimerais que la documentation de l'API s'améliore.
Ce n'est pas une API facile à utiliser à compléter, donc à moins que vous n'ayez une raison spécifique, telle que devoir utiliser une API standard, il semble préférable d'utiliser une autre bibliothèque.