Http 请求和 ServletRequest 对象

Http 请求和 ServletRequest 对象

HTTP 协议

  • HTTP 即 Hyper Text Transfer Protocol,超文本传输协议,该协议定义了客户端和服务器端通信时发送数据的格式
  • HTTP是基于 TCP/IP 的高级协议,基于请求/响应模型即一次请求对应一次响应,而且是无状态的,也就是指每次请求之间相互独立,不能交互数据,HTTP 协议默认端口号为 80。
  • HTTP 1.0 版本中,每一次请求响应都会建立新的连接,1.1 版本中使用复用连接机制

HTTP 请求

HTTP 请求消息包括四部分:请求行、请求头、请求空行、请求体

请求行

  • Request Method:请求方式,HTTP 协议有 7 种请求方式,常用的有 2 种即 GETPOST
    • GET 方式特点是请求参数在请求行中,拼接在 url 后,参数会在浏览器地址栏显示,不太安全,而且请求的 url 长度有限制,所以无法传输大文件
    • POST 方式特点是请求参数在请求体中,浏览器地址栏不会显示参数,相对安全,而且请求的 url 长度没有限制
  • Request URL:请求 url,/ServletDemo/demo1
  • 请求协议和版本:例如 HTTP/1.1

请求头

请求头中封装着客户端浏览器传递给服务器的一些信息,使用键值对存储,格式为 请求头名称:请求头值,常见的请求头如下

请求头名称作用
Host请求目标(服务器端)的 IP 地址和端口号,和请求行中的URL可以组成完整的所请求资源的地址
Connection是否持久连接,该值为 keep-alive 表示持久连接(复用)
User-Agent告知服务器,访问使用的浏览器版本,可以用于解决兼容性问题
Referer告知浏览器,此次请求是从哪个网页 URL 获得点击当前请求中的网址,可以用于防盗链和统计工作
Accept告知服务器,浏览器接受什么样的介质,例如 text/html,application/xhtml+xml,application/xml
Accept-Language告知服务器,浏览器接受什么样的语言,例如 zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2

请求空行

就是一个空行,用于分隔请求头和请求体

请求体

POST 请求才会有的部分,用于封装 POST 请求消息的请求参数

ServletRequest

@Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {

    }

可以看到,要实现的 servic() 方法有两个参数,即 ServletRequest 对象和 ServletResponse 对象,ServletRequest 对象用于获取请求消息,ServletResponse 对象用于设置相应消息,这两个类的对象实例都由服务器进行创建,程序员使用即可。

ServletRequest 的继承体系

ServletRequest继承体系

实际开发中,我们自己编写的 Servlet 类一般继承自 HttpServlet 类,此时需要实现 doPost()doGet() 方法,此时的参数为 HttpServletRequest 接口的对象,利用多态,真正执行的 HttpServletRequest 对象的方法实际上是 org.apache.catalina.connector.RequestFacade 类的方法。

ServletRequest 获取请求行信息

  1. 获取请求方式(GETPOST

    	String getMethod()
    
  2. 获取虚拟目录

    	String getContextPath()
    
  3. 获取 Servlet 路径

    	String getServletPath()
    
  4. 获取请求参数(GET 方式才有值,POST 方式返回 null

    	String getQueryString()
    
  5. 获取请求 URI,可以用于权限控制
    URI:统一资源标识符,例如 /ServletRequest/demo1
    URL:统一资源定位符,例如 http://localhost/ServletRequest/demo1

    	String getRequestURI()
    	StringBuffer getRequestURL()
    
  6. 获取协议及版本

    	String getProtocol()
    
  7. 获取客户机的 IP 地址

    	String getRemoteAddr()
    

测试

@WebServlet(urlPatterns = {"/demo1"})
public class RequestDemo1 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println(request.getMethod());
        System.out.println(request.getContextPath());
        System.out.println(request.getServletPath());
        System.out.println(request.getQueryString());
        System.out.println(request.getRequestURI());
        System.out.println(request.getRequestURL());
        System.out.println(request.getProtocol());
        System.out.println(request.getRemoteAddr());

    }

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

ServletRequest 为虚拟目录启动项目,在浏览器地址栏输入 http://localhost:8080/ServletRequest/demo1?username=cool&password=yzt,直接访问该 Servlet 资源,控制台打印如下内容

GET
/ServletRequest
/demo1
username=cool&password=yzt
/ServletRequest/demo1
http://localhost:8080/ServletRequest/demo1
HTTP/1.1
0:0:0:0:0:0:0:1

ServletRequest 获取请求行信息

  1. 通过请求头的名称获取请求头的值

    	String getHeader(String name)
    
  2. 获取所有的请求头名称

    	Enumeration<String> getHeaderNames()
    

    虽然 Enumeration 翻译为枚举,但是其作用类似集合框架中的 Iterator 迭代器,它有两个方法

    	boolean hasMoreElements(); // 是否还有下一元素
    	E nextElement(); // 获取下一元素,并将指针下移
    

测试

获取所有的请求头名称

Enumeration<String> enumeration = request.getHeaderNames();
while(enumeration.hasMoreElements()) {
	System.out.println(enumeration.nextElement());
}

通过请求头的名称获取一些重要的请求头的值

System.out.println(request.getHeader("referer")); // 获取客户机从哪来,如果是直接输入url跳转,则值为 null
System.out.println(request.getHeader("user-agent")); // 获取客户机的浏览器版本
System.out.println(request.getHeader("host")); // 获取请求的资源的ip和域名

控制台输出

null
Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
localhost:8080

ServletRequest 获取请求体信息

只有 POST 请求方式才有请求体,请求体中封装了 POST 请求的请求参数,请求参数可以很大,比如上传文件。获取请求体信息,需要先从 ServletRequest 对象获取流对象,流对象可以是字符输入流(操作字符数据)或者字节输入流(针对所有类型的数据),然后再从流对象中获取数据,API 如下

BufferedReader getReader(); //获取字符输入流
ServletInputStream getInputStream(); //获取字节输入流

测试
为了完成获取请求体的测试,先编写一个简单的表单页面,将表单的提交地址设置为目标 Servlet,注意表单的 action 值必须带着项目的虚拟目录,如果项目的虚拟目录是 /,则可以直接填写 Servlet 的路径

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>form</title>
</head>
<body>
<form action="/ServletRequest/demo2" method="post">
    <input type="text" name="username" placeholder="请输入用户名"> <br>
    <input type="password" name="password" placeholder="请输入密码"> <br>
    <input type="submit" name="button" value="提交">
</form>
</body>
</html>

编写相应的 Servlet

package cool.yzt.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * @Author: yzt
 * @Description: 获取 POST 请求的请求体
 */
@WebServlet(urlPatterns = {"/demo2"})
public class RequestDemo2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        BufferedReader br = request.getReader();
        String str = null;
        // BufferedReader 有 readLine() 方法,可以一次读取一行
        while((str=br.readLine())!=null) {
            System.out.println(str); // 打印读取到的 POST 参数
        }

    }

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

