2011-01-30 27 views
27

Tôi đang cố viết ra URLConnection#getOutputStream, tuy nhiên, không có dữ liệu nào thực sự được gửi cho đến khi tôi gọi URLConnection#getInputStream. Ngay cả khi tôi đặt URLConnnection#doInput thành sai, nó vẫn sẽ không gửi. Có ai biết tại sao điều này? Không có gì trong tài liệu API mô tả điều này.Tại sao bạn phải gọi URLConnection # getInputStream để có thể ghi ra URLConnection # getOutputStream?

Java API Documentation trên URLConnection: http://download.oracle.com/javase/6/docs/api/java/net/URLConnection.html

Java Hướng dẫn về đọc và ghi vào một URLConnection: 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(); 
      } 
     } 

    } 

} 

Trả lời

34

API cho URLConnection và HttpURLConnection là (cho tốt hơn hoặc tệ hơn) được thiết kế cho người sử dụng theo một rất chuỗi cụ thể của sự kiện:

  1. Set Yêu cầu Thuộc tính
  2. (Không bắt buộc) getOutputStream(), viết vào dòng, đóng dòng
  3. getInputStream(), đọc từ luồng, đóng luồng

Nếu yêu cầu của bạn là POST hoặc PUT, bạn cần bước tùy chọn # 2.

Theo hiểu biết tốt nhất của tôi, OutputStream không giống như một ổ cắm, nó không được kết nối trực tiếp với InputStream trên máy chủ. Thay vào đó, sau khi bạn đóng hoặc xóa luồng, AND gọi getInputStream(), đầu ra của bạn được tích hợp vào một Yêu cầu và được gửi đi. Ngữ nghĩa dựa trên giả định rằng bạn sẽ muốn đọc câu trả lời. Mỗi ví dụ mà tôi đã thấy cho thấy thứ tự các sự kiện này. Tôi chắc chắn sẽ đồng ý với bạn và những người khác rằng API này phản trực giác khi so sánh với API I/O luồng thông thường.

Các tutorial bạn liên kết để tuyên bố rằng "URLConnection là một lớp trung tâm HTTP". Tôi giải thích điều đó có nghĩa là các phương thức được thiết kế xung quanh một mô hình Yêu cầu-Trả lời, và đưa ra giả định rằng chúng sẽ được sử dụng như thế nào.

Đối với những gì đáng giá, tôi đã tìm thấy điều này bug report giải thích hoạt động dự định của lớp học tốt hơn tài liệu javadoc. Việc đánh giá báo cáo nói rằng "Cách duy nhất để gửi yêu cầu là gọi getInputStream."

+0

Tìm tốt trên lỗi! Điều đó thực sự rõ ràng mọi thứ và tôi vui mừng khi biết rằng nó đã được ghi lại ở đâu đó. Cảm ơn! – John

+0

Java 8 vẫn có lỗi này/tính năng không có giấy tờ, như tôi vừa mới phát hiện ra. (Tôi tìm thấy trang này trong khi tìm kiếm một giải pháp hoặc giải pháp cho vấn đề này.) Có một lựa chọn tốt hơn bây giờ, hơn sáu năm sau khi câu hỏi ban đầu đã được yêu cầu? –

1

Calling getInputStream() tín hiệu mà khách hàng xong gửi yêu cầu của nó, và là sẵn sàng nhận phản hồi (theo thông số HTTP). Dường như lớp URLConnection có khái niệm này được xây dựng trong nó, và phải được flush() ing dòng đầu ra khi luồng đầu vào được yêu cầu.

Như người trả lời khác đã lưu ý, bạn có thể gọi flush() để kích hoạt ghi.

+0

Hi James, nhờ các chi tiết liên quan đến thông số HTTP. Câu trả lời đó là dọc theo những gì tôi đang tìm kiếm. Vì vậy, nó là bởi vì HTTP spec yêu cầu phải có một phản ứng cho họ là một yêu cầu? Về việc sử dụng 'flush()', bạn sẽ thấy rằng tôi đã thử nó và tôi không thấy một yêu cầu đến throug ở đầu bên kia. – John

+0

Ngoài ra hãy chắc chắn rằng bạn nhận được các dòng theo thứ tự đúng, gọi tuôn ra trong tiến trình. Không tuôn ra sau khi nhận được ban đầu sẽ dẫn đến các tiêu đề truyền thông không được chuyển giao giữa các bên và, cuối cùng, một bế tắc. – pnt

+0

Vì vậy, 'getOuputStream()', 'flush()', 'write()', và cuối cùng là 'flush()' một lần nữa? – John

-3

(Đăng lại từ câu hỏi đầu tiên của bạn. Tự cắm không dây) Đừng tự loanh quanh với URLConnection, hãy để Resty xử lý.

Dưới đây là mã bạn sẽ cần phải viết (tôi giả sử bạn đang nhận được văn bản trở lại):

import static us.monoid.web.Resty.*; 
import us.monoid.web.Resty; 
...  
new Resty().text(TEST_URL, content("HELLO WORLD")).toString(); 
1

Lý do cơ bản là phải tự động tính toán tiêu đề Độ dài nội dung (trừ khi bạn đang sử dụng chế độ chunk hoặc chế độ phát trực tuyến). Nó không thể làm điều đó cho đến khi nó đã thấy tất cả các đầu ra, và nó phải gửi nó trước đầu ra, vì vậy nó phải đệm đầu ra.Và nó cần một sự kiện quyết định để biết khi nào sản lượng cuối cùng đã thực sự được viết. Vì vậy, nó sử dụng getInputStream() cho điều đó. Vào thời điểm đó, nó viết các tiêu đề bao gồm độ dài nội dung, sau đó đầu ra, sau đó nó bắt đầu đọc đầu vào.

