java HTTP 分块传输

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15115069/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 18:31:07  来源:igfitidea点击:

HTTP chunked transfer

javahttptomcatchunked-encoding

提问by user1309036

I'm writing a client app that connects to a service using the chunked transfer encoding. The service occasionally disconnects and I was told it was because we're sending a zero chunk in the request, so Tomcatcloses the connection.

我正在编写一个客户端应用程序,它使用分块传输编码连接到服务。该服务偶尔会断开连接,我被告知这是因为我们在请求中发送了一个零块,所以Tomcat关闭了连接。

I'm using the JavaHttpUrlConnectionclass to make the connection and I have no idea why it would be sending a zero chunk and how to prevent it from doing that.

我正在使用JavaHttpUrlConnection该类来建立连接,但我不知道为什么它会发送零块以及如何防止它这样做。

Here's the code.

这是代码。

URL m5url = new URL("https://hostedconnect.m5net.com/bobl/bobl?name=org.m5.apps.v1.cti.ClickToDial.subscribe");
StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"standalone=\"yes\"?>" 
                                   + "<Command>" 
                                   + "<Name>org.m5.apps.v1.cti.ClickToDial.subscribe</Name>"
                                   + "<Id>1</Id>" 
                                   + "<User>" + m5username + "</User>" 
                                   + "<Password>" + m5password + "</Password>" 
                                   + "<FormattedXml>true</FormattedXml>" 
                                   + "<ShallowResponse>FULL</ShallowResponse>" 
                                   + "</Command>");

conn = (HttpURLConnection) m5url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setReadTimeout(SESSION_TIMEOUT);
conn.setChunkedStreamingMode(0);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);

out = new DataOutputStream(conn.getOutputStream());
conn.connect();
out.writeBytes(sb.toString());
out.flush();

When I do inputstream.readlineit's null, but sometimes it works and sometimes it doesn't.

当我这样做inputstream.readlinenull,但有时它会起作用,有时却不起作用。

enter image description hereOk, so I'm thoroughly confused. I abandoned using the HttpURLConnection and started using the Socket class and writing all the headers and data manually. Without sending the zero chunk it seems to work all the time. With the zero chunk it seemed to be working all the time except when I ran it in the debugger, where it got the same error as above. So I put a sleep(100) after it sends the headers and before it sends the data and ran it without the debugger and it consistently got the error. So I assume in the HttpURLConnection class there's a delay after sending the headers which is why it works sometimes and doesn't other times. I could just not send the zero chunk but I'd really like to know why that would cause the error. Any ideas? I'm thinking there's a bug in Tomcat.

在此处输入图片说明好吧,所以我彻底糊涂了。我放弃了使用 HttpURLConnection 并开始使用 Socket 类并手动编写所有标头和数据。如果不发送零块,它似乎一直都在工作。使用零块,它似乎一直在工作,除非我在调试器中运行它时,它得到了与上面相同的错误。所以我在它发送标头之后和发送数据之前放置了一个 sleep(100) 并且在没有调试器的情况下运行它并且它始终得到错误。所以我假设在 HttpURLConnection 类中发送标头后会有延迟,这就是为什么它有时工作而其他时候不工作的原因。我不能发送零块,但我真的很想知道为什么会导致错误。有任何想法吗?我在想 Tomcat 中存在一个错误。

Here's the code.

这是代码。

public class M5Connection
{
    public static final String urlBase = "/bobl/bobl";
    public static final String ENCODING = "ISO-8859-1";
    public static final String DELIMITER = "\r\n";
    protected URL url;
    private InputStream inputStream;
    protected OutputStream outputStream;
    protected Socket socket;
    protected BufferedReader reader;
    private boolean bProcessedHeaders;

    protected String resp = null;
    protected String errorMessage = null;


    /**
     * Start a new connection to the BOBL server.
     * @param server server name:port to connect to
     * @throws IOException
     */
    protected void initConnection(String server, int timeout) throws IOException
    {
        url = new URL(server + urlBase);
        int port = url.getPort();
        if (server.startsWith("https"))
        {
            if (port == -1) port = 443;
            else
                if (port == 80 || port == -1)port = 8080;
        }

        if (server.startsWith("https") == false)
        {
            socket = new Socket(url.getHost(), port);
        }
        else
        {           
            SocketFactory socketFactory = SSLSocketFactory.getDefault();
            socket = socketFactory.createSocket(url.getHost(), port);
        }

        socket.setSoTimeout(timeout);
        socket.setKeepAlive(true);
        socket.setSoLinger(false, 0);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        reader = new BufferedReader(new InputStreamReader(inputStream));
    }

    public void initHttpsConnection(String server, int timeout) throws IOException
    {
        initConnection(server,timeout);
        sendHeaders();
        bProcessedHeaders = false;
    }

