HTTP协议请求(request)(下)

255 阅读13分钟

第二章 request&请求数据获取

1、什么是HttpServletRequest

HttpServletRequest接口的对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求中的所有信息都封装在HttpServletRequest接口的对象中,开发人员通过这个对象的方法,可以获得客户端信息。

2、HttpServletRequest的作用

通过HttpServletRequest接口的对象进行的常用操作:

1. 获取请求行信息:请求方式,url,ip地址和HTTP版本。
2. 获取请求头信息:浏览器类型,ip地址等。
3. 获取请求参数或者请求体中的数据:url后面拼接的参数或者请求体中提交的参数;

2.1 获取请求行信息

请求行主要包括:**请求方式****url****协议/版本** 。HttpServletRequest接口的对象提供了以下方法来获取这些信息:
方法说明
String getMethod()获取请求方式的类型 字母是大写的
StringBuffer getRequestURL()获取客户端发出请求完整URL
String getRemoteAddr()获取IP地址
String getProtocol()获取当前协议的名称和版本

说明:对于上述方法StringBuffer getRequestURL() 的返回值是StringBuffer ,它也表示字符串缓冲区,用法和StringBuilder一样,只是StringBuffer 是多线程安全的,效率低。StringBuilder是多线程不安全的,效率高。

【案例】获取请求行信息

【java代码】

@WebServlet("/rowServlet")
public class RowServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //进行请求行API的学习
        //获取请求行的请求方式
        String method = request.getMethod();
        System.out.println("method = " + method);
        //获取请求行的请求URL
        StringBuffer requestURL = request.getRequestURL();
        System.out.println("requestURL = " + requestURL);
        //获取请求行的请求IP
        String remoteAddr = request.getRemoteAddr();
        System.out.println("remoteAddr = " + remoteAddr);
        //获取请求行的协议版本号
        String protocol = request.getProtocol();
        System.out.println("protocol = " + protocol);
    }
}
【结果】

启动服务器,访问Servlet得到结果如下:

method = GET
requestURL = http://localhost:8080/rowServlet
remoteAddr = 0:0:0:0:0:0:0:1
protocol = HTTP/1.1

【注意事项】

  1. 如果浏览器地址栏中的地址为:http://localhost:8080/rowServlet ,获取的ip地址为:0:0:0:0:0:0:0:1。

    0:0:0:0:0:0:0:1是ipv6的表现形式,对应ipv4来说相当于127.0.0.1,也就是本机。导致上述的原因是客户端和服务器在同一台电脑上。如果不想出现ipv6版本的ip地址格式,在访问网址的时候可以书写127.0.0.1。

    或者在访问servlet的时候,输入具体的ip地址,假设我的电脑ip地址是:192.168.23.10

    访问servlet的时候这样输入:http://192.168.23.10:8080/rowServlet

    那么这里获取的ip地址:remoteAddr = 192.168.23.10

通过上述案例,我们可以发现,如果开发中服务器需要获取浏览器发送的行信息,使用对应的api 可以轻松获取!

2.2 获取请求头信息

浏览器的请求头信息是由很多:关键字:值 即key:value 形式的数据组成的。HttpServletRequest对象给我们提供了两个方法用来获取请求的头信息。

方法作用
String getHeader(String name)根据请求头的key关键字获取请求头信息
Enumeration getHeaderNames()返回此请求包含的所有头信息的key,属于对枚举类型进行遍历

说明: 枚举属于一种特殊的数据类型,类似于集合,对他遍历也是类似于集合遍历方式。Enumeration类似于Iterator的迭代器。只是Iterator来迭代1.2之后的集合。而Enumeration用来迭代1.2之前集合和枚举类型的。

【说明】请求头的key关键字如下表所示:

