ServerConnector
首先,除非您完全(我的意思是 100%)了解Jetty 9.x中的整体HttpConnectionFactory
和行为,否则不要扩展。Endpoint
一个小小的错误,你会破坏很多东西。这不是一个可扩展的公共 API,并且可能会在 Jetty 的未来版本中被标记为最终版本。
如果您需要自定义行为,请先查看HttpConfiguration.Customizer
,然后如果您仍需要其他自定义,请改用自定义HttpConnectionFactory
。
接下来,要知道这HttpCompliance
只是设置/集合的持有者HttpComplianceSection
。您可能希望确保您没有HttpComplianceSection.NO_FIELD_FOLDING
包含在您选择的HttpCompliance
设置中。
最后,确保您识别并修复了那些有问题的客户端,近年来的趋势是对 HTTP 越来越严格,这是由于宽松的行为(例如您的线路折叠)导致/创建的众多安全问题。总有一天,即使您的负载均衡器、代理、路由器等也会拒绝这些类型的请求。
过时的 RFC2616 出于多种原因进行了更新,其中很大一部分是使用诸如( RFC2119 第 2 节MUST NOT
中明确定义的短语)之类的语言将某些行为(例如行折叠)专门称为危险的,使得该行为对于更新后的行为是非可选的眼镜。IETF 在 2013 年弃用 Header Field 行折叠的原因是由于许多与 Header 注入漏洞变体相关的安全问题。启用标题字段行折叠后,您将无法防止响应拆分、会话固定、跨站点脚本、安全源检查和恶意重定向。
许多现代防火墙/网关/路由器/负载平衡器不支持标头折叠。
另请注意,HTTP/2 不支持标头折叠。
如果无法纠正那些有问题的客户端(无论出于何种原因),那么您唯一的选择就是不再升级您的任何服务器软件,以延迟这一天的发生。(您无法控制的其他中介可能会在这些请求到达您之前就失败!)
您将不得不解决客户端中使用过时的标题折叠问题,因为您无法控制的许多事情已经拒绝了该概念,您将遇到唯一的选择就是修复客户端行为的那一天。
无论如何,这是一个使用 RFC2616 合规模式和行折叠的独立演示。
package jetty;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URI;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;
public class HttpComplianceDemo
{
public static void main(String[] args) throws Exception
{
Server server = new Server();
HttpConfiguration http_config = new HttpConfiguration();
http_config.setSendServerVersion(true);
HttpCompliance compliance = HttpCompliance.RFC2616;
ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(http_config, compliance));
connector.setPort(9090);
server.addConnector(connector);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(DumpServlet.class, "/dump");
context.addServlet(DefaultServlet.class, "/");
HandlerList handlers = new HandlerList();
handlers.addHandler(context);
handlers.addHandler(new DefaultHandler());
server.setHandler(handlers);
try
{
server.start();
testLineFolding(server.getURI().resolve("/"));
}
finally
{
server.stop();
}
}
private static void testLineFolding(URI serverUri) throws IOException
{
String host = serverUri.getHost();
int port = serverUri.getPort();
try (Socket socket = new Socket(host, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream())
{
StringBuilder rawRequest = new StringBuilder();
rawRequest.append("GET /dump HTTP/1.1\r\n");
rawRequest.append("Host: ").append(serverUri.getRawAuthority()).append("\r\n");
rawRequest.append("Connection: close\r\n");
rawRequest.append("X-Foo: name\r\n"); // the header with line folding
rawRequest.append(" extra\r\n");
rawRequest.append("\r\n");
byte bufRequest[] = rawRequest.toString().getBytes(UTF_8);
System.out.println("--request--");
System.out.println(new String(bufRequest, UTF_8));
out.write(bufRequest);
out.flush();
ByteArrayOutputStream outBuf = new ByteArrayOutputStream();
IO.copy(in, outBuf);
String response = new String(outBuf.toByteArray(), UTF_8);
System.out.println("--Response--");
System.out.println(response);
}
}
public static class DumpServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
Enumeration<String> enNames = req.getHeaderNames();
while (enNames.hasMoreElements())
{
String name = enNames.nextElement();
String value = req.getHeader(name);
out.printf("- [HEADER:%s=%s]\n", name, value);
}
}
}
}
输出 ...
2018-08-08 11:21:27.811:INFO::main: Logging initialized @338ms to org.eclipse.jetty.util.log.StdErrLog
2018-08-08 11:21:27.946:INFO:oejs.Server:main: jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 9.0.4+11
2018-08-08 11:21:28.004:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@78aab498{/,null,AVAILABLE}
2018-08-08 11:21:28.204:INFO:oejs.AbstractConnector:main: Started ServerConnector@15ff3e9e{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
2018-08-08 11:21:28.205:INFO:oejs.Server:main: Started @739ms
--request--
GET /dump HTTP/1.1
Host: 192.168.0.119:9090
Connection: close
X-Foo: name
extra
--Response--
HTTP/1.1 200 OK
Connection: close
Date: Wed, 08 Aug 2018 16:21:28 GMT
Content-Type: text/plain;charset=iso-8859-1
Content-Length: 91
Server: Jetty(9.4.11.v20180605)
- [HEADER:Connection=close]
- [HEADER:X-Foo=name extra]
- [HEADER:Host=192.168.0.119:9090]
2018-08-08 11:21:28.307:INFO:oejs.AbstractConnector:main: Stopped ServerConnector@15ff3e9e{HTTP/1.1,[http/1.1]}{0.0.0.0:9090}
2018-08-08 11:21:28.310:INFO:oejsh.ContextHandler:main: Stopped o.e.j.s.ServletContextHandler@78aab498{/,null,UNAVAILABLE}
如果您将HttpCompliance
模式更改为 RFC7230,您将得到不同的结果。
--request--
GET /dump HTTP/1.1
Host: 192.168.0.119:9090
Connection: close
X-Foo: name
extra
--Response--
HTTP/1.1 400 Header Folding
Content-Type: text/html;charset=iso-8859-1
Content-Length: 57
Connection: close
Server: Jetty(9.4.11.v20180605)
<h1>Bad Message 400</h1><pre>reason: Header Folding</pre>