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();
}
}
}
}
URLConnectionおよびHttpURLConnectionのAPIは、ユーザーが非常に特定のイベントシーケンスに従うように(良くも悪くも)設計されています。
リクエストがPOSTまたはPUTの場合、オプションのステップ#2が必要です。
私の知る限りでは、OutputStreamはソケットのようなものではなく、サーバー上のInputStreamに直接接続されていません。代わりに、ストリームを閉じたりフラッシュしたりして、getInputStream()を呼び出した後、出力がリクエストに組み込まれて送信されます。セマンティクスは、応答を読みたいという前提に基づいています。私が見たすべての例は、このイベントの順序を示しています。このAPIは通常のストリームI/O APIと比較すると直観に反するという点で、あなたや他の人には確かに同意します。
リンクしている tutorial は、「URLConnectionはHTTP中心のクラスである」と述べています。これは、メソッドが要求/応答モデルを中心に設計されていることを意味すると解釈し、それらがどのように使用されるかを想定しています。
それが価値があるものについて、私はこれを見つけました バグレポート クラスの意図された操作をjavadocドキュメントよりもよく説明しています。レポートの評価では、「リクエストを送信する唯一の方法は、getInputStreamを呼び出すことです。」
GetInputStream()メソッドによってURLConnectionオブジェクトがHTTPリクエストを開始することは確かにありますが、これは必須ではありません。
実際のワークフローを検討してください。
ステップ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
私の実験が示したように(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、サーバーが接続を受け入れることができない、サーバーが要求をタイムリーに処理できない)のためにサーバーでの作業が失敗する可能性があります。したがって、理解することが重要です発生した例外が、接続で実際に何が起こっているかについて説明できる例外。
GetInputStream()を呼び出すと、クライアントがリクエストの送信を終了し、HTTP仕様に従って応答を受信する準備ができたことを通知します。 URLConnectionクラスにはこの概念が組み込まれているようで、入力ストリームが要求されたときに出力ストリームをflush()する必要があります。
他のレスポンダーが指摘したように、自分でflush()を呼び出して書き込みをトリガーできるはずです。
基本的な理由は、(チャンクモードまたはストリーミングモードを使用している場合を除き)Content-lengthヘッダーを自動的に計算する必要があるためです。すべての出力を確認し、出力の前に送信する必要があるため、出力をバッファリングする必要があります。そして、最後の出力が実際にいつ書き込まれたかを知るためには、決定的なイベントが必要です。そのため、getInputStream()を使用しています。その時点で、コンテンツ長を含むヘッダー、次に出力を書き込み、次に入力の読み取りを開始します。