起因

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

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

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

验证调用HttpServletResponse.getWriter().close()方法是否真的会关闭http连接的更多相关文章

  1. 浅析调用JSR303的validate方法, 验证失败时抛出ConstraintViolationException

    废话不多说,直接进入正题:如何使用JSR303的validate,进行数据校验,失败后直接抛出异常加入流转信息中,并在form页面提示出来. 首先我们为了启用验证,需要向 项目中添加Bean验证的实现 ...

  2. eclipse 中main()函数中的String[] args如何使用?通过String[] args验证账号密码的登录类?静态的主方法怎样才能调用非static的方法——通过生成对象?在类中制作一个方法——能够修改对象的属性值?

    eclipse 中main()函数中的String[] args如何使用? 右击你的项目,选择run as中选择 run configuration,选择arguments总的program argu ...

  3. 使用RPC 调用NameNode中的方法

    用户在Client 端是很难对 NameNode中的信息进行直接访问的, 所以 ,在Hadoop系统中为 Client端 提供了一系列的方法调用,这些方法调用是通过RPC 方法来实现的, 根据RPC ...

  4. 5种必会的Java异步调用转同步的方法你会几种

    转载请注明本文地址:https://www.jianshu.com/p/f00aa6f66281 源码地址:https://gitee.com/sunnymore/asyncToSync Sunny先 ...

  5. mui---子页面主动调用父页面的方法

    我们在做APP的时候,很多时候会有这样的功能需求,例如:登录,充值,如果登录成功,或充值成功后,需要更改当前页面以及父页面的状态信息,就会用到在子页面调用父页面的方法来实现:在子页面刷新父页面的功能. ...

  6. Jquery如何序列化form表单数据为JSON对象 C# ADO.NET中设置Like模糊查询的参数 从客户端出现小于等于公式符号引发检测到有潜在危险的Request.Form 值 jquery调用iframe里面的方法 Js根据Ip地址自动判断是哪个城市 【我们一起写框架】MVVM的WPF框架(三)—数据控件 设计模式之简单工厂模式(C#语言描述)

    jquery提供的serialize方法能够实现. $("#searchForm").serialize();但是,观察输出的信息,发现serialize()方法做的是将表单中的数 ...

  7. php调用C代码的方法详解和zend_parse_parameters函数详解

    php调用C代码的方法详解 在php程序中需要用到C代码,应该是下面两种情况: 1 已有C代码,在php程序中想直接用 2 由于php的性能问题,需要用C来实现部分功能   针对第一种情况,最合适的方 ...

  8. HttpServletResponse 的 sendError( )方法以及常用的HttpServletResponse常量级错误代码

    HttpServletResponse 的 sendError( )方法以及常用的HttpServletResponse常量级错误代码   转载:http://hi.baidu.com/yanfei_ ...

  9. 为什么字符串类型可以调用构造函数String的方法,却又不是它的实例

    从所周知,在js中定义一个字符串我们有两种办法: var a = new String("a"); var a = "a"; 第一种方法使用构造函数创建,作为S ...

随机推荐

  1. 【转载】docker 应用之动态扩展容器空间大小

    docker 容器默认的空间是 10G, 如果想指定默认容器的大小(在启动容器的时候指定),可以在 docker 配置文件里通过 dm.basesize 参数指定,比如 docker -d --sto ...

  2. 【BZOJ5211】[ZJOI2018]线图(树哈希,动态规划)

    [BZOJ5211][ZJOI2018]线图(树哈希,动态规划) 题面 BZOJ 洛谷 题解 吉老师的题目是真的神仙啊. 去年去现场这题似乎骗了\(20\)分就滚粗了? 首先\(k=2\)直接算\(k ...

  3. 「ZJOI2015」地震后的幻想乡 解题报告

    「ZJOI2015」地震后的幻想乡 想了半天,打开洛谷题解一看,最高票是_rqy的,一堆密密麻麻的积分差点把我吓跑. 据说有三种解法,然而我只学会了一种最辣鸡的凡人解法. 题意:给一个无向图\(G\) ...

  4. 「SCOI2014」方伯伯运椰子 解题报告

    「SCOI2014」方伯伯运椰子 可以看出是分数规划 然后我们可以看出其实只需要改变1的流量就可以了,因为每次改变要保证流量守恒,必须流成一个环,在正负性确定的情况下,变几次是无所谓的. 然后按照套路 ...

  5. [WC2018]州区划分(FWT)

    题目描述 题解 这道题的思路感觉很妙. 题目中有一个很奇怪的不合法条件,貌似和后面做题没有什么关系,所以我们先得搞掉它. 也就是判断一个点集是否合法,也就是判断这个点集是否存在欧拉回路. 如果存在欧拉 ...

  6. Macbook外接显示器模糊解决方法

    解决方法(此方法经本人测试失败) 下载这个http://www.elias.cn/uploads/Mac/patch-edid.zip.如果链接失效可以使用https://gist.github.co ...

  7. 洛谷P3185 分裂游戏

    解:这个毒瘤...... 我们首先发现每一堆的个数对操作不产生影响,每个操作都是针对单个石子的. 所以等价于每个石子都是一个独立的游戏.把它们异或起来作为sg函数值即可. 单个石子的sg值,直接暴力计 ...

  8. [luogu3834][可持久化线段树 1(主席树)]

    题目链接 思路 裸的主席树.查询的时候,通过相减求出区间内左子树中数的个数a.然后判断要查找的k是否比这个z要大.如果比这个值大,那么就去右子树中查找第k - z大,否则去左子树中查找第k大. 代码 ...

  9. python基础面试常见题

    1.为什么学习Python? Python是目前市面上,我个人认为是最简洁.最优雅.最有前途.最全能的编程语言,没有之一. 2.通过什么途径学习的Python? 通过自学,包括网上查看一些视频,购买一 ...

  10. Mybatis非mapper代理配置

    转: Mybatis非mapper代理配置 2017年04月26日 20:13:48 待长的小蘑菇 阅读数:870   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog. ...