1. HTTP 协议解读
什么是协议?
1.1 什么是 HTTP 协议 ?
什么是超文本 ?
什么是解耦合?
这里指定是: B (浏览器) 不 依赖 S(服务器) ,S 也不依赖 B
B/S 表示:B/S 结构的系统(浏览器访问 WEB 服务器的系统)
HTTP 协议的请求 (request) 和 响应(response)
HTTP 协议包括:
HTTP 协议总结:
HTTP 协议就是提前制定好的一种消息模板。
1.2 HTTP 请求协议的具体报文
注意:HTTP 请求协议中有多种请求方式。这里我们说的是常用的 get 请求和 post 请求。在该文章的下文有更加详细的说明。
HTTP 的请求协议(B (浏览器)-> S(服务器))
HTTP 的请求协议包括:如下 4 个部分: 请求行,请求头,空白行,请求体。
如何查看我们提交的数据报文,在浏览器当中
我们可以在提交数据的时候按 F12 快捷键查看,或者是右键鼠标浏览器 ,在弹出的窗口当中,选择点击 检查。如下图所示:
在选择点击检查按钮之后,弹出如下窗口,选择其中的 Network (网络)选项。如下图所示:只要当我们在前端提交了数据,就可以在如下的窗口当中捕获到并显示出相应的报文信息。
测试如下,看看是否可以捕获到我们的请求报文的信息数据。如下图所示:
查看我们提交的数据信息:点击我们捕获到的报文信息,再点击 ——> Paload 选项。
如下是:HTTP 请求协议的具体报文:GET 请求
首先编写一个是 Get 请求提交的表单 html 文件,具体的代码编写如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>get 请求</h2>
<form action="" method="get">
username: <input type="text" name="username" /> <br>
userpassword: <input type="password" name="userpswd" /> <br>
<input type="submit" value="get" />
</form>
<h2>post 请求</h2>
<form action="" method="post">
username: <input type="text" name="username" /> <br>
userpassword: <input type="password" name="userpswd" /> <br>
<input type="submit" value="post" />
</form>
</body>
</html>
复制代码
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.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: keep-alive
# 下面一行是请求行
Host: 127.0.0.1:8080
# 下面的是请求头
Referer: http://127.0.0.1:8080/servlet08/login.html
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
# 下面是空白行:用于分隔请求头 与 请求体
# 下面是请求体:向服务器发送的具体数据。
username=Hello&userpswd=123
复制代码
如下是另一种的格式下 get 请求的具体报文信息
GET /servlet05/getServlet?username=lucy&userpwd=1111 HTTP/1.1
# 请求行
Host: localhost:8080
# 请求头
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 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/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
# 空白行
# 请求体
复制代码
如下 post 请求信息
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.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cache-Control: max-age=0
Connection: keep-alive
Content-Length: 25
Content-Type: application/x-www-form-urlencoded
# 请求头
Host: 127.0.0.1:8080
# 请求行
Origin: http://127.0.0.1:8080
Referer: http://127.0.0.1:8080/servlet08/login.html?username=Hello&userpswd=123
sec-ch-ua: "Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36
# 空白行
# 请求体
复制代码
如下是另一种的格式下 post 请求的具体报文信息
POST /servlet05/postServlet HTTP/1.1
# 请求行
Host: localhost:8080
# 请求头
Connection: keep-alive
Content-Length: 25
Cache-Control: max-age=0
sec-ch-ua: "Google Chrome";v="95", "Chromium";v="95", ";Not A Brand";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 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/servlet05/index.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
# 空白行
# 请求体
username=lisi&userpwd=123
复制代码
请求行: 由三部分组成: 请求方式,URI ,HTTP 版本号 。
URL : 统一资源定位符。 代表网络中某个资源。注意: 可以通过 URL 定位到该资源,可以直接在浏览器当中输入 URI 访问网络当中的资源)。比如: http://localhost:8080/servlet05/index.html
这是 URL。
URI : 统一资源标识符。代表网络中某个资源的名字。注意: URI 是无法定位资源的(就是无法通过直接在浏览器当中输入 URI 访问网络当中的资源)。比如:/servlet05/index.html 这是 URI。
URI 和 URL 什么关系,有什么区别?
URL 是包括了 URI 的。
请求头: 包含 : 请求的主机的 IP 地址,主机的端口号,浏览器信息,平台信息,cookie 等信息。
空白行: 空白行是用来区分“请求头”和“请求体”
请求体: 向服务器发送的具体数据。
1.3 HTTP 响应协议的具体报文
HTTP 的响应协议(S(浏览器) --> B(客户端))HTTP 的响应协议包括 4 部分 : 状态行,响应头,空白行,响应体 。
HTTP 响应协议的具体报文:
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 598
Content-Type: text/html # 响应头
Date: Thu, 30 Mar 2023 02:48:49 GMT
ETag: W/"598-1680144315095"
Keep-Alive: timeout=20
Last-Modified: Thu, 30 Mar 2023 02:45:15 GMT
# 空白行
<!DOCTYPE html> # 响应体:其实就是源代码的信息
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>get 请求</h2>
<form action="" method="get">
username: <input type="text" name="username" /> <br>
userpassword: <input type="password" name="userpswd" /> <br>
<input type="submit" value="get" />
</form>
<h2>post 请求</h2>
<form action="" method="post">
username: <input type="text" name="username" /> <br>
userpassword: <input type="password" name="userpswd" /> <br>
<input type="submit" value="post" />
</form>
</body>
</html>
复制代码
另外一种 post 响应的具体数据报文信息。
HTTP/1.1 200 ok # 状态行
Content-Type: text/html;charset=UTF-8 # 响应头
Content-Length: 160
Date: Mon, 08 Nov 2021 13:19:32 GMT
Keep-Alive: timeout=20
Connection: keep-alive
# 空白行
<!DOCTYPE html> # 响应体:其实就是源代码的信息
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>get 请求</h2>
<form action="" method="get">
username: <input type="text" name="username" /> <br>
userpassword: <input type="password" name="userpswd" /> <br>
<input type="submit" value="get" />
</form>
<h2>post 请求</h2>
<form action="" method="post">
username: <input type="text" name="username" /> <br>
userpassword: <input type="password" name="userpswd" /> <br>
<input type="submit" value="post" />
</form>
</body>
</html>
复制代码
状态行 : 由三部分组成:协议版本号,状态码,状态描述信息。
如下是一些常见的状态码的意义:
状态行,第三部分: 状态的描述信息:
空白行: 用来分隔“响应头”和“响应体”的。
响应体: 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。简单的说就是 html 对应的源代码。
2. GET 请求和 POST 请求有什么区别?
怎么向服务器发送 Get 请求,怎么向服务器发送 Post 请求?
到目前为止,只有一种情况可以发送 post 请求:就是用 form 表单,并且 form 标签当中的 method 的属性值必须为 method = "post"
才行。
其他所有情况一律都是 get 请求:
GET 请求和 POST 请求有什么区别 ?
Get 请求:
补充点:
怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样(缓存的路径也是不一样的),浏览器就不走缓存了。
Post 请求 :
2.1 GET 请求和 POST 请求如何选择,什么时候使用 GET 请求,什么时候使用 POST 请求 ?
Get 请求与 Post 请求的共性
不管你是 get 请求还是 post 请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:都是 :name=value&name=value&name=value&name=value
其中的 name 表示:以 form 表单为例:form 表单中的 input 标签当中的 name 。
<form>
<input type="text" name="username" >
</form>
复制代码
其中的 value 表示的是:同样的以 form 表单为例 :form 表单中的 input 标签当中的 value。
<form>
interest:
smoke<input type="checkbox" name="aihao" value="s"/>
drink <input type="checkbox" name="aihao" value="d"/>
tangtou <input type="checkbox" name="aihao" value="tt"/>
</form>
复制代码
3. HttpServlet 源码分析
这是在 Tomcat 10 的基础上:jakarta.servlet.http.HttpServlet,而后面 Tomcat 9 之前的(包括 Tomcat 9 )以内的包是在 : javax.servlet.http.HttpServlet 包下的。
jakarta.servlet.Servlet 核心接口(接口)
jakarta.servlet.ServletConfig Servlet 配置信息接口(接口)
jakarta.servlet.ServletContext Servlet 上下文接口(接口)
jakarta.servlet.ServletRequest Servlet 请求接口(接口)
jakarta.servlet.ServletResponse Servlet 响应接口(接口)
jakarta.servlet.ServletException Servlet 异常(类)
jakarta.servlet.GenericServlet 标准通用的 Servlet 类(抽象类)
jakarta.servlet.http.HttpServlet (HTTP 协议专用的 Servlet 类,抽象类)
jakarta.servlet.http.HttpServletRequest (HTTP 协议专用的处理请求对象)
jakarta.servlet.http.HttpServletResponse (HTTP 协议专用的处理响应对象)
HttpServletRequest 简称为 request
对象。
HttpServletRequest 中封装了请求协议的全部内容。
Tomcat 服务器(WEB 服务器) 将 “请求协议”中的数据全部解析出来,然后将这些数据全部封装到 request
对象当中了。也就是说,我们只要面向 HttpServletRequest 接口编程,就可以获取请求协议当中的数据了。
HttpServletRequest,简称 request
对象。
回忆 Servlet 生命周期 ?
用户第一次请求
Tomcat 服务器通过反射机制,调用无参数构造方法。创建 Servlet 对象。(web.xml 文件中配置的 Servlet 类对应的对象。)
Tomcat 服务器调用 Servlet 对象的 init 方法完成初始化。
Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。
用户第二次请求
Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。
用户第三次请求
Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。
....
Tomcat 服务器调用 Servlet 对象的 service 方法处理请求。
Tomcat 服务器调用 Servlet 对象的 destroy 方法,做销毁之前的准备工作。
Tomcat 服务器销毁 Servlet 对象。
HttpServlet 源码分析:
public class HelloServlet extends HttpServlet {
// 用户第一次请求,创建HelloServlet对象的时候,会执行这个无参数构造方法。
public HelloServlet() {
}
//override 重写 doGet方法
//override 重写 doPost方法
}
public abstract class GenericServlet implements Servlet, ServletConfig,
java.io.Serializable {
// 用户第一次请求的时候,HelloServlet对象第一次被创建之后,这个init方法会执行。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
// 用户第一次请求的时候,带有参数的init(ServletConfig config)执行之后,会执行这个没有参数的init()
public void init() throws ServletException {
// NOOP by default
}
}
// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
// 用户发送第一次请求的时候这个service会执行
// 用户发送第N次请求的时候,这个service方法还是会执行。
// 用户只要发送一次请求,这个service方法就会执行一次。
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service方法。
service(request, response);
}
// 这个service方法的两个参数都是带有Http的。
// 这个service是一个模板方法。
// 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方式
// 这个请求方式最终可能是:""
// 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
// GET POST PUT DELETE HEAD OPTIONS TRACE
String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 如果请求方式是POST请求,则执行doPost方法。
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
}
/*
通过以上源代码分析:
假设前端发送的请求是get请求,后端程序员重写的方法是doPost
假设前端发送的请求是post请求,后端程序员重写的方法是doGet
会发生什么呢?
发生405这样的一个错误。
405表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。
通过以上源代码可以知道:只要HttpServlet类中的doGet方法或doPost方法执行了,必然405.
怎么避免405的错误呢?
后端重写了doGet方法,前端一定要发get请求。
后端重写了doPost方法,前端一定要发post请求。
这样可以避免405错误。
这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
有的人,你会看到为了避免405错误,在Servlet类当中,将doGet和doPost方法都进行了重写。
这样,确实可以避免405的发生,但是不建议,405错误还是有用的。该报错的时候就应该让他报错。
如果你要是同时重写了doGet和doPost,那还不如你直接重写service方法好了。这样代码还能
少写一点。
*/
复制代码
3.1 HttpServlet 处理 get 请求和 post 请求 源码分析
上面我们在:HTTP 响应协议的具体报文模块当中提到的 状态行当中的状态码部分中的一个为 405
前端用户提交的 get/post 请求与后端服务器不一致导致的错误。如下测试
当: 前端发送的请求是 get 请求 。后端程序员重写的方法是 doPost
对于前端用户提交数据的 html 代码设计如下:注意:这里我们前端提交的是 get
请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>get 请求</h2>
<!--action = /项目名 + web.xml当中url的映射的路径-->
<form action="/servlet08//Test" method="get">
username: <input type="text" name="username"/> <br>
userpassword: <input type="password" name="userpswd"/> <br>
<input type="submit" value="get"/>
</form>
</body>
</html>
复制代码
如下的是对应后端服务器 Servlet 的代码设计。注意了,这里我们 Servlet 服务器端处理的是 doPost
请求的
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置浏览器当中显示的格式
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
writer.println("<h1>Hello World<h1>");
}
}
复制代码
如下运行结果: 报了 405 错误,原因是:我们前后端的处理请求的不一致。
当:前端发送的请求是 post 请求,后端程序员重写的方法是 doGet 出现的错误
对于前端用户提交数据的 html 代码设计如下:注意:这里我们前端提交的是 post
请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>post 请求</h2>
<!--action = /项目名 + web.xml当中url的映射的路径-->
<form action="/servlet08//Test" method="post">
username: <input type="text" name="username"/> <br>
userpassword: <input type="password" name="userpswd"/> <br>
<input type="submit" value="get"/>
</form>
</body>
</html>
复制代码
如下的是对应后端服务器 Servlet 的代码设计。注意了,这里我们 Servlet 服务器端处理的是 doGet
请求的。
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置浏览器当中显示的格式
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
writer.println("<h1>Hello World<h1>");
}
}
复制代码
如下运行结果: 报了 405 错误,原因是:我们前后端的处理请求的不一致。
通过以上源代码分析:
假设前端发送的请求是 get 请求,后端程序员重写的方法是 doPost
假设前端发送的请求是 post 请求,后端程序员重写的方法是 doGet 会发生什么呢?发生 405 这样的一个错误。405 表示前端的错误,发送的请求方式不对。和服务器不一致。不是服务器需要的请求方式。
为什么为发生 405 错误呢?
我们从 HttpServlet 源码上分析:
// HttpServlet模板类。
public abstract class HttpServlet extends GenericServlet {
// 用户发送第一次请求的时候这个service会执行
// 用户发送第N次请求的时候,这个service方法还是会执行。
// 用户只要发送一次请求,这个service方法就会执行一次。
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
// 将ServletRequest和ServletResponse向下转型为带有Http的HttpServletRequest和HttpServletResponse
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
// 调用重载的service方法。
service(request, response);
}
// 这个service方法的两个参数都是带有Http的。
// 这个service是一个模板方法。
// 在该方法中定义核心算法骨架,具体的实现步骤延迟到子类中去完成。
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 获取请求方式
// 这个请求方式最终可能是:""
// 注意:request.getMethod()方法获取的是请求方式,可能是七种之一:
// GET POST PUT DELETE HEAD OPTIONS TRACE
String method = req.getMethod();
// 如果请求方式是GET请求,则执行doGet方法。
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 如果请求方式是POST请求,则执行doPost方法。
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
// 报405错误
String msg = lStrings.getString("http.method_get_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 报405错误
String msg = lStrings.getString("http.method_post_not_supported");
sendMethodNotAllowed(req, resp, msg);
}
复制代码
通过以上源代码阅读以及运行测试可以知道:只要没有重写对应 HttpServlet 类中的 doGet 方法或 doPost 方法,就会报 405 错误,因为没有重写了 doGet 或 doPost 方法就会执行其中的 HttpServlet 当中编写的 doGet / doPost 方法,而如果执行了的是 HttpServlet 当中 doGet / doPost 方法就会报 405 错误,提示你没有重写 对应的 doGet / doPost 方法。
怎么避免 405 的错误呢?
后端重写了 doGet 方法,前端一定要发 get 请求。后端重写了 doPost 方法,前端一定要发 post 请求。这样可以避免 405 错误。这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
补充
有的人,你会看到为了避免 405 错误,在 Servlet 类当中,将 doGet 和 doPost 方法都进行了重写。这样,确实可以避免 405 的发生,但是不建议,405 错误还是有用的。该报错的时候就应该让他报错。如果你要是同时重写了 doGet 和 doPost,那还不如你直接重写 service 方法好了。这样代码还能少写一点。
我们编写的 HelloServlet 直接继承 HttpServlet,直接重写 HttpServlet 类中的 service()方法行吗?
一个 Servlet 类的开发步骤:
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet {
}
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class HttpServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置浏览器当中显示的格式
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
writer.println("<h1>Hello World<h1>");
}
}
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>HttpServletTest</servlet-name>
<servlet-class>com.RainbowSea.servlet.HttpServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HttpServletTest</servlet-name>
<url-pattern>/Test</url-pattern>
</servlet-mapping>
</web-app>
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>get 请求</h2>
<!--action = /项目名 + web.xml当中url的映射的路径-->
<form action="/servlet08//Test" method="get">
username: <input type="text" name="username"/> <br>
userpassword: <input type="password" name="userpswd"/> <br>
<input type="submit" value="get"/>
</form>
</body>
</html>
复制代码
4. HttpServletRequest 接口详解
测试结果说明:Tomcat 服务器(WEB 服务器、WEB 容器)实现了 HttpServletRequest 接口,还是说明了 Tomcat 服务器实现了 Servlet 规范。而对于我们 javaweb 程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可。我们关心的是 HttpServletRequest 接口中有哪些方法,这些方法可以完成什么功能!!!!
4.0.1 HttpServletRequest 对象中都有什么信息?都包装了什么信息 ?
HttpServletRequest 对象是 Tomcat 服务 i 其负责创建的,这个对象中封装了如下信息,以及如下 HTTP 的请求协议?
实际上是用户发送请求的时候,遵循了 HTTP 协议,发送的是 遵循了 HTTP 的请求协议的信息内容(name=value&name=value&...),Tomcat 服务器会将客户端遵循 HTTP 协议发送的信息内容获取到,并将其数据全部解析出来,然后 Tomcat 服务器把这些信息封装到 HttpServletRequest 对象当中,传给了我们 Javaweb 程序员。
所以 Javaweb 程序员面向 HttpServletRequest 接口编程,调用其中的方法就可以获取到用户发送的请求信息了。
4.1 获取前端用户提交的数据信息
request 和 response 对象的生命周期?
我们后端如何怎么获取前端浏览器用户提交的数据 ?
想要获取到前端浏览器用户提交的数据,使用interface ServletRequest
接口当中抽象方法,也就是HttpServletRequest
实现了 ServletRequest 接口的具体实现类中重写其中的抽象方法。
Map<String,String[]> getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中<前端的name,前端的value>
Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值
String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)value
String getParameter(String name) 获取value这个一维数组当中的第一个元素。这个方法最常用。
// 以上的4个方法,和获取用户提交的数据有关系
// 注意: 这里为什么 value 的值是用String[] 数组存储的。后面有说明。
复制代码
思考:如果是你,前端的 form 表单提交了数据之后,你准备怎么存储这些数据,你准备采用什么样的数据结构去存储这些数据呢?
前端提交的数据格式:username=abc&userpwd=111&aihao=learn&aihao=programme&aihao=playbasketball
注意: 前端表单提交数据的时候,假设提交了 120 这样的“数字”,其实是以字符串"120"的方式提交的,所以服务器端获取到的一定是一个字符串的"120",而不是一个数字。(前端永远提交的是字符串,后端获取的也永远是字符串。)所以都是用 String
字符串。
我会采用 Map 集合来存储:
Map<String,String>
key存储String
value存储String
这种想法对吗?不对。
如果采用以上的数据结构存储会发现key重复的时候value覆盖。比如这里的 name=aihao 是多选框内容,有多个值aihao=s&aihao=d&aihao=tt
key value
---------------------
username abc
userpwd 111
aihao learn
aihao programme
aihao playbasketball
这样是不行的,因为map的key不能重复。
Map<String, String[]>
key存储String
value存储String[] // 将 value 值用 String 数组存储起来。这样就避免了上述的key 重复后 value内容上的覆盖,而没有都存储起来的缺点。
key value
-------------------------------
username {"abc"}
userpwd {"111"}
aihao {"learn","programme","playbasketball"}
复制代码
举例使用上述四个方法获取前端用户提交的数据
第一步:编写设计好 html 前端显示页面,如下:注意,这里我们前端使用的是 post 请求,后端的 Servlet 就要用 doPost 请求处理,前后端保持一致,不然报 405 错误。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>user register</title>
</head>
<body>
<h1>user register</h1>
<!--注意:/项目名+/Servlet类的映射路径-->
<form action="/servlet08//Request" method="post">
username:<input type="text" name="username"/><br>
password:<input type="password" name="userpassword"/> <br>
interest:
学习:<input type="checkbox" name="aihao" value="learn"/>
编程: <input type="checkbox" name="aihao" value="programme"/>
打篮球: <input type="checkbox" name="aihao" value="playbasketball"/>
<br>
<input type="submit" value="register"/>
</form>
</body>
</html>
复制代码
第二步: 编写对应处理前端用户提交的请求的 Servlet ,注意前后端请求处理保持一致 : 这里举例使用的是上述四个获取用户请求的其中的一个方法:
Map<String,String[]> getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中<前端的name,前端的value>
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class RequestServletTest extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 获取到前端用户提交的数据信息(name,value),并存储到Map集合当中去。
Map<String, String[]> parameterMap = request.getParameterMap();
// 遍历存储了前端用户提交数据的Map集合
// 获取到 Map 当中所有的 key 值(也就是前端的用户提交的所有的name 值)
Set<String> strings = parameterMap.keySet();
// 获取到 set 的迭代器
Iterator<String> iterator = strings.iterator();
while(iterator.hasNext()) {
String name = iterator.next();
System.out.print(name +"=");
// 根据 key 获取到对应的 value值 (也就是前端用户提交的value值数据)
String[] value = parameterMap.get(name);
// 遍历 value 数组
for(String s : value) {
System.out.print(s);
}
System.out.println();
}
}
}
复制代码
第三步: 对相关的 Servlet 类配置到对应 webapp 项目的 web.xml
文件中去。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>RequestServletTest</servlet-name>
<servlet-class>com.RainbowSea.servlet.RequestServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>RequestServletTest</servlet-name>
<url-pattern>/Request</url-pattern>
</servlet-mapping>
</web-app>
复制代码
第四步: 运行测试。
再举例: 这里的 Serlvelt 获取前端用户提交的数据使用: 如下两个方法
Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值
String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)value
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
public class RequestServletTest extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 获取到前端用户提交的数据的所有 name 值,并存储到 Enumeration<String[]> 枚举泛型中
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) { // 判断是否还有数据,有返回 true,没有返回 false
String name = names.nextElement(); // 获取到其中上述 Enumeration<String> 存储到的元素数据,
System.out.print(name + "=");
// 同时向下移动下标
String[] values = request.getParameterValues(name);// 根据 name 值获取到对应的 value值,前端用户提交的。
// 并存储到String[] 字符串数组当中去.
// 遍历数组
for (String v : values) {
System.out.print(v);
}
System.out.println();
}
}
}
复制代码
再举例: 这里的 Serlvelt 获取前端用户提交的数据使用: 如下两个方法
String getParameter(String name) 获取value这个一维数组当中的第一个元素。这个方法最常用。
String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)value
复制代码
注意: 上述两个方法的,合理使用,当我们的 name
的对应多个 value
值的话,需要使用 getParameterValues() 获取到其中的多个 value 值,而如果这是时候,我们使用的是 getParameter() 方法的话,就仅仅只会获取到其中的第一个 value 值,无法获取到对应 name 后面的 value 值的。
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class RequestServletTest extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 注意:这里直接指明对应的name 值,我们最好去前端编写的html当直接复制其中的name 值
// 不然你可以会手动编写错误,哪怕只有其中的一个字母错误了,该方法都是无法获取到对应
// 前端提交的value数据的。所以为了避免错误,建议直接复制。
String username = request.getParameter("username"); // 因为该name的value值只有一个
System.out.println("username =" + username );
String userpassword = request.getParameter("userpassword"); // 同样的该name值的value也是只有一个
System.out.println("userpassword = " + userpassword);
// 注意:该如下名为 aihaos 的value值是一个多个值的,需要使用数组存储起来。
String[] aihaos = request.getParameterValues("aihao");
System.out.print("aihaos =");
for (String s: aihaos) {
System.out.print(s);
}
}
}
复制代码
直接手动编写 name 值的注意事项
如果我们使用如下方法,其中的参数 name
是我们手动编写的话,存在一个安全隐患,就是:如果我们手动输入的 name 的值,在我们对应前端当中不存在的话(因为当其中的我们手动编写错了,比如说,多/少了字母的话),就无法获取到其中对应的 value 值了。
String getParameter(String name) 获取value这个一维数组当中的第一个元素。这个方法最常用。
复制代码
举例如下:
4.2 请求域对象的详解
HttpServletRequest 对象实际上又称为“请求域”对象。
这里我们回顾一下应用域对象是什么 ?
ServletContext 应用域对象(Servlet 上下文对象)。
什么情况下会考虑向 ServletContext 这个应用域当中绑定数据呢 ?
第一:所有用户的共享数据
第二:这个共享数据量很少
第三:这个共享数据很少进行修改操作,尽可能没有修改操作。
在以上三个条件都满足的情况下,使用这个应用域对象,可以大大提高我们程序执行效率。
实际上向应用域当中绑定数据,就相当于把数据放到了缓存(Cache) 当中,然后用户访问的时候直接从缓存中取,减少 IO 的操作(IO 访问磁盘,其耗费的时间代价十分的大),大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。
你见过哪些缓存技术呢 ?
字符串常量池
整数型常量池[-128~127]
,但凡是在这个范围当中 Integer
对象不再创建新对象,而是直接从这个整数型常量池中获取,大大提升系统性能。
数据库连接池(提前创建好 N 个连接对象,将连接对象放到集合当中,使用连接对象的时候,直接从缓存中拿,省去了连接对象的创建过程,效率提升。)
线程池(Tomcat 服务器就是支持多线程的),所谓的线程池就是提前先创建好 N 个线程对象,将线程对象存储到集合中,然后用户直接去线程池当中获取线程对象,直接拿来用。提升系统性能。)
后期你还会学习更多的缓存技术,例如:redis、mongoDB.....
拉回来,这里我们的主角是 "请求域"
对象:
void setAttribute(String name, Object obj); // 向请求域当中绑定数据。
Object getAttribute(String name); // 从请求域当中根据name获取数据。 注意: 这里的参数是 你向请求域添加数据时,设置的对应数据的,setAttribute(String name ,Object obj)一个 name 值保持一致,尽可以使用复制的方式,防止手动编写错误。导致无法找到,从而为 null 值。
void removeAttribute(String name); // 将请求域当中绑定的数据移除
// 以上的操作类似于Map集合的操作。
Map<String, Object> map;
map.put("name", obj); // 向map集合中放key和value
Object obj = map.get("name"); // 通过map集合的key获取value
map.remove("name"); // 通过Map集合的key删除key和value这个键值对。
复制代码
请求域和应用域的选用原则 ?
尽量使用小的域对象,因为小的域对象占用的资源较少。
举例: 1.将数据存储到请求域当中,2.从请求域当中取出数据
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置,在浏览器上响应的格式类型
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
Date nowTime = new Date(); // 创建当前时间的 Date 对象
// 1. 将 nowTime 的数据存储(绑定)到请求域当中
request.setAttribute("sysTime",nowTime);
// 2. 取出请求域当中的数据: 这里的name值与上面setAttribute(String name,Object obj) 保持一致。
Object sysTime = request.getAttribute("sysTime");
writer.println(sysTime); // 显示到浏览器页面当中的数据
}
}
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>AServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.AServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AServlet</servlet-name>
<url-pattern>/A</url-pattern>
</servlet-mapping>
</web-app>
复制代码
关于 request 对象中两个非常容易混淆的方法:
// uri?username=zhangsan&userpwd=123&sex=1
String username = request.getParameter("username"); // 获取到的是前端用户提交的数据
// 之前一定是执行过:request.setAttribute("name", new Object()) // 将数据绑定/存储到请求域当中
Object obj = request.getAttribute("name");// 获取到的是绑定到请求域当中的数据
// 以上两个方法的区别是什么?
// 第一个方法:获取的是用户在浏览器上提交的数据。
// 第二个方法:获取的是请求域当中绑定的数据。
4.3 跳转 (两个 Servlet 共享数据)
如果我们想要将两个 Servlet 的请求域当中的数据共享:比如 将 AServlet 类当中的请求域存储的数据,在 BServelt 类当中将其存储到 AServlet 请求域当中的数据取出来。如下
观察,思考如下代码:是否可以实现 两个 Servlet 类的数据共享
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置,在浏览器上响应的格式类型
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
Date nowTime = new Date(); // 创建当前时间的 Date 对象
// 将 nowTime 的数据存储(绑定)到请求域当中
request.setAttribute("sysTime",nowTime);
// 这样做可以吗?
// 在AServlet当中new 一个BServlet对象,然后调用BServlet 对象的doGet()方法,把request 对象传过去
// 因为我们数据是存储该 request 请求域当中的。所以我们将该数据传给 BServlet ,让BServlet
// 将其中的请求域当中的数据取出来,这么做可以吗 ?
BServlet bServlet = new BServlet();
bServlet.doGet(request,response);
}
}
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置,在浏览器上响应的格式类型
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
// 取出请求域当中的数据: 这里的name值与上面setAttribute(String name,Object obj) 保持一致。
Object sysTime = request.getAttribute("sysTime");
writer.println("sysTime = " + sysTime); // 显示到浏览器页面当中的数据
}
}
复制代码
如下:运行结果
注意:
注意: 上述这个代码虽然可以实现功能,但是到那时 Servlet 对象不能自己由程序员来 new,因为自己 new 的 Servlet 的对象的生命周期不受 Tomcat 服务器的管理。如果不是被 Tomcat 管理了就无法实现合理的关闭销毁对应的 Servlet 的资源了。
哪要如何合理的将两个 Servlet 数据共享呢 ?*
可以,使用转发机制。
// 第一步:获取请求转发器对象
// 注意:转发的时候,路径的写法要注意,转发的路径以“/”开始,不加项目名。/ 后接 对应转发的Servelt 在web.xml配置文件当中 uRl 映射路径即可。
RequestDispatcher dispatcher = request.getRequestDispatcher("/B");
// 第二步:调用转发器的forward方法完成跳转/转发
dispatcher.forward(request,response);
// 第一步和第二步代码可以联合在一起。
request.getRequestDispatcher("/B").forward(request,response);
复制代码
举例:转发机制,将 AServlett 类当中的信息转发到 BServlet 当中去
package com.RainbowSea.servlet;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
public class AServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置,在浏览器上响应的格式类型
Date nowTime = new Date(); // 创建当前时间的 Date 对象
// 将 nowTime 的数据存储(绑定)到请求域当中
request.setAttribute("sysTime",nowTime);
// 第一步: 获取到转发对象,注意:/ 开始,不家项目名 , / + 对应跳转的 Servlet 当中的 web.xml 当中的url映射的路径
RequestDispatcher requestDispatcher = request.getRequestDispatcher("/B");
// 第二步: 调用转发器的forward方法完成跳转/转发
requestDispatcher.forward(request,response);
}
}
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class BServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置,在浏览器上响应的格式类型
response.setContentType("text/html;charSet=utf-8");
PrintWriter writer = response.getWriter();
// 取出请求域当中的数据: 这里的name值与上面setAttribute(String name,Object obj) 保持一致。
Object sysTime = request.getAttribute("sysTime");
writer.println("sysTime = " + sysTime); // 显示到浏览器页面当中的数据
}
}
复制代码
转发的下一个资源必须是一个 Servlet 吗 ?
不一定,只要是 Tomcat 服务器当中的合法资源,都是可以转发的。例如:html....
举例:转发一个 html 文件
注意: 如果对应的不是 Servlet ,默认是从项目的中的 web 目录开始的,如果是转发 web 的目录下的子目录的话,需要指定对应的子目录的文件。
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 转发的下一个资源不一定是Servlet 资源,
// 只要是Tomcat服务器当中合法的资源,都是可以转发的,例如: html...
// 注意:转发的时候,路径的写法要注意,转发的路径以 “/” 开始,不加项目名
// 默认是从项目的中的web目录开始的,如果是转发web的目录下的子目录的话,需要指定对应的子目录
// 如下是含有子目录的 / 表示 web目录
request.getRequestDispatcher("/test/test.html").forward(request,response);
}
}
复制代码
4.4 post 请求 request 乱码问题 ?
如下:如果我们在前端 post 请求中提交中文字符串信息,当我们在后端 Servlet 接受的时候,会存在一个乱码问题 ?但是英文不会存在这个现象。如下:所示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>Post 请求</h2>
<!--action = /项目名 + web.xml当中url的映射的路径-->
<form action="/servlet08//Test" method="post">
username: <input type="text" name="username"/> <br>
userpassword: <input type="password" name="userpswd"/> <br>
<input type="submit" value="post"/>
</form>
</body>
</html>
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
String username = request.getParameter("username");
System.out.println("username = " + username);
String userpswd = request.getParameter("userpswd");
System.out.println("userpswd = " + userpswd);
}
}
复制代码
如下我们:使用中文,提交数据出现,乱码。
解决方案:
在显示获取前端 post 请求时,执行如下代码就可以解决 post 乱码问题? 注意: 仅仅只能解决 Post 请求的乱码问题,不能解决 get 请求的乱码问题。
// post请求在请求体中提交数据。
// 设置请求体的字符集。(显然这个方法是处理POST请求的乱码问题。这种方式并不能解决get请求的乱码问题。)
// Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。
// Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?执行以下代码。
request.setCharacterEncoding("UTF-8");
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// post请求在请求体中提交数据。
// 设置请求体的字符集。(显然这个方法是处理POST请求的乱码问题。这种方式并不能解决get请求的乱码问题。)
// Tomcat10之后,request请求体当中的字符集默认就是UTF-8,不需要设置字符集,不会出现乱码问题。
// Tomcat9前(包括9在内),如果前端请求体提交的是中文,后端获取之后出现乱码,怎么解决这个乱码?执行以下代码。
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
System.out.println("username = " + username);
String userpswd = request.getParameter("userpswd");
System.out.println("userpswd = " + userpswd);
}
}
复制代码
4.5 response 响应到浏览器中的 乱码问题 ?
如果我们想在 Servlet 直接向前端页面中响应中文信息,会存在乱码问题。如下:
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 设置在页面当显示的格式类型
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<h1> 你好世界 <h1>");
}
}
复制代码
解决方案:
// 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码?可以在响应操作之前,
// 先执行如下代码,设置在页面当显示的格式,以及字符集编码
response.setContentType("text/html;charset=UTF-8");
// 在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了。以上代码就不需要设置UTF-8了。
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 在Tomcat9之前(包括9),响应中文也是有乱码的,怎么解决这个响应的乱码?
response.setContentType("text/html;charset=UTF-8");
// 在Tomcat10之后,包括10在内,响应中文的时候就不在出现乱码问题了。以上代码就不需要设置UTF-8了。
PrintWriter writer = response.getWriter();
writer.println("<h1> 你好 世界 <h1>");
}
}
复制代码
4.6 get 请求的 request 乱码问题 ?
如果我们使用 get 请求中,处理中文乱码问题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录</title>
</head>
<body>
<h2>get 请求</h2>
<!--action = /项目名 + web.xml当中url的映射的路径-->
<form action="/servlet08//Test" method="get">
username: <input type="text" name="username"/> <br>
userpassword: <input type="password" name="userpswd"/> <br>
<input type="submit" value="get"/>
</form>
</body>
</html>
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
String username = request.getParameter("username");
System.out.println("username = " + username);
String userpswd = request.getParameter("userpswd");
System.out.println("userpswd = " + userpswd);
}
}
复制代码
解决方案:
首先找我们安装的 Tomcat 的安装目录,在 conf 目录下,找到一个名为 server.xml
的文件名,如下
打开该 server.xml 文件,找到其中的如下这一段代码
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
复制代码
在该 server.xml 配置文件当中的上述对应的一段代码上,多添加上一个字符集设置 URIEncoding="UTF-8"
注意 />
结尾就可以了。
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8" />
复制代码
// get请求乱码问题怎么解决?
// get请求发送的时候,数据是在请求行上提交的,不是在请求体当中提交的。
// get请求乱码怎么解决
// 方案:修改CATALINA_HOME/conf/server.xml配置文件
<Connector URIEncoding="UTF-8" />
// 注意:从Tomcat8之后,URIEncoding的默认值就是UTF-8,所以GET请求也没有乱码问题了。
复制代码
4.7 HttpServletRequest 接口的其他常用方法
// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();。
// 获取应用的根路径
String contextPath = request.getContextPath();
// 获取请求方式
String method = request.getMethod();
// 获取请求的URI
String uri = request.getRequestURI();
// 获取 servlet path
String servletPath = request.getServletPath();
复制代码
举例上述方法的使用:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>com.RainbowSea.servlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/Test</url-pattern>
</servlet-mapping>
</web-app>
复制代码
package com.RainbowSea.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 获取 客户端的IP地址
String ip = request.getRemoteAddr();
System.out.println("客户端的ip地址: " + ip);
// 获取用户的请求方式
String method = request.getMethod();
System.out.println("用户的请求方式: " + method);
// 获取webapp的根路径
String contextPath = request.getContextPath();
System.out.println("webapp的根路径: " + contextPath);
// 获取请求的URI
String requestURI = request.getRequestURI();
System.out.println("请求的URI:" + requestURI);
// 获取 Servlet path 路径
String servletPath = request.getServletPath();
System.out.println("Servlet 的路径: " + servletPath);
}
}
复制代码
5. 补充:
在 WEB-INF 目录下新建了一个文件:welcome.html
打开浏览器访问:http://localhost:8080/servlet07/WEB-INF/welcome.html
出现了 404 错误。
6. 总结:
HTTP 的请求协议包括:如下 4 个部分: 请求行,请求头,空白行,请求体。
HTTP 的响应协议(S(浏览器) --> B(客户端))HTTP 的响应协议包括 4 部分 : 状态行,响应头,空白行,响应体 。
GET 请求和 POST 请求有什么区别 ?
不管你是 get 请求还是 post 请求,发送的请求数据格式是完全相同的,只不过位置不同,格式都是统一的:都是 :name=value&name=value&name=value&name=value
GET 请求和 POST 请求如何选择,什么时候使用 GET 请求,什么时候使用 POST 请求 ?
HttpServlet 类是专门为 HTTP 协议准备的。比 GenericServlet 更加适合 HTTP 协议下的开发。
HttpServlet 在哪个包下?这是在 Tomcat 10 的基础上:jakarta.servlet.http.HttpServlet
,而后面 Tomcat 9 之前的(包括 Tomcat 9 )以内的包是在 : javax.servlet.http.HttpServlet
包下的。
只要没有重写对应 HttpServlet 类中的 doGet 方法或 doPost 方法,就会报 405 错误,因为没有重写了 doGet 或 doPost 方法就会执行其中的 HttpServlet 当中编写的 doGet / doPost 方法,而如果执行了的是 HttpServlet 当中 doGet / doPost 方法就会报 405 错误,提示你没有重写 对应的 doGet / doPost 方法。怎么避免 405 的错误呢?
后端重写了 doGet 方法,前端一定要发 get 请求。后端重写了 doPost 方法,前端一定要发 post 请求。这样可以避免 405 错误。这种前端到底需要发什么样的请求,其实应该后端说了算。后端让发什么方式,前端就得发什么方式。
request 对象和 response 对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。一次请求对应一个 request。两次请求则对应两个 request。
后端如何获取前端浏览器用户提交的数据的常用方法。
Map<String,String[]> getParameterMap() 这个是获取用户提交的数据,并存储到Map集合当中<前端的name,前端的value>
Enumeration<String> getParameterNames() 这个是获取Map集合中所有的key就是前端中所有的name 值
String[] getParameterValues(String name) 根据(前端的name值)key获取Map集合的(前端的value值)value
String getParameter(String name) 获取value这个一维数组当中的第一个元素。这个方法最常用。
// 以上的4个方法,和获取用户提交的数据有关系
// 注意: 这里为什么 value 的值是用String[] 数组存储的。后面有说明。
复制代码
HttpServletRequest
对象又称为:"请求域" 请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。一个请求对象 request 对应一个请求域对象。一次请求结束之后,这个请求域就销毁了。如下是“请求域”的增删改操作的方法。
void setAttribute(String name, Object obj); // 向请求域当中绑定数据。
Object getAttribute(String name); // 从请求域当中根据name获取数据。 注意: 这里的参数是 你向请求域添加数据时,设置的对应数据的,setAttribute(String name ,Object obj)一个 name 值保持一致,尽可以使用复制的方式,防止手动编写错误。导致无法找到,从而为 null 值。
void removeAttribute(String name); // 将请求域当中绑定的数据移除
// 以上的操作类似于Map集合的操作。
Map<String, Object> map;
map.put("name", obj); // 向map集合中放key和value
Object obj = map.get("name"); // 通过map集合的key获取value
map.remove("name"); // 通过Map集合的key删除key和value这个键值对。
复制代码
跳转 (两个 Servlet 共享数据)
Post 请求以及 get 请求,response 响应的三者的中文乱码问题。
HttpServletRequest 接口的常用方法。
文章转载自:Rainbow-Sea
原文链接:https://www.cnblogs.com/TheMagicalRainbowSea/p/17285259.html
评论