基於java得web伺服器實現[Z]
最後更新:2017-02-28
來源:互聯網
上載者:User
web|web服務|web伺服器 基於Java的Web伺服器工作原理1
一個 Web 服務器也被稱為 HTTP 伺服器,它通過 HTTP 協議與用戶端通訊。這個用戶端通常指的是 網頁瀏覽器。一個基於 Java 的 Web 服務器用到二個重要的類,java.net.Socket 與 java.net.ServerSocket ,並通過 HTTP 訊息通訊。因此,本文從討論 HTTP 與這二個類開始,然後我將解釋一個與本文相關的簡單的 Web 應用程式。
The Hypertext Transfer Protocol(HTTP)
HTTP 是一種讓 Web 服務器與瀏覽器(用戶端)通過 Internet 發送與接收資料的協議。它是一個請求、響應協議--用戶端發出一個請求,伺服器響應這個請求。HTTP 運用可靠的 TCP 串連,通常用的 TCP 80 連接埠。它的第一個版本是 HTTP/0.9 ,然後被 HTTP/1.0 取代。當前的版本是 HTTP/1.1 ,由 RFC2616(.pdf) 定義。
本節主要對應 HTTP 1.1 ,足夠使你充分理解由 Web 服務器程式發出的訊息。如果你對更加詳細的知識有興趣,可以參考 RFC2616 。
在 HTTP 中,用戶端總是通過建立一個串連與發送一個 HTTP 要求來發起一個事務。伺服器不能主動去與用戶端聯絡,也不能給用戶端發出一個回叫串連。用戶端與伺服器端都可以提前中斷一個串連。例如,當用一個瀏覽器下載一個檔案時,你可以通過點擊“停止”鍵來中斷檔案的下載,關閉與伺服器的 HTTP 串連。
HTTP 要求
一個 HTTP 要求包含三個部分:
Method-URI-Protocol/Version 方法-地址-版本
Request header 要求標頭
Entity body 請求實體
下面是一個 HTTP 要求執行個體:
POST /servlet/default.jsp HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
Referer: http://localhost/ch8/SendDetails.htm
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
LastName=Franks&FirstName=Michael
The Method-URI-Protocol/Version 在這個請求的第一行:
POST /servlet/default.jsp HTTP/1.1
其中 POST 是請求的類型。每個用戶端 HTTP 要求可以是 HTTP 規範中指定的許多請求類型中的一種。HTTP 1.1 支援七種類型的請求,它們是 GET,POST,HEAD,OPTIONS,PUT,DELETE,TRACE。其中 GET 與 POST 是 Internet 應用中經常用到的二種請求類型。
URI 完整地指定了 Internet 資源。一個 URI 通常被解析為相對伺服器的根目錄。這樣,它應該總是以一個 '/' 首碼開始。一個 URL 實際上是 URI 的一種類型。
Version 指的是該 HTTP 要求所用到的 HTTP 協議版本。
要求標頭包含了用戶端環境與請求實體的一些有用的資訊。例如它包含瀏覽器設定的語言、實體的長度等等。每條要求標頭用斷行符號分行符號(CRLF)分開。
一個非常重要的空行分開了要求標頭與實體,它標誌著實體內容的開始。一些 Internet 開發書籍認為這個 CRLF 空行是 HTTP 要求的第四個部分。
在上面的 HTTP 要求中,實體只是簡單以下的一行:
LastName=Franks&FirstName=Michael
在一個典型的 HTTP 要求中,請求實體內容會長得多。
HTTP 響應
與請求相似,HTTP 響應也由三部分組成:
Protocol-Status code-Description 協議狀態 描述代碼
Response headers 回應標頭
Entity body 響應實體
以下是一個 HTTP 響應的執行個體:
HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 3 Jan 1998 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 11 Jan 1998 13:23:42 GMT
Content-Length: 112
<html>
<head>
<title>HTTP Response Example</title></head><body>
Welcome to Brainy Software
</body>
</html>
回應標頭的第一行類似要求標頭的第一行,告訴你所用的協議是 HTTP 1.1 ,請求成功(200=success),以及沒有任何問題。
回應標頭類似要求標頭也包含了一些有用的資訊。響應的實體響應本身的 HTML 內容。頭與實體之間由斷行符號換行的空行(CRLF)分開。
Socket 類
一個 socket 是一個網路連接的端點,它使得一個應用可以從網路讀與寫。在不同電腦上的二個應用軟體能夠通過收發位元組流而彼此通訊。要發一個資訊到另一個應用程式,你需要知道它的IP地址,以及它的 socket 連接埠號碼。在 Java 中,一個 socket 用 java.net.Socket 來實現。
要建立一個 socket ,你可以用 Socket 類中幾個構建方法中的一個。其中一個接受主機名稱與連接埠號碼作為參數:
new Socket("yahoo.com", 80);
一旦你成功地建立了一個 Socket 類的執行個體,你就可以用它去發送與接收位元組流了。要發送位元組流,你需要呼叫 Socket 類的 getOutputStream 方法來得到一個 java.io.OutputSteam 對象。要發送文本到遠端程式,你通常需要從返回的 OutputStream 建立一個 java.io.PrintWriter 對象。要從串連的另一端接收位元組流,你需要呼叫 Socket 類的 getInputStream 方法,它返回一個 java.io.InputStream 對象。
以下代碼建立一個可以與本地 HTTP 伺服器通訊的 socket (127.0.0.1 表示一個本地的主機),發送一個 HTTP 要求,並接收從伺服器的響應。它還建立一個 StringBuffer 對象來接受響應,並列印到控制台。
Socket socket = new Socket("127.0.0.1", "8080");
OutputStream os = socket.getOutputStream();
boolean autoflush = true;
PrintWriter out = new PrintWriter( socket.getOutputStream(),
autoflush );
BufferedReader in = new BufferedReader(
new InputStreamReader( socket.getInputStream() ));
// send an HTTP request to the web server
out.println("GET /index.jsp HTTP/1.1");
out.println("Host: localhost:8080");
out.println("Connection: Close");
out.println();
// read the response
boolean loop = true;
StringBuffer sb = new StringBuffer(8096);
while (loop) {
if ( in.ready() ) {
int i=0;
while (i!=-1) {
i = in.read();
sb.append((char) i);
}
loop = false;
}
Thread.currentThread().sleep(50);
}
// display the response to the out console
System.out.println(sb.toString());
socket.close();
注意要從 網頁伺服器得到正確的響應,你必須要發送用 HTTP 協議編譯了的 HTTP 要求。如果你看了上面的 HTTP 部分,你應該能夠理解上面代碼中的 HTTP 要求。
編者註:這篇文章節選自 budi 自己出版的書<Tomcat 內幕>。你可以在他的網站得到更多的相關資料。
基於Java的Web伺服器工作原理2
作者:fajaven譯 發文時間:2003.09.12 17:00:38
ServerSocket 類
Socket 類描述的是“用戶端” socket,當你需要建立與遠程服務程式串連時需要用到它。如果你想實現一個服務程式,如 HTTP 伺服器或者 FTP 伺服器,則需要另外不同的方法。這是因為你的伺服器必須隨時服務,它不知道什麼時候會有一個用戶端程式需要串連它。
因為這個目的,你需要用到 java.net.ServerSocket 這個類,它是伺服器端 socket 的一個實現。伺服器端 socket 等待來自用戶端的串連請求。一旦它收到一個串連請求,它建立一個 socket 執行個體來與用戶端進行通訊。
要建立伺服器端 socket ,需要用到 ServerSocket 類提供的四個構建方法中的一個。你需要指定伺服器端 socket 偵聽的 IP 位址與連接埠號碼。比較典型地,這個 IP 位址可以是 127.0.0.1 ,意思是該伺服器端 socket 偵聽的是本地機器。伺服器端 socket 偵聽的 IP 位址指的是綁定地址。伺服器端 socket 另一個重要的屬性是隊列長度,即它拒絕請求前所接受的最大請求排隊長度。
ServerSocket 類的構建方法之一如下:
public ServerSocket(int port,int backLog,InetAddress bindingAddress);
對於這個構建方法,綁定地址必須是 java.net.InetAddress 類的執行個體。建立一個 InetAddress 類的對象的簡單方法是呼叫其靜態方法 getByName ,傳遞一個包含主機名稱的字串。
InetAddress.getByName("127.0.0.1");
以下行的代碼建立了一個伺服器端 socket ,它偵聽本地機器的 8080 連接埠,限制隊列長度為 1 。
new ServerSocket(8080,1,InetAddress.getByName("127.0.0.1"));
一旦有了一個 ServerSocket 執行個體,就可以通過呼叫其 accept 方法來讓它等待進來的連結請求。這個方法只有當接收到請求時才返回,它返回的是 Socket 類的執行個體。這個 Socket 對象就可以用來從用戶端應用程式發送與接收位元組流,正如上節據說的那樣。實際上,accept 方法是本文例子中用到的唯一方法。
應用執行個體
我們的 網頁伺服器程式是 ex01.pyrmont 包的一部分,它包含三個類:HttpServer;Request;Response。
整個程式的入口(靜態 main 方法)是 HttpServer 類。它建立一個 HttpServer 的執行個體,並呼叫其 await 方法。正如名字表達的,await 在一個特定的連接埠等待 HTTP 要求,處理它們,並返迴響應給用戶端。它保持等待狀態,直到收到停止命令。(用方法名 await 代替 wait ,是因為 System 中有一個重要的與線程相關的方法)
這個程式只從一個特定的目錄發送靜態資源,如 HTML 與影像檔。它只支援沒有檔案頭(如日期與 cookie)的情況。現在我們將在如下的幾節中看一下這三個類。
HttpServer 類
HttpServer 實現了一個 網頁伺服器,它可以提供(serve)特定目錄及其子目錄下的靜態資源。這個特定的目錄由 public static final WEB_ROOT 指定。
WEB_ROOT 初始化如下:
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webroot";
代碼清單中包含了一具叫做 webroot 的目錄,裡面有一些靜態資源,你可以用來測試本應用。你也可以看到一個 servlet ,在我的下一篇文章將會被用到:“Servlets 容器是怎樣工作的”。
為了請求一個靜態資源,在瀏覽器的地址欄輸入如是地址:http://machinename:port/staticResources
如果你從不同的機器上發送請求到運行本應用的機器,則 machinename 是運行應用機器的機器名或 IP 位址,port 是 8080 ,staticResources 是被請求的檔案名稱,它必須包含在 WEB_ROOT 目錄內。
例如,如果你用同一台電腦來測試這個應用,你想要 HttpServer 發送 index.html 這個檔案,用以下的地址:http://localhost:8080/index.html
要停止服務,只需要從瀏覽器發送一個停止(shutdown)命令,即在瀏覽器的地址欄輸入 host:port 欄位後,加上預先定義好的字串。在我們的 HttpServer 類中,停止命令被定義為 SHUTDOWN ,一個 static final 變數。
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
因此,要停止服務,你可以這樣:http://localhost:8080/SHUTDOWN
現在,讓我們看一下列表 1.1 中給出的 await 方法。代碼清單後面將對這段代碼做一些解釋。
Listing 1.1. The HttpServer class' await method
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1,
InetAddress.getByName("127.0.0.1"));
}
catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// Loop waiting for a request
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
// create Request object and parse
Request request = new Request(input);
request.parse();
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}
catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
await 方法以建立一個 ServerSocket 執行個體開始,然後進入一個 while 的迴圈。
serverSocket = new ServerSocket(
port, 1, InetAddress.getByName("127.0.0.1"));
...
// Loop waiting for a request
while (!shutdown) {
...
}
在 while 迴圈中的代碼,運行到 ServerSocket 的 accept 方法即停止。這個方法只有在 8080 連接埠接收到 HTTP 要求才返回:
socket = serverSocket.accept();
收到請求後,await 方法從 accept 方法返回的 Socket 執行個體中等到 java.io.InputStream 與 java.io.OutputStream:
input = socket.getInputStream();
output = socket.getOutputStream();
然後 await 方法建立一個 Request 對象,呼叫它的 parse 方法來解析這個原始的 HTTP 要求:
// create Request object and parse
Request request = new Request(input);
request.parse();
下一步,await 方法建立一個 Response 對象並把 Request 對象設定給它,呼叫它的 sendStaticResource 方法:
// create Response object
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
最後,await 方法關閉 Socket ,呼叫 Request 的 getUri 方法來檢查 HTTP 要求的地址是否是一個停止命令。如果是,則 shutdown 變數被設定為 true ,程式退出 while 迴圈:
// Close the socket
socket.close();
//check if the previous URI is a shutdown command
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
基於Java的Web伺服器工作原理3
作者:fajaven 發文時間:2003.09.12 17:11:54
Request 類
Request 類對應 HTTP 要求。建立這個類的執行個體,並傳給它從 Socket 獲得的 InputStream 對象,從而捕獲與用戶端的通訊。呼叫 InputStream 對象的 read 方法中的一個就可以得到 HTTP 要求的未經處理資料。
Request 類有二個 public 方法 parse 與 getUri。parse 方法解析 HTTP 要求的未經處理資料。它做的事情不多--唯一它使之有效資訊是 HTTP 要求的 URI ,這個通過呼叫私人方法 parseUri 來獲得。parseUri 方法把 URI 作為一個變數。調用 getUri 方法可以得到 HTTP 要求的 URI 。
要明白 parse 與 parseUri 的工作原理,你需要知道 HTTP 要求的結構,由 RFC2616 定義。
一個 HTTP 要求包括三個部分:Request line;Headers;Message body 。
現在,我們只需要關注 HTTP 要求的第一部分--請求行。請求行以方法記號開始,接著是請求的 URI 與協議版本,以斷行符號分行符號結束。請求行的元素之間以空格分開。例如,一個用 GET 方法的 index.html 檔案的請求行如下:
GET /index.html HTTP/1.1
parse 方法從 socket 的 InputStream 傳遞給 Request 對象中讀取位元組流,把這個位元組數組存在緩衝裡。然後,它把 buffer 位元組數組裡的位元組放入叫做 request 的 StringBuffer 對象中,再把 StringBuffer 替換成 String 傳遞給 parseUri 方法。
parse 方法的代碼如列表 1.2
Listing 1.2. The Request class' parse method
public void parse() {
// Read a set of characters from the socket
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
}
catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j=0; j<i; j++) {
request.append((char) buffer[j]);
}
System.out.print(request.toString());
uri = parseUri(request.toString());
}
parseUri 方法尋找請求行的第一個與第二個空格,從而從請求行獲得了 URI 。列表 1.3 展示了 parseUri 方法的代碼。
Listing 1.3. The Request class' parseUri method
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
Response 類
Response 類描述 HTTP 響應。它的構建方法接受 OutputStream 對象,如下:
public Response(OutputStream output) {
this.output = output;
}
Response 對象通過傳遞從 socket 獲得的 OutputStream 對象到 HttpServer 類的 await 方法而建立。
Response 類有二個公用方法 setRequest 與 setStaticResource 。setRequest 用來傳遞 Request 對象到 Response 對象。它比較簡單,代碼如列表 1.4 所示:
Listing 1.4. The Response class' setRequest method
public void setRequest(Request request) {
this.request = request;
}
sendStaticResource 方法用來發送靜態資源,例如 HTML 檔案。它的實現如列表 1.5 所示:
Listing 1.5. The Response class' sendStaticResource method
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file=new File(HttpServer.WEB_ROOT, request.getUri());
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch != -1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
else {
// file not found
String errorMessage="HTTP/1.1 404 File Not Found\r\n"+
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
}
}
catch (Exception e) {
// thrown if cannot instantiate a File object
System.out.println(e.toString() );
}
finally {
if (fis != null)
fis.close();
}
}
SendStaticResource 方法非常簡單。它首先通過傳遞父與子目錄到 File 類的構建方法從而執行個體化 java.io.File 類。
File file new File(HttpServer.WEB_ROOT, request.getUri());
然後檢查這個檔案是否存在。如果存在,則 sendStaticResource 方法傳遞 File 對象建立 java.io.FileInputStream 對象。然後調用 FileInputStream 的 read 方法,並把位元組數組寫到 OutputStream 對象 output 。就這樣,靜態資源的內容作為未經處理資料被發送到瀏覽器。
if (file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
while (ch != -1) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
}
如果檔案不存在,sendStaticResource 發送一個錯誤資訊到瀏覽器。
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(errorMessage.getBytes());
編譯與運行應用程式
要編輯與運行本文的應用,首先你需要解壓源碼 zip 檔案。直接解壓出來的目錄被稱為工作目錄,它有三個子目錄:src/,classes/,lib/。要編譯應用,從工作目錄輸入如下命令:
javac -d . src/ex01/pyrmont/*.java
-d 選項把結果寫到目前的目錄,而不是 src/ 目錄。
要運行應用,在當前工作目錄輸入如下命令:
java ex01.pyrmont.HttpServer
測試這個應用,開啟你的瀏覽器,在地址欄輸入如下地址:http://localhost:8080/index.html
你將在你的瀏覽器看到 index.html 顯示出來,如圖1所示。
圖1:網頁伺服器的輸出顯示
在控制台,你看到如下的內容:
GET /index.html HTTP/1.1
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Host: localhost:8080
Connection: Keep-Alive
GET /images/logo.gif HTTP/1.1
Accept: */*
Referer: http://localhost:8080/index.html
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Host: localhost:8080
Connection: Keep-Alive
總結
在這篇文章中(分為三個部分),你看到了一個簡單的 網頁伺服器的工作原理。本文相關的應用只包括了三個類,功能是不全面的。然而,它仍不失為一個好的學習工具。