web-dev-qa-db-fra.com

Pourquoi devez-vous appeler URLConnection # getInputStream pour pouvoir écrire dans URLConnection # getOutputStream?

J'essaie d'écrire à URLConnection#getOutputStream , cependant, aucune donnée n'est réellement envoyée avant que j'appelle URLConnection#getInputStream . Même si je mets URLConnnection#doInput à false, il n'enverra toujours pas. Est-ce que quelqu'un sait pourquoi c'est comme ça? Il n'y a rien dans la documentation de l'API qui décrit cela.

Documentation de l'API Java sur URLConnection: http://download.Oracle.com/javase/6/docs/api/Java/net/URLConnection.html

Tutoriel Java sur la lecture et l'écriture sur une connexion URLC: http://download.Oracle.com/javase/tutorial/networking/urls/readingWriting.html

import Java.io.IOException;
import Java.io.OutputStreamWriter;
import Java.net.URL;
import Java.net.URLConnection;

public class UrlConnectionTest {

    private static final String TEST_URL = "http://localhost:3000/test/hitme";

    public static void main(String[] args) throws IOException  {

        URLConnection urlCon = null;
        URL url = null;
        OutputStreamWriter osw = null;

        try {
            url = new URL(TEST_URL);
            urlCon = url.openConnection();
            urlCon.setDoOutput(true);
            urlCon.setRequestProperty("Content-Type", "text/plain");            

            ////////////////////////////////////////
            // SETTING THIS TO FALSE DOES NOTHING //
            ////////////////////////////////////////
            // urlCon.setDoInput(false);

            osw = new OutputStreamWriter(urlCon.getOutputStream());
            osw.write("HELLO WORLD");
            osw.flush();

            /////////////////////////////////////////////////
            // MUST CALL THIS OTHERWISE WILL NOT WRITE OUT //
            /////////////////////////////////////////////////
            urlCon.getInputStream();

            /////////////////////////////////////////////////////////////////////////////////////////////////////////
            // If getInputStream is called while doInput=false, the following exception is thrown:                 //
            // Java.net.ProtocolException: Cannot read from URLConnection if doInput=false (call setDoInput(true)) //
            /////////////////////////////////////////////////////////////////////////////////////////////////////////

        } catch (Exception e) {
            e.printStackTrace();                
        } finally {
            if (osw != null) {
                osw.close();
            }
        }

    }

}
32
John

Les API pour URLConnection et HttpURLConnection sont (pour le meilleur ou pour le pire) conçues pour que l'utilisateur puisse suivre une séquence d'événements très spécifique:

  1. Définir les propriétés de la demande
  2. (Facultatif) getOutputStream (), écrivez dans le flux, fermez le flux
  3. getInputStream (), lire le flux, fermer le flux

Si votre demande est un POST ou PUT, vous avez besoin de l'étape facultative # 2.

À ma connaissance, le OutputStream n'est pas comme un socket, il n'est pas directement connecté à un InputStream sur le serveur. Au lieu de cela, après avoir fermé ou vidé le flux ET appelé getInputStream (), votre sortie est intégrée dans une demande et envoyée. La sémantique est basée sur l'hypothèse que vous voudrez lire la réponse. Chaque exemple que j'ai vu montre cet ordre des événements. Je suis certainement d'accord avec vous et d'autres que cette API est contre-intuitive par rapport à l'API d'E/S de flux normal.

Le tutoriel que vous liez indique que "URLConnection est une classe HTTP-centrée". J'interprète cela comme signifiant que les méthodes sont conçues autour d'un modèle de demande-réponse, et je fais l'hypothèse que c'est ainsi qu'elles seront utilisées.

Pour ce que ça vaut, j'ai trouvé ceci rapport de bogue qui explique le fonctionnement prévu de la classe mieux que la documentation javadoc. L'évaluation du rapport indique que "la seule façon d'envoyer la demande est d'appeler getInputStream."

39
robert_x44

Bien que la méthode getInputStream () puisse certainement amener un objet URLConnection à lancer une requête HTTP, il n'est pas obligatoire de le faire.

Considérez le flux de travail réel:

  1. Créer une demande
  2. Soumettre
  3. Traiter la réponse

L'étape 1 comprend la possibilité d'inclure des données dans la demande, au moyen d'une entité HTTP. Il se trouve que la classe URLConnection fournit un objet OutputStream comme mécanisme pour fournir ces données (et à juste titre pour de nombreuses raisons qui ne sont pas particulièrement pertinentes ici). Il suffit de dire que la nature en streaming de ce mécanisme offre au programmeur une grande flexibilité lors de la fourniture des données, y compris la possibilité de fermer le flux de sortie (et tous les flux d'entrée qui l'alimentent), avant de terminer la demande.

En d'autres termes, l'étape 1 permet de fournir une entité de données pour la demande, puis de continuer à la construire (par exemple en ajoutant des en-têtes).

L'étape 2 est vraiment une étape virtuelle et peut être automatisée (comme c'est le cas dans la classe URLConnection), car soumettre une demande n'a pas de sens sans réponse (au moins dans les limites du protocole HTTP).

Ce qui nous amène à l'étape 3. Lors du traitement d'une réponse HTTP, l'entité de réponse - récupérée en appelant getInputSteam () - n'est qu'une des choses qui pourraient nous intéresser. Une réponse se compose d'un état, d'en-têtes et éventuellement d'un entité. La première fois que l'un de ces éléments est demandé, URLConnection exécute l'étape virtuelle 2 et soumet la demande.

