web-dev-qa-db-ja.com

URLConnection#getOutputStreamに書き出すために、なぜURLConnection#getInputStreamを呼び出さなければならないのですか?

URLConnection#getOutputStream に書き込もうとしていますが、実際には URLConnection#getInputStream を呼び出すまでデータは送信されません。 URLConnnection#doInput をfalseに設定しても、送信されません。これがなぜなのか誰か知っていますか?これを説明するAPIドキュメントには何もありません。

URLConnectionのJava APIドキュメント: http://download.Oracle.com/javase/6/docs/api/Java/net/URLConnection.html

URLConnectionの読み取りと書き込みに関するJavaのチュートリアル: 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

URLConnectionおよびHttpURLConnectionのAPIは、ユーザーが非常に特定のイベントシーケンスに従うように(良くも悪くも)設計されています。

  1. リクエストプロパティの設定
  2. (オプション)getOutputStream()、ストリームへの書き込み、ストリームのクローズ
  3. getInputStream()、ストリームから読み取り、ストリームを閉じます

リクエストがPOSTまたはPUTの場合、オプションのステップ#2が必要です。

私の知る限りでは、OutputStreamはソケットのようなものではなく、サーバー上のInputStreamに直接接続されていません。代わりに、ストリームを閉じたりフラッシュしたりして、getInputStream()を呼び出した後、出力がリクエストに組み込まれて送信されます。セマンティクスは、応答を読みたいという前提に基づいています。私が見たすべての例は、このイベントの順序を示しています。このAPIは通常のストリームI/O APIと比較すると直観に反するという点で、あなたや他の人には確かに同意します。

リンクしている tutorial は、「URLConnectionはHTTP中心のクラスである」と述べています。これは、メソッドが要求/応答モデルを中心に設計されていることを意味すると解釈し、それらがどのように使用されるかを想定しています。

それが価値があるものについて、私はこれを見つけました バグレポート クラスの意図された操作をjavadocドキュメントよりもよく説明しています。レポートの評価では、「リクエストを送信する唯一の方法は、getInputStreamを呼び出すことです。」

39
robert_x44

GetInputStream()メソッドによってURLConnectionオブジェクトがHTTPリクエストを開始することは確かにありますが、これは必須ではありません。

実際のワークフローを検討してください。

  1. リクエストを作成する
  2. 参加する
  3. 応答を処理する

ステップ1には、HTTPエンティティを介して、要求にデータを含める可能性が含まれます。 URLConnectionクラスが、このデータを提供するためのメカニズムとしてOutputStreamオブジェクトを提供することは偶然にも起こります(当然のことながら、ここでは特に関連のない多くの理由によります)。このメカニズムのストリーミングの性質により、プログラマーは、要求を完了する前に、出力ストリーム(およびそれを供給している入力ストリーム)を閉じる機能を含め、データを提供する際にある程度の柔軟性を提供します。

言い換えると、ステップ1では、要求にデータエンティティを指定し、それを(ヘッダーを追加するなどして)引き続き構築できます。

ステップ2は実際には仮想ステップであり、自動化できます(URLConnectionクラスにあるように)。これは、要求を送信しても、応答がなければ(少なくともHTTPプロトコルの範囲内では)意味がないためです。

これにより、ステップ3に進みます。HTTP応答を処理する場合、応答エンティティ(getInputSteam()の呼び出しによって取得)は、関心のあるものの1つにすぎません。応答は、ステータス、ヘッダー、およびオプションでエンティティ。これらのいずれかが初めて要求されると、URLConnectionは仮想ステップ2を実行し、要求を送信します。

エンティティが接続の出力ストリームを介して送信されているかどうかに関係なく、また応答エンティティが予期されるかどうかに関係なく、プログラムは常に(HTTPステータスコードによって提供されるように)結果を知りたいと思っています。 URLConnectionでgetResponseCode()を呼び出すとこのステータスが提供され、結果を切り替えると、getInputStream()を呼び出さなくてもHTTP会話が終了する場合があります。

したがって、データが送信されていて、応答エンティティが予期されていない場合は、これを行わないでください。

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

... これを行う:

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

私の実験が示したように(Java 1.7.0_01)コード:

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

サーバーには何も送信しません。そこに書き込まれた内容をメモリバッファに保存するだけです。したがって、POST-を介して大きなファイルをアップロードする場合は、十分なメモリがあることを確認する必要があります。デスクトップ/サーバーでは、それほど大きな問題ではないかもしれませんが、 Androidメモリ不足エラーが発生する可能性があります。出力ストリームに書き込もうとしたときにスタックトレースがどのように表示され、メモリが不足するかの例を以下に示します。

_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)
_

トレースの下部には、次の処理を行うmakePOST()メソッドがあります。

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

そしてwriter.write()は例外をスローします。また、私の実験では、サーバーとの実際の接続/ IOに関連する例外は、urlCon.getOutputStream()が呼び出された後にのみスローされることが示されています。 urlCon.connect()でさえ、物理的な接続を行わない「ダミー」メソッドのようです。ただし、サーバーのレスポンスヘッダーからContent-Length:ヘッダーフィールドを返すurlCon.getContentLengthLong()を呼び出すと、URLConnection.getOutputStream()が自動的に呼び出され、例外がある場合はスローされます。

urlCon.getOutputStream()によってスローされる例外はすべてIOExceptionであり、次の例外を満たしています。

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

うまくいけば、私の小さな研究が人々に役立つでしょう。URLConnectionクラスは、場合によっては、実装時に少し直感に反するため、それが何を扱っているかを知る必要があります。

2番目の理由は、サーバーを操作する場合-多くの理由(接続、dns、ファイアウォール、httpresponses、サーバーが接続を受け入れることができない、サーバーが要求をタイムリーに処理できない)のためにサーバーでの作業が失敗する可能性があります。したがって、理解することが重要です発生した例外が、接続で実際に何が起こっているかについて説明できる例外

2
Dimitry K

GetInputStream()を呼び出すと、クライアントがリクエストの送信を終了し、HTTP仕様に従って応答を受信する準備ができたことを通知します。 URLConnectionクラスにはこの概念が組み込まれているようで、入力ストリームが要求されたときに出力ストリームをflush()する必要があります。

他のレスポンダーが指摘したように、自分でflush()を呼び出して書き込みをトリガーできるはずです。

1
jhouse

基本的な理由は、(チャンクモードまたはストリーミングモードを使用している場合を除き)Content-lengthヘッダーを自動的に計算する必要があるためです。すべての出力を確認し、出力の前に送信する必要があるため、出力をバッファリングする必要があります。そして、最後の出力が実際にいつ書き込まれたかを知るためには、決定的なイベントが必要です。そのため、getInputStream()を使用しています。その時点で、コンテンツ長を含むヘッダー、次に出力を書き込み、次に入力の読み取りを開始します。

1
user207421