2015-12-31 219 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。

希望這有助於!

相關問題