请求头key请求头value
referer浏览器通知服务器,当前请求来自何处,如果是直接访问,则不会有这个头。
If-modified-Since浏览器通知服务器,本地缓存的最后变更时间。与另一个响应头组合控制浏览器页面的缓存。
cookie与会话有关技术,用于存放浏览器缓存的cookie信息。
user‐agent浏览器通知服务器,客户端浏览器与操作系统相关信息
connection保持连接状态。Keep-Alive 连接中,close 已关闭
host请求的服务器主机名
content-length请求体的长度
content-type如果是POST请求,会有这个头,默认值为application/x-www-form-urlencoded,表示请求体内容使用url编码
accept浏览器可支持的MIME类型。文件类型的一种描述方式。
mime格式浏览器请求数据的类型,例如: text/html ,html文件 text/css,css文件 text/javascript,js文件 image/*,所有图片文件
accept-encoding浏览器通知服务器,浏览器支持的数据压缩格式。如:GZIP压缩
accept-language浏览器通知服务器,浏览器支持的语言。各国语言(国际化i18n)
.........

【代码演示】:编写一个Servlet类来演示获取请求头信息:user-agent

@WebServlet("/headerServlet")
public class HeaderServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //请求头学习 
        //获取所有请求key信息
        Enumeration<String> headerNames = request.getHeaderNames();
        //取出每个key
        while (headerNames.hasMoreElements()){
            System.out.println(headerNames.nextElement());
        } 
        //获取user-agent 对应value值
        String header = request.getHeader("user-agent");
        System.out.println("header = " + header);
    }
}

【运行结果】

host
connection
upgrade-insecure-requests
user-agent
accept
accept-encoding
accept-language
cookie
header = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

2.3 获取请求体(请求数据)【重要】

学习完了对请求行和请求头的内容,最后一部分就是请求体了,在请求体中,包含的是用户通过浏览器发送的请求参数,因此,我们主要学习的就是获取请求参数的方法。

首先,我们来观察一下浏览器中的请求数据是怎么提交的:

  1. get请求:get请求提交的数据是拼接在url后面的,如下图所示:

image.png

2.post请求:post请求的数据是在请求体中发送到后台的:

【请求体】 username: lisi password: 1234

通过对比,发现数据传递的格式都是:name=value

(1)获取请求参数的方法
方法名描述
String getParameter(String name)getParameter获得指定参数名对应的值。如果没有返回null,如果值有多个获得第一个。 例如:username=jack。注意:参数name是form表单中的name属性值。
String[] getParameterValues(name)getParameterValues[] 获取请求数据key相同的多个数据.一般获取的是复选框。例如爱好选项。
Map<String,String[]> request.getParameterMap();获得表单中所有的数据,key表示input标签name的属性值:,value是一个数组,表示input输入框的值

image.png

(2)案例:获取请求参数练习

1、编写用户表单user.html,提供表单字段:username、password、hobby、education,以post方式提交。

 <form action="/requestServlet" method="post">
        用户名:<input type="text" name="username"/><br/>
        密码:  <input type="text" name="password"/><br/>
        爱好:  <input type="checkbox" name="hobby" value="basketball"/> 篮球
               <input type="checkbox" name="hobby" value="football"/> 足球
               <input type="checkbox" name="hobby" value="film"/> 电影 <br/>
        学历:<select name="education">
                <option value="gz">高中</option>
                <option value="dz">大专</option>
                <option value="bk">本科</option>
             </select><br>
        <input type="submit" value="post提交"/>
 </form>

2、编写Servlet接受用户表单发送的数据.

@WebServlet("/requestServlet")
public class RequestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //非常重要API  请求参数/请求体API
        //获取用户名
        String username = request.getParameter("username");
        System.out.println("username = " + username);
        String password = request.getParameter("password");
        System.out.println("password = " + password);
        //请求爱好
        String[] hobbies = request.getParameterValues("hobby");
        String s = Arrays.toString(hobbies);
        System.out.println("s = " + s);
​
         //获取学历
        String education = request.getParameter("education");
        System.out.println("education = " + education);
​
        //获取表单所有信息
        Map<String, String[]> parameterMap = request.getParameterMap();
        Set<String> strings1 = parameterMap.keySet();
        System.out.println("strings1 = " + strings1);
        //获取用户输入所有值
       Collection<String[]> values = parameterMap.values();
        for (String[] strings : values) {
            System.out.println(strings.length);
            System.out.println("strings = " + Arrays.toString(strings));
        }
    }
}
​

说明:

对于 Map<String, String[]> parameterMap = request.getParameterMap(); 的返回值是一个Map集合,用来获取用户输入的所有值。

Map集合的key:表单的name属性值。

Map集合的value:以String类型的数组形式存储表单提交的每一项的值。

【运行结果】

image.png

image.png

(3) BeanUtils

BeanUtils 是 Apache commons组件的成员之一,主要用于简化JavaBean封装数据的操作。它可以给JavaBean封装一个字符串数据,也可以将一个表单提交的所有数据封装到JavaBean中。

要用BeanUtils 表单项的name属性值 和 javaBean的属性名一致即可

① 导入jar包

image.png

② 使用工具类封装数据
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/request/ParamServlet" method="get">
        用户 <input type="text" name="username" > <br>
        密码 <input type="text" name="password" > <br>
        性别
        <input type="radio" name="gender" value="male"> 男
        <input type="radio" name="gender" value="female"><br>
        爱好
        <input type="checkbox" name="hobby" value="smoke"> 抽烟
        <input type="checkbox" name="hobby" value="drink"> 喝酒
        <input type="checkbox" name="hobby" value="perm"> 烫头 <br>
        <input type="submit">
    </form>
</body>
</html>
@WebServlet("/ParamServlet")
public class ParamServlet2 extends HttpServlet {
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
​
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
​
        request.setCharacterEncoding("utf-8");
        String username = request.getParameter("username"); // 一个name对应一个value
        String password = request.getParameter("password");
        String gender = request.getParameter("gender");
        String[] hobbies = request.getParameterValues("hobby"); // 一个name对应多个value
​
        //封装到数据到对象中(JavaBean)
        /*User user = new User();
        user.setUsername(username);
        user.setPassword(password);
        user.setGender(gender);
        user.setHobby(hobbies);
        System.out.println(user);*/
