命运总是不如人愿,但往往是在无数的痛苦总,在重重的矛盾和艰辛中,才是人成熟起来。
你,为这瞬间的辉煌,忍耐了多少暗淡无光的日月,你会死亡,但你也会证明生命有多强大。
一次邂逅,一次目光的交融,就是永远的合二为一,就是与上帝的契约;总是风暴雷电,
也无法分解这种心灵的粘结。
直到在哪远离故乡的地方发生过那场刻苦铭心的感谢悲剧后,他才理解了人活在世界上有多少幸福,
又有多少苦难!生活不能等待别人来安排,要自己去争取和奋斗;而不论其结果是喜和悲,但可以慰籍,你总不枉在这
世界上活了一场,有了这样的认识,你就会珍重生活,而不会玩世不恭;同时也会给人自身注入一种强大的内在力量......
—————— 《平凡的世界》路遥
什么是协议?
- 协议实际上是某些人,或者某些组织提前制定好的一套规范,大家都按照这个规范来,这样可以做到沟通无障碍。
- 协议就是一套规范,就是一套标准。由其他人或其他组织来负责制定的。
- 我说的话你能听懂,你说的话,我也能听懂,这说明我们之间是有一套规范的,一套协议的,这套协议就是:中国普通话协议。我们都遵守这套协议,我们之间就可以沟通无障碍。但是如果我们遵守不同的协议规范的话,就无法通信了,比如:你说的是阿拉伯语,我说的是汉语,我听不懂你说什么,你也听不同我说什么。你我两者之间无法通信交流。
什么是超文本 ?
超文本说的就是:不是普通文本,比如流媒体:声音、视频、图片等。
HTTP协议支持:不但可以传送普通字符串,同样支持传递声音、视频、图片等流媒体信息。
这种协议游走在B (浏览器)和S (服务器)之间。B向S发数据要遵循HTTP协议。S向B发数据同样需要遵循HTTP协议。这样B和S才能解耦合。
什么是解耦合?
这里指定是: B (浏览器) 不 依赖 S(服务器) ,S 也不依赖 B
B/S表示:B/S结构的系统(浏览器访问WEB服务器的系统)
HTTP协议的请求 (request) 和 响应(response)
HTTP协议包括:
HTTP协议总结:
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版本号 。
请求行:第一部分:请求方式 (7种): get(常用的),post(常用的),delete,put,head,options,trace 。
请求行:第二部分: URI
URL : 统一资源定位符。 代表网络中某个资源。注意: 可以通过 URL 定位到该资源,可以直接在浏览器当中输入URI 访问网络当中的资源)。比如: http://localhost:8080/servlet05/index.html
这是URL。
URI : 统一资源标识符。代表网络中某个资源的名字。注意: URI 是无法定位资源的(就是无法通过直接在浏览器当中输入URI 访问网络当中的资源)。比如:/servlet05/index.html 这是URI。
URI 和 URL 什么关系,有什么区别?
URL 是包括了 URI 的。
请求头: 包含 : 请求的主机的IP地址,主机的端口号,浏览器信息,平台信息,cookie 等信息。
空白行: 空白行是用来区分“请求头”和“请求体”
请求体: 向服务器发送的具体数据。
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>
状态行 : 由三部分组成:协议版本号,状态码,状态描述信息。
如下是一些常见的状态码的意义:
- 200 : 表示请求响应成功,正常结束。
- 404: 表示访问的资源不存在,通常是因为要么你填写的路径写错了,要么是你路径写对了,但是服务器中对应的 资源并没有启动成。总之 404 错误是基本是前端错误。
- 405 : 表示前端发送的请求方式与后端接受处理(该请求)的方式不一致时发生的:基本上时如下两种情况:
- 前端是POST请求,后端的处理方式按照get方式进行处理时,发生405
- 前端是GET请求,后端的处理方式按照post方式进行处理时,发生405
- 具体的该文章后面有详细的说明。所以请不要走开。
- 500 表示服务器端的程序出现了异常。一般会认为是服务器端的错误导致的。
- 总结:
- 以4开始的,一般是浏览器端的错误导致的。
- 以5开始的,一般是服务器端的错误导致的。
状态行,第三部分: 状态的描述信息:
空白行: 用来分隔“响应头”和“响应体”的。
响应体: 响应体就是响应的正文,这些内容是一个长的字符串,这个字符串被浏览器渲染,解释并执行,最终展示出效果。简单的说就是html 对应的源代码。
怎么向服务器发送Get 请求,怎么向服务器发送 Post 请求?
到目前为止,只有一种情况可以发送post 请求:就是用 form 表单,并且 form 标签当中的 method 的属性值必须为
method = "post"
才行。其他所有情况一律都是get请求:
- 在浏览器地址栏上直接输入URL,敲回车,属于get请求。
- 在浏览器上直接点击超链接,属于get请求。
- 使用form表单提交数据时,form标签中没有写method属性,默认就是get
- 或者使用form的时候,form标签中method属性值为:method=“get”
- …
GET请求和POST请求有什么区别 ?
Get请求:
"?"
," ? " 后面的就是数据了,这样会导致发送的数据回显到浏览器的地址栏上如下显示的:。(get 请求在 “请求行”上发送数据)。补充点:
- 任何一个get请求最终的“响应结果”都会被浏览器缓存起来。在浏览器缓存当中:
- 一个get请求的路径a 对应 一个资源。
- 一个get请求的路径b 对应 一个资源。
- 一个get请求的路径c 对应 一个资源。
- …
- 实际上,你只要发送get请求,浏览器做的第一件事都是先从本地浏览器缓存中找,找不到的时候才会去服务器上获取。这种缓存机制目的是为了提高用户的体验。
- 有没有这样一个需求:我们不希望get请求走缓存,怎么办?怎么避免走缓存?我希望每一次这个get请求都去服务器上找资源,我不想从本地浏览器的缓存中取。
- 只要每一次get请求的请求路径不同即可。
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897898
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=789789787897899
- https://n.sinaimg.cn/finance/590/w240h350/20211101/7cabc342ff5b9dc018b4b00cc.jpg?t=系统毫秒数
- 怎么解决?可以在路径的后面添加一个每时每刻都在变化的“时间戳”,这样,每一次的请求路径都不一样(缓存的路径也是不一样的),浏览器就不走缓存了。
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>
HttpServlet类是专门为 HTTP协议准备的。比 GenericServlet更加适合 HTTP协议下的开发。
HttpServlet在哪个包下?
到目前为止我们接触了servlet规范中哪些接口 ?。注意: 如下的是基于 Tomcat 10 的基础上的,而 Tomcat 9 之前的包括 9 将如下的 jakarta
替换为 javax
就可以了,具体原因大家可以移步至:javaEE Web(Tomcat)深度理解 和 Servlet的本质_ChinaRainbowSea的博客-CSDN博客 中。
http包下都有哪些类和接口呢?jakarta.servlet.http.*
HttpServletRequest对象中封装了什么信息?
HttpServletRequest 简称为 request
对象。
HttpServletRequest 中封装了请求协议的全部内容。
request
对象当中了。也就是说,我们只要面向 HttpServletRequest 接口编程,就可以获取请求协议当中的数据了。HttpServletRequest,简称 request
对象。
HttpServletResponse对象 是专门用来响应HTTP协议到浏览器的。
回忆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方法好了。这样代码还能
少写一点。
*/
上面我们在: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>
<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("Hello World"
);
}
}
如下运行结果: 报了 405 错误,原因是:我们前后端的处理请求的不一致。
当:前端发送的请求是post请求,后端程序员重写的方法是doGet 出现的错误
对于前端用户提交数据的html 代码设计如下:注意:这里我们前端提交的是 post
请求。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录title>
head>
<body>
<h2>post 请求h2>
<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("Hello World"
);
}
}
如下运行结果: 报了 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("Hello World"
);
}
}
<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>HttpServletTestservlet-name>
<servlet-class>com.RainbowSea.servlet.HttpServletTestservlet-class>
servlet>
<servlet-mapping>
<servlet-name>HttpServletTestservlet-name>
<url-pattern>/Testurl-pattern>
servlet-mapping>
web-app>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录title>
head>
<body>
<h2>get 请求h2>
<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>
HttpServletRequest是一个接口,全限定名称:jakarta.servlet.http.HttpServletRequest
这个是基于 Tomcat 10 下的对应的包路径,如果是 Tomcat 9 (包括9)就是将其中的 jakarta
修改为 javax
就可以了。
HttpServletRequest接口是Servlet规范中的一员。
HttpServletRequest接口的父接口:ServletRequest
public interface HttpServletRequest extends ServletRequest {}
HttpServletRequest接口的实现类谁写的? HttpServletRequest对象是谁给创建的?
org.apache.catalina.connector.RequestFacade
实现了 HttpServletRequest接口public class RequestFacade implements HttpServletRequest {}
测试结果说明:Tomcat服务器(WEB服务器、WEB容器)实现了HttpServletRequest接口,还是说明了Tomcat服务器实现了Servlet规范。而对于我们javaweb程序员来说,实际上不需要关心这个,我们只需要面向接口编程即可。我们关心的是HttpServletRequest接口中有哪些方法,这些方法可以完成什么功能!!!!
request和response对象的生命周期?
- request对象和response对象,一个是请求对象,一个是响应对象。这两个对象只在当前请求中有效。
- 一次请求对应一个request。
- 两次请求则对应两个request。
- …
我们后端如何怎么获取前端浏览器用户提交的数据 ?
想要获取到前端浏览器用户提交的数据,使用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 registertitle>
head>
<body>
<h1>user registerh1>
<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
文件中去。
<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>RequestServletTestservlet-name>
<servlet-class>com.RainbowSea.servlet.RequestServletTestservlet-class>
servlet>
<servlet-mapping>
<servlet-name>RequestServletTestservlet-name>
<url-pattern>/Requesturl-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 枚举泛型中
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) { // 判断是否还有数据,有返回 true,没有返回 false
String name = names.nextElement(); // 获取到其中上述 Enumeration 存储到的元素数据,
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这个一维数组当中的第一个元素。这个方法最常用。
举例如下:
HttpServletRequest 对象实际上又称为“请求域”对象。
这里我们回顾一下应用域对象是什么 ?
ServletContext 应用域对象(Servlet 上下文对象)。
什么情况下会考虑向 ServletContext 这个应用域当中绑定数据呢 ?
- 第一:所有用户的共享数据
- 第二:这个共享数据量很少
- 第三:这个共享数据很少进行修改操作,尽可能没有修改操作。
- 在以上三个条件都满足的情况下,使用这个应用域对象,可以大大提高我们程序执行效率。
- 实际上向应用域当中绑定数据,就相当于把数据放到了缓存(Cache) 当中,然后用户访问的时候直接从缓存中取,减少 IO 的操作(IO访问磁盘,其耗费的时间代价十分的大),大大提升系统的性能,所以缓存技术是提高系统性能的重要手段。
- 关于 ServletContext 具体的使用大家可以移步至: 解读 Servlet 源码:GenericServlet,ServletConfig,ServletContext_ChinaRainbowSea的博客-CSDN博客
你见过哪些缓存技术呢 ?
- 字符串常量池
- 整数型常量池
[-128~127]
,但凡是在这个范围当中Integer
对象不再创建新对象,而是直接从这个整数型常量池中获取,大大提升系统性能。- 数据库连接池(提前创建好 N 个连接对象,将连接对象放到集合当中,使用连接对象的时候,直接从缓存中拿,省去了连接对象的创建过程,效率提升。)
- 线程池(Tomcat 服务器就是支持多线程的),所谓的线程池就是提前先创建好 N 个线程对象,将线程对象存储到集合中,然后用户直接去线程池当中获取线程对象,直接拿来用。提升系统性能。)
- 后期你还会学习更多的缓存技术,例如:redis、mongoDB…
拉回来,这里我们的主角是 "请求域"
对象:
“请求域”对象要比“应用域”对象范围小很多。生命周期短很多。请求域只在一次请求内有效。
一个请求对象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这个键值对。
请求域和应用域的选用原则 ?
尽量使用小的域对象,因为小的域对象占用的资源较少。
举例: 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); // 显示到浏览器页面当中的数据
}
}
<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>AServletservlet-name>
<servlet-class>com.RainbowSea.servlet.AServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>AServletservlet-name>
<url-pattern>/Aurl-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");// 获取到的是绑定到请求域当中的数据 // 以上两个方法的区别是什么? // 第一个方法:获取的是用户在浏览器上提交的数据。 // 第二个方法:获取的是请求域当中绑定的数据。
如果我们想要将两个 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);
}
}
如下:如果我们在前端 post 请求中提交中文字符串信息,当我们在后端 Servlet 接受的时候,会存在一个乱码问题 ?但是英文不会存在这个现象。如下:所示
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录title>
head>
<body>
<h2>Post 请求h2>
<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);
}
}
如果我们想在 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(" 你好世界 "
);
}
}
解决方案:
// 在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(" 你好 世界 "
);
}
}
如果我们使用 get 请求中,处理中文乱码问题。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTTP请求登录title>
head>
<body>
<h2>get 请求h2>
<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请求也没有乱码问题了。
// 获取客户端的IP地址
String remoteAddr = request.getRemoteAddr();。
// 获取应用的根路径
String contextPath = request.getContextPath();
// 获取请求方式
String method = request.getMethod();
// 获取请求的URI
String uri = request.getRequestURI();
// 获取 servlet path
String servletPath = request.getServletPath();
举例上述方法的使用:
<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>TestServletservlet-name>
<servlet-class>com.RainbowSea.servlet.TestServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>TestServletservlet-name>
<url-pattern>/Testurl-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);
}
}
在WEB-INF目录下新建了一个文件:welcome.html
打开浏览器访问:
http://localhost:8080/servlet07/WEB-INF/welcome.html
出现了404错误。
- 注意:放在WEB-INF目录下的资源是受保护的。在浏览器上不能够通过路径直接访问。所以像HTML、CSS、JS、image等静态资源一定要放到WEB-INF目录之外。
- 如果非要访问的话,也有方法,详细内容请关注我,后续为您更新。
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这个键值对。
限于自身水平,其中存在的错误,希望大家给予指教,韩信点兵——多多益善,谢谢大家,后会有期,江湖再见!!!