JavaWeb之Servlet的请求与响应
一、请求
1.1 请求概述
请求:就是用户通过浏览器向服务器发送信息(携带数据)
用户通过浏览器访问服务器时,Tomcat将HTTP请求中所有的信息都封装在Request对象中,开发人员可以通过request对象方法,来获取浏览器发送的所有信息。
Request体系结构:
ServletRequest 接口
|
HttpServletRequest 接口
|
org.apache.catalina.connector.RequestFacade 实现类(由tomcat提供的)
1.2 获取Http请求信息
1.2.1 请求行
请求行格式: 请求方式 请求路径 协议版本
http://localhost:8080/servlet/requestLine?name=a&age=b&score=c HTTP/1.1
通过get
方式访问请求行会有参数,而post
请求不会有参数
获取请求行的信息的API:
返回值 | 方法名 | 说明 |
---|---|---|
String | getMethod() | 获取请求方式的类型 |
StringBuffer | getRequestURL() | 获取客户端发出请求完整URL |
String | getRequestURI() | 获取请求行中的资源名部分 |
String | getContextPath() | 获取当前项目根路径 |
String | getRemoteAddr() | 获取客户机IP地址 |
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* GET http://localhost:8080/servlet/requestLine?name=a&age=b&score=c HTTP/1.1
* 请求方式 请求路径 协议版本
*/
//1.重要部分的三个
//1.1 获得请求方式
String method = request.getMethod();
System.out.println("获得请求方式:"+ method);
//1.2 获取当前项目根路径(项目的别名)
String contextPath = request.getContextPath();
System.out.println("获取当前项目根路径:" +contextPath );
//1.3 获取请求行中的资源名部分
String requestURI = request. getRequestURI();
System.out.println("获取请求行中的资源名部分:" +requestURI );
//1.4 获得远程主机的ip地址(客户端地址) ipv6的地址 0:0:0:0:0:0:0:1等效于localhost 等效127.0.0.1
String remoteAddr = request.getRemoteAddr();
System.out.println("客户端地址为: " + remoteAddr);
//2.非重要部分的其他内容
//2.1 获得协议
String scheme = request.getScheme();
System.out.println("协议:" +scheme );
//2.2 获得主机名称
String serverName = request.getServerName();
System.out.println("主机名称:" +serverName );
//2.3 获得端口
int serverPort = request.getServerPort();
System.out.println("端口:" + serverPort);
//2.4 获得资源名称
String servletPath = request.getServletPath();
System.out.println("资源名称:" + servletPath);
//2.5 获得?号之后的内容
String queryString = request.getQueryString();
System.out.println("?号之后的参数:" + queryString);
//2.6 获得协议版本
String protocol = request.getProtocol();
System.out.println("协议版本:" + protocol);
//3.重要的两个概念
//3.1 统一资源定位符(绝对路径) http://localhost:8080/servlet/requestLine
StringBuffer requestURL = request.getRequestURL();
System.out.println(requestURL.toString());
//3.2 统一资源描述符(相对路径) 服务器的相对路径 /servlet/requestLine
String requestURI = request.getRequestURI();
System.out.println(requestURI);
}
1.2.2 请求消息头
请求消息头:客户端浏览器告诉服务器一些信息
格式:
请求名称:请求头值(多个请求头值之间用逗号隔开,部分请求头值是键值对的形式)
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:8080/a_form.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
与请求消息头相关的API:
返回值 | 方法名 | 说明 |
---|---|---|
String | getHeader(String name) | 根据请求头名称获取一个值 |
public class RequestHeaderServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* 根据请求头key获得请求头的value
*/
String accept = request.getHeader("Accept");
System.out.println(accept);
/**
* Enumeration 迭代器的前身
*/
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
//遍历所有的请求头名称
System.out.println(headerNames.nextElement());
}
}
}
1.2.3 请求参数
在开发中,我们更关注的是用户所提交的数据,而不是浏览器自动拼接的数据。
请求参数就是请求体中的内容,get
方式没有请求体,post
方式有请求体。
不论get还是post请求方式,都可以使用下列方法来获取请求参数:
返回值 | 方法名 | 说明 |
---|---|---|
String | getParameter(String name) | 根据名称获取数据 |
String[] | getParameterValues(String name) | 根据名称获取所有数据 |
Enumeration<String> | getParameterNames() | 获取所有的参数名 |
Map<String,String[]> | getParameterMap() | 获取所有参数的键值对 |
public class RequestParamServlet extends HttpServlet {
//post提交
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);//不希望书写代码重复 直接调用一下即可
}
//get提交
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/**
* request.getParameter("name"); 根据名称获得一个值
* request.getParameterValues("name") 根据名称获得一组值
* request.getParameterMap(); 表单的所有key 就是map的key 表单里的值 就是map的值
*/
String name = request.getParameter("name");
String age = request.getParameter("age");
String score = request.getParameter("score");
System.out.println(name + "," + age + "," + score);
String[] hobbies = request.getParameterValues("hobby");
System.out.println(Arrays.toString(hobbies));
Map<String, String[]> map = request.getParameterMap();
//map.entrySet().iter + table
//map.keySet().iter + table
for (Map.Entry<String, String[]> entry : map.entrySet()) {
System.out.println(entry.getKey() + "," + Arrays.toString(entry.getValue()));
}
}
}
1.2.3.1 实体类
实体类:用于封装数据的简单java对象,里面只包含私有成员变量,和公有get和set方法。
实体类,有书写规范:
- 类都是public的
- 成员变量都是private
- 所有私有成员变量都有public的get和set方法
- 类中必须有默认构造函数
- 一般都要实现序列化接口(java.io.Serializable)
1.2.3.2 请求参数的中文乱码问题
请求参数的中文乱码问题只出现在POST方式请求,在Tomcat8.x版本之后,GET方式的乱码问题就没有了。
乱码的来源:
POST方式的乱码问题是通过设置请求参数字符集的方式来解决的:
要求:放在程序的第一行
request.setCharacterEncoding("UTF-8");
1.2.4 用流的方式读取请求正文
相关API:
返回值 | 方法名 | 说明 |
---|---|---|
BufferedReader | getReader() | 获取字符输入流 |
ServletInputStream | getInputStream() | 获取字节输入流 |
public class RequestDemo4 extends HttpServlet {
/**
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取字节输入流
ServletInputStream inputStream = req.getInputStream();
//2.定义字节数组
byte[] bytes = new byte[1024];
int len = 0;
//3.输出
while((len = inputStream.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
通常情况下,文件上传时会用到用流的方式读取请求正文
细节:从服务器中获取的流对象,可以不用关闭,由服务器帮我们关闭。
1.2.5 请求转发
请求转发:一种在服务器内部的资源跳转方式,必须结合reqest域
使用。只能在当前应用内部转发,不能转向外部应用,在内部转发时,不仅可以转到html,还可以转到jsp或者是Servlet。
request.getRequestDispatcher("/内部路径").forward(request,response);
reqest域:浏览器每发起一次请求都是产生一个新的request对象 , 每次响应之后request即销毁,作用是在这次请求中共享数据,是一个域对象。
请求域的API:
返回值 | 方法名 | 说明 |
---|---|---|
void | setAttribute(String name,Object value) | 向请求域对象中存储数据 |
Object | getAttribute(String name) | 通过名称获取请求域对象中的数据 |
void | removeAttribute(String name) | 通过名称移除请求域对象中的数据 |
public class RequestDemo5 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取学生列表
List<User> users = new ArrayList<>();
for(int i=0;i<5;i++) {
User user = new User();
user.setUsername("username"+i);
user.setPassword("password"+i);
if(i%2==0){
user.setGender("男");
}else {
user.setGender("女");
}
users.add(user);
}
//2.把学生列表信息存起来
req.setAttribute("users",users);
//3.把地址转向到显示学生列表的页面
//获取请求分发对象
//RequestDispatcher dispatcher = req.getRequestDispatcher("/list.html");//要前往的地址
//转发到指定的地址
//dispatcher.forward(req,resp);//真正前往指定的页面
req.getRequestDispatcher("/RequestDemo6").forward(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
public class RequestDemo6 extends HttpServlet {
//转发的目的地
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("欢迎来到RequestDemo6");
//取出请求域中的数据
List<User> users = (List<User>)req.getAttribute("users");
// List<User> users = (List<User>)this.getServletContext().getAttribute("users");
//遍历输出
for(User user : users){
System.out.println(user);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
请求转发的特点:
- 一次请求多个servlet
- 可以借助request域对象进行,数据共享
- 地址栏不变(显示请求转发之前的地址)
- 请求转发是服务器内部的事情 不能跳转站外(网站以外, 例如百度)资源
二、响应
2.1 响应概述
响应是服务器给浏览器的一个反馈,每次服务器响应浏览器的请求时,都会生成一个response对象。
response对象包含web服务器给浏览器返回的响应信息,开发人员可以使用response对象的方法,设置要返回给浏览器的响应信息。
2.2 响应行
格式
协议/版本号 状态码
例如
HTTP/1.1 200
1xx : 表示请求开始没用
200 : 正常响应回来了
302/307 : 重定向,重新改变方向再次发起请求
304 : 请求资源未改变,使用缓存
400 : 请求错误,常见于请求参数错误
404 : 浏览器的地址输入错误,找不到资源.确定路径是否书写错误, 确定资源是否存在指定路径上
405 : 执行了HttpServlet类的doXxx方法.要么没写doget或者dopost 要么手动调用了父类方法
500 : 服务器内部代码执行报错(代码写错了)
相关API:
方法名 | 说明 |
---|---|
response.setStatus(int 状态码) | 设置状态码 |
response.sendError | 发送错误 |
2.3 响应信息头
格式
响应头名称:响应头的值
常用响应头:
location
:设置重定向的地址content-type
:设置返回的数据的mime类型及编码 如text/html;charset=utf-8
content-disposition
:设置用声明方式打开响应回去的内容,如用在文件下载的值为attachment;filename=文件的名称.后缀
相关的API:
方法名 | 说明 |
---|---|
setHeader(String name,String value) | 设置值为string类型的响应头,后设置的会把前面设置的覆盖 |
addHeader(String name,String value) | 追加值为string类型的响应头若之前有这个头,则往后追加值;若之前没有这个头,就会设置这个响应头 |
setDateHeader(String name,long value) | 设置日期类型的响应消息头 |
setIntHeader(String name,int value) | 设置数值类型的响应消息头 |
2.3.1 重定向
重定向:客户端的一次请求到达后,发现需要借助其他 Servlet 来实现功能,重新跳转到其他其他服务器
特点:
- 浏览器地址栏会发生改变
- 会产生两次请求、请求域对象中不能共享数据
- 既能跳转站外资源也能跳转站内资源
- 重定向的路径是绝对路径
两种设置方式
- 需要设置状态码 和 location
response.setStatus(302);
response.setHeader("location" , "/servlet/two");
- 使用sendRedirect(“重定向的地址”)方法
response.sendRedirect("/servlet/two");
重定向和请求转发的区别:
- 重定向:2次请求,浏览器行为,地址栏改变,请求域中的数据会丢失,可以定向到外部应用
- 请求转发:1次请求,服务器行为,地址栏不变,请求域中的数据不会丢失,不能转向到外部应用
2.4 响应体
响应体的内容是服务器响应给浏览器的源代码,服务器响应什么内容,浏览器就展示什么内容。
字节流API:
返回值 | 方法名 | 说明 |
---|---|---|
PrintWriter | getWriter() | 获取响应字符输出流对象 |
void | setContentType(“text/html;charset=UTF-8”) | 设置响应内容类型,解决中文乱码问题 |
返回值 | 方法名 | 说明 |
---|---|---|
ServletOutputStream | getOutputStream() | 获取响应字节输出流对象 |
void | setContentType(“text/html;charset=UTF-8”) | 设置响应内容类型,解决中文乱码问题 |
public class ResponseDemo2 extends HttpServlet {
/**
*
* @param req 请求对象
* @param resp 响应对象
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应正文的类型和字符集
resp.setContentType("text/html;charset=UTF-8");
//1.往浏览器上输出 haha——>哈哈
ServletOutputStream out = resp.getOutputStream();
//2.输出
out.write("<font size='7' color='red'>哈哈</font>".getBytes("UTF-8"));//getBytes()转成的字节数组是本地系统字符集的
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
public class ResponseDemo1 extends HttpServlet {
/**
*
* @param req 请求对象
* @param resp 响应对象
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应对象的字符集
// resp.setCharacterEncoding("UTF-8");
//设置响应消息头
//resp.setHeader("Content-Type","text/html;charset=UTF-8");
resp.setContentType("text/html;charset=UTF-8");
//1.往浏览器上输出 haha——>哈哈
PrintWriter out = resp.getWriter();
//2.输出
// out.write("<font size='7' color='red'>haha</font>");
out.write("<font size='7' color='red'>哈哈</font>");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
乱码产生的原因:存和取的字符集不一致
- 当字符集是ISO-8859-1时,它不支持中文,就会显示
?
- 当字符集是GBK(GB2312)时,它支持中文,如果用UTF-8转码的话,就是看到一堆不是汉字内容
- GBK(GB2312) 一个汉字占两个字节 UTF-8一个汉字占三个字节
- PrintWriter它在获取时默认的字符集就不支持中文,我们需要设置让他支持中文。
三、文件下载案例
配置xml:
<!--文件下载-->
<servlet>
<servlet-name>ResponseDemo3</servlet-name>
<servlet-class>com.itcast.servlet.response.ResponseDemo3</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ResponseDemo3</servlet-name>
<url-pattern>/ResponseDemo3</url-pattern>
</servlet-mapping>
/**
* 文件下载
*/
public class ResponseDemo3 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取下载文件的路径,根据虚拟路径获取服务器实际地址
String path = getServletContext().getRealPath("/downloads/girl.jpg");
//用字节输入流读取文件
InputStream is = new FileInputStream(path);//自己获取的流,需要自己关闭
//获取字节输出流
ServletOutputStream out = resp.getOutputStream();
//设置响应消息头
//告知浏览器,用下载的方式打开图片
resp.setHeader("Content-Disposition","attachment;filename=girl.jpg");
//告知浏览器响应正文的类型
resp.setHeader("Content-Tpye","application/octet-stream");
//把字节流入流的内容通过输出流写到客服端浏览器
byte[] bArr = new byte[1024];
int len = -1;
while((len=is.read(bArr)) != -1){
out.write(bArr,0,len);
}
//关闭自己获取的流
is.close();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}