HttpsURLConnection中哪个方法调用实际发送HTTP请求?

3

编辑:现在我已经找到了一些行为,我想知道,为什么它不会在我调用flush()close()时立即发送请求?为什么要等到我调用getResponseCode()


原始问题:

我对java中的HttpsURLConnection类还不太熟悉,在文档上查阅后并没有得到很好的解释。我在一个项目中继承了这段代码(不是我自己写的),想知道它在做什么以及如何改进。例如,我无法确定“writeBytes”是否实际发送数据或者是“close”。我查看了许多javadoc,并发现它们含糊不清,在书籍或在线博客文章中也没有找到关于此主题的好资源。请问有人可以给我指点一下,或者指向一些好的资源吗?

顺便说一句,这段代码是我正在开发的android SDK库。

注意:我非常了解HTTP请求的理论知识。我学过这方面的课程。我知道所有关于URL参数(使用?name=value)和cookies和RESTful服务和TCP和IP等内容。只是在寻找好的文档,以便我知道如何使用java库。

编辑:将此更改为HttpClient代码,因为事实证明我无法使用Https并仍然从回显服务器中查看请求。但它的思想是一样的。

import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

public class HttpClient
{
    public static final String DEBUG_URL = "http://10.20.1.61:8001/api/ad/v5/";
    public static final String MAPPED_KEYWORDS = "get_mapped_keywords/";

    private static byte[] buffer = new byte[1024];

    static NetworkReturn sendHttpPost(String urlString, String postData)
    {
        URL url;
        HttpURLConnection con = null;

        try {
            url = new URL(urlString);
            con = (HttpURLConnection) url.openConnection();

            con.setRequestMethod("POST");
            con.setRequestProperty("Content-length", String.valueOf(postData.length()));
            con.setRequestProperty("Content-Type", "application/json");
            con.setDoOutput(true);

            DataOutputStream output = new DataOutputStream(con.getOutputStream());

            output.writeBytes(postData);
            output.close();

            StringBuilder result = new StringBuilder(), error = new StringBuilder();

            int responseCode = con.getResponseCode();
            if (responseCode >= 200 && responseCode < 300) 
            {
                if (con.getInputStream() != null) 
                {
                    InputStream in = new BufferedInputStream(con.getInputStream());
                    int length;
                    while ((length = in.read(buffer)) != -1) 
                    {
                        result.append(new String(buffer, 0, length));
                    }
                }
            }
            else 
            {
                if (con.getErrorStream() != null) 
                {
                    InputStream in = new BufferedInputStream(con.getErrorStream());
                    int length;
                    while ((length = in.read(buffer)) != -1) 
                    {
                        error.append(new String(buffer, 0, length));
                    }
                }
            }
            return new NetworkReturn(responseCode, result.toString(), error.toString());

        }
        catch (MalformedURLException e) {
            return new NetworkReturn(-1, "", "MalformedURLException: " + e.getLocalizedMessage());
        }
        catch (IOException e) {
            e.printStackTrace();
            return new NetworkReturn(-1, "", "IOEXCEPTION: " + e.getLocalizedMessage());
        }
        finally {
            if (con != null)
                con.disconnect();
        }
    }
}

这与https://dev59.com/l3I95IYBdhLWcg3w8iz1非常相似。 - Brett Okken
3个回答

4

你调用的第一个从服务器获取输入的方法将设置内容长度头并写入输出。这可以是getResponseCode()getInputStream()或者getErrorStream()

如果你正在使用固定长度或分块传输模式,写入是直接的。


我尝试在getResponseCode()的位置上进行测试。在这种情况下,getResponseCode()getInputStream()有效,但是getErrorStream()无效,可能是因为没有错误? - Rock Lee
我可能对 getErrorStream() 的理解有误,因为通常只有在调用 getResponseCode() 后才会调用它。我应该补充说明,如果您使用固定长度或分块传输模型,则写入是直接进行的,而不像这样被延迟。延迟仅是为了为您设置 content-length,而在这些模式下并不必要。 - user207421

