java - Tomcat-Servlet响应阻止-刷新问题

标签 java tomcat response blocking flush

我正在使用Tomcat 6.0.36和JRE 1.5.0,并且正在Windows 7上进行开发工作。

作为我正在做的一些工作的概念证明,从Java代码中,我通过HTTP将一些XML通过套接字发布到servlet。的
然后,servlet回显xml。在我的第一个实现中,我将两端的输入流都交给了XML
文档工厂以提取通过网络发送的xml。这在servlet中工作顺利,但失败了
在客户端。事实证明,它在客户端失败,因为响应的读取被阻止
到文档工厂超时并在整个响应到达之前引发异常的地步。
(文档工厂的行为现在尚无定论,因为,正如我在下文所述,我遇到了相同的阻止问题
而不使用文档工厂。)

为了解决此阻塞问题,我提出了一个更简单的客户端代码版本,
servlet。在这个更简单的版本中,我从公式中删除了文档生成器。现在双方的代码
只需从它们各自的输入流中读取文本即可。

不幸的是,我仍然在响应中遇到阻塞问题,如下所述,该问题尚未得到解决
只需调用response.flushBuffer()。 Google搜索仅检索到我可以找到的一个相关主题
(Tomcat does not flush the response buffer),但这并不完全相同
问题。

我已经包含了我的代码,并在下面解释了确切的问题。

这是我的Servlet代码(请记住,这是准概念验证代码,而不是生产代码),

import java.io.InputStreamReader;
import java.io.LineNumberReader;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public final class EchoXmlServlet extends HttpServlet {

    public void init(ServletConfig config) throws ServletException {
        System.out.println("EchoXmlServlet loaded.");
    }

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {

        try {
            processRequest(request, response);
        }
        catch(Exception e) {
            e.printStackTrace();
            throw new ServletException(e);
        }

        System.out.println("Response sent.");

        return;
    }

    private final void processRequest(HttpServletRequest request, final HttpServletResponse response) throws Exception {

        String line = null;

        StringBuilder sb = new StringBuilder();
        LineNumberReader lineReader = new LineNumberReader(new InputStreamReader(request.getInputStream(), "UTF-8"));

        while((line = lineReader.readLine()) != null) {

            System.out.println("line: " + line);

            sb.append(line);
            sb.append("\n");
        }

        sb.append("An additional line to see when it turns up on the client.");

        System.out.println(sb);

        response.setHeader("Content-Type", "text/xml;charset=UTF-8");
        response.getOutputStream().write(sb.toString().getBytes("UTF-8"));

        // Some things that were tried.
        //response.getOutputStream().print(sb.toString());
        //response.getOutputStream().print("\r\n");
        //response.getOutputStream().flush();
        //response.flushBuffer();
    }

    public void destroy() {
    }

}

这是我的客户端代码,
import java.io.BufferedOutputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.net.Socket;

public final class SimpleSender {

    private String host;
    private String path;
    private int port;

    public SimpleSender(String host, String path, int port) {

        this.host = host;
        this.path = path;
        this.port = port;

    }

    public void execute() {

        Socket connection = null;
        String line;

        try {
            byte[] xmlBytes = getXmlBytes();
            byte[] headerBytes = getHeaderBytes(xmlBytes.length);

            connection = new Socket(this.host, this.port);

            OutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
            outputStream.write(headerBytes);
            outputStream.write(xmlBytes);
            outputStream.flush();

            LineNumberReader lineReader
                = new LineNumberReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));

            while((line = lineReader.readLine()) != null) {
                System.out.println("line: " + line);
            }

            System.out.println("The response is read.");
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        finally {
            try {
                connection.close();
            }
            catch(Exception e) {}
        }
    }

    private byte[] getXmlBytes() throws Exception {

        StringBuffer sb = null;

        sb = new StringBuffer()
            .append("<my-xml>\n")
            .append("Hello to myself.\n")
            .append("</my-xml>\n");

        return sb.toString().getBytes("UTF-8");
    }

    private byte[] getHeaderBytes(int contentLength) throws Exception {

        StringBuffer sb = null;

        sb = new StringBuffer()
            .append("POST ")
            .append(this.path)
            .append(" HTTP/1.1\r\n")
            .append("Host: ")
            .append(this.host)
            .append("\r\n")
            .append("Content-Type: text/xml;charset=UTF-8\r\n")
            .append("Content-Length: ")
            .append(contentLength)
            .append("\r\n")
            .append("\r\n");

        return sb.toString().getBytes("UTF-8");
    }

}

