day17-WebServer(七)

                                          WebServer(七)

Readme:

~該版本改動:

       支持所有介質類型。tomcat中的conf目錄裏有一個web.xml文件,該文件中配置着所有資源後綴與對應的Content-Type的值。我們將該文件拷貝到我們的項目中,並在HttpContex的initMimeMapping方法中解析該文件來初始化介質映射。

~步驟:

  1. 在項目中新建一個conf目錄。
  2. 將tomcat中的web.xml文件拷貝到conf目錄中。
  3. 導入dom4j的jar包。
  4. 修改HttpContext初始化介質類型的映射方法:initMimeMapping。

WebServer:

                                                        HttpContext

package com.senbao.webserver.http;


/*
該類定義了HTTP協議相關信息
*/
public class HttpContext{
    /*
    狀態代碼與對應狀態描述的映射關係
    key:狀態代碼
    value:狀態描述
    */
    private static Map<Integer,String> STATUS_REASON_MAPPING = new HashMap<Integer,String>();
    /*
    資源後綴與Content-Type之間的映射關係
    key:資源的後綴名
    value:該資源對應的Content-Type的值
    注:不同的資源對應的Content-Type的值在w3c上都有定義,可前往w3c官網查詢MIME定義
    */
    private static Map<String,String> MIME_MAPPING = new HashMap<>();

    static{
        initStatusReasonMapping();
		initMIMEMAPPING();
    }

    /*
    讀取conf/web.xml文件,將根元素下所有的名爲<mime-mapping>的子元素讀取出來,然後將每個<mime-mapping>元素中的子元素<extension>之間的文本作爲key,將子元素<mime-type>中間的文本作爲value,存入到MIME_MAPPING中,完成初始化
    */
    private static void initMIMEMAPPING(){
        try {
            SAXReader reader = new SAXReader();
            Doucument doc = reader.read(new File("conf/web.xml"));
            Element root = doc.getRooyElement();
            List<Element> mimeList = root.elements("mime-mapping");
            for(Element e : mimeList){
                MIME_MAPPING.put(e.element("extension").getText(),e.element("mime-type").getText());
            }
        }catch(Exception e){
            e.printStckTrace();
        }
                
    }
    
    /*
    初始化狀態代碼與描述的映射MAP
    */
    private static void initMIMEMAPPING(){
        STATUS_REASON_MAPPING.put(200, "OK");
		STATUS_REASON_MAPPING.put(302, "Move Temporaily");
		STATUS_REASON_MAPPING.put(404, "Not Found");
		STATUS_REASON_MAPPING.put(500, "Internal Server Error");
    }

   	/**
	 * 根據給定的狀態代碼獲取對應的狀態描述
	 * @param statusCode
	 * @return
	 */
	public static String getStatusReason(int statusCode){
		return STATUS_REASON_MAPPING.get(statusCode);
	}
	/**
	 * 根據資源後綴名獲取對應的Content-Type的值
	 * @param ext
	 * @return
	 */
	public static String getMimeType(String ext) {
		return MIME_MAPPING.get(ext);
	}

    public static void main(String[] args) {
		String reason = getStatusReason(200);
		System.out.println(reason);
		//介質
		String type = getMimeType("css");
		System.out.println(type);
	}
    
}

                                                         WebServer

package com.senbao.webserver.core;

/*
WebServer主類
*/
public class WebServer{
    private ServerSocket server;
    
