使用response.getWriter()实现SSE、流出现的BUG

报错信息

o.a.catalina.connector.CoyoteAdapter     : Encountered a non-recycled response and recycled it forcedly.

org.apache.catalina.connector.CoyoteAdapter$RecycleRequiredException: null
        at org.apache.catalina.connector.CoyoteAdapter.checkRecycled(CoyoteAdapter.java:521)
        at org.apache.coyote.http11.Http11Processor.recycle(Http11Processor.java:1417)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.release(AbstractProtocol.java:1129)
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:1052)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791)
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.base/java.lang.Thread.run(Thread.java:840)

报错的代码

@Slf4j
public class V1SSEListener extends EventSourceListener {

    @Getter
    protected CountDownLatch countDownLatch = new CountDownLatch(1);

    protected HttpServletResponse response;

    @Override
    public void onEvent(EventSource eventSource, String id, String type, String data) {
        try {
            response.getWriter().write("event:data\n");
            JSONObject jsonObject = JSONUtil.parseObj(data);
            String res = jsonObject.getStr("res");
            if (StringUtils.isNotEmpty(res)) {
                //  省略处理逻辑
            }
        } catch (Exception e) {
            countDownLatch.countDown();
            throw new RuntimeException(e);
        }
    }

    @Override
    public void onFailure(final EventSource eventSource, final Throwable t, final Response response) {
        try {
            this.response.getWriter().write("event:end\n\n");
            this.response.getWriter().flush();
        } catch (IOException e) {
            log.error("使用事件源时出现异常....");
            throw new RuntimeException(e);
        } finally {
            countDownLatch.countDown();
        }
    }
}

简单解释一下这段代码,就是从data的 res 里面拿到一些数据,做一些处理并且返回,如果处理的时候有异常的话,那么执行 countDownLatch.countDown(); 即让等待的线程继续执行,放开执行权限
onFailure 因为OpenAi的接口不会调 close() 方法(这里没写出来),但是观察到似乎会调 onFailure 方法,于是想当然的就认为这个就相当于 close()

排错

思路一:
Encountered a non-recycled response and recycled it forcedly. 中文意思为:遇到未回收的回复,强行回收。
就想着是不是 response.getWriter() 没有关闭,所以就想着,加上 this.response.getWriter().close(); ,但是问题依然在,并且在本地调试的时候还发现,第一次请求不报错,第二次请求就一定会报错
思路二:
打断点,在 onFailure 方法内打断点,看下会不会调用 onFailure 方法
这时候发现,在调用 onFailure 方法的时候,countDownLatch 就已经是 0 了,震惊到我了,为什么???
然后去 onFailurecatch 里打断点,发现最后一条消息不是JSON数据,而是 [DONE],所以报错,然后执行了 countDownLatch.countDown(); 并且又抛出了一个异常

修复

@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
    try {
        response.getWriter().write("event:data\n");
        JSONObject jsonObject = JSONUtil.parseObj(data);
        String res = jsonObject.getStr("res");
        if (StringUtils.isNotEmpty(res)) {
            //  省略处理逻辑
        }
    } catch (JSONException e) {
        log.info("[DONE]");
    } catch (Exception e) {
        countDownLatch.countDown();
        throw new RuntimeException(e);
    }
}

就是在碰到这个异常的时候,不用执行 countDownLatch.countDown();
然后又发现,这么改根本不会执行 onFailure 方法,此时意识到,只有异常才会走 onFailure 方法,我giao

再次修复

@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
    try {
        response.getWriter().write("event:data\n");
        JSONObject jsonObject = JSONUtil.parseObj(data);
        String res = jsonObject.getStr("res");
        if (StringUtils.isNotEmpty(res)) {
            //  省略处理逻辑
        }
    } catch (JSONException e) {
        log.info("[DONE]");
        // 把 onFailure 方法里的逻辑移过来了
        this.response.getWriter().write("event:end\n\n");
        this.response.getWriter().flush();
    } catch (Exception e) {
        throw new RuntimeException(e);
    } finally {
      	try {
            this.response.getWriter().close();
        } catch (IOException e) {
            log.error("关闭Writer时出现异常", e);
        } finally {
    		countDownLatch.countDown();
		}
    }
}

至此,已经BUG已经修复完毕
完整代码如下:

@Slf4j
public class V1SSEListener extends EventSourceListener {

