JAVA簡易WEB伺服器(一)
這一篇部落格開始將和大家一起使用JAVA編寫一個簡易的Web伺服器。
眾所周知Web伺服器與用戶端之間的通訊是使用HTTP協議的。HTTP是一個用戶端和伺服器端請求和應答的標準(TCP)。因為HTTP協議是基於TCP協議的,所以我將使用JAVA中的Socket完成這個簡易的Web伺服器。關於HTTP更詳細的資料,各位可以查閱相關資料進行瞭解。
在伺服器編寫之前,我們還是先來看一下瀏覽器與伺服器之間通訊的規則到底如何。
首先,我們是用ServerSocket來類比一個服務端,通過瀏覽器訪問,查看瀏覽器請求的內容:
import java.io.BufferedWriter;import java.io.InputStream;import java.io.OutputStreamWriter;import java.net.InetAddress;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.net.Socket;import org.junit.Test;/** * HTTP協議測試 * * @author jianggujin * */public class HQHttpProtocolTest{ @Test public void server() throws Exception { ServerSocket serverSocket = new ServerSocket(80); Socket socket = serverSocket.accept(); InputStream stream = socket.getInputStream(); int r = -1; while ((r = stream.read()) != -1) { System.out.print((char) r); } }}
使用junit運行,並通過瀏覽器訪問:http://127.0.0.1,我們可以看到控制台上輸出瀏覽器的請求內容如下:
GET / HTTP/1.1Host: 127.0.0.1Connection: keep-aliveAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36Accept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8
為了更好的分析請求內容,我們編寫一個HTML頁面提交一些資料,再次查看請求內容:
<code class="language-html hljs "></code>
在輸入框中輸入bob,點擊按鈕提交,觀察控制台輸出:
POST /?test=123 HTTP/1.1Host: 127.0.0.1Connection: keep-aliveContent-Length: 8Cache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Origin: nullUser-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36Content-Type: application/x-www-form-urlencodedAccept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8name=bob
我們來分析一下這段請求內容:
第一行:由三部分組成,中間以空格分開,第一部分為要求方法(GET、POST),第二部分為請求路徑以及查詢參數,第三部分為HTTP協議版本(HTTP/1.1)
第二行到第十行:請求的頭資訊,要求標頭名稱與值之間通過:分隔
第十一行:空行
第十二行:提交的表單內容
綜上,我們可以得到如下結論:請求資訊第一行為要求方法、請求路徑以及查詢參數、HTTP協議版本,通過\r\n換行後緊跟著要求標頭資訊,各頭資訊之間通過\r\n換行,要求標頭資訊結束後跟著一個空行,空行之後緊跟著一行為請求資料,需要注意的是,這裡面只類比了最簡單的表單提交,至於複雜的檔案提交等,這裡面不討論,請求內容格式略有不同。
至此,用戶端請求的內容我們已經知道了,下面我們再來看看服務端在接收到請求後響應資料的格式,我們建立一個Web項目用於測試,編輯Html頁面內容如下:
this is test page.
啟動伺服器,然後編寫用戶端測試代碼,獲得服務端返回資料:
import java.io.BufferedWriter;import java.io.InputStream;import java.io.OutputStreamWriter;import java.net.ServerSocket;import java.net.Socket;import org.junit.Test;/** * HTTP協議測試 * * @author jianggujin * */public class HQHttpProtocolTest{ public void server() throws Exception { ServerSocket serverSocket = new ServerSocket(80); Socket socket = serverSocket.accept(); InputStream stream = socket.getInputStream(); // BufferedInputStream inputStream = new BufferedInputStream(stream); int r = -1; while ((r = stream.read()) != -1) { System.out.print((char) r); } } @Test public void client() throws Exception { Socket socket = new Socket("127.0.0.1", 80); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream())); writer.write("GET /Servlet/test.html HTTP/1.1\r\n"); writer.write("Host: 127.0.0.1\r\n"); writer.write("Connection: keep-alive\r\n"); writer.write("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"); writer.write("User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36\r\n"); writer.write("Accept-Encoding: gzip,deflate,sdch\r\n"); writer.write("Accept-Language: zh-CN,zh;q=0.8\r\n"); writer.write("\r\n"); writer.flush(); InputStream stream = socket.getInputStream(); int r = -1; while ((r = stream.read()) != -1) { System.out.print((char) r); } }}
運行程式獲得伺服器返回內容如下:
HTTP/1.1 200 OKServer: Apache-Coyote/1.1Accept-Ranges: bytesETag: W/"129-1456125361109"Last-Modified: Mon, 22 Feb 2016 07:16:01 GMTContent-Type: text/htmlContent-Length: 129Date: Mon, 22 Feb 2016 08:08:32 GMT
this is test page.
同樣的,我們來分析一下這段返回訊息:
第一行由三部分組成,中間以空格分開,第一部分為HTTP協議版本(HTTP/1.1),第二部分為響應狀態代碼,第三部分為響應狀態原因
第二行到第七行為回應標頭資訊,回應標頭名稱與值之間通過:分隔
第八行:空行
第九行到結束:響應內容
綜上,我們可以得到如下結論:請求資訊第一行為HTTP協議版本、響應狀態代碼、響應狀態原因,通過\r\n換行後緊跟著回應標頭資訊,各頭資訊之間通過\r\n換行,回應標頭資訊結束後跟著一個空行,空行之後緊跟著響應資料,需要注意的是,除這種響應外,其實還有其他的相應方式,比如chunk,此處不討論,可查閱相關資料。
到現在為止,我們已經分析完了用戶端的請求內容格式以及服務端相應內容的格式,這一篇就到此為止了,接下來的部落格中,我們將一步一步的進行伺服器端的編寫。