Servlet2-request、respone、请求转发、重定向
request对象
作用:request对象中封存了当前请求的所有请求信息
Request 继承体系
1、Tomcat需要解析请求数据,封装为request对象,并且创建request对象传递到service方法中
2、使用request对象,查阅JavaEE API文档的HttpServletRequest接口
Request 获取请求数据
获取请求行数据
GET /request-demo/req1?username=zhangsan HTTP/1.1
String getMethod():获取请求方式: GET
String getContextPath():获取虚拟目录(项目访问路径): /request-demo
StringBuffer getRequestURL(): 获取URL(统一资源定位符):http://localhost:8080/request-demo/req1
String getRequestURI():获取URI(统一资源标识符): /request-demo/req1
String getQueryString():获取请求参数(GET方式): username=zhangsan&password=123
String getScheme()://获取协议
获取请求头数据
User-Agent: Mozilla/5.0 Chrome/91.0.4472.106
Enumeration<String> getHeaderNames():返回请求头的键名的枚举集合
1 public class RequestServlet extends HttpServlet { 2 @Override 3 protected void service(HttpServletRequest req, HttpServletResponse resp) 4 throws ServletException, IOException { 5 //获取请求头数据 6 //获取请求方式:/GET 7 String method = req.getMethod(); 8 System.out.println(method); 9 //获取虚拟目录(项目访问路径):/request-demo 10 String contextPath = req.getContextPath(); 11 System.out.println(contextPath); 12 //获取请求URL(统一资源定位符):http://localhost:8080/request-demo/req1 13 StringBuffer url=req.getRequestURL(); 14 System.out.println(url); 15 //获取URI(统一资源标识符): /request-demo/req1 16 String uri=req.getRequestURI(); 17 System.out.println(uri); 18 //获取请求参数(GET方式): username=zhangsan 19 String queryString = req.getQueryString(); 20 System.out.println(queryString); 21 //获取协议 22 String h=req.getScheme(); 23 System.out.println(h); 24 //获取请求行数据 25 //获取指定的请求行信息 26 String value=req.getHeader("aaa"); 27 System.out.println(value); 28 //获取所有的请求行的键的枚举 29 Enumeration e = req.getHeaderNames(); 30 while(e.hasMoreElements()){ 31 String name=(String) e.nextElement(); 32 String value2=req.getHeader(name); 33 System.out.println(name+":"+value2); 34 } 35 } 36 }
浏览器在发送GET请求的时候是没有请求体的,所以需要把请求方式变更为POST
username=superbaby&password=123
ServletInputStream getInputStream():获取字节输入流
BufferedReader getReader():获取字符输入流
1 @WebServlet("/req1") 2 public class RequestDemo1 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 5 } 6 @Override 7 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 8 //获取post 请求体:请求参数 9 //1. 获取字符输入流 10 BufferedReader br = req.getReader(); 11 //2. 读取数据 12 String line = br.readLine(); 13 System.out.println(line); 14 } 15 }
注:BufferedReader流是通过request对象来获取的,当请求完成后request对象就会被销毁,request对象被销毁后,BufferedReader流就会自动关闭,所以此处就不需要手动关闭流了。
获取请求参数的通用方式
请求参数获取方式:
GET方式:String getQueryString()
POST方式:BufferedReader getReader()
Request通用方式获取请求参数:
Map<String, String[ ]> getParameterMap():获取所有参数Map集合
String[ ] getParameterValues(String name) :根据名称获取参数值(数组)
String getParameter(String name):根据名称获取参数值(单个值)
Enumeration<String> getParameterNames():返回所有用户请求数据的枚举集合
注:如果要获取的请求数据不存在,不会报错,返回null。
1 @WebServlet("/req2") 2 public class RequestDemo2 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 5 //GET请求逻辑 6 //1. 获取所有参数的Map集合 7 Map<String, String[]> map = req.getParameterMap(); 8 for (String key : map.keySet()) { 9 // username:zhangsan lisi 10 System.out.print(key+":"); 11 //获取值 12 String[] values = map.get(key); 13 for (String value : values) { 14 System.out.print(value + " "); 15 } 16 System.out.println(); 17 } 18 19 String[] hobbies = req.getParameterValues("hobby"); 20 for (String hobby : hobbies) { 21 System.out.println(hobby); 22 } 23 24 String username = req.getParameter("username"); 25 String password = req.getParameter("password"); 26 System.out.println(username); 27 System.out.println(password); 28 } 29 30 @Override 31 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 32 this.doGet(req,resp); 33 } 34 }
由于格式固定,所以我们可以使用IDEA提供的模板来制作一个Servlet的模板,这样我们后期在创建Servlet的时候就会更高效,具体如何实现:
1、按照自己的需求,修改Servlet创建的模板内容,点击File --> Setting
2、使用servlet模板创建Servlet类
-->
请求参数如果存在中文数据,则会乱码
POST请求解决方案
分析POST出现中文乱码的原因:
POST的请求参数是通过request的getReader()来获取流中的数据;
TOMCAT在获取流的时候采用的编码是ISO-8859-1;
ISO-8859-1编码是不支持中文的,所以会出现乱码。
解决方案:
因为页面设置的编码格式为UTF-8;把TOMCAT在获取流数据之前的编码设置为UTF-8;
通过request.setCharacterEncoding("UTF-8")设置编码,UTF-8也可以写成小写。
1 @WebServlet("/req4") 2 public class RequestDemo4 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 //1. 解决乱码: POST getReader() 6 //设置字符输入流的编码,设置的字符集要和页面保持一致 7 request.setCharacterEncoding("UTF-8"); 8 //2. 获取username 9 String username = request.getParameter("username"); 10 System.out.println(username); 11 } 12 13 @Override 14 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 15 this.doGet(request, response); 16 } 17 }
GET请求解决方案
POST请求的中文乱码解决方案为什么不适用GET请求:
GET请求获取请求参数的方式是 request.getQueryString();
POST请求获取请求参数的方式是 request.getReader();
request.setCharacterEncoding("utf-8")是设置request处理流的编码;
getQueryString方法并没有通过流的方式获取数据。
分析GET请求出现乱码的原因:
1、浏览器通过HTTP协议发送请求和数据给后台服务器(Tomcat)
2、浏览器在发送HTTP的过程中会对中文数据进行URL编码
3、在进行URL编码的时候会采用页面<meta>标签指定的UTF-8的方式进行编码,"张三"编码后的结果为`%E5%BC%A0%E4%B8%89`
4、后台服务器(Tomcat)接收到`%E5%BC%A0%E4%B8%89`后会默认按照ISO-8859-1进行URL解码
5、由于前后编码与解码采用的格式不一样,就会导致后台获取到的数据为乱码。
URL编码:
1. 将字符串按照编码方式转为二进制
2. 每个字节转为2个16进制数并在前边加上%
`张三`按照UTF-8的方式转换成二进制的结果为:(用utf-8方式编码:一个汉字占3个字节,两个汉字6个字节,每个字节8位,一共6*8=48位)
URL编码实现方式:
编码:URLEncoder.encode(str, "utf-8");
解码:URLDecoder.decode(s, "ISO-8859-1");
1 public class URLDemo { 2 public static void main(String[] args) throws UnsupportedEncodingException { 3 String username = "张三"; 4 //1. URL编码 5 String encode = URLEncoder.encode(username, "utf-8"); 6 System.out.println(encode); //打印:%E5%BC%A0%E4%B8%89 7 //2. URL解码 8 //String decode = URLDecoder.decode(encode, "utf-8");//打印:张三 9 String decode = URLDecoder.decode(encode, "ISO-8859-1"); 10 System.out.println(decode); //打印:`å¼ ä¸ ` 11 12 //3. 转换为字节数据,编码 13 byte[] bytes = decode.getBytes("ISO-8859-1"); 14 for (byte b : bytes) { 15 System.out.print(b + " "); 16 } 17 //此处打印的是:-27 -68 -96 -28 -72 -119 18 //4. 将字节数组转为字符串,解码 19 String s = new String(bytes, "utf-8"); 20 System.out.println(s); //此处打印的是张三 21 } 22 }
解决方案:
先编码,再解码。通用方式(GET/POST都可用此方法解决)
new String(username.getBytes("ISO-8859-1"),"UTF-8");
1 @WebServlet("/req4") 2 public class RequestDemo4 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 //1. 解决乱码:POST,getReader() 6 //request.setCharacterEncoding("UTF-8");//设置字符输入流的编码 7 8 //2. 获取username 9 String username = request.getParameter("username"); 10 System.out.println("解决乱码前:"+username); 11 12 //3. GET,获取参数的方式:getQueryString 13 // 乱码原因:tomcat进行URL解码,默认的字符集ISO-8859-1 14 /*//3.1 先对乱码数据进行编码:转为字节数组 15 byte[] bytes = username.getBytes(StandardCharsets.ISO_8859_1); 16 //3.2 字节数组解码 17 username = new String(bytes, StandardCharsets.UTF_8);*/ 18 19 username = new String(username.getBytes(StandardCharsets.ISO_8859_1),StandardCharsets.UTF_8); 20 System.out.println("解决乱码后:"+username); 21 } 22 @Override 23 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 24 this.doGet(request, response); 25 } 26 }
注:Tomcat 8.0 之后,已将GET请求乱码问题解决,设置默认的解码方式为UTF-8
请求转发(forward)
作用:一种在服务器内部的资源跳转方式。实现多个servlet联动操作处理请求,这样避免代码冗余,让servlet的职责更加明确。
使用:
req.getRequestDispatcher("要转发的地址").forward(req, resp);
地址:相对路径,直接书写servlet的别名即可。
特点:
浏览器地址栏路径不发生变化;
只能转发到当前服务器的内部资源;
一次请求,可以在转发的资源间使用request共享数据。
注意:
请求转发后直接return结束即可。
reuqest作用域
问题:使用请求转发后,数据怎么从一个servlet流转给另一个servlet?
解决:使用request对象的作用域
使用:
void setAttribute(String name, Object o):存储数据到 request域中
Object getAttribute(String name):根据 key,获取值
void removeAttribute(String name):根据 key,删除该键值对
作用:解决了一次请求内不同的servlet的数据(请求数据+其他数据)共享问题。
注意:使用Request对象进行数据流转,数据只在一次请求内有效。
特点:
服务器创建
每次请求都会创建
生命周期一次请求
respone对象
Response 设置响应数据
设置响应行
HTTP/1.1 200 OK
void setStatus(int sc) :设置响应状态码
Content-Type: text/html
void addHeader(String name,String value);//在响应头中添加响应信息,但是不会覆盖。
sendError(int num,String msg);//自定义响应状态码。
1 public class ResponseServlet extends HttpServlet { 2 @Override 3 protected void service(HttpServletRequest req, HttpServletResponse resp) 4 throws ServletException, IOException { 5 //响应处理结果 6 //设置响应头 7 resp.setHeader("mouse", "two fly birds"); 8 resp.setHeader("mouse", "su"); 9 resp.addHeader("key", "thinkpad"); 10 resp.addHeader("key", "wollo"); 11 //设置响应状态码 12 resp.sendError(404, "this Method is not supported"); 13 } 14 }
<html><head>head><body></body></html>
PrintWriter getWriter():获取字符输出流
ServletOutputStream getOutputStream():获取字节输出流
通过Response对象获取字符输出流: PrintWriter writer = resp.getWriter();
通过字符输出流写数据: writer.write("aaa");
1 @WebServlet("/resp3") 2 public class ResponseDemo3 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 response.setContentType("text/html;charset=utf-8"); 6 //1. 获取字符输出流 7 PrintWriter writer = response.getWriter(); 8 writer.write("aaa"); 9 } 10 @Override 11 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 12 this.doGet(request, response); 13 } 14 }
返回一串html字符串,并且能被浏览器解析
1 PrintWriter writer = response.getWriter(); 2 //content-type,告诉浏览器返回的数据类型是HTML类型数据,这样浏览器才会解析HTML标签 3 response.setHeader("content-type","text/html"); 4 writer.write("<h1>aaa</h1>");
返回一个中文的字符串`你好`,需要注意设置响应数据的编码为`utf-8`
设置响应编码格式:
resp.setContentType("text/html;charset=utf-8");
1 //设置响应的数据格式及数据的编码 2 response.setContentType("text/html;charset=utf-8"); 3 PrintWriter writer = response.getWriter(); 4 writer.write("你好");
注意:一次请求响应结束后,response对象就会被销毁掉,所以不要手动关闭流。
Response 响应字节数据
通过Response对象获取字节输出流:ServletOutputStream outputStream = resp.getOutputStream();
通过字节输出流写数据: outputStream.write(字节数据);
1 @WebServlet("/resp4") 2 public class ResponseDemo4 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 //1. 读取文件 6 FileInputStream fis = new FileInputStream("d://a.jpg"); 7 //2. 获取response字节输出流 8 ServletOutputStream os = response.getOutputStream(); 9 //3. 完成流的copy 10 byte[] buff = new byte[1024]; 11 int len = 0; 12 while ((len = fis.read(buff))!= -1){ 13 os.write(buff,0,len); 14 } 15 fis.close(); 16 } 17 18 @Override 19 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 20 this.doGet(request, response); 21 } 22 }
上述代码中,对于流的copy的代码还是比较复杂的,所以我们可以使用别人提供好的方法来简化代码的开发,具体的步骤是:
1.pom.xml添加依赖
<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
2.调用工具类方法
IOUtils.copy(输入流,输出流);
1 @WebServlet("/resp4") 2 public class ResponseDemo4 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 //1. 读取文件 6 FileInputStream fis = new FileInputStream("d://a.jpg"); 7 //2. 获取response字节输出流 8 ServletOutputStream os = response.getOutputStream(); 9 //3. 完成流的copy 10 IOUtils.copy(fis,os); 11 fis.close(); 12 } 13 14 @Override 15 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 16 this.doGet(request, response); 17 } 18 }
请求重定向(Redirect)
一种资源跳转方式。解决了表单重复提交的问题,以及当前servlet无法处理的请求的问题。
实现方式:
方式一:resp.setStatus(302);
resp.setHeader("location","资源B的路径");
方式二:resp.sendRedirect("资源B的路径");
示例:
resp.sendRedirect("/login/main");
特点:
浏览器地址栏路径发生变化;
可以重定向到任意位置的资源(服务器内部、外部均可);
两次请求,不能在多个资源使用request共享数据。
时机:
如果请求中有表单数据,而数据又比较重要,不能重复提交,建议使用重定向。
如果请求被Servlet接收后,无法进行处理,建议使用重定向定位到可以处理的资源。
代码:
1 @WebServlet("/resp1") 2 public class ResponseDemo1 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 //重定向 6 /*//1.设置响应状态码 302 7 response.setStatus(302); 8 //2. 设置响应头 Location 9 response.setHeader("Location","/request-demo/resp2");*/ 10 11 //重定向 12 resposne.sendRedirect("/request-demo/resp2"); 13 } 14 15 @Override 16 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 17 this.doGet(request, response); 18 } 19 }
请求重定向 vs 请求转发 区别
请求重定向:(浏览器行为)
1)地址栏改变,改变为重定向到的地址
2)可以重定向到当前web应用,其他web应用,甚至是其他站点资源。
3)处于两次不同的请求。不可以使用request域对象来共享数据。
请求转发:(服务器行为)
1)地址栏不会改变。
2)只能转发到当前web应用内部资源。
3)处于同一次请求。可以使用request域对象来共享数据
请求重定向路径 vs 请求转发路径
相对路径:从当前请求的路径查找资源的路径
相对路径如果servlet的别名中包含目录,会造成重定向资源查找失败。
绝对路径:第一个/表示服务器根目录
/虚拟项目名/资源路径
resp.sendRedirect("/mg/login.jsp");//虚拟项目名为mg,login.jsp在WebContent下
/表示项目根目录。
req.getRequestDispatcher("/资源路径").forward(req, resp);
req.getRequestDispatcher("/user/showUser.jsp").forward(req, resp);//虚拟项目名为mg,user是WebContent下的文件夹,showUser.jsp在user下
明确路径谁使用:
浏览器使用:需要加虚拟目录(项目访问路径)
服务端使用:不需要加虚拟目录
可以动态获取项目访问的虚拟路径,从而降低代码的耦合度:
1 @WebServlet("/resp1") 2 public class ResponseDemo1 extends HttpServlet { 3 @Override 4 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 //简化方式完成重定向 6 //动态获取虚拟目录 7 String contextPath = request.getContextPath(); 8 response.sendRedirect(contextPath+"/resp2"); 9 } 10 11 @Override 12 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 13 this.doGet(request, response); 14 } 15 }