+0

Nó không phải tự động tính toán Độ dài nội dung. Bạn có thể gọi setFixedLengthStreamingMode để cho nó độ dài. Đệm nội bộ sau đó sẽ bị tắt. – vocaro

+0

@vocaro đồng ý nhưng OP không làm điều đó, đó là trường hợp tôi đã giải quyết, và điều này giải thích hành vi mà anh ta đang thấy. – EJP

+1

Sẽ không đóng luồng đầu ra cũng làm điều này, để gọi getInputStream sẽ không cần thiết? –

2

Như thí nghiệm của tôi đã chỉ ra (java 1.7.0_01) mã:

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

Không gửi bất cứ điều gì đến máy chủ. Nó chỉ lưu những gì được ghi vào bộ nhớ đệm. Vì vậy, trong trường hợp bạn sẽ tải lên một tệp lớn thông qua POST - bạn cần phải chắc chắn rằng bạn có đủ bộ nhớ. Trên máy tính để bàn/máy chủ, nó có thể không phải là một vấn đề lớn, nhưng trên Android có thể dẫn đến lỗi bộ nhớ. Dưới đây là ví dụ về cách dấu vết ngăn xếp trông như thế nào khi cố ghi vào luồng đầu ra và bộ nhớ hết.

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) 

Trên dưới cùng của dấu vết bạn sẽ nhìn thấy phương pháp makePOST() mà làm như sau:

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

writer.write() ném ngoại lệ. Ngoài ra các thử nghiệm của tôi đã chỉ ra rằng bất kỳ ngoại lệ nào liên quan đến kết nối/IO thực tế với máy chủ chỉ được ném sau khi urlCon.getOutputStream() được gọi. Thậm chí urlCon.connect() có vẻ là phương pháp "giả" không thực hiện bất kỳ kết nối vật lý nào. Tuy nhiên nếu bạn gọi urlCon.getContentLengthLong() trả về trường Nội dung Độ dài: tiêu đề từ tiêu đề phản hồi của máy chủ - thì URLConnection.getOutputStream() sẽ được gọi tự động và trong trường hợp có ngoại lệ - nó sẽ bị ném.

Các trường hợp ngoại lệ ném bởi urlCon.getOutputStream() đều IOException, và tôi đã gặp những người follwing:

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

Hy vọng rằng nghiên cứu nhỏ của tôi sẽ giúp cho mọi người, như URLConnection lớp là một chút phản trực giác trong một số trường hợp như vậy, khi triển khai nó - người ta cần phải biết những gì nó đề cập đến.

Lý do thứ hai là: khi làm việc với máy chủ - công việc với máy chủ có thể bị lỗi vì nhiều lý do (kết nối, dns, tường lửa, httpresponses, máy chủ không thể chấp nhận kết nối, máy chủ không thể xử lý yêu cầu kịp thời). Vì vậy, điều quan trọng là phải hiểu cách ngoại lệ được nêu ra có thể giải thích về những gì thực sự xảy ra với kết nối.

3

Mặc dù phương thức getInputStream() chắc chắn có thể gây ra một đối tượng URLConnection để khởi tạo một yêu cầu HTTP, nó không phải là một yêu cầu để làm như vậy.

Hãy xem xét các công việc thực tế:

  1. Xây dựng yêu cầu
  2. Gửi
  3. Process phản ứng

Bước 1 bao gồm khả năng bao gồm cả dữ liệu trong yêu cầu, bằng cách của một Thực thể HTTP. Nó chỉ xảy ra khi lớp URLConnection cung cấp một đối tượng OutputStream làm cơ chế cung cấp dữ liệu này (và đúng như vậy vì nhiều lý do không liên quan đặc biệt ở đây). Đủ để nói rằng bản chất luồng của cơ chế này cung cấp cho người lập trình một lượng linh hoạt khi cung cấp dữ liệu, bao gồm khả năng đóng luồng đầu ra (và bất kỳ luồng đầu vào nào cho ăn), trước khi hoàn thành yêu cầu.

Nói cách khác, bước 1 cho phép cung cấp thực thể dữ liệu cho yêu cầu, sau đó tiếp tục xây dựng nó (chẳng hạn như bằng cách thêm tiêu đề).

Bước 2 thực sự là một bước ảo và có thể được tự động hóa (như trong lớp URLConnection), vì việc gửi yêu cầu là vô nghĩa nếu không có phản hồi (ít nhất là trong giới hạn giao thức HTTP).

Điều này đưa chúng ta đến Bước 3. Khi xử lý phản hồi HTTP, thực thể phản hồi - được truy xuất bằng cách gọi getInputSteam() - chỉ là một trong những điều chúng ta có thể quan tâm. và tùy chọn một thực thể. Lần đầu tiên bất kỳ yêu cầu nào trong số này được yêu cầu, URLConnection sẽ thực hiện bước ảo 2 và gửi yêu cầu.

Không có vấn đề nếu thực thể được gửi qua luồng đầu ra của kết nối hay không và cho dù thực thể phản hồi được dự kiến ​​trở lại hay không, một chương trình sẽ luôn biết kết quả (như được cung cấp bởi mã trạng thái HTTP). Việc gọi hàm getResponseCode() trên URLConnection cung cấp trạng thái này và việc bật kết quả có thể kết thúc cuộc trò chuyện HTTP mà không bao giờ gọi hàm getInputStream().

Vì vậy, nếu dữ liệu đang được gửi, và một thực thể phản ứng không mong đợi, không làm điều này:

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

... làm điều này:

// request is now built, so... 
int result = urlConnection.getResponseCode(); 
// act based on this result 
Các vấn đề liên quan