1
我终于搞明白了。"getResponseCode()"方法是压垮骆驼的最后一根稻草(这么说吧)。因为当我注释掉一些东西时,正是当我注释掉那部分时,它没有在我使用的回显服务器上“回显”我的响应(并将接收到的请求打印到终端)。
注意:我使用了一个几乎相同的HttpClient类,否则回显服务器会打印出像这样的加密代码:

����5D��s����l!���RMav*X?�+gJ.�+�/ � �����32E98�/A5� P localhost�

当我将此代码作为Java应用程序运行时,服务器没有将任何内容打印到终端: (请注意已注释掉的部分)
static NetworkReturn sendHttpPost(String urlString, String postData)
    {
        URL url;
        HttpURLConnection con = null;

        try {
            url = new URL(urlString);
            con = (HttpURLConnection) url.openConnection();

            con.setRequestMethod("POST");
            con.setRequestProperty("Content-length", String.valueOf(postData.length()));
            con.setRequestProperty("Content-Type", "application/json");
            con.setDoOutput(true);

            DataOutputStream output = new DataOutputStream(con.getOutputStream());
            output.writeBytes(postData);

            output.close();

            //StringBuilder result = new StringBuilder(), error = new StringBuilder();

            //int responseCode = con.getResponseCode();
            /*
            if (responseCode >= 200 && responseCode < 300) 
            {
                if (con.getInputStream() != null) 
                {
                    InputStream in = new BufferedInputStream(con.getInputStream());
                    int length;
                    while ((length = in.read(buffer)) != -1) 
                    {
                        result.append(new String(buffer, 0, length));
                    }
                }
            }
            else 
            {
                if (con.getErrorStream() != null) 
                {
                    InputStream in = new BufferedInputStream(con.getErrorStream());
                    int length;
                    while ((length = in.read(buffer)) != -1) 
                    {
                        error.append(new String(buffer, 0, length));
                    }
                }
            }
            return new NetworkReturn(responseCode, result.toString(), error.toString());
            */
            return new NetworkReturn();

        }
        catch (MalformedURLException e) {
            return new NetworkReturn(-1, "", "MalformedURLException: " + e.getLocalizedMessage());
        }
        catch (IOException e) {
            e.printStackTrace();
            return new NetworkReturn(-1, "", "IOEXCEPTION: " + e.getLocalizedMessage());
        }
        finally {
            if (con != null)
                con.disconnect();
        }
    }

然而,这段代码确实使服务器在终端上打印了:

static NetworkReturn sendHttpPost(String urlString, String postData)
    {
        URL url;
        HttpURLConnection con = null;

        try {
            url = new URL(urlString);
            con = (HttpURLConnection) url.openConnection();

            con.setRequestMethod("POST");
            con.setRequestProperty("Content-length", String.valueOf(postData.length()));
            con.setRequestProperty("Content-Type", "application/json");
            con.setDoOutput(true);

            DataOutputStream output = new DataOutputStream(con.getOutputStream());
            output.writeBytes(postData);

            output.close();

            //StringBuilder result = new StringBuilder(), error = new StringBuilder();

            int responseCode = con.getResponseCode();
            /*
            if (responseCode >= 200 && responseCode < 300) 
            {
                if (con.getInputStream() != null) 
                {
                    InputStream in = new BufferedInputStream(con.getInputStream());
                    int length;
                    while ((length = in.read(buffer)) != -1) 
                    {
                        result.append(new String(buffer, 0, length));
                    }
                }
            }
            else 
            {
                if (con.getErrorStream() != null) 
                {
                    InputStream in = new BufferedInputStream(con.getErrorStream());
                    int length;
                    while ((length = in.read(buffer)) != -1) 
                    {
                        error.append(new String(buffer, 0, length));
                    }
                }
            }
            return new NetworkReturn(responseCode, result.toString(), error.toString());
            */
            return new NetworkReturn();

        }
        catch (MalformedURLException e) {
            return new NetworkReturn(-1, "", "MalformedURLException: " + e.getLocalizedMessage());
        }
        catch (IOException e) {
            e.printStackTrace();
            return new NetworkReturn(-1, "", "IOEXCEPTION: " + e.getLocalizedMessage());
        }
        finally {
            if (con != null)
                con.disconnect();
        }
    }

