Skip to content

Latest commit

 

History

History
296 lines (271 loc) · 13.3 KB

服務器.md

File metadata and controls

296 lines (271 loc) · 13.3 KB

服務器

服務器處理請求流程

服務器每次收到請求時,都會為這個請求開闢一個新的線程。 服務器會把客戶端的請求數據封裝到 request 對象中,request 就是請求數據的載體。(EX: 袋子) 服務器還會創建 response 對象,這個對象與客戶端連接在一起,它可以用來向客戶端發送響應。(EX: 手機)

response (類型為 HttpServletResponse)

  • ServletResponse -> 與協議無關的類型。 HttpServletResponse -> 與 http 協議相關的類型。
  • 狀態碼: 200 表示成功,302 表示重定向,404 表示客戶端錯誤(訪問資源不存在),500 表示服務端錯誤
  • sendError(int sc): 發送錯誤狀態碼(EX: 404, 500)
  • sendError(int sc, String msg): 發送錯誤狀態碼,可以帶錯誤信息
  • setStatus(int sc): 發送成功狀態碼(EX: 302)。
  • 案例: 發送 404
  • 響應頭: Content-Type, Refresh, Location 等。 頭就是一個鍵值對,可能會存在一個頭(一個名稱,一個值),也可能會存在一個頭(一個名稱,多個值)
  • setHeader(String name, String value): 適用於單值的響應頭,例如: response.setHeader("aaa",AAA");
  • addHeader(String name, String value): 適用於多值的響應頭。
  • setIntHeader(String name, int value): 適用於單值的 int 類型的響應頭。
  • addIntHeader(String name, int value): 適用於多值的 int 類型的響應頭。
  • setDateHeader(String name, long value): 適用於單值得毫秒類型的響應頭。
  • addDateHeader(String name, long value): 適用於多值的毫秒類型的響應頭。
  • 案例:
    • 發送 302, 設置 Location 頭,完成重定向。
    • 定時刷新,設置 Refresh 頭。
    • 禁用瀏覽器緩存: Cache-Control, pragma, expires
    • meta 標籤可以代替響應頭。
  • 響應體: 通常是 html, 也可以是圖片
  • response 的兩個流: - ServletOutputStream: 用來向客戶端發送字節數據。 - PrintWriter: 用來向客戶端發送字符數據,需要設置編碼。 - 兩個流不能同時使用。
  • 案例: - 使用 PrintWriter 發送字符數據。 - 使用 ServletOutputStream 發送字節數據(圖片)。
  • 重定向 -> 設置 302,設置 Location。 其中變化的只有 Location,所以 Java 提供了一個快捷方法完成重定向。
  • sendRedirect(String location) 方法
    /**
     * 演示重定向
     * @author asus
     * 
     * 1. 用戶請求 ServletDemo2,然後 ServletDemo2 響應 302, 給出 Location 頭
     */
    public class ServletDemo2 extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("ServletDemo2");
    		/*
    		 * 重定向:
    		 * 1. 設置 Location
    		 * 2. 發送 302 狀態碼
    		 */
    //		response.setHeader("Location", "/Java_Web/servlet_2/ServletDemo3");
    //		response.setStatus(302);
    		
    		//快捷的重定向方法
    		response.sendRedirect("Java_Web/servlet_2/ServletDemo3/");
    	}
    }
    
    /**
     * 瀏覽器會重定向到這
     * @author asus
     */
    public class ServletDemo3 extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("ServletDemo3");
    	}
    }

request -> 封裝了客戶端所有的請求數據

