2015-12-31 168 views
0

我已经用com.sun.net.httpserver.HttpsServer库设置了我自己的https服务器。如何使用Java HTTPS服务器管理HTTP请求?

的代码看起来是这样的:

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会很好。

我该如何做到这一点?

+1

你有没有具体的理由这样做,而不是像Tomcat提供的嵌入式Web服务器或将Apache放在Front中? – Marged

+0

@Marged我可以使用这些网络服务器之一,但我想知道是否有可能通过这种方式实现。 – PerwinCZ

回答

0

经过数小时的努力和哭泣,我想出了它。

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

然后我查看了该lib的源代码,发现类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布尔对象(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 [...] 

闭它设置正确的OutputStream rawout用于未加密输出流连接之前,然后将其发送重定向代码( 301永久移动),并将位置地址重定向到。

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

现在,您网页的访问者将从HTTP协议重定向到HTTPS。

希望这有助于!

相关问题