java - HTTP 分块传输

标签 java http tomcat chunked-encoding

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

我正在使用 Java HttpUrlConnection 类来建立连接,我不知道为什么它会发送一个零 block 以及如何防止它这样做.

这是代码。

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

当我执行 inputstream.readline 时,它是 null,但有时有效,有时无效。

enter image description here 好的,所以我很困惑。我放弃了使用 HttpURLConnection 并开始使用 Socket 类并手动编写所有 header 和数据。在不发送零 block 的情况下,它似乎一直在工作。对于零 block ,它似乎一直在工作,除了当我在调试器中运行它时,它得到了与上面相同的错误。所以我在发送 header 之后和发送数据之前放置了一个 sleep(100) 并在没有调试器的情况下运行它并且它始终得到错误。所以我假设在 HttpURLConnection 类中发送 header 后会有延迟,这就是为什么它有时有效而其他时间无效的原因。我不能发送零 block ,但我真的很想知道为什么会导致错误。有任何想法吗?我认为 Tomcat 中存在错误。

这是代码。

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

最佳答案

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

你被误导了。最后一个 chunk 为 0 是正确的,表示传输结束。参见 RFC 2616 #3.6.1 .发送一个没有错,Tomcat 不应该(而且几乎肯定不会)通过关闭连接来使用react。

关于java - HTTP 分块传输,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15115069/

相关文章:

python - 如何在没有 HTTPS 的情况下运行 Django 1.6 LiveServerTestCase?

jsp - 在 Tomcat 服务器中运行 JSP 页面?

Java 泛型 - 使用元素列表实现接口(interface)

java - Jacoco 输掉了测试。覆盖率始终为 0

java - 我如何测试我的连接池是否以正确的方式工作?

node.js - 从 MJPEG 流中提取 JPEG 并通过 websocket 发布 base64 编码图像

firefox - tomcat(apache-tomcat-6.0.32)的 CSRF 方法在 firefox 14.0.1 上无法正常工作

tomcat - 我构建我的 servlet 了吗?

java - 每个日期单元格样式都可配置的 Android 自定义日历 View ?

使用 bouncy caSTLe 的 Java ME MD5 字符串 - 不能多次散列