請求行 請求頭 空行 請求體 回憶 http 協議,請求協議中的數據都可以通過 request 對象來獲取。

  • 獲取常用信息
  • 獲取客戶端 IP,案例: 封鎖 IP -> request.getRemoteAddr()
  • 請求方式
  • 獲取http請求頭
  • String getHeader(String name),適用於單值頭
  • int getIntHeader(String name),適用於單值 int 類型的請求頭
  • long getDateHeader(String name),適用於單值毫秒類型的請求頭
  • Enumeration getHeaders(String name),適用於多值請求頭
  • 案例:
  • 通過 User-Agent 識別用戶瀏覽器類型
  • 防盜鏈: 如果請求不是通過本站的超鏈接發出的,發送錯誤狀態碼 404。-> Referer 這個請求頭,表示請求的來源。
    public class ServletDemo3 extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		/*
		 * 使用 Referer 請求頭,來防盜鏈
		 */
		String referer = request.getHeader("Referer");
		System.out.println(referer);
		if(referer == null || !referer.contains("localhost")) {
			System.out.println("Hello");
		} else {
			response.sendRedirect("http://www.baidu.com");
		}
	}
}
  • Example
    /**
     * 演示: 獲取客戶端的 ip 地址、獲取請求方式、獲取 User-Agent,得到客戶端信息(操作系統瀏覽器)
     * @author asus
     */
    public class ServletDemo extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		String ip = request.getRemoteAddr();//獲取客戶端的 ip 地址
    		System.out.println("IP: " + ip);
    		System.out.println("請求方式: " + request.getMethod());//獲取請求方式
    		String userAgent = request.getHeader("User-Agent");//獲取名為 User-Agent 的請求頭
    		/*
    		 * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36
    		 */
    		//是否包含 Chrome,如果包含,說明用戶使用的是 google 瀏覽器
    		if(userAgent.toLowerCase().contains("chrome")) {
    			System.out.println("Hello " + ip + ", you are using google");
    		} else if(userAgent.toLowerCase().contains("firefox")) {
    			System.out.println("Hello " + ip + ", you are using firefox");
    		} else if(userAgent.toLowerCase().contains("msie")) {
    			System.out.println("Hello " + ip + ", you are using msie");
    		}
    		System.out.println(userAgent);
    	}
    }
  • 獲取請求 URL http://localhost:8080/Java_Web/servlet_3/ServletDemo?username=xxx&password=yyy
    • String getScheme(): 獲取協議 -> http
  • String getServerName(): 獲取服務器名 -> localhost
  • String getServerPort(): 獲取服務器端口 -> 8080
  • String getContextPath(): 獲取項目名 -> /Java_Web
  • String getServletPath(): 獲取 Servlet 路徑 -> /servlet_3/ServletDemo
  • String getQueryString(): 獲取參數部分,即問號後面的部分。-> username=xxx&password=yyy
  • String getRequestURI(): 獲取請求 URI,等於項目名 + Servlet 路徑 -> /servlet_3/ServletDemo
  • String getRequestURL(); 獲取請求 URL,等於不包含參數的整個請求路徑。-> http://localhost:8080/Java_Web/servlet_3/ServletDemo
    public class ServletDemo2 extends HttpServlet {
    	
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		/*
    		 * 通過 request 來獲取 url 的相關方法
    		 */
    		response.getWriter().println(request.getScheme());//獲取請求協議
    		response.getWriter().println(request.getServerName());//獲取服務器名稱
    		response.getWriter().println(request.getServerPort());//獲取服務器端口號
    		response.getWriter().println(request.getContextPath());//獲取項目名稱
    		response.getWriter().println(request.getServletPath());//獲取 Servlet 路徑
    		response.getWriter().println(request.getQueryString());//獲取參數部分
    		response.getWriter().println(request.getRequestURI());//獲取請求 URI
    		response.getWriter().println(request.getRequestURL());//獲取請求 URL
    	}
    }