    private void sendHeaders() throws IOException {
        String path = url.getPath();
        StringBuffer outputBuffer = new StringBuffer();
        outputBuffer.append("POST " + path + " HTTP/1.1" + DELIMITER);
        outputBuffer.append("Host: " + url.getHost() + DELIMITER);
        outputBuffer.append("User-Agent: CometTest" + DELIMITER);
        outputBuffer.append("Connection: keep-alive" + DELIMITER);
        outputBuffer.append("Content-Type: text/plain" + DELIMITER);
        outputBuffer.append("Transfer-Encoding: chunked" + DELIMITER);
        outputBuffer.append(DELIMITER);
        byte[] outputBytes = outputBuffer.toString().getBytes(ENCODING);
        outputStream.write(outputBytes);
        outputStream.flush();
    }

    /** Send some data to the server, HTTP/1.1 chunked style. */
    public void send(String chunkData) throws IOException {
        byte[] chunkBytes = chunkData.getBytes(ENCODING);
        String hexChunkLength = Integer.toHexString(chunkBytes.length);
        StringBuffer outputBuffer = new StringBuffer();
        outputBuffer.append(hexChunkLength);
        outputBuffer.append(DELIMITER);
        outputBuffer.append(chunkData);
        outputBuffer.append(DELIMITER);
        byte[] outputBytes = outputBuffer.toString().getBytes(ENCODING);
        outputStream.write(outputBytes);
        outputStream.flush();

        outputBuffer = new StringBuffer();
        outputBuffer.append("0");
        outputBuffer.append(DELIMITER);
        outputBuffer.append(DELIMITER);
        outputBytes = outputBuffer.toString().getBytes(ENCODING);
        outputStream.write(outputBytes);
        outputStream.flush();
    }

    /**
     * Wait for a response from the server.
     * @return the string that the server returned.
     * @throws IOException
     */
    public String getRawResponse() throws IOException
    {
        String s;

        // just after we connect we expect to see the HTTP headers. Read and discard
        if (!bProcessedHeaders) {
            while (true){
                String line = reader.readLine();
                System.out.println("HEADER: " + line);

                if (line == null || line.equals("\r\n") || line.equals(""))
                    break;
            }
            bProcessedHeaders = true;
        }

        while (true)
        {       
            s = getChunk();     

            if (s == null)
                return null;

            if (s.equals("")) {
                continue;
            }

            // server will not emit XML if it is having real troubles
            if (s.charAt(0) != '<' || s.startsWith("<html>")) {
                System.out.println("Server says: " + s);
                continue;
            }
            return s;
        }
    }   

    /**
     * Expect chunked excoding back from the server. Read and return a chunk.
     * @return a string containing the HTTP chunk
     * @throws IOException
     */
    private String getChunk() throws IOException
    {
        StringBuffer buf = new StringBuffer();
        while (true)
        {
        // HTTP chunked mode, expect to see a line with the length in hex of the chunk that follows
            String s = reader.readLine();           

            if (s == null)
                throw new IOException();
            if (s.length() == 0)
                continue;

            int toread;
            try {
                toread = Integer.parseInt(s, 16);
            } catch (NumberFormatException e) {
                System.out.println("Number format error: " + s);
                return "";
            }

            if (toread == 0)
            {
                return null;
            }

            // read the chunk
            char[] data = new char[toread];
            int read = 0;
            while (read != toread)
            {
                read += reader.read(data, read, toread - read);
            }
            buf.append(data, 0, read);

            // for some reason tomcat only sends data in up to 8192 byte chunks
            if (toread != 8192)
                break;
        }
        return buf.toString();
    }   

    public void close()
    {
        try { socket.close(); } catch (IOException e) {}
    }

    public static void main(String[] args) throws Exception
    {
        M5Connection cnx = new M5Connection();
        cnx.initHttpsConnection("https://hostedconnect.m5net.com/bobl/bobl?name=org.m5.apps.v1.cti.ClickToDial.subscribe", 0);

        Thread.sleep(100);
        //
        // Create and send an XML command to listen for call state changes on our TN
        //
        String format = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
        "<Command>" +
        "    <Name>org.m5.apps.v1.cti.ClickToDial.subscribe</Name>" +
        "    <Id>1</Id>" +
        "    <User></User>" +
        "    <Password></Password>" +
        "    <FormattedXml>true</FormattedXml>" +
        "    <ShallowResponse>FULL</ShallowResponse>" +
        "</Command>";
        String command = format;
        System.out.println("SENDING " + command + "\n ------------ ");
        cnx.send(command);

        //
        // Now just wait for the responses
        //
        while (true)
        {
            String resp = cnx.getRawResponse();
            System.out.println(resp);
        }
    }
}

回答by user207421

I was told it was because we're sending a zero chunk in the request

有人告诉我这是因为我们在请求中发送了一个零块

You were misinformed. The final chunk of zero is correct, indicating the end of the transfer. See RFC 2616 #3.6.1. There's nothing wrong with sending one, and Tomcat shouldn't (and almost certainly doesn't) react by closing the connection.

你被误导了。最后一块零是正确的,表示传输结束。请参阅RFC 2616 #3.6.1。发送一个没有错,Tomcat 不应该(而且几乎肯定不会)通过关闭连接来做出反应。