​
        /*
        *  问题: 只有4个属性,封装比较简单, 如果有40个,还那么些很冗余
        *
        *  解决: BeanUtils (apache出品工具包)
        *       大大简化封装javaBean数据的代码
        *
        *  使用: 1. BeanUtils.jar 放入 web-inf/lib/xx.jar
        *           (两个jar包)
        *       2. 编写代码
        *       BeanUtils.populate(bean,map)
        *           将map中数据的映射到bean中
        *
        *  方法底层原理: 反射(内省)
        *       BeanUtils.populate(user,map);
        *       1. 遍历这个map
        *               key     value
        *        举例:  username  李四
        *
        *       2. 方法反射
        *           Class clazz = user.getClass();  // User.class 对象
        *               Method 指定的方法  = clazz.getMethod(方法名,方法参数列表对应Class对象);
        *               method.invoke(调用此方法的对象,实参);
        *
        *               // setUsername  根据map中遍历出来的key 推导来的
        *          Method method = clazz.getMethod("setUsername",String.class); //获取User类里的setUsername方法对象
         *              //李四 就是 map中遍历出来的key对应的value
          *         method.invoke(user,"李四") // user.setUserName("李四")
        *     总结:
        *       javaBean 的 setXXX 的 xxx 跟Map的key一致 !!!
        *           1. map中的key跟表单项的name属性值 一致
        *           2. setXXX 习惯于 本类的 属性名一致
        *
        *       最终结论:  表单的name 跟 javaBean的属性名一致即可
        *
        * */
        User user = new User(); //空的对象
        Map<String, String[]> map = request.getParameterMap();//获取所有请求参数
​
        try {
            BeanUtils.populate(user,map);
            System.out.println(user);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
​
    }
​
}

javaBean

/*
*   JavaBean: java标准类 (三个必须规范)
*   1. public 空参构造
*   2. private 属性
*   2. public get set 方法
*
*
* */
public class User {
    private String username;
    private String password;
    private String gender;
    private String[] hobby;
    // 一个类若不显式声明任何构造, 默认一个public空参构造(隐式声明)
​
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + ''' +
                ", password='" + password + ''' +
                ", gender='" + gender + ''' +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }
​
    public String getUsername() {
        return username;
    }
​
    public void setUsername(String username) {
        System.out.println("setUsername被调用了");
        this.username = username;
    }
​
    public String getPassword() {
        return password;
    }
​
    public void setPassword(String password) {
        this.password = password;
    }
​
    public String getGender() {
        return gender;
    }
​
    public void setGender(String gender) {
        this.gender = gender;
    }
​
    public String[] getHobby() {
        return hobby;
    }
​
    public void setHobby(String[] hobby) {
        this.hobby = hobby;
    }
}

第三章 request作用域

1、request的生命周期

request对象生命周期如下图所示:

request生命周期.bmp

【一次请求和响应的完整流程】
1、浏览器向servlet发送请求
2、tomcat收到请求后,创建RequestResponse两个对象,并将请求数据封装到request对象中,然后传递给Servlet
3、Servlet接收到请求后,调用doget或者dopost方法。处理浏览器的请求信息,然后通过Response返回tomcat服务器信息
4、tomcat接收到返回的信息,返回给浏览器。
5、浏览器接收到返回消息后,tomcat销毁RequestResponse两个对象,同时销毁这两个对象所获得的信息。
  • 创建:浏览器给服务器发送请求后,tomcat创建request对象封装请求数据;
  • 销毁:服务器给浏览器响应信息结束后销毁;

特点:浏览器每次给服务器发送请求,服务器都会为这次请求创建一个新的request对象。

2 、request域对象(重要)

问:什么是域对象?

在web阶段需要对象来存储数据,获取数据和移除数据,就可以使用域对象来实现。域对象是一个容器,这种容器主要用于servlet与servlet之间的数据传输使用的 .

今天讲解的request域对象,就可以在一次请求中的多个servlet之间进行数据共享.

request域对象,一个存储数据的区域对象.是把request当成一个容器来存储数据,request域存储数据主要是用于在两个servlet之间传递数据。request作为域对象,常用的方法如下:

方法说明
void setAttribute(String name, Object o)往request域中设置值
Object getAttribute(String name)从request域中根据name取值,这里的name和setAttribute()方法中的第一个参数name一致
void removeAttribute(String name)从request域中移除值,这里的name和setAttribute()方法中的第一个参数name一致

以上三个方法都是操作request中域对象中的数据,与请求参数无关。

【案例】request域对象练习

需求:

1. 创建一个ScopeServlet类继承HttpServlet类;
2. 往request域中设置值;
3. 从request域中取值;
4. 将request域中的值移除;
@WebServlet("/scopeServlet")
public class ScopeServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用request域对象,进行存储值,删除值,获取值操作
​
        Student student = new Student("xiaozhou","1234");
​
        //向request存储值
        request.setAttribute("msg","柳岩");
        request.setAttribute("stu",student);
​
        //可以获取值
        String msg = (String) request.getAttribute("msg");
        System.out.println("msg = " + msg);
​
        //移除request中值
        request.removeAttribute("msg");
​
        Object msg1 = request.getAttribute("msg");
        if (null== msg1){
            System.out.println("没有获取到值");
        }
​
        Student student1 = (Student) request.getAttribute("stu");
        System.out.println("student1 = " + student1);
    }
}
​

实体类代码

package cn.itha.value05;
​
public class Student {
   private String name;
    private String password;
    public Student(String name, String password) {
        this.name = name;
        this.password = password;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", password='" + password + ''' +
                '}';
    }
}
​

