GVKun编程网logo

使用ServletOutputStream在Java Servlet中写入非常大的文件而不会出现内存问题

27

在本文中,您将会了解到关于使用ServletOutputStream在JavaServlet中写入非常大的文件而不会出现内存问题的新资讯,并给出一些关于FailedtoclosetheServletO

在本文中,您将会了解到关于使用ServletOutputStream在Java Servlet中写入非常大的文件而不会出现内存问题的新资讯,并给出一些关于Failed to close the ServletOutputStream connection cleanly, Broken pipe、groovy servlet java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getStatus()I、HttpServletRequest.getInputStream()多次读取问题、java – 将ServletOutputStream读取到String的实用技巧。

本文目录一览:

使用ServletOutputStream在Java Servlet中写入非常大的文件而不会出现内存问题

使用ServletOutputStream在Java Servlet中写入非常大的文件而不会出现内存问题

我正在使用IBM Websphere Application Server v6和Java
1.4,并试图将CSV大文件写入ServletOutputStream以便用户下载。目前文件大小为50-750MB。

较小的文件并不会引起太大的问题,但是对于较大的文件,似乎是将其写入堆中,这随后导致OutOfMemory错误并导致整个服务器停机。

这些文件只能通过HTTPS提供给经过身份验证的用户,这就是为什么我通过Servlet而不是仅仅将它们粘贴在Apache中为它们提供服务的原因。

我正在使用的代码是(消除了一些绒毛):

    resp.setHeader("Content-length", "" + fileLength);    resp.setContentType("application/vnd.ms-excel");    resp.setHeader("Content-Disposition","attachment; filename=\"export.csv\"");    FileInputStream inputStream = null;    try    {        inputStream = new FileInputStream(path);        byte[] buffer = new byte[1024];        int bytesRead = 0;        do        {            bytesRead = inputStream.read(buffer, offset, buffer.length);            resp.getOutputStream().write(buffer, 0, bytesRead);        }        while (bytesRead == buffer.length);        resp.getOutputStream().flush();    }    finally    {        if(inputStream != null)            inputStream.close();    }

FileInputStream似乎没有,如果我写到另一个文件或只是删除完全写入内存使用情况似乎并不成为一个问题而导致问题。

我在想的是将resp.getOutputStream().write其存储在内存中,直到可以将数据发送到客户端为止。因此,可能会读取整个文件并将其存储在resp.getOutputStream()导致我的内存问题并崩溃的地方!

我已经尝试过缓冲这些流,还尝试过使用from的Channels
java.nio,这些似乎都没有对我的内存问题产生任何影响。我还刷新了OutputStream循环的每个迭代一次,并在循环之后刷新了一次,但这没有帮助。

答案1

小编典典

默认情况下,平均体面的servlet容器本身每2KB刷新一次流。你真的应该没有需要再调用flush()OutputStreamHttpServletResponse间隔顺序时,从一个相同的源流数据。例如,在Tomcat(和Websphere!)中,可以将其配置为bufferSizeHTTP连接器的属性。

如果内容长度是事先未知的(按照Servlet
API规范!),并且客户端支持HTTP
1.1,那么普通的体面servlet容器也只会分块传输数据。

问题症状至少表明servlet容器在刷新之前正在缓冲整个流在内存中。这可能意味着未设置内容长度标头和/或servlet容器不支持分块编码和/或客户端不支持分块编码(即,它正在使用HTTP
1.0)。

要解决一个或另一个问题,只需预先设置内容长度:

response.setHeader("Content-Length", String.valueOf(new File(path).length()));

Failed to close the ServletOutputStream connection cleanly, Broken pipe

Failed to close the ServletOutputStream connection cleanly, Broken pipe