Peu importe si une entité est envoyée via le flux de sortie de la connexion ou non, et peu importe si une entité de réponse est attendue en retour, un programme voudra TOUJOURS connaître le résultat (tel que fourni par le code d'état HTTP). L'appel de getResponseCode () sur URLConnection fournit ce statut et l'activation du résultat peut mettre fin à la conversation HTTP sans jamais appeler getInputStream ().

Donc, si des données sont soumises et qu'aucune entité de réponse n'est attendue, ne faites pas ceci:

// request is now built, so...
InputStream ignored = urlConnection.getInputStream();

... faites ceci:

// request is now built, so...
int result = urlConnection.getResponseCode();
// act based on this result
4
slash_rick_dot

Comme mes expériences l'ont montré (Java 1.7.0_01) le code:

osw = new OutputStreamWriter(urlCon.getOutputStream());
osw.write("HELLO WORLD");
osw.flush();

N'envoie rien au serveur. Il enregistre simplement ce qui y est écrit dans le tampon de mémoire. Ainsi, dans le cas où vous allez télécharger un gros fichier via POST - vous devez être sûr que vous avez suffisamment de mémoire. Sur le bureau/serveur, ce n'est peut-être pas un gros problème, mais sur Android qui peut entraîner une erreur de mémoire insuffisante. Voici l'exemple de l'apparence de la trace de pile lorsque vous essayez d'écrire dans le flux de sortie et la mémoire est épuisée.

Exception in thread "Thread-488" Java.lang.OutOfMemoryError: GC overhead limit exceeded
    at Java.util.Arrays.copyOf(Arrays.Java:2271)
    at Java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.Java:113)
    at Java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.Java:93)
    at Java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.Java:140)
    at Sun.net.www.http.PosterOutputStream.write(PosterOutputStream.Java:78)
    at Sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.Java:221)
    at Sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.Java:282)
    at Sun.nio.cs.StreamEncoder.write(StreamEncoder.Java:125)
    at Sun.nio.cs.StreamEncoder.write(StreamEncoder.Java:135)
    at Java.io.OutputStreamWriter.write(OutputStreamWriter.Java:220)
    at Java.io.Writer.write(Writer.Java:157)
    at maxela.tables.weboperations.POSTRequest.makePOST(POSTRequest.Java:138)

Au bas de la trace, vous pouvez voir la méthode makePOST() qui effectue les opérations suivantes:

     writer = new OutputStreamWriter(conn.getOutputStream());                      
    for (int j = 0 ; j < 3000 * 100 ; j++)
    {
      writer.write("&var" + j + "=garbagegarbagegarbage_"+ j);
    }
   writer.flush();

Et writer.write() lève l'exception. Mes expériences ont également montré que toute exception liée à la connexion/E/S réelle avec le serveur n'est levée qu'après l'appel de urlCon.getOutputStream(). Même urlCon.connect() semble être une méthode "factice" qui ne fait aucune connexion physique. Cependant, si vous appelez urlCon.getContentLengthLong() qui renvoie le champ d'en-tête Content-Length: à partir des en-têtes de réponse du serveur - alors URLConnection.getOutputStream () sera appelé automatiquement et en cas d'exception - il sera levé.

Les exceptions levées par urlCon.getOutputStream() sont toutes des IOException, et j'ai rencontré les suivantes:

                try
                {
                    urlCon.getOutputStream();
                }
                catch (UnknownServiceException ex)
                {
                    System.out.println("UnkownServiceException():" + ex.getMessage());
                }

                catch (ConnectException ex)
                {
                    System.out.println("ConnectException()");
                    Logger.getLogger(POSTRequest.class.getName()).log(Level.SEVERE, null, ex);
                }

                catch (IOException ex) {
                    System.out.println("IOException():" + ex.getMessage());
                    Logger.getLogger(POSTRequest.class.getName()).log(Level.SEVERE, null, ex);
                }

J'espère que ma petite recherche aide les gens, car la classe URLConnection est un peu contre-intuitive dans certains cas donc, lors de sa mise en œuvre - il faut savoir de quoi il s'agit.

La deuxième raison est: lorsque vous travaillez avec des serveurs - le travail avec le serveur peut échouer pour de nombreuses raisons (connexion, DNS, pare-feu, réponses http, serveur ne pouvant pas accepter la connexion, serveur ne pouvant pas traiter la demande en temps opportun). Il est donc important de comprendre comment les exceptions levées peuvent expliquer ce qui se passe réellement avec la connexion.

2
Dimitry K

L'appel de getInputStream () signale que le client a fini d'envoyer sa demande et qu'il est prêt à recevoir la réponse (selon les spécifications HTTP). Il semble que la classe URLConnection intègre cette notion, et doit vider () le flux de sortie lorsque le flux d'entrée est demandé.

Comme l'a noté l'autre intervenant, vous devriez pouvoir appeler flush () vous-même pour déclencher l'écriture.

1
jhouse

La raison fondamentale est qu'il doit calculer automatiquement un en-tête Content-length (sauf si vous utilisez le mode fragmenté ou en continu). Il ne peut pas faire cela tant qu'il n'a pas vu toute la sortie, et il doit l'envoyer avant la sortie, il doit donc mettre la sortie en mémoire tampon. Et il faut un événement décisif pour savoir quand la dernière sortie a été réellement écrite. Il utilise donc getInputStream () pour cela. À ce moment, il écrit les en-têtes, y compris la longueur du contenu, puis la sortie, puis il commence à lire l'entrée.

1
user207421