在浏览器输入地址:http://localhost:8080/scopeServlet

效果图:

image.png

【注意】getParameter()方法和getAttribute()方法的区别

  1. getParameter()方法获取的是浏览器提交的数据(是表单提交的数据);
  2. getAttribute()方法获取的是request域中的数据(即获取的都是通过request.setAttribute()设置的值);

3、 请求转发(重要)

我们上面学习了域对象的一种request,主要是负责向request域对象中存储数据,然后以key作为键将数据取出。这种使用存储和获取数据的方式在开发中最为常见的就是请求转发。

请求转发:可以从某个servlet到某个html等静态资源,还可以去其他的servlet中。我们之前使用到的请求都是从页面发出,然后请求到Servlet。其实,在Servlet中。请求也可以从一个Servlet发起,然后请求到另一个Servlet或静态页面。这项技术叫做请求转发。

请求转发的描述可以参考如下图:

转发图解.bmp

请求转发需要借助以下两个方法实现:

方法说明
RequestDispatcher getRequestDispatcher(String path)获取请求转发器(该方法需要使用request对象调用)。参数:path指定指向资源的路径名的 String。如果该路径名是相对的,那么它必须是相对于当前 servlet 的。
void forward(ServletRequest request, ServletResponse response)将请求转发到另一个资源(Servlet)上。参数:request表示客户端对 servlet 发出的请求的 ServletRequest 对象 response 表示 servlet 向客户端返回的响应的 ServletResponse 对象。
请求转发练习

要求:

  1. 从一个Servlet转发到一个静态页面;
  2. 从一个Servlet转发到另一个Servlet;

【练习一】从一个Servlet转发到dispatcher.html

@WebServlet("/oneServlet")
public class OneServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
​
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("oneServlet...");
        //转发到静态资源
        request.getRequestDispatcher("/dispatcher.html").forward(request,response);
    }
}

dispatcher.html代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>转发到html静态资源</h1>
</body>
</html>

效果图:

image.png

回车

image.png

注意:从OneServlet转发到dispatcher.html页面之后,浏览器地址栏并没有发生变化。

【练习二】从一个TwoServlet转发到ThrServlet;

【TwoServlet】

@WebServlet("/twoServlet")
public class TwoServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用转发  去ThrServelt
        //System.out.println("twoServlet...");
        //引入request域对象,向request存储值
        request.setAttribute("flag","help me");
        request.getRequestDispatcher("/thrServlet").forward(request,response);
    }
}

【ThrServlet】

@WebServlet("/thrServlet")
public class ThrServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //System.out.println("thrServlet...");
        //获取request域对象中值
        String flag = (String) request.getAttribute("flag");
        System.out.println("flag = " + flag);
    }
}

效果图:

image.png

回车后,TwoServlet会转发到ThrServlet,我们可以从ThrServlet中获取域对象中的数据,查看控制台:

image.png

注意:从TwoServlet转发到ThrServlet之后,浏览器地址栏并没有发生变化。

【小结】转发
  1. 转发是服务器内部的跳转行为;
  2. 从一个Servlet转发到另一个资源(静态或动态),能够实现跳转,但是浏览器地址栏地址没有发生改变。因为对浏览器来说本质上就只有一次请求;
  3. 请求转发的时候,共享request域中的数据;
  4. 无论转发多少次,都是只有一次请求,同时会有一次响应;