Problem1: 服务端报错:Broken pipe
java.io.IOException: Connection timed out
at sun.nio.ch.FileDispatcherImpl.read0(Native Method)
at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
at sun.nio.ch.IOUtil.read(IOUtil.java:197)
at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:380)
at org.apache.tomcat.util.net.SecureNioChannel.read(SecureNioChannel.java:581)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1248)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.fillReadBuffer(NioEndpoint.java:1221)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.read(NioEndpoint.java:1194)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:72)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
05-Nov-2018 09:17:07.881 INFO [https-jsse-nio-443-exec-6] org.apache.tomcat.websocket.server.WsRemoteEndpointImplServer.doClose Failed to close the ServletOutputStream connection cleanly
java.io.IOException: Broken pipe
at sun.nio.ch.FileDispatcherImpl.write0(Native Method)
at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
at sun.nio.ch.IOUtil.write(IOUtil.java:65)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
at org.apache.tomcat.util.net.SecureNioChannel.flush(SecureNioChannel.java:144)
at org.apache.tomcat.util.net.SecureNioChannel.close(SecureNioChannel.java:526)
at org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.close(NioEndpoint.java:1209)
at org.apache.tomcat.websocket.server.WsRemoteEndpointImplServer.doClose(WsRemoteEndpointImplServer.java:167)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.close(WsRemoteEndpointImplBase.java:710)
at org.apache.tomcat.websocket.WsSession.onClose(WsSession.java:518)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.close(WsHttpUpgradeHandler.java:240)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:162)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
报出超时的原因可能是后端在向前端发起数据请求时,没有成功。可能的原因是,在服务端,负责和前端通信的websocket连接Session存放在一个Map中,当由于某种未知的原因,导致前后端连接断开后,即客户端失联了,但是Map里的Session并未被移除。所以当服务端有新消息要发送给这个已经失联的Session,便会报错:Connection timed out, Broken pipe。

Solution:当产生这个异常后,后端从Map中移除(remove)已断开连接的客户端Session。
Note:也可以在前端把错误打印出来:WebSocket断开时,会触发CloseEvent, CloseEvent的code字段表示了WebSocket断开的原因。可以从该字段中分析断开的原因。

CloseEvent的三个字段:

CloseEvent.code: code是错误码,是整数类型
CloseEvent.reason: reason是断开原因,是字符串
CloseEvent.wasClean: wasClean表示是否正常断开,是布尔值。一般异常断开时,该值为false
1
2
3
websocket.onclose = function (e) {
console.log(''WebSocket连接断开:Code:'' + e.code + '' Reason:'' + e.reason + '' wasClean:'' + e.wasClean);
}
1
2
3
Problem2: 客户端断开后,如何做到尝试再次连接,即断线重连
Solution: websocket断线重连解决方案: ReconnectingWebSocket
let ws = new WebSocket(''ws://host:port'');
// 替换为:
let ws = new ReconnectingWebSocket(''ws://host:port'');
1
2
3
当然ReconnectingWebSocket中还有其他诸如重试间隔等各类配置项,可以查看文档或源码。

Note:如果服务器域名为HTTPS,那么使用的WebSocket协议也必须是wss

If you have any questions or any bugs are found, please feel free to contact me.

Your comments and suggestions are welcome!

groovy servlet java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getStatus()I

groovy servlet java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getStatus()I

java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getStatus()I
at org.eclipse.jetty.server.handler.ErrorHandler.handle(ErrorHandler.java:111)
at org.eclipse.jetty.server.Response.sendError(Response.java:597)
at javax.servlet.http.HttpServlet.doGet(HttpServlet.java:187)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:769)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:577)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:223)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1125)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1059)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:311)
at org.eclipse.jetty.server.httpconnection.onFillable(httpconnection.java:248)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:610)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:539)

at java.lang.Thread.run(Thread.java:745)


问题原因: groovy libraries 中有个servlet api 那个是比较老的.而且被优先加载了.

解决方法: 将自己引用的servlet api 放在前面 

HttpServletRequest.getInputStream()多次读取问题

HttpServletRequest.getInputStream()多次读取问题

转自:https://www.jianshu.com/p/85feeb30c1ed

HttpServletRequest.getInputStream()多次读取问题

 

背景

使用POST方法发送数据时,我们习惯于把数据包装成json格式。

 

 
image.png

