java - 如何使用 Java HTTPS 服务器管理 HTTP 请求?

标签 java redirect ssl https httpserver

我已经使用 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 协议(protocol)打开网页(由我的 HttpHandler 处理)时,它显示错误 ERR_EMPTY_RESPONSE,只有 HTTPS 协议(protocol)有效。

我想自动将所有传入连接从 HTTP 重定向到 HTTPS。或者对两种协议(protocol)使用不同的 HttpHandler 会很棒。

我怎样才能做到这一点?

最佳答案

经过数小时的尝试和哭泣,我想通了。

首先,我将 HTTP 服务器库从官方更改为 org.jboss.com.sun.net.httpserver。 ( download source code here )

然后我查看了该库的源代码,发现 org.jboss.sun.net.httpserver.ServerImpl 类负责所有 HTTP(S) 流量处理。当您查看静态可运行子类 Exchange 中的方法 run 时,您可以看到以下代码:

/* 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();
[...]

这个可运行类决定连接类型是什么(HTTP 或 HTTPS)。该决定仅基于 https boolean 对象的值 (true/false) - 它声明您是使用 HttpsServer 还是 HttpServer 类作为您的服务器。 这就是问题所在 - 您希望根据访问者选择的协议(protocol)向网页访问者显示内容,而不是根据服务器默认使用的协议(protocol)。

下一个重要的事情是,当您使用 HTTP 协议(protocol)访问 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 永久移动)以及要重定向到的位置地址。

只需将 www.example.com 更改为您的主机名。端口是自动添加的。唯一的问题是也尝试自动获取主机名。您可以从输入流(从请求 header )中提取此信息的唯一地方,但此流在抛出异常后不再可用。

现在您网页的访问者从 HTTP 协议(protocol)重定向到 HTTPS。

希望这对您有所帮助!

关于java - 如何使用 Java HTTPS 服务器管理 HTTP 请求?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34548174/

相关文章:

java - html5检查器编译

apache-flex - Flex/java 网络应用程序的 Tomcat 重定向

ssl - Protractor Webdriver-manager 更新 SSL 错误

Python 3.4 电子邮件

java - 使用类型推断时,Tomcat 8.5.27 编译失败,JSP 页面出现错误 'illegal start of type'

java - 为什么 repaint() 不会总是调用 PaintComponent 以及为什么它在被调用时并不总是表现正常

java - 从日期加时间创建日期

php - 重定向与 WooCommerce 购物车计数条件冲突

javascript - 如何重定向到给定一组网站中的一个?

Apache 在 Glassfish v3 前面使用 mod_proxy_ajp 使用 SSL