终端输出如下(一个回显服务器,所以请求应该是这样的):

POST / HTTP/1.1
Content-Type: application/json
User-Agent: Java/1.7.0_60
Host: localhost:3000
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 16

blah blah blah

这是我做的方法调用:

public class Main
{
    public static void main(String[] args)
    {
        NetworkReturn result = HttpClient.sendHttpPost("http://localhost:3000/", "blah blah blah\n\n");
    }
}

编辑: 我尝试了Brett的建议,通过调用输出流中的flush方法(getResponseCode()仍被注释掉),现在我正在看看flush是否能使其工作:

...

            DataOutputStream output = new DataOutputStream(con.getOutputStream());
            output.writeBytes(postData);
            output.flush(); //<------------here

            output.close();

            //StringBuilder result = new StringBuilder(), error = new StringBuilder();

            //int responseCode = con.getResponseCode();
            /*
            if (responseCode >= 200 && responseCode < 300) 
            {
                if
...

但是我的回显服务器没有打印任何内容。现在想想,这很有道理,因为 "output.close();"可能已经调用了 "flush()”。如果 flush() 起作用的话,那么我的第一个代码片段(其中 getResponseCode() 被注释掉)可能就可以工作了。

编辑:尝试查看 512 千字节的请求体是否会产生差异并导致请求被发送,但当 getResponseCode() 被注释掉时它没有起作用。我验证了当我取消注释 getResponseCode()时它可以工作。

我的新测试大型 "文件" 的主要方法,假设一个 char 是 1 个字节,所以应该超过 512 千字节:

public class Main
{
    public static void main(String[] args)
    {
        StringBuilder largeString = new StringBuilder();
        for (int i = 0; i < 524288; i++)
        {
            largeString.append('a');
        }
        largeString.append("\n\n");
        NetworkReturn result = HttpClient.sendHttpPost("http://localhost:3000/", largeString.toString());

    }
}

写出一个更大的请求。大约512KB的大小应该足够了。在调用获取响应代码之前,您几乎肯定会看到已经写出的内容。 - Brett Okken
@BrettOkken 我试过了,但是它并没有起作用。请看我对这篇文章的编辑。 - Rock Lee
@BrettOkken 除非您处于分块或固定长度传输模式,否则不可能。在其他情况下,负载之前必须编写内容长度标头,而Java在知道整个负载之前不知道内容长度。请自行尝试。 - user207421

0
例如,我无法确定 "writeBytes" 是否实际发送数据或 "close"。这是故意模糊的,以给实现留有余地。
问题是是否使用固定长度或分块请求。默认情况下似乎使用固定长度请求,尽管规范并未要求如此。该行为可以通过HttpURLConnection上的方法进行控制。要指定使用分块,请调用 setChunkedStreamingMode。您还可以调用 setFixedLengthStreamingMode 来定义请求的固定长度。即使在这种情况下,实现也可能选择提前开始发送数据,因为总长度已知,因此可以在请求头中正确设置。
实际上,调用writeBytes通常会导致数据缓冲,直到达到某个阈值。此时,缓冲的数据将被实际写入,该过程将重复。当您调用close时,任何缓冲的数据都将被写入以及必要的字节来表示http请求的结束。

显式调用flush也通常会导致任何缓冲的数据被写出。


谢谢您的回复。我尝试了"flush()"作为一个实验,但我猜它在我的机器实现中没有起作用。(请看我的回答) - Rock Lee
1
没有阈值。所有数据都会被缓冲,直到进行输入操作。这是为了正确设置内容长度。请注意我的回答中的注意事项。 - user207421

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