目录
本章重点
- 掌握创建
Servlet
项目的七步骤 - 了解
Servlet
运行原理 - 熟悉使用
Servlet
中的关键api
(HttpServlet/HttpServletRequest/HttpServletResponse
)的方法 - 熟悉用户层协议报文的设计
- 能够通过
Servlet
编写http
请求和响应 - 熟悉掌握在
tomcat
下部署web
项目
Servlet创建项目流程
创建
Servlet
项目七步骤:
- 创建
Maven
项目- 引入依赖(
Servlet
jar包导入到pom.xml
)- 创建目录
src/main/webapp/WEB-INF/web.xml
- 编写
servlet
代码- 打包
- 部署(这里打包和部署可以通过引入
smart tomcat
插件完成)- 验证
Servlet常见出错响应状态码
**
405
** Method Not Allowed
方法不匹配
我们构造的
doGet
就只能用
get
请求处理!
doPost
需要通过
post
请求处理!
如果不匹配响应就会访问这个
405
状态码!
可以看到我们需要用
Post
请求处理该
servlet
代码,而我们却直接输入
url
这样的方式就是直接通过
get
请求访问服务器!
当我们没有把调用父类下的
doGet
方法注释掉时,也会返回
405
状态码!我们看一下源码就知道了!
这里父类的
doGet
方法直接返回
405
!
***我们怎样区分
get
和
post
请求呢?***
get请求
- 直接在浏览器搜索框中输入
url
- 我们
html
下的<a>
标签,img/linkscript
标签等等! form
表单指定method
属性为get
ajax
构造get请求在type
设置为get
post请求
- 通过
form
表单,method
指定为post
ajax
构造post请求,type
指定为post
**
500
** Internal Server Error
服务器出错
这里的500状态码对我们初学者来说是很常见的,就是我们的服务器出错,也就是我们的
Servlet
代码发生异常并没有处理掉!这回将异常抛到
tomcat
而tomcat直接将异常返回给客户端!
这里出
bug
了,但是浏览器还是将响应信息返回到浏览器上了!
如果我们将上面的响应信息给去掉,就可以看到返回的错误信息在页面上,通过这个错误可以精准找到我们的bug!
服务器未启动或者端口号被占用
出现这个错误,说明是
TCP
连接出现了问题!
这个错误说明我们该
Servlet
类的路径没有按指定规则编写!
我们需要加上
/
Servlet运行原理
***我们的
Servlet
代码连一个
main
方法都没有是怎么运行呢?***
这里我们需要了解一下
tomcat
帮我们做的工作和处理机制!
tomcat定位
我们知道tomcat就是一个
http
服务器,而
http
是用户层协议!
所以我们的
Servlet
代码是基于
tomcat
运行的!
当用户在浏览器中发送请求后,
tomcat
作为应用层服务器就可以接这个请求,而
http
只是一个应用层协议,需要通过其他层协议协助传输!这里的传输过程也是要经过5层协议进行封装分用,这里和之前的一样!
我们分析一下上述流程
- 接收请求
我们浏览器客户端发送一个请求,然后用户的请求参数随着查询字符串或者body构造了一个
http
请求然后到达了用户层,用户层协议就是http,然后调用操作系统内核下的,
socket
api发送到网络层,网络层加上
TCP
报头,到达传输层加上
IP
协议报头,然后就传输到了数据链路层,加上帧头帧尾,最后到达物理层调用网卡设备将这些信息转换成光信号或者高低电平,通过网络设备传输到达服务器主机,服务器主机通过网卡接收到这一组信号解析成以太网数据帧,进行分用!层层解析最后解析成一个
http
请求并交给
tomcat
进程进行处理!
tomcat
拿到
http
协议报(字符串)按照协议报格式进行解析,根据
ContentPath
路径确定
webapp
,在通过
ServletPath
确定具体的类,根据请求的方法,决定调用
doGET/POST
方法,此时我们的
HttpServletResquest
对象就包含了这个请求的详细信息!
- 根据请求处理响应
我们通过
HttpServletRequest
中的请求信息,计算相应的响应信息,通过
HttpServletResponse
这个对象,存放响应信息!比如我们可以设置一些响应的状态码,body字段等!
- 返回响应
我们的
doGet/doPost
执行结束后,就会自动把
HttpServletResponse
以及我们已经设置的一些属性转换成相应的
http
响应,通过
socket
发送!后面的过程就是网络传输层层分用分装的过程,最后将响应中的body信息展现在浏览器上给用户!
tomcat伪代码
通过下面
tomcat
伪代码,了解tomcat初始化/接收请求两部分核心内容!
tomcat
初始化流程
classTomcat{// 用来存储所有的 Servlet 对象privateList<Servlet> instanceList =newArrayList<>();publicvoidstart(){// 根据约定,读取 WEB-INF/web.xml 配置文件;// 并解析被 @WebServlet 注解修饰的类// 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类.Class<Servlet>[] allServletClasses =...;// 这里要做的的是实例化出所有的 Servlet 对象出来;for(Class<Servlet> cls : allServletClasses){// 这里是利用 java 中的反射特性做的// 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的// 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是// 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。Servlet ins = cls.newInstance();
instanceList.add(ins);}// 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次;for(Servlet ins : instanceList){
ins.init();}// 利用我们之前学过的知识,启动一个 HTTP 服务器// 并用线程池的方式分别处理每一个 RequestServerSocket serverSocket =newServerSocket(8080);// 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况ExecuteService pool =Executors.newFixedThreadPool(100);while(true){Socket socket =ServerSocket.accept();// 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
pool.execute(newRunnable(){doHttpRequest(socket);});}// 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;for(Servlet ins : instanceList){
ins.destroy();}}publicstaticvoidmain(String[] args){newTomcat().start();}}
这里就是
tomcat
初始化
我们看到这里
tomcat
其实是有
main
方法的,tomcat启动就从
main
方法开始!
启动后就会将@webServlet
标记的类获取到,这些类已经是
.class
文件,需要通过反射机制创建好对应的实例,这些实例创建好就会调用
init
方法进行初始化,这个方法在
HttpServlet
类中,我们也可以重写这个方法!
这些请求处理业务完成后,就会将这些实例销毁调用其destroy
方法,这里的方法也是在
HttpServlet
类中,我们也可以进行重写!
我们可以看到tomcat
的内部也是调用操作系统中的
socket
进行网络通信的!
还有这里tomcat
需要处理多个
htttp
请求,这里采取了多线程的方式,
Servlet
运行在多线程状态下的!
- 处理请求流程
classTomcat{voiddoHttpRequest(Socket socket){// 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建HttpServletRequest req =HttpServletRequest.parse(socket);HttpServletRequest resp =HttpServletRequest.build(socket);// 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态
内容
// 直接使用我们学习过的 IO 进行内容输出if(file.exists()){// 返回静态内容return;}// 走到这里的逻辑都是动态内容了// 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条// 最终找到要处理本次请求的 Servlet 对象Servlet ins =findInstance(req.getURL());// 调用 Servlet 对象的 service 方法// 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了try{
ins.service(req, resp);}catch(Exception e){// 返回 500 页面,表示服务器内部错误}}}
我们这里的
tomcat
通过调用socket
api
然后获取到
http
请求,然后将请求按照
http
协议报的格式解析成
HttpServlet
对象,然后通过
url
中的资源目录,获取到对应的
ContentPath
和
Servlet
路径获取到对应的文件,如果是静态资源就直接通过
socket
返回给客户端,如果是动态资源就会调用
HttpServlet
下的
service
方法,通过这个方法就可以调用对应的
doGET/doPOST
处理请求,然后再将计算对应的响应,最后返回!
Servlet
的service
的实现
classServlet{publicvoidservice(HttpServletRequest req,HttpServletResponse resp){String method = req.getMethod();if(method.equals("GET")){doGet(req, resp);}elseif(method.equals("POST")){doPost(req, resp);}elseif(method.equals("PUT")){doPut(req, resp);}elseif(method.equals("DELETE")){doDelete(req, resp);}......}}
这里就是根据对应的请求调用对应的请求处理方法,这里是通过多态的机制处理!
Servlet API详解
Servlet API有很多,我们只需要掌握
HttpServlet/HttpServletRequset/HttpServletResponse
这时个关键类中的核心方法即可!
HttpServlet
我们编写
Servlet
代码第一步就是继承
HttpServlet
类,并重写该类中的某些方法,处理请求!
核心方法:
方法名称调用时机initHttpServlet实例化后就调用destoryHttpServlet实例不再使用就调用该方法service收到Http请求就调用,为了匹配相应的处理请求方法doGet/doPost/doPut…收到匹配的请求时,由
service
方法调用对应的方法!
而我们实际开发很少重写
init/destory
这些tomcat会帮我们调用!
我们主要任务还是处理对应请求,对不同方法请求,重写匹配的doXxx方法,处理不同的请求,返回对应的响应即可!
这些上述方法的调用时机,又称
Servlert
的生命周期!
这里的
init
方法,当
HttpServlet
实例化后就会通过该方法进行初始化,然后生命周期结束就是在
destroy
方法调用后将
HttpServlet
实例化对象销毁!期间可能要处理不同方法请求,所以可能会多次调用
service
方法!
注意:
HttpServlet
实例只是在程序启动后创建一次就好了,并不是每次收到
http
请求都创建实例!
上述这些方法我们都可以进行重写,从而设置某些特有的属性,当时我们很少这样做,我们最常用的就是重写处理请求的方法
doXXX
!
代码示例
处理一个
Get
请求
我们分别通过
ajax
和
form
表单进行构造!
- 基于
ajax
<!--引入jQuery--><scriptsrc="http://code.jquery.com/jquery-2.1.1.min.js"></script><script>//构造请求
$.ajax({type:'get',url:'test',//不要加/表示绝对路径!success:function(body){
console.log(body);//在浏览器控制台打印body信息!},error:function(){
console.log("请求失败");}});</script>
注意:
这里的
url
不用加
/
和
servlet
不同,而且这里的
url
就和我们的
@WebServlet
注释对应!
我们通过
ajax
的方式用前端构造请求,记得将这个
html
文件放入到
webapp
目录下,通过访问这个网页就向服务器发送了一个
get
方式的请求!
然后这里
ajax
通过回调的方式,如果请求成功,就会在控制台打印服务器给我们放回的
body
内容!
- 基于
form
表单
我们通过
form
表单构造一个
post
请求!
<formaction="test"method="post"><inputtype="text"name="name"><inputtype="password"name="password"><inputtype="submit"name="post请求"value="post请求"></form>
客户端:服务器
importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;@WebServlet("/test")publicclassTestextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequest req,HttpServletResponse resp)throwsServletException,IOException{
resp.getWriter().write("hello world!");}@OverrideprotectedvoiddoPost(HttpServletRequest req,HttpServletResponse resp)throwsServletException,IOException{String nameValue = req.getParameter("name");String passwordValue = req.getParameter("password");
resp.getWriter().write("name:"+nameValue+" password:"+passwordValue);}}
将响应输入到了浏览器上!
HttpServletResquest
我们知道
HttpServletResquest
类就是我们收到的请求,通过这个类将接收到了
http
的请求信息然后转换了对应
http
协议格式的字符串!我们通过该类提供的一些方法就可以获取到请求的报头信息还有内容了!
核心方法
上述方法,我们根据其英文意思和之前对
http
协议报头的学习就可以大概得出什么功能!
我们们通过上述方法就是为了得到一个
http
请求的报头和内容!
所以我们多使用上述方法就知道使用场景如何了!
代码示例
importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.util.Enumeration;@WebServlet("/request")publicclassHttpServletRequestextendsHttpServlet{@OverrideprotectedvoiddoGet(javax.servlet.http.HttpServletRequest req,HttpServletResponse resp)throwsServletException,IOException{StringProtocolname= req.getProtocol();//返回协议名称和版本!String method = req.getMethod();//返回请求方法String url = req.getRequestURI();StringQueuryString= req.getQueryString();//获取到查询字符串Enumeration<String> headernames = req.getHeaderNames();//请求header部分!
resp.setContentType("text/html;charset=utf8");//响应的格式以及编码方式!
resp.getWriter().write(Protocolname+"<br>"+method+"<br>"+url+"<br>"+QueuryString);while(headernames.hasMoreElements()){String headerKey = headernames.nextElement();//获取到header中的key值String headerVal = req.getHeader(headerKey);//通过key值找到val值!
resp.getWriter().write(headerKey+":"+headerVal+"<br>");}}}
浏览器返回的结果!
fiddler
抓取的响应
这就是
HttpServletRequest
类中核心方法的使用!
HttpServletRespondse
这个类就是我们服务器用来返回响应的类!
我们
Servlet
处理请求的
doxx
方法,我们根据请求计算出响应,我们可以根据请求将响应信息通过该类中的方法构造好,然后该类方法的对象通过
http
协议格式,转化成一个字符串,并通过
socket
写会给浏览器!
核心方法:
代码示例
importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/**
* Created with IntelliJ IDEA.
* Description:
* User: hold on
* Date: 2022-06-30
* Time: 13:03
*/@WebServlet("/response")publicclassHttpResponseextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequest req,HttpServletResponse resp)throwsServletException,IOException{
resp.setContentType("text/html;charset=utf8");//设置响应内容类型,和编码方式!
resp.setHeader("name","bug郭");//设置响应头部分,传入键值对信息!
resp.setHeader("password","666666");
resp.setStatus(404);//设置响应状态码!
resp.getWriter().write("收到响应!");}}
浏览器获取到的结果:
fiddler
抓包获取到的响应
可以看到我们自己构造的响应头部分,出现了中文乱码!!!
但是我们刚刚不是已经设置过了编码方式
utf8
嘛?
为啥还这样呢?
我们看到这里的
setContentType
只是设置响应内容,就是body部分的格式和字符编码!我们的响应头部分中的属性一般都是已有的属性,一般没有中文,所以并不能设置!
resp.setStatus(304);//设置响应状态码重定向!(也可以省略)
resp.sendRedirect("https://www.bilibili.com/");//重定向后跳转的网页
请求
重定向
fiddler
抓取到的响应!
版权归原作者 bug 郭 所有, 如有侵权,请联系我们删除。