验证调用HttpServletResponse.getWriter().close()方法是否真的会关闭http连接
2017-09-08 20:06
483 查看
起因
线上项目突然遭到大量的非法参数攻击,由于历史问题,之前的代码从未对请求参数进行校验。导致大量请求落到了数据访问层,给应用服务器和数据库都带来了很大压力。
针对这个问题,只能对请求真正到Controller方法调用之前直接将非法参数请求拒绝掉,所以在Filter中对参数进行统一校验,非法参数直接返回400。
我的建议是不但要设置响应状态码设置为400,还应该明确调用HttpServletResponse.getWriter().close(),希望此举能在服务端主动断开连接,释放资源。
但是同事认为不必要明确调用HttpServletResponse.getWriter().close(),于是就有了这个验证实验。
实验
1.应用容器:tomcat 7.0.59
2.如何验证服务器是否真的断开连接:观察http响应消息头“Connection”值是否为“close”。
不明确close时httpresponse返回的消息头
HTTP/1.1 400 Bad Request Server: Apache-Coyote/1.1 Content-Length: 21 Date: Tue, 05 Sep 2017 11:39:00 GMT Connection: close
明确close时httpresponse返回的消息头
HTTP/1.1 400 Bad Request Server: Apache-Coyote/1.1 Content-Length: 0 Date: Tue, 05 Sep 2017 11:39:25 GMT Connection: close
结论
根据上述结果,如果根据http响应消息头“Connection”值是否为“close”来验证服务端是否会主动断开连接。那么在servlet中是否明确调用“HttpServletResponse.getWriter().close()”结果都是一样的。
因为在org.apache.coyote.http11.AbstractHttp11Processor中会根据响应状态码判断返回消息头Connection值。
private void prepareResponse() { ... // If we know that the request is bad this early, add the // Connection: close header. keepAlive = keepAlive && !statusDropsConnection(statusCode); if (!keepAlive) { // Avoid adding the close header twice if (!connectionClosePresent) { headers.addValue(Constants.CONNECTION).setString( Constants.CLOSE); } } else if (!http11 && !getErrorState().isError()) { headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE); } ... } /** * Determine if we must drop the connection because of the HTTP status * code. Use the same list of codes as Apache/httpd. */ protected boolean statusDropsConnection(int status) { return status == 400 /* SC_BAD_REQUEST */ || status == 408 /* SC_REQUEST_TIMEOUT */ || status == 411 /* SC_LENGTH_REQUIRED */ || status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ || status == 414 /* SC_REQUEST_URI_TOO_LONG */ || status == 500 /* SC_INTERNAL_SERVER_ERROR */ || status == 503 /* SC_SERVICE_UNAVAILABLE */ || status == 501 /* SC_NOT_IMPLEMENTED */; }
也就是说,当响应状态码为400时,不论是否明确调用“HttpServletResponse.getWriter().close()”,都会在响应消息头中设置“Connection: close”。
那么,问题来了:HTTP的响应消息头“Connection”值为“close”时是否就意味着服务端会主动断开连接了呢?
根据rfc2616的对于HTTP协议的定义(详见:https://www.ietf.org/rfc/rfc2616.txt):
HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion of the response. For example, Connection: close
也就是,一旦在服务端设置响应消息头“Connection”为“close”,就意味着在本次请求响应完成后,对应的连接应该会被关闭。
然而,这对于不同的Servlet容器实现来说,真的就会关闭连接吗?
跟踪tomcat源码,并没有发现会明确关闭连接。
明确调用“HttpServletResponse.getWriter().close()”时tomcat又做了什么事情
(1)org.apache.catalina.connector.CoyoteWriter @Override public void close() { // We don't close the PrintWriter - super() is not called, // so the stream can be reused. We close ob. try { ob.close(); } catch (IOException ex ) { // Ignore } error = false; } (2)org.apache.catalina.connector.OutputBuffer /** * Close the output buffer. This tries to calculate the response size if * the response has not been committed yet. * * @throws IOException An underlying IOException occurred */ @Override public void close() throws IOException { if (closed) { return; } if (suspended) { return; } // If there are chars, flush all of them to the byte buffer now as bytes are used to // calculate the content-length (if everything fits into the byte buffer, of course). if (cb.getLength() > 0) { cb.flushBuffer(); } if ((!coyoteResponse.isCommitted()) && (coyoteResponse.getContentLengthLong() == -1) && !coyoteResponse.getRequest().method().equals("HEAD")) { // If this didn't cause a commit of the response, the final content // length can be calculated. Only do this if this is not a HEAD // request since in that case no body should have been written and // setting a value of zero here will result in an explicit content // length of zero being set on the response. if (!coyoteResponse.isCommitted()) { coyoteResponse.setContentLength(bb.getLength()); } } if (coyoteResponse.getStatus() == HttpServletResponse.SC_SWITCHING_PROTOCOLS) { doFlush(true); } else { doFlush(false); } closed = true; // The request should have been completely read by the time the response // is closed. Further reads of the input a) are pointless and b) really // confuse AJP (bug 50189) so close the input buffer to prevent them. Request req = (Request) coyoteResponse.getRequest().getNote( CoyoteAdapter.ADAPTER_NOTES); req.inputBuffer.close(); coyoteResponse.finish(); } (3)org.apache.coyote.Response public void finish() { action(ActionCode.CLOSE, this); } public void action(ActionCode actionCode, Object param) { if (hook != null) { if( param==null ) hook.action(actionCode, this); else hook.action(actionCode, param); } } (4)org.apache.coyote.http11.AbstractHttp11Processor /** * Send an action to the connector. * * @param actionCode Type of the action * @param param Action parameter */ @Override @SuppressWarnings("deprecation") // Inbound/Outbound based upgrade mechanism public final void action(ActionCode actionCode, Object param) { switch (actionCode) { case CLOSE: { // End the processing of the current request try { getOutputBuffer().endRequest(); } catch (IOException e) { setErrorState(ErrorState.CLOSE_NOW, e); } break; } ... } } (5)org.apache.coyote.http11.InternalNioOutputBuffer /** * End request. * * @throws IOException an underlying I/O error occurred */ @Override public void endRequest() throws IOException { super.endRequest(); flushBuffer(); } /** * Callback to write data from the buffer. */ private void flushBuffer() throws IOException { //prevent timeout for async, SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector()); if (key != null) { NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment(); attach.access(); } //write to the socket, if there is anything to write if (socket.getBufHandler().getWriteBuffer().position() > 0) { socket.getBufHandler().getWriteBuffer().flip(); writeToSocket(socket.getBufHandler().getWriteBuffer(),true, false); } }
实际上,明确调用“HttpServletResponse.getWriter().close()”时只是确保将数据发送给客户端,并不会执行关闭连接。
但是,这是与容器实现有关的,建议在应用程序中明确调用close()方法,以便进行相关资源释放。
针对非法参数的DDoS攻击的请求,都应该在应用服务器前端进行拦截,杜绝请求直接到应用层。
如:在nginx端进行IP拦截,参考:https://zhangge.net/5096.html。
相关文章推荐
- android中HttpURLConnection调用getResponseCode()时崩溃 解决方法
- 使用using{},去掉数据库连接的.Close()方法,using自动会调用Dispose()来关闭数据库连接
- 用完HttpWebResponse时别忘了调用Close方法
- 被PrintStream包装的FileOutputStream在PrintStream调用close()后是否需要单独调用自己的close()方法关闭
- HttpWebResponse.GetResponse() 基础连接已经关闭: 服务器关闭了本应保持活动状态的连接。
- (5A)HttpServletResponse:getWriter和getOutputStream()、sendRedirect()、session(简单理解)、文件下载、页面缓存
- 关于jetty和webx对于HttpServletResponse getWriter和getOutputStream的处理
- 关于jetty和webx对于HttpServletResponse getWriter和getOutputStream的处理
- 用httpWebRequest调用https开头的php webservice,报“基础连接已经关闭: 未能为SSL/TLS 安全通道建立信任关系”错误的解决方法
- 关于jetty和webx对于HttpServletResponse getWriter和getOutputStream的处理
- HttpWebResponse.GetResponse() 基础连接已经关闭: 服务器关闭了本应保持活动状态的连接。
- .NET HttpWebResponse.GetResponse() 基础连接已经关闭: 服务器关闭了本应保持活动状态的连接。
- 调用DbHelperACE获取OleDbDataReader后手动关闭连接方法
- idea编辑器HttpServlet httpServlet = ServletActionContext.getServletContext().getRealPath();方法无法使用
- Spring找不到方法:The method getDispatcherType() is undefined for the type HttpServletRequest
- Android HttpURLConnection.getResponseCode()错误解决方法
- Java httpservletresponse 中的sendRedirect()方法的重定位乱码的总结
- RequestDispatcher.forward() 方法和HttpServletResponse.sendRedirect()方法的区别
- 《pro Spring》学习笔记之Spring HTTP 远程方法调用集成Tomcat实现安全验证
- NoSuchMethodError: javax.servlet.http.HttpServletResponse.getStatus()I