问题描述
我正在使用嵌入式Jetty构建沙盒RESTful API.我的概念验证设计:一个简单的嵌入式码头服务器,该服务器(1)接受SSL端口上的连接,(2)使用ContextHandlerCollection来基于URI前缀调用适当的处理程序.
I'm building a sandbox RESTful API using embedded Jetty. My Proof-of-Concept design: a simple embedded jetty server that (1) accepts connections on an SSL port and (2) uses a ContextHandlerCollection to invoke the proper Handler based on the URI prefix.
我的原始测试使用一个简单的非SSL连接,似乎运行良好(请注意,用于导入的代码和APPENDIX中的帮助器HelloHandler类).
My original test, using a simple non-SSL connection, seemed to work perfectly (note, code for imports and helper HelloHandler class in APPENDIX).
public static void main(String[] args) throws Exception {
Server server = new Server(12000);
ContextHandler test1Context = new ContextHandler();
test1Context.setContextPath("/test1");
test1Context.setHandler(new HelloHandler("Hello1"));
ContextHandler test2Context = new ContextHandler();
test2Context.setContextPath("/test2");
test2Context.setHandler(new HelloHandler("Hello2"));
ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
contextHandlers.setHandlers(new Handler[] { test1Context, test2Context });
server.setHandler(contextHandlers);
server.start();
server.join();
}
但是,在测试此代码时,我忽略了当我省略尾随的前斜杠时浏览器重定向发生的情况,因此http://localhost:12000/test1
被重定向到了http://localhost:12000/test1/
. (FWIW,该异地站点稍后将转化为4个小时以上的故障排除时间).
However, while testing this, it escaped my attention that browser redirects were happening when I omitted a trailing forward slash, so http://localhost:12000/test1
was getting redirected to http://localhost:12000/test1/
. (FWIW, that oversite would later translate to 4+ hours of troubleshooting).
当我切换到HTTPS SSL连接时,一切都出错了.下面的代码:
When I switched to an HTTPS SSL connection, everything went wrong. Code below:
public static void main(String[] args) throws Exception {
Server server = new Server();
// Setups
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath("C:/keystore.jks");
sslContextFactory.setKeyStorePassword("password");
sslContextFactory.setKeyManagerPassword("password");
ContextHandler test1Context = new ContextHandler();
test1Context.setContextPath("/test1");
test1Context.setHandler(new HelloHandler("Hello1"));
ContextHandler test2Context = new ContextHandler();
test2Context.setContextPath("/test2");
test2Context.setHandler(new HelloHandler("Hello2"));
ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
contextHandlers.setHandlers(new Handler[] { test1Context, test2Context });
ServerConnector serverConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, "http/1.1"),
new HttpConnectionFactory());
serverConnector.setPort(12000);
server.setConnectors(new Connector[] { serverConnector });
server.setHandler(contextHandlers);
server.start();
server.join();
}
症状:
尝试使用https://localhost:12000/test1
(不带斜杠)将导致浏览器报告服务器已重置连接.此外,最初我发现 not 的操作是将URI重定向到http://localhost:12000/test1/
(不是https!).有趣的是(以一种幽默的幽默感),我偶尔会在代码中更改一些无关紧要的内容,然后不经意地使用https://localhost:12000/test1/
进行测试,它将起作用.言语不能弥补这种假阳性所带来的挫败感.
Attempting to use https://localhost:12000/test1
(no trailing slash) would cause browser to report that the server had reset the connection. Additionally, and what I did not spot initially, the URI was being redirected to http://localhost:12000/test1/
(not https!). Amusingly (in a sadistic-sense-of-humor sort of way), on a couple occasions, I'd change something inconsequential in the code and then inadvertently test with https://localhost:12000/test1/
and it would work. Words don't do justice to the frustration such false-positives caused.
除了浏览器重定向并报告连接重置错误外,我的服务器日志中还会出现以下异常:
In addition to the browser redirecting and reporting a connection-reset error, I would get the following exception in my server logs:
2013-11-23 13:57:48 DEBUG org.eclipse.jetty.server.HttpConnection:282 -
org.eclipse.jetty.io.EofException
at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:653)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:240)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.run(AbstractConnection.java:358)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:601)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:532)
at java.lang.Thread.run(Thread.java:722)
Caused by: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at sun.security.ssl.EngineInputRecord.bytesInCompletePacket(EngineInputRecord.java:171)
at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:845)
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:758)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:518)
... 5 more
不幸的是,当关键线索在浏览器重定向的URL中时,我花费了所有时间尝试直接解决此异常.事实证明ContextHandler代码具有默认行为,当不存在尾部正斜杠时,该行为将导致其仅重定向到具有尾部斜杠的URI.不幸的是,此重定向是到HTTP URI的,因此HTTPS方案被丢弃,这导致服务器抱怨纯文本.
Unfortunately, I spent all of my time trying to troubleshoot this exception directly, when the key clue was in the browser redirected URL. It turns out that the ContextHandler code has a default behavior which, when no trailing forward slash exists, causes it to just redirect to a URI that has the trailing slash. Unfortunately, this redirect is to an HTTP URI - so the HTTPS scheme is dropped, which caused the server to complain about the plain-text.
担心:
一旦我知道了这种重定向行为,谷歌对那个实际问题的快速搜索就导致我找到ContextHandler.setAllowNullPathInfo(true)方法-这将关闭此重定向行为.在上面的代码中,这是用两行代码完成的:
Once this redirect behavior became clear to me, a quick Google search of that actual issue led me to the ContextHandler.setAllowNullPathInfo(true) method - which turns off this redirect behavior. In my code above, this is accomplished with 2 lines:
test1Context.setAllowNullPathInfo(true);
test2Context.setAllowNullPathInfo(true);
此职位的主要观点:
我花了3-4个小时来尝试解决"javax.net.ssl.SSLException:无法识别的SSL消息,纯文本连接?"异常,并且在网上都找不到与上述解决方案/解决方案相关联的异常.如果我从经历的挫折中拯救了另一个开发人员,那么任务就完成了.
I spent 3 - 4 hours trying to troubleshoot the "javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?" exception, and nowhere on the web did I find that exception linked to the solution/workaround above. If I save even one other developer from the frustration I experienced, then mission accomplished.
挥之不去的问题(发布此问题的其他原因):好的,所以我使这段代码起作用了,但是不得不承认:我非常难以接受的事实是,我做的非常简单的概念验证测试(我认为这很普遍)遇到了这种情况,这似乎是史无前例的.这就告诉我,我可能正在做一些超出最佳实践"范围的事情.或者,更糟糕的是,在设计此方法时完全犯了错误.所以,问题:
LINGERING QUESTIONS (OTHER REASON FOR POSTING THIS):Okay, so I got this code working, but have to confess: I'm incredibly uneasy about the fact that my incredibly simple proof-of-concept test, doing something that I assume would be quite common, encountered this situation which seems utterly unprecedented. That tells me, I'm probably doing something beyond the realm of "best practice"; or, worse, doing something utterly wrong in how I'm designing this. So, questions:
1)我做错了吗?
2)为什么ContextHandler的默认行为是重定向缺少尾随空间的URI?通过setAllowNullPathInfo(true)覆盖默认行为,会带来什么风险?
2) Why is the default behavior of ContextHandler to redirect URI's which lack trailing space? What risk(s) am I incurring by over-riding that default behavior with setAllowNullPathInfo(true)?
附录(帮助程序类和导入的代码)进口: 导入java.io.IOException;
APPENDIX (code of helper class and imports)Imports: import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
助手类:
static class HelloHandler extends AbstractHandler {
final String _greeting;
public HelloHandler(String greeting) {
_greeting = greeting;
}
public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
baseRequest.setHandled(true);
response.getWriter().println("<h1>" + _greeting + "</h1>");
}
}
推荐答案
您缺少HttpConfiguration设置.
You are missing the HttpConfiguration setup.
这里...
package jetty.examples;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
public class SecureContexts
{
public static void main(String[] args) throws Exception
{
Server server = new Server();
int port = 12000;
// Setup SSL
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(System.getProperty("jetty.keystore.path","C:/keystore.jks"));
sslContextFactory.setKeyStorePassword(System.getProperty("jetty.keystore.password","password"));
sslContextFactory.setKeyManagerPassword(System.getProperty("jetty.keymanager.password","password"));
// Setup HTTP Configuration
HttpConfiguration httpConf = new HttpConfiguration();
httpConf.setSecurePort(port);
httpConf.setSecureScheme("https");
httpConf.addCustomizer(new SecureRequestCustomizer());
ContextHandler test1Context = new ContextHandler();
test1Context.setContextPath("/test1");
test1Context.setHandler(new HelloHandler("Hello1"));
ContextHandler test2Context = new ContextHandler();
test2Context.setContextPath("/test2");
test2Context.setHandler(new HelloHandler("Hello2"));
ContextHandlerCollection contextHandlers = new ContextHandlerCollection();
contextHandlers.setHandlers(new Handler[]
{ test1Context, test2Context });
ServerConnector serverConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory,"http/1.1"),
new HttpConnectionFactory(httpConf)); // <-- use it!
serverConnector.setPort(port);
server.setConnectors(new Connector[]
{ serverConnector });
server.setHandler(contextHandlers);
server.start();
server.join();
}
}
这篇关于嵌入式Jetty:在安全的https服务器中,ContextHandler重定向到http URI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!