本身動手實現簡單web容器一

1、前言html

    主流系統架構無非分兩種:C/S(客戶端/服務器)、B/S(瀏覽器/服務器)java

    一、C/S架構web

        優勢:安全編程

        缺點:升級維護成本高,一旦要升級,全部終端要逐個升級瀏覽器

    二、B/S架構安全

        優勢:升級維護、部署簡單,只需維護服務端便可服務器

        缺點:安全性有所欠缺多線程

    當下B/S無疑是系統開發中主流模式,在B/S架構流行的前提下,就JavaEE規範下出現了各類各樣的Web框架,如Struts二、SpringMVC,而這些框架,無不基於Servlet規範,而Servlet容器中幾乎沒有不基於HTTP協議的實現(固然本文討論的不是HTTP協議),在開發中咱們到處使用框架,忽略底層細節,在部署時,打包扔進Tomcat、Jetty等web容器,簡單方便。那麼這些web容器的底層實現又是如何的呢?架構

    今天本文就展現如何一步步實現一個簡單web服務器。併發

2、需求

    一、實現一個web容器,並模擬登錄操做

    二、可併發

    三、線程安全

3、技術點

    一、Socket編程

    二、Java IO

    三、線程池

    四、ThreadLocal

    五、多線程

    六、NIO、BIO

4、單線程模式實現 

    一、寫一個HTML頁面,裏面有登錄須要的用戶名、密碼等輸入元素,值得一提的是,本例使用的是GET方式提交請求,已經足夠說明問題了,必須提到的是GET和POST獲取參數的方式是有區別的。

<html>
	<head>
	<meta charset="UTF-8">
	<title>登陸頁</title>
	</head>
	<body>
		<form action="login" method="get">
			<table>
				<tr>
					<td>用戶名:</td>
					<td><input type="text" name="username" ></td>
				</tr>
				<tr>
					<td>密碼:</td>
					<td><input type="text" name="password" ></td>
				</tr>
				<tr>
					<td><input type="reset" value="重置"></td>
					<td><input type="submit" value="登陸"></td>
				</tr>
			</table>
		</form>
	</body>
</html>

     二、有了HTML頁面,那麼如何把這個頁面讀到程序裏呢,這個時候就要用到IO了,請看下面的方法:

// 爲了方便main方法調用,並聲明爲static方法
private static String getLoginPage() {
	// 用於存儲從文件讀出的文件
	StringBuilder sb = new StringBuilder();
	try {
		// 聲明一個BufferedReader準備讀文件
		BufferedReader htmlReader = new BufferedReader(new InputStreamReader(
				new FileInputStream(new File(System.getProperty("user.dir") + "/webapp/login.html"))));
		String html = null;
		// 按行讀取,直到最後一行結束
		while ((html = htmlReader.readLine()) != null) {
			sb.append(html);
		}
		// 關閉
		htmlReader.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
	// 以字符串形式返回讀到的HTML頁面
	return sb.toString();
}

    三、那麼服務端怎麼與瀏覽器端交互,那麼須要Socket,請看下面例子:

    (1)、先定義要返回的響應頭

// HTTP請求的返回頭
private static final StringBuilder responseContent = new StringBuilder();
static {
	responseContent.append("HTTP/1.1 200 OK");
	responseContent.append("Content-Type: text/html; charset=utf-8");
	responseContent.append("Content-Length:%s");
	responseContent.append("Date:%s");
	responseContent.append("Server:This is a simulation web container");
}

    (2)、啓動web服務

public static void main(String[] args) {
	try {
		// 監聽在8080端口
		ServerSocket serverSocket = new ServerSocket(8080);
		while (true) {
			// 接收請求
			Socket accept = serverSocket.accept();
			// 獲取請求中的流
			BufferedReader br = new BufferedReader(new InputStreamReader(accept.getInputStream()));
			// 只讀取第一行數據
			String request = br.readLine();
			String result = "";
			if (!"".equals(request) && null != request) {
				String urlAndPrams = request.split(" ")[1];
				if (urlAndPrams.indexOf("/login") != -1) {
					// 獲取用戶傳過來的用戶名密碼
					String[] params = urlAndPrams.split("\\?")[1].split("&");
					String username = params[0].split("=").length < 2 ? null : params[0].split("=")[1];
					String password = params[1].split("=").length < 2 ? null : params[1].split("=")[1];
					if ("admin".equals(username) && "admin".equals(password)) {
						result = "login success";
					} else {
						result = "username or password error";
					}
				} else {
					// 調用前1面定義的獲取登陸頁方法,獲取已經寫好的登陸頁
					result = getLoginPage();
				}
			} else {
				// 調用前面定義的獲取登陸頁方法,獲取已經寫好的登陸頁
				result = getLoginPage();
			}
			// 拿到該請求對應Socket的輸出流,準備向瀏覽器寫數據了
			PrintWriter printWriter = new PrintWriter(accept.getOutputStream());
			// 把文件長度、日期填回Response字符串中
			printWriter.println(String.format(responseContent.toString(), result.length(), new Date()));
			// 必須有個換行,換行以後才能向瀏覽器端寫要傳回的數據(HTTP協議)
			printWriter.println();
			// 寫頁面數據
			printWriter.println(result);
			printWriter.flush();
			printWriter.close();
			accept.close();
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

    (3)、說明

    上一步代碼,只讀一行數據   拿到用戶傳過來的參數,實際上是能夠拿到HTTP請求的全部信息的,以下:

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: _ga=GA1.1.1755609982.1449039124; CNZZDATA1258600948=1953676425-1469624561-%7C1469624561; CNZZDATA1258740907=123042277-1469628131-%7C1469628131; userName=admin

5、執行效果

    一、啓動程序,瀏覽器器敲下http://localhost:8080/,回車,便能看到以下界面

      

    二、輸入用戶名:admin,密碼:admin,回車,提示登陸成功

    

    三、輸入錯誤用戶名或密碼

     

6、後記

    至此,一個單線程的web服務完成,其中存在以下問題

    一、只能處理單個用戶請求,多用戶沒法同時使用

    二、如何實現並行處理用戶請求

    三、並行時線程如何安全

    四、阻塞式的IO效率低,如何改進

    請期待下一篇《本身動手實現簡單web容器二》

    快樂源於分享。

此博客乃做者原創, 轉載請註明出處

相關文章
相關標籤/搜索