一個輕量級的純Java Http服務器的實現---Java SE 6 新特性: HTTP 增強

引:Java語言從誕生的那天起,就非常注重網絡編程方面的應用。隨着互聯網應用的飛速發展,Java的基礎類庫也不斷地對網絡相關的API進行加強和擴展。在Java SE 6 當中,圍繞着HTTP協議出現了很多實用的新特性   

概述

  Java語言從誕生的那天起,就非常注重網絡編程方面的應用。隨着互聯網應用的飛速發展,Java的基礎類庫也不斷地對網絡相關的API進行加強和擴展。在Java SE 6 當中,圍繞着HTTP協議出現了很多實用的新特性:NTLM認證提供了一種Window平臺下較爲安全的認證機制;JDK當中提供了一個輕量級的HTTP服務器;提供了較爲完善的HTTP Cookie管理功能;更爲實用的NetworkInterface;DNS域名的國際化支持等等。

  NTLM 認證

  不可避免,網絡中有很多資源是被安全域保護起來的。訪問這些資源需要對用戶的身份進行認證。下面是一個簡單的例子:

import java.net.*;

  import java.io.*;

  public class Test {public static void main(String[] args) throws Exception {

  URL url = new URL("http://PROTECTED.com");

  URLConnection connection = url.openConnection();

  InputStream in = connection.getInputStream();

  byte[] data = new byte[1024];

  while(in.read(data)>0)

  {

  //do something for data

  }

  in.close();}}

  當Java程序試圖從一個要求認證的網站讀取信息的時候,也就是說,從聯繫於http://Protected.com這個URLConnection的 InputStream中 read數據時,會引發FileNotFoundException。儘管筆者認爲,這個Exception的類型與實際錯誤發生的原因實在是相去甚遠;但這個錯誤確實是由網絡認證失敗所導致的。

  要解決這個問題,有兩種方法:

  其一,是給URLConnection設定一個“Authentication”屬性:

String credit = USERNAME + ":" + PASSWORD;

  String encoding = new sun.misc.BASE64Encoder().encode (credit.getBytes());connection.setRequestProperty ("Authorization", "Basic " + encoding);

  這裏假設http://PROTECTED.COM使用了基本(Basic)認證類型。

  從上面的例子,我們可以看出,設定Authentication屬性還是比較複雜的:用戶必須瞭解認證方式的細節,才能將用戶名/密碼以一定的規範給出,然後用特定的編碼方式加以編碼。Java類庫有沒有提供一個封裝了認證細節,只需要給出用戶名/密碼的工具呢?

  這就是我們要介紹的另一種方法,使用java.net.Authentication類。

  每當遇到網站需要認證的時候,HttpURLConnection都會向Authentication類詢問用戶名和密碼。

  Authentication類不會知道究竟用戶應該使用哪個username/password那麼用戶如何向Authentication類提供自己的用戶名和密碼呢?

  提供一個繼承於Authentication的類,實現getPasswordAuthentication方法,在PasswordAuthentication中給出用戶名和密碼:

class DefaultAuthenticator extends Authenticator {

  public PasswordAuthentication getPasswordAuthentication () {

  return new PasswordAuthentication ("USER", "PASSWORD".toCharArray());}}

  然後,將它設爲默認的(全局)Authentication:

Authenticator.setDefault (new DefaultAuthenticator());

  那麼,不同的網站需要不同的用戶名/密碼又怎麼辦呢?

  Authentication提供了關於認證發起者的足夠多的信息,讓繼承類根據這些信息進行判斷,在getPasswordAuthentication方法中給出了不同的認證信息:

getRequestingHost()

  getRequestingPort() getRequestingPrompt() getRequestingProtocol() getRequestingScheme() getRequestingURL() getRequestingSite() getRequestorType()

  另一件關於Authentication的重要問題是認證類型。不同的認證類型需要Authentication執行不同的協議。至Java SE 6.0爲止,Authentication支持的認證方式有:

