我已经使用com.sun.net.httpserver.HttpsServer
库设置了自己的https服务器。
代码如下所示:
int PORT = 12345;
File KEYSTORE = new File("path/to/my.keystore");
String PASS = "mykeystorepass";
HttpsServer server = HttpsServer.create(new InetSocketAddress(PORT), 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
char[] password = PASS.toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream(KEYSTORE);
ks.load(fis, password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
server.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
SSLContext c = SSLContext.getDefault();
SSLEngine engine = c.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
params.setSSLParameters(defaultSSLParameters);
} catch(Exception ex) {
ex.printStackTrace();
}
}
});
server.createContext("/", new Handler());
server.setExecutor(null);
server.start();
一切正常,但现在我正在尝试管理在同一端口上发送到此HTTPS服务器的HTTP请求。当我使用HTTP协议打开网页(由我的HttpHandler处理)时,它显示错误ERR_EMPTY_RESPONSE
,只有HTTPS协议有效。
我想自动将所有传入连接从HTTP重定向到HTTPS。或者对两种协议使用不同的HttpHandler会很棒。
我怎样才能做到这一点?
答案 0 :(得分:1)
经过几个小时的尝试和哭泣后,我想出来了。
首先,我将HTTP服务器lib从官方更改为org.jboss.com.sun.net.httpserver
。 (download source code here)
然后我查看了该lib的源代码,发现类org.jboss.sun.net.httpserver.ServerImpl
负责所有HTTP(S)流量处理。当您查看静态可运行子类run
中的方法Exchange
时,您可以看到以下代码:
/* context will be null for new connections */
context = connection.getHttpContext();
boolean newconnection;
SSLEngine engine = null;
String requestLine = null;
SSLStreams sslStreams = null;
try {
if(context != null) {
this.rawin = connection.getInputStream();
this.rawout = connection.getRawOutputStream();
newconnection = false;
} else {
/* figure out what kind of connection this is */
newconnection = true;
if(https) {
if(sslContext == null) {
logger.warning("SSL connection received. No https contxt created");
throw new HttpError("No SSL context established");
}
sslStreams = new SSLStreams(ServerImpl.this, sslContext, chan);
rawin = sslStreams.getInputStream();
rawout = sslStreams.getOutputStream();
engine = sslStreams.getSSLEngine();
connection.sslStreams = sslStreams;
} else {
rawin = new BufferedInputStream(new Request.ReadStream(ServerImpl.this, chan));
rawout = new Request.WriteStream(ServerImpl.this, chan);
}
connection.raw = rawin;
connection.rawout = rawout;
}
Request req = new Request(rawin, rawout);
requestLine = req.requestLine();
[...]
此runnable类决定连接类型(HTTP或HTTPS)。该决定仅基于https
布尔对象的值(true / false) - 它声明您是否使用HttpsServer或HttpServer类作为您的服务器。
这就是问题 - 您希望根据访问者选择的协议显示您网页的访问者内容,而不是默认情况下服务器使用的协议。
接下来重要的是,当您使用HTTP协议访问HttpsServer时,服务器会尝试与您打开SSL安全连接,然后无法解码未加密的数据(即代码new Request(rawin, rawout);
,新请求开始从输入流rawin
读取字节并失败)。
然后,课程Request
会引发IOException
,特别是javax.net.ssl.SSLException
的实例。
IOException在此方法的底部处理:
} catch(IOException e1) {
logger.log(Level.FINER, "ServerImpl.Exchange (1)", e1);
closeConnection(this.connection);
} catch(NumberFormatException e3) {
reject(Code.HTTP_BAD_REQUEST, requestLine, "NumberFormatException thrown");
} catch(URISyntaxException e) {
reject(Code.HTTP_BAD_REQUEST, requestLine, "URISyntaxException thrown");
} catch(Exception e4) {
logger.log(Level.FINER, "ServerImpl.Exchange (2)", e4);
closeConnection(connection);
}
因此,您可以看到它在捕获异常时默认关闭连接。
(因此Chrome显示错误ERR_EMPTY_RESPONSE
)
解决方案很简单 - 用以下代码替换代码:
} catch(IOException e1) {
logger.log(Level.FINER, "ServerImpl.Exchange (1)", e1);
if(e1 instanceof SSLException) {
try {
rawout = new Request.WriteStream(ServerImpl.this, chan);
StringBuilder builder = new StringBuilder(512);
int code = Code.HTTP_MOVED_PERM;
builder.append("HTTP/1.1 ").append(code).append(Code.msg(code)).append("\r\n");
builder.append("Location: https://www.example.com:"+ wrapper.getAddress().getPort() +"\r\n");
builder.append("Content-Length: 0\r\n");
String s = builder.toString();
byte[] b = s.getBytes("ISO8859_1");
rawout.write(b);
rawout.flush();
} catch(IOException e) {
e.printStackTrace();
}
}
closeConnection(connection);
} catch [...]
在关闭连接之前,它为未加密的输出流设置正确的输出流rawout
,然后发送重定向代码(301 Moved Permanently),并将位置地址重定向到。
只需将 www.example.com 更改为您的主机名即可。端口自动添加。唯一的问题是尝试自动获取主机名。只有您可以提取此信息的位置来自输入流(来自请求标头),但此流是在抛出不再可用的异常之后。
现在,您网页的访问者将从HTTP协议重定向到HTTPS。
希望这有帮助!