http
localhost
8080
/Java_Web
/servlet_3/ServletDemo2
username=jack&password=123
/Java_Web/servlet_3/ServletDemo2
http://localhost:8080/Java_Web/servlet_3/ServletDemo2
  • 獲取請求參數: 請由參數是由客戶端發送給服務器的,有可能是在請求體中 (POST),也可能是在 URL 之後 (GET)

    /**
     * 演示 request 請求參數
     * @author asus
     */
    public class ServletDemo4 extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("GET: " + request.getParameter("xxx"));
    		System.out.println("GET: " + request.getParameter("yyy"));
    	}
    	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		String username = request.getParameter("username");
    		String password = request.getParameter("password");
    		String[] hobby = request.getParameterValues("hobby");
    		
    		System.out.println(username + ", " + password + ", " + Arrays.toString(hobby));
    		
    		/*
    		 * 測試獲取所有請求參數的名稱 
    		 */
    		Enumeration names = request.getParameterNames();
    		while(names.hasMoreElements()) {
    			System.out.println(names.nextElement());
    		}
    		
    		/*
    		 * 獲取所有請求參數
    		 */
    		Map<String, String[]> map = request.getParameterMap();
    		for(String name : map.keySet()) {
    			String[] values = map.get(name);
    			System.out.println(name + " @ " + Arrays.toString(values));
    		}
    	}
    }
    • String getParameter(String name): 獲取指定名稱的請求參數值,適用於單值請求參數。
  • String[] getParameterValues(String name): 獲取指定名稱的請求參數值,適用於多值請求參數。

  • Enumeration getParameterNames(): 獲取所有請求參數名稱。

  • Map getParameterMap(): 獲取所有請求參數,其中 key 為參數名,value 為參數值。

  • 案例: 超鏈接參數

  • 案例: 表單數據

  • 請求轉發和請求包含 RequestDispatcher rd = request.getRequestDispatcher("/MyServlet"); 使用 request 獲取 RequestDispatcher 對象,方法的參數是被轉發或包含的 Servlet 的 Servlet 路徑。 請求轉發: rd.forward(request, response); -> 常用

    /**
     * 演示請求轉發
     * @author asus
     */
    public class ServletDemo extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("SerlvetDemo...");
    		response.setHeader("aaa", "AAA");//設置響應頭
    		
    //		for(int i = 0; i < 1024 * 24 + 1; i++) {
    //			response.getWriter().print("a");//設置響應體
    //		}
    		
    		//等同於調用 ServletDemo2 的 service() 方法
    		request.getRequestDispatcher("/ServletDemo2").forward(request, response);//請求轉發
    	}
    }
    
    public class ServletDemo2 extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("ServletDemo2...");
    		response.getWriter().print("Hello ServletDemo2!");//設置響應體
    	}
    }

    請求包含: rd.include(request, response);

    /**
     * 演示請求包含
     * @author asus
     */
    public class ServletDemo extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("ServletDemo...");
    		response.setHeader("aaa", "AAA");//設置響應頭
    		
    		response.getWriter().print("aaaa");//設置響應體
    		request.getRequestDispatcher("/ServletDemo2").include(request, response);//請求轉發
    	}
    }
    
    public class ServletDemo2 extends HttpServlet {
    
    	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    		System.out.println("ServletDemo2");
    		response.getWriter().print("Hello ServletDemo2!");//設置響應體
    	}
    }

    有時一個請求需要多個 Serlvet 協作才能完成,所以需要再一個 Servlet 跳到另一個 Servlet

    • 一個請求跨多個 Serlvet,需要使用轉法和包含。
    • 請求轉發,由下一個 Servlet 完成響應體,當前 Servlet 可以設置響應頭。(留頭不留體)
    • 請求包含,由兩個 Servlet 共同完成響應體。(都留)
    • 無論是請求轉發還是請求包含,都在一個請求範圍內,使用同個 request & response
  • request 域 Servlet 中三大域對象: request, session, application,都有如下三個方法:

  • void setAttribute(String name, Object value)

  • Object getAttribute(String name)

  • void removeAttribute(String name)

  • 同一請求範圍內使用 request.setAttribute()、request.getAttribute() 來傳值,前一個 Servlet 調用 setAttribute() 保存值,後一個 Servlet 調用 getAttribute()

  • 請求轉發和重定向的區別

  • 請求轉發是一個請求一次響應,而重定向是兩次請求兩次響應

  • 請求轉發地址欄不變化,而重定向會顯示後一個請求的地址。

  • 請求轉發只能轉發到本項目其他 Servlet,而重定向不只能重定向到本項目的其他 Servlet,還能定向到其他項目。

  • 請求轉發是服務器端行為,只須出轉發的 Servlet 路徑,而重定向需要給出 requestURI,即包含項目名。

  • 請求轉發效率較高,因為是一個請求。

  • 需要地址欄發生變化,必須使用重定向

  • 需要在下一個 Servlet 中獲取 request 域中的數據,必須使用轉發