day16-WebServer(四)

                                           WebServer(四)

Readme:

~該版本改動:

  1. 將客戶端請求的頁面響應給客戶端。
  2. 該版本要了解HTTP協議中的響應(response)規則。要嚴格按照HTTP協議的響應格式給客戶端響應,這樣瀏覽器才能得到正確的結果。
  3. 步驟:
    ~在項目中添加一個新目錄webapps,使用該目錄保存不同網站所需要的素材等內容。
    ~在webapps目錄下新建子目錄myweb,作爲我們測試的網站資源目錄。
      注:在tomcat中webapps目錄是存放所有web應用的,其每一個子目錄都是一個具體的web應用(一個網站內容、涵蓋頁面、圖片等素材以及處理業務邏輯的java代碼)。
    ~在myweb中添加頁面index.html
    ~瀏覽器的地址欄請求某個服務端資源時,是無法寫在服務端資源的絕對路徑的,只能寫相對路徑(沒有平臺差異性)。
      eg:http://localhost:8080/index.html   協議部分   服務器地址信息  資源路徑部分(請求行url內容)
      我們可以規定在資源路徑部分中的gen根爲我們定義的保存所有資源目錄webapps。若想找到webapps/myweb/index.html頁面的話,瀏覽器地址欄要輸入爲:http://localhost:8080/myweb/index.html
      當該請求發送到服務端時,我們解析請求中請求行裏的url部分會得到/myweb/index.html,那我們就對應的從webapps目錄中找到對應資源:webapps/myweb/index.html並將該資源響應給客戶端(在ClientHandler中添加分支判斷,查找客戶端請求的資源是否存在)。
    ~用一個標準的http響應格式,將用戶請求的資源回覆給客戶端。

HTTP:

~響應:

  1. 是服務端發送給客戶端的內容。
  2. 組成:
    ~狀態行:protocol    status_code    status_reason(CRLF)
                     協議版本       狀態代碼         狀態描述
              狀態代碼是一個三位數字,有5類:
                    1xx:消息,在HTTP1.0協議時爲保留部分,未使用
                    2xx:成功,表示客戶端請求成功
                    3xx:重定向,表示要求客戶端需要進一步操作才能完成請求
                    4xx:客戶端錯誤,表示客戶端的請求無效
                    5xx:服務端錯誤,表示請求被接收,但是服務端處理髮生了錯誤
               常見狀態碼:
                    200:請求已接收,並正常響應客戶端
                    302:要求客戶端進一步請求服務端指定的路徑
                    404:客戶端請求的資源未找到
                    500:服務端發生錯誤

    ~響應頭:響應頭的格式和意義跟請求中的消息頭一樣。
    ~響應正文:是二進制數據,是服務端響應給客戶端的實體數據,通常就是客戶端所請求的資源。
              一個響應中是否有響應正文可以通過響應頭中的兩個頭信息得知:
                    Content-Type:說明響應正文中的數據類型
                    Content-Length:說明響應正文的長度(字節量)
                    客戶端就是通過這兩個頭來讀取並理解響應正文內容的。
  3. 一個響應的內容大致爲:
           HTTP/1.1 200 OK(CRLF)
           Content-Type: text/html(CRLF)
           Content-Length: 14424(CRLF)(CRLF)
           101001010100100101010...

WebServer:

                                                       ClientHandler

