java response close_验证调用HttpServletResponse.getWriter().close()方法是否真的会关闭http连接...

起因

线上项目突然遭到大量的非法参数攻击,由于历史问题,之前的代码从未对请求参数进行校验。

导致大量请求落到了数据访问层,给应用服务器和数据库都带来了很大压力。

针对这个问题,只能对请求真正到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

结论

1.根据上述结果,如果根据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

esponse. For example,

Connection: close

也就是说,一旦在服务端设置响应消息头“Connection”为“close”,就意味着在本次请求响应完成后,对应的连接应该会被关闭。

然而,这对于不同的Servlet容器实现来说,真的就会关闭连接吗?

跟踪tomcat源码发现,即使明确调用close()方法也不是直接就关闭连接。

2.明确调用“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()方法?在我遇到的这个校验非法参数的场景,其实是不必要的。但是,当HTTP状态码返回400时,Connection值一定会被设置为close。

那么,这个问题被引申一下:Http协议头中的“Connection”字段到底有和意义呢?这需要从HTTP协议说起。在Http1.0中是没有这个字段的,也就是说每一次HTTP请求都会建立新的TCP连接。而随着Web应用的发展,通过HTTP协议请求的资源越来越丰富,除了文本还可能存在图片等其他资源了,为了能够在一次TCP连接中能最快地获取到这些资源,在HTTP1.1中增加了“Connection”字段,取值为close或keep-alive。其作用在于告诉使用HTTP协议通信的2端在建立TCP连接并完成第一次HTTP数据响应之后不要直接断开对应的TCP连接,而是维持这个TCP连接,继续在这个连接上传输后续的HTTP数据,这样可以大大提高通信效率。当然,当“Connection”字段值为close时,说明双方不再需要通信了,希望断开TCP连接。

所以,对于使用HTTP协议的Web应用来讲,如果希望服务器端与客户端在本次HTTP协议通信之后断开连接,需要将“Connection”值设置为close;否则应该设置为keep-alive。

3.针对非法参数的DDoS攻击的请求,都应该在应用服务器前端进行拦截,杜绝请求直接到应用层。

如:在nginx端进行IP拦截,参考:https://zhangge.net/5096.html。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值