day18-WebServer(八)

                                         WebServer(八)

Readme:

~該版本改動:

      使服務端支持處理業務操作:完成註冊業務

~步驟:

  1. 在webapps/myweb/下新建一個註冊頁面reg.html
  2. 當頁面上的form表單以get形式提交時,表單中用戶輸入的內容會被拼接到地址欄url中。所有服務端在
    解析請求時獲取請求行中url部分時,可能會得到兩種情況的內容:
    ·不含參數的:/myweb/index.html
    ·含參數的:/myweb/reg?username=xxx&age=17&...
    因此我們要重構HttpRequest這個類的解析工作,對請求行中url部分要進行進一步的解析工作:
    ·在HttpRequest中添加三個屬性,用於保存url中的各個部分內容:
            String requestURI:保存請求部分
            String queryString:保存參數部分
            Map parameters:保存每個參數(通過解析queryString得到)
    ·提供一個專門用來解析請求行中url部分的方法,解析後,將url各部分內容設置到2.1定義的屬性上。
    ·在解析請求行的方法中,得到url後,調用2.2提供的方法進一步解析url。
    ·爲2.1定義的屬性提供對應的get方法,便於外面獲取這些信息
  3. 在ClientHandler中添加一個新的分支,判斷請求是否爲請求註冊業務。
  4. 若是請求註冊業務,則通過request獲取用戶在註冊頁面上輸入的註冊信息,並通過RandomAccessFile打開user.dat文件,將該記錄寫入文件。
    每條記錄佔100字節,用戶名、密碼、暱稱爲字符串,各佔32字節,年齡爲int,佔4個字節。具體寫入的操作可以參考SE項目中raf包中Demo1.java
  5. 在webapps/myweb/下新建一個頁面reg_success.html
  6. 當註冊完畢後,設置response跳轉reg_success.html完成註冊流程。

WebServer:

                                                         WebServer

package com.senbao.webserver.core;

/*
WebServer主類
*/
public class WebServer{
    private ServerSocket server;

    public WebServer(){
        try{
            server = new ServerSocket(8080);
        }catsh(Exception 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();
    }
}



                                                   EmptyRequestException

package com.senbao.webserver.core;

/*
空請求異常
當客戶端連接後發生空請求,HttpRequest的構造方法會拋出該異常
*/

public class EmptyRequestException extends Exception{

	private static final long serialVersionUID = 1L;

	public EmptyRequestException() {
		super();
	}

	public EmptyRequestException(String message, Throwable cause, boolean enableSuppression,
			boolean writableStackTrace) {
		super(message, cause, enableSuppression, writableStackTrace);
	}

	public EmptyRequestException(String message, Throwable cause) {
		super(message, cause);
	}

	public EmptyRequestException(String message) {
		super(message);
	}

	public EmptyRequestException(Throwable cause) {
		super(cause);
	}
	
}

                                                      HttpRequest

package com.senbao.webserver.http;

/*
HttpRequest表示一個http協議要求的請求信息
一個請求信息包含三部分:
請求行 消息頭 消息正文
*/
public class HttpRequest {
    private Socket socket;
    private InputStream in;

    /*
    請求行相關信息定義
    */
    //請求方式
    private String method;
	
	//資源路徑
	private String url;
	
	//請求使用的協議版本
	private String protocol;

    //url中的請求部分
	private String requestURI;
	
	//url中的參數部分
	private String queryString;
	
	//url中的所有參數 key是參數名 value是參數值
	private Map<String,String> parameters = new HashMap<>();

    /*
    消息頭相關信息定義
    */
    private Map<String,String> headers = new HashMap<String,String>();