package com.senbao.webserver.core;
public class ClientHandler implements Runnable{
    private Socket socket;
	public ClientHandler(Socket socket) {
		this.socket = socket;
	}
	public void run() {
        /*
		 * 處理該客戶端的請求的大致步驟
		 * 1.解析請求
		 * 2.處理請求
		 * 3.給予響應
		 */
		try {
			//1.解析請求,生成HttpRequest對象
            HttpRequest request = new HttpRequest(socket);
            //2處理請求
			/*
			 * 通過request獲取請求資源路徑,從webapps中尋找對應資源
			 * http://localhost:8080/myweb/index.html
			 */
			String url = request.getUrl();
			File file = new File("webapps" + url);
            if(file.exists()) {
				System.out.println("資源已存在");
				/*
				 * 一個標準的http響應格式回覆客戶該資源
				 */
        		OutputStream out = socket.getOutputStream();
				//發送狀態行
				String line = "HTTP/1.1 200 OK";
				out.write(line.getBytes("ISO8859-1"));
				out.write(13);//written CR 
				out.write(10); //written LF
				
				//發送響應頭
				line = "Content-Type: text/html";
				out.write(line.getBytes("ISO8859-1"));
				out.write(13);//written CR 
				out.write(10); //written LF
				
				line = "Content-Length:"+file.length();
				out.write(line.getBytes("ISO8859-1"));
				out.write(13);//written CR 
				out.write(10); //written LF
				//表示響應頭髮送完畢
				out.write(13);//written CR 
				out.write(10); //written LF
				
				//發送響應正文
				FileInputStream fis = new FileInputStream(file);
				byte[] data = new byte[1024*10];
				int len = -1;
				while((len = fis.read(data)) != -1) {
			    	out.write(data, 0, len);
				}	
            }else{
                System.out.println("資源未找到");
				/*
				 * 一個標準的http響應格式回覆客戶該資源
				 */
				file = new File("webapps/myweb/404.html");
				OutputStream out = socket.getOutputStream();
				//發送狀態行
				String line = "HTTP/1.1 400 ERROR";
				out.write(line.getBytes("ISO8859-1"));
				out.write(13);
				out.write(10);
				
				//發送響應頭
				line = "Content-Type: text/html";
				out.write(line.getBytes("ISO8859-1"));
				out.write(13);
				out.write(10);
				
				line = "Content-Length: " + file.length();
				out.write(line.getBytes("ISO8859-1"));
				out.write(13);
				out.write(10);
				//響應頭髮送完畢
				out.write(13);
				out.write(10);
				
				//發送消息正文
				FileInputStream fis = new FileInputStream(file);
				byte[] data = new byte[1024*10];
				int len = -1;
				while((len = fis.read(data)) != -1) {
					out.write(data, 0, len);
				}
            }

        } catch (Exception e) {
			e.printStackTrace();
		} finally {
			//響應後與客戶端斷開連接
			try {
				socket.close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
    }
        
}

                                                               HttpRequest

package com.senbao.webserver.http;

/*
 * HttpRequest表示一個Http協議要求的請求信息
 * 一個請求包含三部分:請求行 消息頭 消息正文
 */
public class HttpRequest {
	//對應客戶端的socket
	private Socket socket;
	//通過socket獲取到的輸入流,用於讀取客戶端發送的請求
	private InputStream in;

	/*
	 * 請求行相關信息定義
	 */
	//請求方式
	private String method;
	
	//資源路徑
	private String url;
	
	//請求使用的協議版本
	private String protocol;
	/*
	 * 消息頭相關信息
	 */
	private Map<String,String> handlers = new HashMap<>(); 
	/**
	 *  實例化HttpRequest使用的構造方法,需要將對應客戶端的socket傳入,
	 *  以便讀取客戶端發送過來的請求內容
	 * @param socket
	 */
	public HttpRequest(Socket socket) {
		System.out.println("HttpRequest:開始解析請求");
		try {
			this.socket = socket;
			this.in = socket.getInputStream();
			
			/*
			 * 1.解析請求行
			 * 2.解析消息頭
			 * 3.解析消息正文
			 */
			//1
			parseRequestLine();
			//2
			parseHeaders();
			//3
			parseContent();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * 解析請求行
	 */
	private void parseRequestLine() {
		System.out.println("解析請求行");
		/*
		 * 大致流程:
		 * 1.通過輸入流讀取第一行字符串
		 * 2.將請求行按照空格拆分爲三項
		 * 3.將拆分的三項分別設置到method,url,protocol三個屬性上
		 * 
		 * 解析請求行時,在獲取才分後的數組元素時可能會引發數組下標越界,這是由於HTTP協議
		 * 允許客戶端發送一個空請求過來導致的
		 * 我們後面解決
		 */
		String line = readLine();
		String[] array = line.split("\\s");
		method = array[0];
		url = array[1];
		protocol = array[2];
		
		System.out.println("method:"+method);
		System.out.println("url:"+url);
		System.out.println("protocal:"+protocol);
		System.out.println("解析完畢");
	}
	/**
	 * 解析消息頭
	 */
	private void parseHeaders(){
		System.out.println("解析消息頭");
		/*
		 * 大致步驟
		 * 1.繼續使用readLine方法讀取若干行內容,每一行都是一個消息頭
		 * 2.當readLine方法返回值爲空字符串時,停止循環(單獨讀到CRLF時,readLine返回空字符串)
		 * 3.每當讀取一個消息頭信息時應當按照“:”拆分爲兩項,第一項作爲消息頭名字,第二項作爲
		 * 	消息頭對應的值,將名字作爲key,值作爲value存入到handlers這個map中
		 */
		while(true) {
			String line = readLine();
			//判斷是否單獨讀取到了CRLF
			if("".equals(line)) {
				break;
			}
			String[] data = line.split(":\\s");
			handlers.put(data[0], data[1]);
		}
		System.out.println("handler:" + handlers);
	
		System.out.println("解析完畢");
	}
	/**
	 * 解析消息正文
	 */
	private void parseContent() {
		System.out.println("解析消息正文");
		System.out.println("解析完畢");
	}
	
	/**
	 * 通過給定的輸入流讀取一行字符串(以CRLF結尾)
	 * @param in
	 * @return
	 */
	private String readLine() {
		try {
			StringBuilder bulder = new StringBuilder();
			int d = -1;
			char c1 = 'a',c2 = 'a';
			while((d = in.read())!=-1) {
				c2 = (char)d;
				/*
				 * 在ASCII碼中CR的編碼對應的數字爲13, LF對應的編碼爲10
				 * 就好比字符a對應的編碼爲97
				 */
				if(c1 == 13 && c2 == 10) {
					break;
				}
				bulder.append(c2);
				c1 = c2;
			}
			return bulder.toString().trim();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}
	
	public String getMethod() {
		return method;
	}
	public String getUrl() {
		return url;
	}
	public String getProtocol() {
		return protocol;
	}
	public String getHeader(String name) {
		return handlers.get(name);
	}
	
	
}

                                                            WebServer

package com.tedu.webserver.core;
public class WebServer {
		private ServerSocket server;
		public WebServer(){
			try {
				//Tomcat默認開啓的端口就是8080
				server = new ServerSocket(8080);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		public void start() {
			try {
//				while(true) {
					System.out.println("等待客戶端連接……");
					Socket socket = server.accept();
					System.out.println("一個客戶端連接了!");
					
					//啓動一個線程,處理客戶端請求
					ClientHandler handler = new ClientHandler(socket);
					Thread t = new Thread(handler);
					t.start();
//				}
				
			} catch (IOException e) {
				e.printStackTrace();
			} 
		}
		public static void main(String[] args) {
			WebServer server = new WebServer();
			server.start();
		}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章