    public WebServer(){
        try{
            //tomcat默認開啓的端口號是8080
            server = new ServerSocket(8080);
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public void start(){
        try{
            while(true){
                System.out.println("等待客戶端連接...");
                Socket socket = server.accept();
                System.out.println("一個客戶端連接了!");
    
                //啓動一個線程處理客戶端請求
                ClientHandler handler = new CilentHandler(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);
	}
}

                                                           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:解析請求,創建HttpRequest
            創建響應對象HttpResponse
        2:處理請求
        3:給予響應
        */   
        try{
            //1.解析請求,生成HttpRequest
            HttpRequest request = new HttpRequest(socket);
            HttpResponse response = new HttpResponse(socket);
            
            //2.處理請求
            /*
            通過request獲取請求的資源路徑,從webapps中尋找資源
            */
            String url = request.getUrl();
            File file = new File("webaps" + url);
            if(file.exists()){
                System.out.println("資源已找到!");
                /*
                以一個標準的TTP響應格式回覆給客戶端
                */
                response.setStatusCode(200);
                response.setEntity(file);
            }else{
                System.out.println("資源未找到!");
                File file = new File("webapps/myweb/404.html");
                response.setStatusCode(404);
                response.setEntity(file);
            }                
            //響應客戶端
            response.flush();

        }catch(EmptyRequestExceptione){
            System.out.println("空請求!");
        }catch(Exception e){
            e.printStackTrace();
        }filnally{
            try{
                socket.close();
            }catsh(IOException e){
                e.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> headers = new HashMap<String,String>();

    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();
		}
    }

    /*
    解析請求行
    */
    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];
        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("請求行解析完畢");
    }

    /*
    解析請求頭
    */
    private void parseHeaders(){
        System.out.println("解析消息頭...");
        /*
        大致步驟:
        1:繼續使用readLine方法讀取若干行內容,每一行都應該是一個消息頭
        2:當readLine方法返回值爲空字符的時候則停止循環(單獨讀到了CRLF時readLine方法返回值應該當爲空字符串)
        3:每當讀取一個消息頭信息時都應當按照“: ”拆分爲兩項,第一項爲消息頭名字,第二項爲消息頭對應的值,將名字作爲key,將對應的值作爲value存入到headers中
        */
        while(true){
            String line = readLine();
            //判斷是否單獨讀到了CRLF
            if("".equals(line)){
                break;
            }
            String[] data = line.split(":\\s");
            headers.put(data[0],data[1]);
        }
   		System.out.println("headers:"+headers);
		System.out.println("消息頭解析完畢");
    }

    /**
	 * 解析消息正文
	 */
	private void parseContent(){
		System.out.println("解析消息正文...");
		
		
		System.out.println("消息正文解析完畢");
	}

    /*
    通過給定的輸入流讀取一行字符串(以CRLF結尾)
    */
    private String readLine(){
        try{
            StringBuilder builder = new StringBuiler();
            //c1表示上次讀到的字符,c2表示本次讀到的字符
            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 "";
    }
    
   	public String getMethod() {
		return method;
	}
	public String getUrl() {
		return url;
	}
	public String getProtocol() {
		return protocol;
	}
	public String getHeader(String name) {
		return headers.get(name);
	}

}

                                                        HttpResponse

package com.senbao.webserver.http;

/*
響應對象
該類的每個實例用於表示一個服務端發送給客戶端的響應內容
*/

public class HttpResponse {
    private Socket socket;
    private OutputStream out;

    /*
    狀態行相關信息定義
    */
    //狀態代碼
    private int statusCode;

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

    /*
    響應正文相關信息定義
    */
    //要響應的實體文件
    private File entity;

    public HttpResponse(Socket socket){
        try{
            this.socket = socket;
            this.out = socket.getOutputStream();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    
    /*
    將響應的內容按照Http協議格式發送給客戶端
    */
    public void flush(){
        /*
        響應客戶端做三件事
        1:發送狀態行
        2:發送響應頭
        3:發送響應正文
        */
        sendStatusLine();
		sendHeaders();
		sendContent();
    }

    /*
    發送狀態行
    */
    private void sendStatusLine(){
        try{
            String line = "HTTP/1.1" + " " + statusCode + " " + HttpContext.getStatusReason(statusCode);
            println(line);
        }catch (Exception e) {
			e.printStackTrace();
		}
    }
    
    /*
    發送響應頭
    */
    private void sendHeaders(){
        try{
            Set<Entry<String,String>> set = header.entrySet();
            for(Entry<String,String> header : set){
                String name = header.getKey();
                String value = header.getValue();
                String line = name + " " + value;
                println(line);
            }
            //表示響應頭部分發送完畢
            println("");
        }catch (Exception e) {
			e.printStackTrace();
		}
    }

    /*
    發送響應正文
    */
    private void sendContent(){
        try(
            FileInputStream fis = new FileInputStream(entity);
        ){
            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();
		}
    }

    private void println(String line){
        try{
            out.write(line.getBytes("ISO8859-1"));
            out.write(13);
            out.write(10);
        }catch (Exception e) {
			e.printStackTrace();
		}
    }

    public int getStatusCode() {
		return statusCode;
	}


	public void setStatusCode(int statusCode) {
		this.statusCode = statusCode;
	}

	public File getEntity() {
		return entity;	
	}

    /*
    設置響應的實體文件數據,該方法會自動添加對應的兩個響應頭
    Cotent-Type  Content-Length
    */
    public void setEntity(File entity){
        this.entity = entity;
        /*
        添加響應頭Content-Length
        */
        headers.put("Content-Length",entity.length()+"");

        /*
        添加響應頭Content-Type
        1:先通過Entity獲取該文件的名字
        2:獲取該文件名的後綴名
        3:通過HttpContext根據後綴名獲取到對應的Content-Type的值
        4:向headers中設置該響應頭的信息    
        */
        String name = entity.getName();
        String ext = name.subString(name.lastIndexOf(".")+1);
        String type = HttpContext.getMimeType(ext);
        this.headers.put("Content-Type",type);
    }
    /*
    添加一個響應頭
    name:響應頭的名字
    value:響應頭的值
    */
    public void putHeders(String name,String value){
        this.headers.put(name,value);
    }

}

 

 

 

 

 

 

 

 

 

 

 

 

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