HTTP Basic authentication

  HTTP Digest authentication NTLM Http SPNEGO Negotiate Kerberos NTLM

  這裏我們着重介紹NTLM。

  NTLM是 NT LAN Manager的縮寫。早期的SMB協議在網絡上明文傳輸口令,這是很不安全的。微軟隨後提出了WindowsNT挑戰/響應驗證機制,即NTLM。

  NTLM協議是這樣的:

  ·客戶端首先將用戶的密碼加密成爲密碼散列;

  ·客戶端向服務器發送自己的用戶名,這個用戶名是用明文直接傳輸的;

  ·服務器產生一個16位的隨機數字發送給客戶端,作爲一個challenge(挑戰) ;

  ·客戶端用步驟1得到的密碼散列來加密這個challenge ,然後把這個返回給服務器;

  ·服務器把用戶名、給客戶端的challenge 、客戶端返回的response這三個東西,發送域控制器;

  ·域控制器用這個用戶名在SAM密碼管理庫中找到這個用戶的密碼散列,然後使用這個密碼散列來加密challenge;

  ·域控制器比較兩次加密的challenge ,如果一樣,那麼認證成功;

  Java 6 以前的版本,是不支持NTLM認證的。用戶若想使用HttpConnection連接到一個使用有Windows域保護的網站時,是無法通過NTLM認證的。另一種方法,是用戶自己用Socket這樣的底層單元實現整個協議過程,這無疑是十分複雜的。

  終於,Java 6 的Authentication類提供了對NTLM的支持。使用十分方便,就像其他的認證協議一樣:

class DefaultAuthenticator extends Authenticator {

  private static String username = "username ";private static String domain = "domain ";private static String password = "password ";

  public PasswordAuthentication getPasswordAuthentication() {

  String usernamewithdomain = domain + "/ "+username;

  return (new PasswordAuthentication(usernamewithdomain, password.toCharArray()));}}

  這裏,根據Windows域賬戶的命名規範,賬戶名爲域名+”/”+域用戶名。如果不想每生成PasswordAuthentication時,每次添加域名,可以設定一個系統變量名“http.auth.ntlm.domain“。

  Java 6 中Authentication的另一個特性是認證協商。目前的服務器一般同時提供幾種認證協議,根據客戶端的不同能力,協商出一種認證方式。比如,IIS服務器會同時提供NTLM with kerberos和 NTLM兩種認證方式,當客戶端不支持NTLM with kerberos時,執行NTLM認證。

  目前,Authentication的默認協商次序是:

  GSS/SPNEGO -> Digest -> NTLM -> Basic

  那麼kerberos的位置究竟在哪裏呢?

  事實上,GSS/SPNEGO以 JAAS爲基石,而後者實際上就是使用kerberos的。

  輕量級 HTTP 服務器

  Java 6 還提供了一個輕量級的純Java Http服務器的實現。下面是一個簡單的例子:

public static void main(String[] args) throws Exception{

  HttpServerProvider httpServerProvider = HttpServerProvider.provider();

InetSocketAddress addr = new InetSocketAddress(7778);

HttpServer httpServer = httpServerProvider.createHttpServer(addr, 1)

;httpServer.createContext("/myapp/", new MyHttpHandler());

httpServer.setExecutor(null);

httpServer.start();

System.out.println("started");

}

  static class MyHttpHandler implements HttpHandler{

public void handle(HttpExchange httpExchange) throws IOException {

  String response = "Hello world!";

  httpExchange.sendResponseHeaders(200, response.length());

  OutputStream out = httpExchange.getResponseBody();

  out.write(response.getBytes());

  out.close();}

}

  然後,在瀏覽器中訪問http://localhost:7778/myapp/,我們得到:


  圖一瀏覽器顯示

  首先,HttpServer是從HttpProvider處得到的,這裏我們使用了JDK 6 提供的實現。用戶也可以自行實現一個HttpProvider和相應的HttpServer實現。

  其次,HttpServer是有上下文(context)的概念的。比如,http://localhost:7778/myapp/中“/myapp/”就是相對於HttpServer Root的上下文。對於每個上下文,都有一個HttpHandler來接收http請求並給出回答。

  最後,在HttpHandler給出具體回答之前,一般先要返回一個Http head。這裏使用HttpExchange.sendResponseHeaders(int code, int length)。其中code是 Http響應的返回值,比如那個著名的404。length指的是response的長度,以字節爲單位
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章