0

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

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

这是代码。

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.readlinenull,但有时它会起作用,有时它不会。

在此处输入图像描述 好的,所以我很困惑。我放弃了使用 HttpURLConnection 并开始使用 Socket 类并手动编写所有标头和数据。如果不发送零块,它似乎一直在工作。对于零块,它似乎一直在工作,除非我在调试器中运行它,它得到与上面相同的错误。因此,我在发送标头之后和发送数据之前放置了一个 sleep(100) 并在没有调试器的情况下运行它,并且始终出现错误。所以我假设在 HttpURLConnection 类中发送标头后会有延迟,这就是为什么它有时有效而其他时候无效的原因。我不能发送零块,但我真的很想知道为什么会导致错误。有任何想法吗?我认为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);
        }
    }
}
4

1 回答 1

3

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

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

于 2013-02-27T22:24:24.663 回答