启动项目,在表单页面,填入用户名和密码为 yzt.cool123,提交表单,首先,浏览器页面跳转至 http://localhost:8080/ServletRequest/demo2,其次,控制台打印如下内容

username=yzt.cool&password=123&button=%E6%8F%90%E4%BA%A4

获取请求参数通用方式

可以看到,用以上方法获取参数,较为麻烦,需要分为 GET 请求和 POST 请求两种情况。ServletRequest 提供了一种通用的获取参数的方式,无论是哪种请求方式,都可以通过以下方法获取请求参数。

  1. 根据参数名称获取参数值

    	String getParameter(String name)
    
  2. 根据参数名称获取参数值的数组,例如表单的复选框,一个参数名称可能包含数个参数值

    	String[] getParameterValues(String name)
    
  3. 获取所有请求的参数名称

    	Enumeration<String> getParameterNames()
    
  4. 取所有参数的 Map 集合,参数名与参数值作为键和值

    	Map<String,String[]> getParameterMap()
    

注意 中文乱码问题
* GET 方式:Tomcat8 已经将 GET 方式乱码问题解决了
* POST 方式:会出现乱码,需要在在获取参数前手动设置 ServletRequest 的编码,例如

request.setCharacterEncoding("utf-8"); // 设置 request 的编码为 utrf-8

测试
依旧是上面的表单案例,这次使用通用方式获取 POST 参数

package cool.yzt.web;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;

/**
 * @Author: yzt
 * @Description: 获取 POST 请求的请求体
 */
@WebServlet(urlPatterns = {"/demo2"})
public class RequestDemo2 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 设置编码方式,防止中文参数值乱码
        request.setCharacterEncoding("utf-8");

        // 参数中的字符串对应页面表单中表单项的 name 属性值
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        System.out.println(username);
        System.out.println(password);
    }

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

请求转发与数据共享

请求转发

请求转发是一种在服务器内部的资源跳转方式,当发生请求转发时,客户端浏览器的地址栏中路径不会发生变化,但是请求只能转发至服务器的内部资源,而且转发的请求是同一次请求

  1. 通过 ServletRequest 对象获取请求转发器对象 RequestDispatcher

    	getRequestDispatcher(String path) // path 为转发目标资源的路径
    
  2. 使用 RequestDispatcher 对象完成转发

    	forward(ServletRequest request, ServletResponse response) 
    

域对象
域对象是指一个有作用范围的对象,在该范围内可以使用域对象共享数据

Request 域
Request 域是指一次请求的范围,可以用于请求转发中的多个资源进行共享数据,主要 API 如下

void setAttribute(String name,Object obj) // 以键值对的形式存储数据,name 为键,obj 为在 Request 域中共享的对象
Object getAttitude(String name) // 通过键获取值(共享的对象)
void removeAttribute(String name) // 通过键移除键值对

请求转发

测试
编写两个 Servlet,省去了 packageimport 语句

@WebServlet(urlPatterns = {"/demo3"})
public class RequestDemo3 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理部分请求
        System.out.println("demo3 被访问了");

        // 存储数据到 Request 域中,用于共享
        String msg = "demo3";
        request.setAttribute("msg",msg);

        // 请求转发到 /demo4 继续处理
        request.getRequestDispatcher("/demo4").forward(request,response);
    }

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

@WebServlet(urlPatterns = {"/demo4"})
public class RequestDemo4 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理请求
        System.out.println("demo4 被访问了");

        // 获取 Request 域中的共享对象数据
        System.out.println("收到来自 " + request.getAttribute("msg") +" 的信息");

    }

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

启动项目,访问 http://localhost:8080/ServletRequest/demo3,控制台打印以下消息,但是浏览器地址栏并没有发生变化

demo3 被访问了
demo4 被访问了
收到来自 demo3 的信息

参考

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://yzt.cool/archives/http请求和servletrequest对象