通过对SimpleSender.execute()的调用将请求发送到Servlet时,接收到请求的Servlet中的代码
请求读取xml时没有任何障碍。我的Servlet代码也从其processRequest()和doPost()退出,而没有
拴住。这是服务器上的即时输出(即,任何输出线之间都没有阻塞):
line: <my-xml>
line: Hello to myself.
line: </my-xml>
<my-xml>
Hello to myself.
</my-xml>
An additional line to see when it turns up on the client.
Response sent.

上面的输出完全符合预期。

但是,在客户端,代码输出以下内容,然后阻止:
HELLO FROM MAIN
line: HTTP/1.1 200 OK
line: Server: Apache-Coyote/1.1
line: Content-Type: text/xml;charset=UTF-8
line: Content-Length: 74
line: Date: Sun, 18 Nov 2012 23:58:43 GMT
line:
line: <my-xml>
line: Hello to myself.
line: </my-xml>

在大约20秒钟的阻塞(我为它计时)之后,在客户端输出以下行,
line: An additional line to see when it turns up on the client.
The response is read.
GOODBYE FROM MAIN

请注意,当客户端发生阻塞时,服务器端的整个输出是完全可见的。

从那里,我尝试在服务器端进行刷新以尝试解决此问题。我独立尝试了两种冲洗方法:
response.flushBuffer()和response.getOutputStream()。flush()。使用这两种冲洗方法,我仍然无法使用
客户端(但在响应的不同部分),但是我还有其他问题。这是客户的地方
被封锁
HELLO FROM MAIN
line: HTTP/1.1 200 OK
line: Server: Apache-Coyote/1.1
line: Content-Type: text/xml;charset=UTF-8
line: Transfer-Encoding: chunked
line: Date: Mon, 19 Nov 2012 00:21:53 GMT
line:
line: 4a
line: <my-xml>
line: Hello to myself.
line: </my-xml>
line: An additional line to see when it turns up on the client.
line: 0
line: 

阻塞大约20秒后,在客户端输出以下内容,
The response is read.
GOODBYE FROM MAIN

在客户端,此输出存在三个问题。首先,响应的读取仍然受阻,这是
只是在响应的不同部分之后阻塞。其次,我返回了意外的字符(“4a”,“0”)。
最后,标题已更改。我丢失了Content-Length标头,并且获得了
“传输编码:分块”标头。

因此,没有刷新,我的响应将在发送最后一行和响应的终止之前被阻塞。然而,
刷新后,响应仍然受阻,但是现在我收到了不需要的字符,并且更改了标题
不想要

在Tomcat中,我的连接器具有默认定义,
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

connectionTimeout设置为20秒。当我将其更改为10秒时,我的客户端代码被阻止10
秒而不是20秒。因此,似乎是由Tomcat管理的连接超时导致了
响应将被完全刷新并终止。

我应该在servlet代码中做些其他事情来指示响应已完成吗?

在发送最后一行和终止指示符之前,是否有人对我的响应被阻止的原因有任何建议?

有没有人有任何建议,说明为什么flush会发送不需要的字符以及为什么响应在之后仍然阻塞
潮红?

如果有人有时间,如果您尝试运行本文中包含的代码,是否可以告诉我是否遇到相同的问题?

编辑-回应Guido的第一个回复

圭多

非常感谢您的回复。

您的客户端正在阻止,因为您正在使用readLine读取
邮件正文。 readLine挂起,因为主体没有以
换行

不,我不认为这是真的。首先,在我的代码的原始版本中,我没有在客户端或服务器端使用行读取器。双方,我都将流传递到xml文档工厂,并允许其从流中读取。在服务器上,这很好。在客户端上,它超时。 (在客户端上,在将流传递到文档工厂之前,我正在阅读标题的末尾。)