有些情况下,我们会在Filter中读取body数据进行数据校验,
GET方法获取参数比较简单。对于POST方法,可使用如下方法从request中获取body参数:

 private String getBody(HttpServletRequest request) throws IOException { InputStream in = request.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8"))); StringBuffer sb = new StringBuffer(""); String temp; while ((temp = br.readLine()) != null) { sb.append(temp); } if (in != null) { in.close(); } if (br != null) { br.close(); } return sb.toString(); } 

注意,这里有了一次request.getInputStream()调用。

但是在测试时,一直报JSON格式不正确的错误。经调查发现,项目中使用了公司基础组件中的Filter,而该Filter中也解析了body。同时,不出所料,也是通过调用getInputStream()方法获取的。

原来:

  • 一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;
  • InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

因此,当自己写的Filter中调用了一次getInputStream()后,后面再调用getInputStream()读取的数据都为空,所以才报JSON格式不正确的错误。

解决方法

  1. 缓存数据
  2. 使用HttpServletRequestWrapper进行包装

缓存数据
所谓缓存数据,其实就是调用ServletRequestsetAttribute(String s, Object o)来存储数据。

  1. 获取到body后,直接缓存
String body = getBody(request); request.setAttribute("body", body); 

优点:
方便
缺点:
不能控制第三方Filter

  1. 其他地方需要使用body时,只需调用getAttribute方法就能获取数据了:
request.getAttribute("body"); 

HttpServletRequestWrapper包装

public class RequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RequestWrapper(HttpServletRequest request) throws IOException { super(request); body = getBodyStringFromReq(request).getBytes(Charset.forName("UTF-8")); } public String getBodyString() { try { return new String(body, "UTF-8"); } catch (UnsupportedEncodingException ex) { return new String(body); } } private String getBodyStringFromReq(ServletRequest request) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; BufferedReader reader = null; try { inputStream = request.getInputStream(); reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))); String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); } } 

在Filter中使用时,FilterChain.doFilter()传入Wrapper对象:

public class TestFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest)request); String body = requestWrapper.getBodyString(); chain.doFilter(requestWrapper, response); //传入Wrapper对象 } @Override public void init(FilterConfig arg0) throws ServletException { } } 

这样,位于后面的Filter就可以拥有唯一一次调用HttpServletRequest.getInputStream()的机会了。

优点:
不影响第三方Filter

缺点:
多写了这么多代码,麻烦了一些

参考

java – 将ServletOutputStream读取到String

java – 将ServletOutputStream读取到String

我试图阅读FreemarkerView渲染的结果:

View view = viewResolver.resolveViewName(viewName,locale);
view.render(model,request,mockResponse);

为了读取结果,我创建了mockResponse,它封装了HttpServletResponse:

public class HttpServletResponseEx extends HttpServletResponseWrapper {

    ServletoutputStream outputStream;

    public HttpServletResponseEx(HttpServletResponse response) throws IOException {
        super(response);
        outputStream = new ServletoutputStreamEx();
    }

    @Override
    public ServletoutputStream getoutputStream() {
        return outputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        return new PrintWriter(new OutputStreamWriter(outputStream,"UTF-8"));
    }
}

还有我的ServletoutputStream,它使用StringBuilder构建String:

public class ServletoutputStreamEx extends ServletoutputStream {

    StringBuilder stringBuilder;

    public ServletoutputStreamEx() {
        this.stringBuilder = new StringBuilder();
    }

    @Override
    public void write(int b) throws IOException {
    } 

    @Override
    public void write(byte b[],int off,int len) throws IOException {
        stringBuilder.append(new String(b,"UTF-8"));
    }

    @Override
    public String toString() {
        return stringBuilder.toString();
    }
}

有了这些,我可以使用方法ServletoutputStreamEx.toString轻松读取响应.

我的问题是write方法没有按正确的顺序调用,最后最终的String混合而且顺序不正确.这可能是由Freemarker中的并发引起的,但我不知道如何修复它.

最佳答案
感谢您的回复:write(int b)未实现,因为它永远不会被调用.最后的问题是字节数​​组,它也包含前一个字符串.所以String需要创建为String(b,off,len,“UTF-8”).

今天关于使用ServletOutputStream在Java Servlet中写入非常大的文件而不会出现内存问题的讲解已经结束,谢谢您的阅读,如果想了解更多关于Failed to close the ServletOutputStream connection cleanly, Broken pipe、groovy servlet java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getStatus()I、HttpServletRequest.getInputStream()多次读取问题、java – 将ServletOutputStream读取到String的相关知识,请在本站搜索。

本文标签: