【JavaWeb】請求和響應Request&Response

gonghr 發表於 2021-07-26
Java

請求

請求物件

關於請求

顧名思義,意思就是請求一個“物件”
請求不到的,別想了
請求,就是使用者希望從伺服器端索取一些資源,向伺服器發出詢問。在B/S架構中,就是客戶瀏覽器向伺服器發出詢問。在JavaEE工程中,客戶瀏覽器發出詢問,要遵循HTTP協議規定。

請求物件,就是在JavaEE工程中,用於傳送請求的物件。我們常用的物件就是ServletRequest和HttpServletRequest,它們的區別就是是否和HTTP協議有關。

常用請求物件

image

常用請求方法

image

請求物件的使用示例

常用方法一:請求各種路徑

/*
    獲取路徑的相關方法
 */
@WebServlet("/servletDemo01")
public class ServletDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.獲取虛擬目錄名稱 getContextPath()
        String contextPath = req.getContextPath();
        System.out.println(contextPath);

        //2.獲取Servlet對映路徑 getServletPath()
        String servletPath = req.getServletPath();
        System.out.println(servletPath);

        //3.獲取訪問者ip getRemoteAddr()
        String ip = req.getRemoteAddr();
        System.out.println(ip);

        //4.獲取請求訊息的資料 getQueryString()
        String queryString = req.getQueryString();
        System.out.println(queryString);

        //5.獲取統一資源識別符號 getRequestURI()    /request/servletDemo01   
        String requestURI = req.getRequestURI();
        System.out.println(requestURI);

        //6.獲取統一資源定位符 getRequestURL()    http://localhost:8080/request/servletDemo01  
        StringBuffer requestURL = req.getRequestURL();
        System.out.println(requestURL);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

常用方法二:獲取請求引數以及封裝(非常重要)

我們常常會使用HttpServletRequest物件獲取請求引數,然後將其封裝到實體類中
image

/*
    獲取請求引數資訊的相關方法
 */