其次,当我更改客户端代码以不使用行读取器时,仍然会发生阻塞。这是不使用行读取器的SimpleSender.execute()版本,
public void execute() {

    Socket connection = null;
    int byteCount = 0;

    try {
        byte[] xmlBytes = getXmlBytes();
        byte[] headerBytes = getHeaderBytes(xmlBytes.length);

        connection = new Socket(this.host, this.port);

        OutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
        outputStream.write(headerBytes);
        outputStream.write(xmlBytes);
        outputStream.flush();

        while(connection.getInputStream().read(new byte[1]) >= 0) {
            ++byteCount;
        }

        System.out.println("The response is read: " + byteCount);
    }
    catch(Exception e) {
        e.printStackTrace();
    }
    finally {
        try {
            connection.close();
        }
        catch(Exception e) {}
    }

    return;
}

上面的代码块位于
HELLO FROM MAIN

然后20秒后,完成,
The response is read: 235
GOODBYE FROM MAIN

我认为以上结论性地表明了问题不在于在客户端使用线路读取器。
sb.append("An additional line to see when it turns up on the client.\n");

在上面的行中添加return只是将块推迟到一行。我在OP之前已经对此进行了测试,然后再次进行了测试。
If you want to do your own HTTP parser, you have to read through the headers until you get two blank lines.

是的,我确实知道这一点,但是在这个人为的简单示例中,这是有争议的。在客户端上,我只是输出返回的HTTP消息,标头和所有内容。
Then you need to scan the headers to see if you had a Content-Length header. If there is no Content-Length then you are done. If there is a Content-Length you need to parse it for the length, then read exactly that number of additional bytes from the stream. This allows HTTP to transport both text data and also binary data which has no line feeds.

是的,在这个虚构的简单示例中,所有内容都是正确的,但并不重要。
I recommend you replace the guts of your client code HTTP writer/parse with a pre-written client library that handles these details for you.

我完全同意。我实际上希望将对流的处理传递给xml文档工厂。作为解决阻塞问题的一种方式,我还研究了Apache commons-httpclient。新版本(httpcomponents)仍将其留给开发人员来处理回邮流(据我所知),因此没有用。如果您可以建议其他图书馆,我肯定会感兴趣。

我不同意您的观点,但感谢您的答复,我的意思是说,我没有冒犯或任何负面暗示。我显然在做错事或没有做我应该做的事,但我不认为线阅读器是问题。另外,如果我冲洗,那些时髦的字符从哪里来?当客户端不使用线路读取器时,为什么会发生阻塞?

另外,我已经在码头上复制了这个问题。因此,这绝对不是Tomcat问题,而很大程度上是“我”问题。我做错了事,但我不知道那是什么。

最佳答案

您的服务器代码看起来不错。问题出在您的客户代码上。它不遵循HTTP协议,并且将响应视为一堆线。

在服务器上快速修复。改成:

    sb.append("An additional line to see when it turns up on the client.\n");

您的客户端被阻止,因为您正在使用readLine读取邮件的正文。 readLine挂起,因为正文未以换行结尾。最终Tomcat超时,关闭连接,您的缓冲读取器将检测到该错误并返回剩余数据。

如果您对服务器进行了上述更改,这将使您的客户端看起来像您期望的那样工作。即使它仍然是错误的。

如果要执行自己的HTTP解析器,则必须通读标头,直到得到两个空白行。然后,您需要扫描标题以查看是否具有Content-Length标题。如果没有Content-Length,那么您就完成了。如果存在Content-Length,则需要对其解析长度,然后从流中准确读取该数目的附加字节。这允许HTTP传输文本数据以及没有换行符的二进制数据。

我建议您将客户端代码HTTP writer / parse的内容替换为可为您处理这些详细信息的预先编写的客户端库。

关于java - Tomcat-Servlet响应阻止-刷新问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13446483/

相关文章:

java - 调用 repaint() 绘制新形状,但保留旧形状

java - Vaadin 7 - 找不到请求的资源

java.util.MissingFormatArgumentException : Format specifier '%s'

java - ffmpeg命令不适用于Android中目录路径的空白

java - 使用 Google Drive Api 上传文件

java - 如何在 Hibernate 中通过第三个实体将一个实体连接到第二个实体?

c# - 创建继承的响应对象

tomcat - 如何路由来自本地主机 :8080 to localhost:8080/myapp 的流量

java - 如何使用 JSP 连接到 Mysql 数据库?我的代码哪里错了?

php - 高性能服务器上的 Apache 响应时间慢