    /*
    實例化HttpRequest使用的構造方法,需要將對應客戶端的Socket傳入,
    以便讀取該客戶端發送過來的請求內容
    */
    public HttpRequest(Socket socket) throws EmptyRequestException{
        System.out.println("HttpRequest:開始解析請求");
		try{
			this.socket = socket;
			this.in = socket.getInputStream();
			/*
			 * 1:解析請求行
			 * 2:解析消息頭
			 * 3:解析消息正文
			 */		
			//1
			parseRequestLine();
			//2
			parseHeaders();
			//3
			parseContent();
		}catch(EmptyRequestException e) {
			//將空請求拋出給ClientHandler
			throw e;
		}catch(Exception e){
			e.printStackTrace();
		}
    }

    /**
	 * 解析請求行
	 * @throws EmptyRequestException 
	 */
	private void parseRequestLine() throws EmptyRequestException{
		System.out.println("解析請求行...");
        /*
        大致流程:
        1:通過輸入流讀取第一行字符串
        2:將請求行按照空格拆分爲三項
        3:將拆分的三項分別設置到method,url,protocol三個屬性上  
        解析請求行時,在獲取拆分後的數組元素時可能會引發數組下標越界,這是由於HTTP協議允許客戶端發送一個空請求過來導致的。
        */
        String line = readLine();
        String[] data = line.split("\\s");
        //判斷拆分請求行內容是否能達到三項
        if(data.length<3){
            throw new EmptyRequestException();
        }
        this.method = data[0];
		this.url = data[1];
		//進一步解析URL部分
		parseURL();
		this.protocol = data[2];
		
		System.out.println("method:"+method);//  GET
		System.out.println("url:"+url);//  /index.html
		System.out.println("protocol:"+protocol);// HTTP/1.1
		System.out.println("請求行解析完畢");

    }
    
    /*
    進一步對url進行解析
    將url中的請求部分設置到屬性requestURI上
    將url中的參數部分設置到屬性queryString上
    再對參數部分進行進一步解析,將每個參數都存入到屬性parameters中

    若該url不含有參數,則直接將url的值賦給requestURI,參數部分不做任何處理
    */
    private void parseURL(){
        System.out.println("開始解析url:"+url);
        
        /*
        思路:
        url中是否有參數,可以根據該url中是否有?來決定。若有則按照?拆分爲兩部分
        第一部分爲請求部分,第二部分爲參數部分,設置到對應屬性即可。
        然後再對參數進行拆分,最終將每個參數的名字作爲key,值作爲value存到parameters中
        若不含參數,則直接將url賦值給requestURI    
        */
        if(this.url.indexOf("?") != -1){
            String[] data = url.split("\\?");
            this.requestURI = data[0];
            if(data.length>1){
                this.queryString = data[1];
                String[] paras = queryString.split("&");
                for(String paraStr : paras){
                    String[] paraDate = paraStr.split("=");
                    if(paraDate.length>1){
                        this.parameters.put(paraDate[0],paraDate[1]);
                    }else{
                        this.parameters.put(paraDate[0],null);
                    }
                }
            }
        }else{
            this.requestURI = this.url;
        }
        System.out.println("requestURI:"+requestURI);
		System.out.println("queryString:"+queryString);
		System.out.println("parameters:"+parameters);
		System.out.println("url解析完畢");    
    }

    /**
	 * 解析消息頭
	 */
	private void parseHeaders(){
		System.out.println("解析消息頭...");
        /*
        大致步驟:
        1:繼續使用readLine方法讀取若干行內容,每一行應該都是一個消息頭
        2:當readLine方法返回值爲空字符串時則停止循環讀取工作(單獨讀取到了CRLF時
        */
        


    }

    /**
	 * 通過給定的輸入流讀取一行字符串(以CRLF結尾)
	 * @param in
	 * @return
	 */
	private String readLine(){
        try {
            StringBuilder builder = new StringBuilder();
            char c1 = 'a', c2 = 'a';
            int d = -1;
            while((d = in.read()) != -1){
                c2 = (char)d;
                if(c1==13&&c2==10){
					break;
				}
                builder.append(c2);
                c1 = c2;
            }
            return builder.toString().trim();
        }catch (Exception e) {
			e.printStackTrace();
		}
		return "";
    }

}

 

 

 

 

 

 

 

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