@WebServlet("/servletDemo03")
public class ServletDemo03 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.根據名稱獲取資料   getParameter()
        String username = req.getParameter("username");
        System.out.println(username);
        String password = req.getParameter("password");
        System.out.println(password);
        System.out.println("--------------------");

        //2.根據名稱獲取所有資料 getParameterValues()
        String[] hobbies = req.getParameterValues("hobby");
        for(String hobby : hobbies) {
            System.out.println(hobby);
        }
        System.out.println("--------------------");

        //3.獲取所有名稱  getParameterNames()
        Enumeration<String> names = req.getParameterNames();
        while(names.hasMoreElements()) {
            String name = names.nextElement();
            System.out.println(name);
        }
        System.out.println("--------------------");

        //4.獲取所有引數的鍵值對 getParameterMap()
        Map<String, String[]> map = req.getParameterMap();
        for(String key : map.keySet()) {
            String[] values = map.get(key);
            System.out.print(key + ":");
            for(String value : values) {
                System.out.print(value + " ");
            }
            System.out.println();
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

下面通過例項說明幾種封裝方式

需求:我們要實現從網頁填寫學生註冊資訊,然後把獲取請求引數並把相應的資訊封裝到每一個Student類中。

第一步:編寫一個頁面html程式
image


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>註冊頁面</title>
</head>
<body>
    <form action="/request/servletDemo08" method="post" autocomplete="off">
        姓名:<input type="text" name="username"> <br>
        密碼:<input type="password" name="password"> <br>
        愛好:<input type="checkbox" name="hobby" value="study">學習
              <input type="checkbox" name="hobby" value="game">遊戲 <br>
        <button type="submit">註冊</button>
    </form>
</body>
</html>

第二步:編寫Student的javabean類,注意其資料成員最好(必須)與html檔案表單的name屬性一致

public class Student {
    private String username;
    private String password;
    private String[] hobby;

    public Student() {
    }

    public Student(String username, String password, String[] hobby) {
        this.username = username;
        this.password = password;
        this.hobby = hobby;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String[] getHobby() {
        return hobby;
    }

    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }

    @Override
    public String toString() {
        return "Student{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }
}

第三步:獲取引數資訊,並封裝資料

法一:直接手動封裝(簡單粗暴)

/*
    封裝物件-手動方式
 */
@WebServlet("/servletDemo04")
public class ServletDemo04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.獲取所有的資料
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String[] hobbies = req.getParameterValues("hobby");

        //2.封裝學生物件
        Student stu = new Student(username,password,hobbies);

        //3.輸出物件
        System.out.println(stu);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

法二:通過反射封裝
PropertyDescriptor: Describes a Java Bean property hosting validation constraints
呼叫javabean類的有參建構函式建立物件
建構函式 PropertyDescriptor(String,class); 注意第一個引數是javabean建構函式的第一個形式引數,第二個引數是已經建立的實類的位元組碼。

/*
    封裝物件-反射方式
 */
@WebServlet("/servletDemo05")
public class ServletDemo05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.獲取所有的資料
        Map<String, String[]> map = req.getParameterMap();

        //2.封裝學生物件
        Student stu = new Student();
        //2.1遍歷集合
        for(String name : map.keySet()) {
            String[] value = map.get(name);
            try {
                //2.2獲取Student物件的屬性描述器
                PropertyDescriptor pd = new PropertyDescriptor(name,stu.getClass());
                //2.3獲取對應的setXxx方法
                Method writeMethod = pd.getWriteMethod();
                //2.4執行方法
                if(value.length > 1) {
                    writeMethod.invoke(stu,(Object)value);
                }else {
                    writeMethod.invoke(stu,value);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //3.輸出物件
        System.out.println(stu);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

法三:BeanUtils工具類封裝
BeanUtils.populate(stu,map);(實類,引數map)

/*
    封裝物件-工具類方式
 */
@WebServlet("/servletDemo06")
public class ServletDemo06 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.獲取所有的資料
        Map<String, String[]> map = req.getParameterMap();

        //2.封裝學生物件
        Student stu = new Student();
        try {
            BeanUtils.populate(stu,map);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //3.輸出物件
        System.out.println(stu);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

用流的形式讀取請求資訊

/*
    流物件獲取資料
 */
@WebServlet("/servletDemo07")
public class ServletDemo07 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //字元流(必須是post方式)
        /*BufferedReader br = req.getReader();
        String line;
        while((line = br.readLine()) != null) {
            System.out.println(line);
        }*/
        //br.close();

        //位元組流
        ServletInputStream is = req.getInputStream();
        byte[] arr = new byte[1024];
        int len;
        while((len = is.read(arr)) != -1) {
            System.out.println(new String(arr,0,len));
        }
        //is.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

請求正文中中文編碼問題

1.POST方式請求

問題:獲取請求正文,會有亂碼問題。是在獲取的時候就已經亂碼了。

解決:是request物件的編碼出問題了。設定request物件的字符集
request.setCharacterEncoding("編碼方式")它只能解決POST的請求方式,GET方式解決不了

/*
    中文亂碼
 */
@WebServlet("/servletDemo08")
public class ServletDemo08 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //設定編碼格式
        req.setCharacterEncoding("UTF-8");
        String username = req.getParameter("username");
        System.out.println(username);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

2.GET方式請求

問題:GET方式:正文在位址列username=%D5%C5%C8%FD%D5%C5%C8%FD是已經被編過一次碼了

GET方式請求的正文是在位址列中,在Tomcat8.5版本及以後,Tomcat伺服器已經幫我們解決了,所以不會有亂碼問題了。

而如果我們使用的不是Tomcat伺服器,或者Tomcat的版本是8.5以前,那麼GET方式仍然會有亂碼問題,解決方式如下:

使用正確的碼錶對已經編過碼的資料進行解碼。就是把取出的內容轉成一個位元組陣列,但是要使用正確的碼錶。(ISO-8859-1)再使用正確的碼錶進行編碼,把位元組陣列再轉成一個字串,需要使用正確的碼錶,是看瀏覽器當時用的是什麼碼錶。

/**
 * 在Servlet的doGet方法中新增如下程式碼
 */
public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String username = request.getParameter("username");
        byte[] by = username.getBytes("ISO-8859-1");
        username = new String(by,"GBK");

        //輸出到瀏覽器:注意響應的亂碼問題已經解決了
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.write(username);
}

public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    doGet(request, response);
}

請求轉發(與重定向的區別)

重定向特點:兩次請求,瀏覽器行為,位址列改變,請求域中的資料會丟失

請求轉發:一次請求,伺服器行為,位址列不變,請求域中的資料不丟失

請求域的作用範圍:當前請求(一次請求),和當前請求的轉發之中

請求傳送方:

/*
    請求轉發
 */
@WebServlet("/servletDemo09")
public class ServletDemo09 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //設定共享資料
        req.setAttribute("encoding","gbk");

        //獲取請求排程物件
        RequestDispatcher rd = req.getRequestDispatcher("/servletDemo10");
        //實現轉發功能
        rd.forward(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

請求接收方:

/*
    請求轉發
 */
@WebServlet("/servletDemo10")
public class ServletDemo10 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //獲取共享資料
        Object encoding = req.getAttribute("encoding");
        System.out.println(encoding);

        System.out.println("servletDemo10執行了...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

開啟伺服器後進入/servletDemo09之後會在控制檯輸出

encoding
servletDemo10執行了...

而此時瀏覽器的url依然是/servletDemo09,不會跳轉

請求重定向

resp.sendRedirect(req.getContextPath() + "/servletDemo07");

請求傳送方:

/*
    請求重定向
 */
@WebServlet("/servletDemo06")
public class ServletDemo06 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //設定請求域資料
        req.setAttribute("username","zhangsan");

        //設定重定向
        resp.sendRedirect(req.getContextPath() + "/servletDemo07");
		
		// resp.sendRedirect("https://www.baidu.com");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

請求接收方:

/*
    請求重定向
 */
@WebServlet("/servletDemo07")
public class ServletDemo07 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servletDemo07執行了...");
        Object username = req.getAttribute("username");
        System.out.println(username);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

請求包含

需求:把兩個Servlet的內容合併到一起來響應瀏覽器
問題:HTTP協議的特點是一請求,一響應的方式。所以絕對不可能出現有兩個Servlet同時響應方式。
解決:把兩個Servlet的響應內容合併輸出。

/*
    請求包含
 */
@WebServlet("/servletDemo11")
public class ServletDemo11 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servletDemo11執行了...");

        //獲取請求排程物件
        RequestDispatcher rd = req.getRequestDispatcher("/servletDemo12");
        //實現包含功能
        rd.include(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}
/*
    請求包含
 */
@WebServlet("/servletDemo12")
public class ServletDemo12 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("servletDemo12執行了...");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

控制檯輸出
servletDemo11執行了...
servletDemo12執行了...
而且瀏覽器的url依然是/servletDemo11,不會跳轉

細節

請求轉發的注意事項:負責轉發的Servlet,轉發前後的響應正文丟失,由轉發目的地來響應瀏覽器。

請求包含的注意事項:被包含者的響應訊息頭丟失。因為它被包含起來了。

響應

響應物件

關於響應

伺服器端收到請求,同時也已經處理完成,把處理的結果告知使用者。
在B/S架構中,響應就是把結果帶回瀏覽器。

常用響應物件

協議無關的物件標準是:ServletResponse介面

協議相關的物件標準是:HttpServletResponse介面

image

常用方法介紹

image

常用狀態碼:

狀態碼 說明
200 執行成功
302 它和307一樣,都是用於重定向的狀態碼。只是307目前已不再使用
304 請求資源未改變,使用快取。
400 請求錯誤。最常見的就是請求引數有問題
404 請求資源未找到
405 請求方式不被支援
500 伺服器執行內部錯誤

狀態碼首位含義:

狀態碼 說明
1xx 訊息
2xx 成功
3xx 重定向
4xx 客戶端錯誤
5xx 伺服器錯誤

響應物件的使用示例

位元組流輸出中文問題

專案中常用的編碼格式是u8,而瀏覽器預設使用的編碼是gbk。導致亂碼!

解決方式一:修改瀏覽器的編碼格式(不推薦,不能讓使用者做修改的動作)
解決方式二:通過輸出流寫出一個標籤:response.getOutputStream().write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'>")
解決方式三:response.setHeader("Content-Type","text/html;charset=UTF-8"); 指定響應頭資訊
解決方式四:response.setContentType("text/html;charset=UTF-8"); (常用)

/*
    位元組流響應訊息及亂碼的解決
 */
@WebServlet("/servletDemo01")
public class ServletDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String str = "你好";

        resp.setContentType("text/html;charset=UTF-8");

        sos.write(str.getBytes("UTF-8"));
        sos.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

字元流輸出中文問題

/*
    字元流響應訊息及亂碼的解決
 */
@WebServlet("/servletDemo02")
public class ServletDemo02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String str = "你好";

        //解決中文亂碼
        resp.setContentType("text/html;charset=UTF-8");

        //獲取字元流物件
        PrintWriter pw = resp.getWriter();
        //pw.println(str);
        pw.write(str);
        pw.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

響應圖片到瀏覽器

/*
    響應圖片到瀏覽器
 */
@WebServlet("/servletDemo03")
public class ServletDemo03 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通過檔案的相對路徑來獲取檔案的絕對路徑
        String realPath = getServletContext().getRealPath("/img/hm.png");
        System.out.println(realPath);
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(realPath));

        //獲取位元組輸出流物件
        ServletOutputStream sos = resp.getOutputStream();

        //迴圈讀寫
        byte[] arr = new byte[1024];
        int len;
        while((len = bis.read(arr)) != -1) {
            sos.write(arr,0,len);
        }

        bis.close();
        sos.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

控制快取

resp.setDateHeader("Expires",(System.currentTimeMillis()+時間));

/*
    快取
 */
@WebServlet("/servletDemo04")
public class ServletDemo04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String news = "這是一條很火爆的新聞~~";

        //設定快取時間
        resp.setDateHeader("Expires",(System.currentTimeMillis()+1*60*60*1000L));

        //設定編碼格式
        resp.setContentType("text/html;charset=UTF-8");
        //寫出資料
        resp.getWriter().write(news);
        System.out.println("aaa");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

定時重新整理

resp.setHeader("Refresh","定時時間(秒);URL=/虛擬路徑/頁面路徑");

/*
    定時重新整理
 */
@WebServlet("/servletDemo05")
public class ServletDemo05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String news = "您的使用者名稱或密碼錯誤,3秒後自動跳轉到登入頁面...";

        //設定編碼格式
        resp.setContentType("text/html;charset=UTF-8");
        //寫出資料
        resp.getWriter().write(news);

        //設定響應訊息頭定時重新整理
        resp.setHeader("Refresh","3;URL=/虛擬路徑/login.html");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}

檔案下載

/*
    檔案下載
 */
@WebServlet("/servletDemo08")
public class ServletDemo08 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.建立位元組輸入流,關聯讀取的檔案
        //獲取檔案的絕對路徑
        String realPath = getServletContext().getRealPath("/img/hm.png");
        //建立位元組輸出流物件
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(realPath));

        //2.設定響應頭支援的型別  應用支援的型別為位元組流
        /*
            Content-Type 訊息頭名稱   支援的型別
            application/octet-stream   訊息頭引數  應用型別為位元組流
         */
        resp.setHeader("Content-Type","application/octet-stream");

        //3.設定響應頭以下載方式開啟  以附件形式處理內容
        /*
            Content-Disposition  訊息頭名稱  處理的形式
            attachment;filename=  訊息頭引數  附件形式進行處理
         */
        resp.setHeader("Content-Disposition","attachment;filename=" + System.currentTimeMillis() + ".png");

        //4.獲取位元組輸出流物件
        ServletOutputStream sos = resp.getOutputStream();

        //5.迴圈讀寫檔案
        byte[] arr = new byte[1024];
        int len;
        while((len = bis.read(arr)) != -1) {
            sos.write(arr,0,len);
        }

        //6.釋放資源
        bis.close();
        sos.close();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req,resp);
    }
}