First, don't extend from ServerConnector
unless you fully (and I mean 100%) understand the entire HttpConnectionFactory
and Endpoint
behaviors within Jetty 9.x. One tiny mistake and you will break many things. This is not intended to be an extensible public API and will likely be marked final in a future version of Jetty.
If you need custom behavior, start by looking at the HttpConfiguration.Customizer
, then if you still need other customization, use a custom HttpConnectionFactory
instead.
Next, know that HttpCompliance
is just a holder for a Set/Collection of HttpComplianceSection
settings. You might want to ensure that you don't have the HttpComplianceSection.NO_FIELD_FOLDING
included in your chosen HttpCompliance
setting.
Finally, make sure you get those problematic clients identified and fixed, the trend in recent years to be more and more strict with HTTP, due to the numerous security issues that the relaxed behaviors (such as your line folding) cause/create. There will be a day where even your load balancer, proxy, router, etc will reject those kinds of requests too.
The obsolete RFC2616 was updated for many reasons, a large chunk was to specifically call out certain behaviors (such as line folding) as dangerous using language such as MUST NOT
(a phrase defined clearly in RFC2119 Section 2) making the behavior non-optional for the updated specs.
The reason the IETF deprecated Header Field line folding in 2013 was due to many security issues related to variations of Header injection vulnerabilities. With header field line folding enabled, you have no protection against response splitting, session fixation, cross-site scripting, security origin checks, and malicious redirection.
Many modern firewalls / gateways / routers / load balancers do not support header folding.
Also note that HTTP/2 does not support header folding.
If correcting those problematic clients is not possible (for whatever reason) then your only option is to not upgrade any of your server software from here on out to delay that day from occurring. (other intermediaries outside of your control can fail those requests before it even reaches you!)
You will have to address the usage obsolete header folding in your clients, as many things outside of your control are already rejecting that concept, you will hit a day where your only option left is to fix the client behavior.
Anyway, here's a standalone demo of this behavior with RFC2616 compliance mode and Line Folding.
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);
}
}
}
}
Output ...
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}
If you change the HttpCompliance
mode to say RFC7230, you'll get a different result.
--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>