    @Getter
    protected CountDownLatch countDownLatch = new CountDownLatch(1);

    protected HttpServletResponse response;

    @Override
    public void onEvent(EventSource eventSource, String id, String type, String data) {
        try {
	        response.getWriter().write("event:data\n");
	        JSONObject jsonObject = JSONUtil.parseObj(data);
	        String res = jsonObject.getStr("res");
	        if (StringUtils.isNotEmpty(res)) {
	            //  省略处理逻辑
	        }
	    } catch (JSONException e) {
	        log.info("[DONE]");
	        // 把 onFailure 方法里的逻辑移过来了
	        this.response.getWriter().write("event:end\n\n");
	        this.response.getWriter().flush();
	    } catch (Exception e) {
	        throw new RuntimeException(e);
	    } finally {
	      	try {
	            this.response.getWriter().close();
	        } catch (IOException e) {
            	log.error("关闭Writer时出现异常", e);
	        } finally {
	    		countDownLatch.countDown();
			}
	    }
	}

    @Override
    public void onFailure(final EventSource eventSource, final Throwable t, final Response response) {
    	log.error("使用事件源时出现异常....");
    }
}

总结

  1. OpenAI只会触发 onEvent 方法,onFailure 是处理异常才会来到这个方法,不会调用 onClose方法
  2. LangServer在关闭连接的时候会调用 onClose 方法
  3. 使用的资源需要关闭

但是我还是没有明白为什么第一次请求不报错,而是第二次请求报错????

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: response.getWriter() 方法返回一个 PrintWriter 对象,可以将响应内容写入到客户端的浏览器。通常在 Servlet 中使用该方法来生成动态的 HTML 页面或者其他类型的文档。例如,下面的代码片段会将一个简单的 HTML 页面作为响应内容输出到客户端的浏览器: ``` response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head><title>My Page</title></head>"); out.println("<body><h1>Hello World!</h1></body>"); out.println("</html>"); ``` ### 回答2: response.getWriter是Java Servlet中的一个方法,它用于获取输出,以便将数据写入客户端的响应。通过这个方法,我们可以在Servlet中向客户端发送文本、HTML、JSON等内容。 使用response.getWriter时,我们首先需要通过HttpServletResponse对象调用getWriter()方法来获取响应的输出。然后,我们可以使用这个输出对象的方法来写入响应的内容。 一般来说,我们可以通过调用输出对象的print()、println()等方法,将字符串、数字等数据写入响应。例如,我们可以使用response.getWriter().print("Hello World!")来向客户端发送一个字符串"Hello World!"。 此外,我们还可以通过输出对象的flush()方法来刷新输出,确保数据被实际发送到客户端。同时,我们还需要在完成数据写入后,调用输出对象的close()方法关闭输出,释放资源。 总之,response.getWriter方法是Java Servlet中的一个关键方法,用于获取输出,通过这个方法我们可以将数据写入响应,向客户端发送内容。 ### 回答3: response.getWriter是Java Servlet中的一个方法,用于获取一个PrintWriter对象,可以用来向客户端发送HTTP响应。通过调用该方法,我们可以将内容写入网络响应,从而将数据传输给客户端。 在Servlet的doGet()或doPost()方法中,可以使用response.getWriter()方法获取PrintWriter对象。然后,可以使用该对象的print()或println()方法将数据以文本的形式发送给客户端。可以发送HTML代码、文本、XML数据或其他格式的数据。 例如,我们可以使用以下代码将一个简单的HTML页面发送给客户端: ``` response.setContentType("text/html;charset=UTF-8"); // 设置响应内容类型为HTML PrintWriter out = response.getWriter(); // 获取输出 out.println("<html>"); out.println("<head>"); out.println("<title>欢迎页面</title>"); out.println("</head>"); out.println("<body>"); out.println("<h1>Hello, World!</h1>"); out.println("</body>"); out.println("</html>"); out.close(); // 关闭 ``` 在上述代码中,首先设置了响应的内容类型为HTML,然后通过response.getWriter()方法获取PrintWriter对象。接下来,我们使用PrintWriter对象的println()方法将HTML页面的代码逐行写入输出中,最后关闭输出。 总之,response.getWriter()方法是Servlet中用于向客户端发送HTTP响应的重要方法。通过获取PrintWriter对象,我们可以将内容发送给客户端,实现数据的传输和展示。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值