HttpURLConnection无法读取完整的响应

8

我使用HttpURLConnection进行HTTP POST,但并非总能获得完整的响应。我想调试问题,但是逐行执行代码时它可以正常工作。我认为这一定是一个时间问题,所以我添加了Thread.sleep,并确实使我的代码工作了,但这只是一个临时解决方法。我想知道为什么会出现这种情况以及如何解决。以下是我的代码:

public static InputStream doPOST(String input, String inputMimeType, String url, Map<String, String> httpHeaders, String expectedMimeType) throws MalformedURLException, IOException {

    URL u = new URL(url);
    URLConnection c = u.openConnection();
    InputStream in = null;
    String mediaType = null;
    if (c instanceof HttpURLConnection) {

        //c.setConnectTimeout(1000000);
        //c.setReadTimeout(1000000);

        HttpURLConnection h = (HttpURLConnection)c;
        h.setRequestMethod("POST");
        //h.setChunkedStreamingMode(-1);
        setAccept(h, expectedMimeType);
        h.setRequestProperty("Content-Type", inputMimeType);

        for(String key: httpHeaders.keySet()) {
            h.setRequestProperty(key, httpHeaders.get(key));

            if (logger.isDebugEnabled()) {
                logger.debug("Request property key : " + key + " / value : " + httpHeaders.get(key));
            }

        }

        h.setDoOutput(true);
        h.connect();

        OutputStream out = h.getOutputStream();

        out.write(input.getBytes());

        out.close();

        mediaType = h.getContentType();

        logger.debug(" ------------------ sleep ------------------ START");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        logger.debug(" ------------------ sleep ------------------ END");

        if (h.getResponseCode() < 400) {
            in = h.getInputStream();
        } else {
            in = h.getErrorStream();
        }
    }
    return in;

}

稍后我将按照以下方式读取输入流:
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while (is.available() > 0) {
            bos.write(is.read());
        }
        is.close();

        //is.read(bytes);
        if (logger.isDebugEnabled()) {
            logger.debug(" Response lenght is : " + is.available());
            //logger.debug("RAW response is " + new String(bytes));
            logger.debug("RAW response is " + new String(bos.toByteArray()));
        }

它生成以下HTTP标头。
POST /emailauthentication/ HTTP/1.1
Accept: application/xml
Content-Type: application/xml
Authorization: OAuth oauth_consumer_key="b465472b-d872-42b9-030e-4e74b9b60e39",oauth_nonce="YnDb5eepuLm%2Fbs",oauth_signature="dbN%2FWeWs2G00mk%2BX6uIi3thJxlM%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1276524919", oauth_token="", oauth_version="1.0"
User-Agent: Java/1.6.0_20
Host: test:6580
Connection: keep-alive
Content-Length: 1107

在其他文章中,建议通过使用 keep-alive 关闭来关闭 keep-alive。

http.keepAlive=false

系统属性,我尝试过这个方法,标头已经更改为

POST /emailauthentication/ HTTP/1.1
Accept: application/xml
Content-Type: application/xml
Authorization: OAuth oauth_consumer_key="b465472b-d872-42b9-030e-4e74b9b60e39", oauth_nonce="Eaiezrj6X4Ttt0", oauth_signature="ND9fAdZMqbYPR2j%2FXUCZmI90rSI%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1276526608", oauth_token="", oauth_version="1.0"
User-Agent: Java/1.6.0_20
Host: test:6580
Connection: close
Content-Length: 1107

连接头是“关闭”,但我仍然无法读取整个响应。有什么想法我做错了什么吗?

1
你的问题存在歧义。你指的是哪个“响应”?通过整个代码,实际上你并没有在读取响应,而是在创建请求。你在底部使用h.getInputStream()来读取它的响应,但实际上你忽略了它和/或者没有展示你如何处理它。 - BalusC
嗨,BalusC我添加了丢失的部分 :) - Peter Szanto
3个回答

14
我认为你的问题在这行代码里:
while (is.available() > 0) {

根据javadoc文档,available方法并不会阻塞等待所有数据都可用,因此你可能只获取到第一个数据包就返回了false。从InputStream中读取数据的正确方法如下:

int len;
byte[] buffer = new byte[4096];
while (-1 != (len = in.read(buffer))) {
  bos.write(buffer, 0, len);
}

当输入流中没有剩余内容或连接关闭时,read方法将返回-1,并在此过程中会阻塞并等待网络。与使用单个字节相比,读取数组的性能也要更高。


嗨Jörn,这就是问题所在,我确信HTTP传输层出了问题,所以阅读了HttpURLConnection的整个JavaDoc,并没有注意到InputStream。谢谢!彼得 - Peter Szanto
哦,我忘了提到这个错误不仅出现在我的代码中,还出现在Sun的JAXB中。最初我直接将InputStream传递给Object o = unmarshaller.unmarshal(bis);其中unmarshaller是javax.xml.bind.Unmarshaller的实例,结果出现了同样的问题。 - Peter Szanto

0
如果您一次阅读整个消息,可以将isr.available()与预期的内容长度进行比较。这是我做的方式:
public byte[] readData(HttpURLConnection conn)
        throws IOException, InterruptedException {
    String _connlen = conn.getHeaderField("Content-Length");
    int connlen = Integer.parseInt(_connlen);
    InputStream isr = null;
    byte[] bytes = new byte[connlen];

    try {
        isr = conn.getInputStream();

        //security count that it doesn't begin to hang
        int maxcounter = 0;
        //wait till all data is avalibal, max 5sec
        while((isr.available() != connlen) && (maxcounter  < 5000)){
            Thread.sleep(1);
            maxcounter++;
        }
        //Throw if not all data could be read
        if(maxcounter >= 5000)
            throw new IllegalAccessError(); 

        //read the data         
        if(isr.read(bytes, 0, connlen) < 0)
            throw new IllegalAccessError();     


    } finally {
        if (isr != null)
            isr.close();
    }

    return bytes;
}

0
也许我错过了,但是你代码中的“input”数据类型是什么?一般来说,InputStreams 的奇怪之处在于 read(...) 方法倾向于阻塞直到有数据可用,然后只返回该数据。实际上,您需要不断从 InputStream 中读取并追加到 ByteArrayInputStream 或其他结构中,直到您明确强制出现 EOFException。

1
很可能是代表查询字符串的“String”。另请参阅如何使用URLConnection - BalusC
嗨,Curtis。我编辑了我的帖子,包括方法签名,解释了输入是什么以及读取InputStream的代码片段。 - Peter Szanto
直到你明确强制EOFException。但是,如果您调用read()或readLine(),则不会收到该异常。只有在调用任何其他X的readXXX()时才会收到EOFException。read()方法在EOS时返回-1。Jörn Horstmann回复中的代码示例是正确的技术。 - user207421

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接