Servlet运行原理_API详解_请求响应构造进阶之路(Servlet_2)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Servlet运行原理_API详解_请求响应构造进阶之路(Servlet_2)

Servlet创建项目流程

创建Servlet项目七步骤:


创建Maven项目

引入依赖(Servletjar包导入到pom.xml)

创建目录src/main/webapp/WEB-INF/web.xml

编写servlet代码

打包

部署(这里打包和部署可以通过引入smart tomcat插件完成)

验证

Servlet常见出错响应状态码

405 Method Not Allowed

方法不匹配


我们构造的doGet就只能用get请求处理!

doPost需要通过post请求处理!

如果不匹配响应就会访问这个405状态码!

image.png


可以看到我们需要用Post请求处理该servlet代码,而我们却直接输入url这样的方式就是直接通过get请求访问服务器!

image.png

当我们没有把调用父类下的doGet方法注释掉时,也会返回405状态码!

image.png

我们看一下源码就知道了!

image.png

这里父类的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直接将异常返回给客户端!

image.png

这里出bug了,但是浏览器还是将响应信息返回到浏览器上了!

image.png

如果我们将上面的响应信息给去掉,就可以看到返回的错误信息在页面上,通过这个错误可以精准找到我们的bug!

image.png


服务器未启动或者端口号被占用

image.png


出现这个错误,说明是TCP连接出现了问题!

image.png

这个错误说明我们该Servlet类的路径没有按指定规则编写!

image.png

我们需要加上/


Servlet运行原理

我们的Servlet代码连一个main方法都没有是怎么运行呢?

这里我们需要了解一下tomcat帮我们做的工作和处理机制!


tomcat定位

我们知道tomcat就是一个http服务器,而http是用户层协议!

所以我们的Servlet代码是基于tomcat 运行的!

image.png


当用户在浏览器中发送请求后,tomcat作为应用层服务器就可以接这个请求,而http只是一个应用层协议,需要通过其他层协议协助传输!

image.png

这里的传输过程也是要经过5层协议进行封装分用,这里和之前的一样!

image.png

我们分析一下上述流程


接收请求

我们浏览器客户端发送一个请求,然后用户的请求参数随着查询字符串或者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初始化流程

class Tomcat {
  // 用来存储所有的 Servlet 对象
  private List<Servlet> instanceList = new ArrayList<>();
  public void start() {
  // 根据约定,读取 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 服务器
  // 并用线程池的方式分别处理每一个 Request
  ServerSocket serverSocket = new ServerSocket(8080);
  // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况
  ExecuteService pool = Executors.newFixedThreadPool(100);
  while (true) {
  Socket socket = ServerSocket.accept();
  // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的
  pool.execute(new Runnable() {
  doHttpRequest(socket);
  });
  }
  // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次;
  for (Servlet ins : instanceList) {
  ins.destroy();
  }
  }
  public static void main(String[] args) {
  new Tomcat().start();
  }
}

这里就是tomcat初始化


我们看到这里tomcat其实是有main方法的,tomcat启动就从main方法开始!

启动后就会将@webServlet标记的类获取到,这些类已经是.class文件,需要通过反射机制创建好对应的实例,这些实例创建好就会调用init方法进行初始化,这个方法在HttpServlet类中,我们也可以重写这个方法!

这些请求处理业务完成后,就会将这些实例销毁调用其destroy方法,这里的方法也是在HttpServlet类中,我们也可以进行重写!

我们可以看到tomcat的内部也是调用操作系统中的socket进行网络通信的!

还有这里tomcat需要处理多个htttp请求,这里采取了多线程的方式,Servlet运行在多线程状态下的!


处理请求流程

class Tomcat {
  void doHttpRequest(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通过调用socketapi然后获取到http请求,然后将请求按照http协议报的格式解析成HttpServlet对象,然后通过url中的资源目录,获取到对应的ContentPath和Servlet路径获取到对应的文件,如果是静态资源就直接通过socket返回给客户端,如果是动态资源就会调用HttpServlet下的service方法,通过这个方法就可以调用对应的doGET/doPOST处理请求,然后再将计算对应的响应,最后返回!


Servlet 的service的实现

class Servlet {
  public void service(HttpServletRequest req, HttpServletResponse resp) {
  String method = req.getMethod();
  if (method.equals("GET")) {
  doGet(req, resp);
  } else if (method.equals("POST")) {
  doPost(req, resp);
  } else if (method.equals("PUT")) {
  doPut(req, resp);
  } else if (method.equals("DELETE")) {
  doDelete(req, resp);
  }
  ......
  }
}

这里就是根据对应的请求调用对应的请求处理方法,这里是通过多态的机制处理!


Servlet API详解

Servlet API有很多,我们只需要掌握HttpServlet/HttpServletRequset/HttpServletResponse这时个关键类中的核心方法即可!


HttpServlet

我们编写Servlet代码第一步就是继承HttpServlet类,并重写该类中的某些方法,处理请求!


核心方法:

image.png

而我们实际开发很少重写init/destory这些tomcat会帮我们调用!

我们主要任务还是处理对应请求,对不同方法请求,重写匹配的doXxx方法,处理不同的请求,返回对应的响应即可!


这些上述方法的调用时机,又称Servlert的生命周期!

image.png

这里的init方法,当HttpServlet实例化后就会通过该方法进行初始化,然后生命周期结束就是在destroy方法调用后将HttpServlet实例化对象销毁!期间可能要处理不同方法请求,所以可能会多次调用service方法!

注意:


HttpServlet实例只是在程序启动后创建一次就好了,并不是每次收到http请求都创建实例!


image.png

上述这些方法我们都可以进行重写,从而设置某些特有的属性,当时我们很少这样做,我们最常用的就是重写处理请求的方法doXXX!

代码示例


处理一个Get请求


我们分别通过ajax和form表单进行构造!


基于ajax

<!--引入jQuery-->
    <script src="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注释对应!

image.png



我们通过ajax的方式用前端构造请求,记得将这个html文件放入到webapp目录下,通过访问这个网页就向服务器发送了一个get方式的请求!

image.png

然后这里ajax通过回调的方式,如果请求成功,就会在控制台打印服务器给我们放回的body内容!

image.png


基于form表单

我们通过form表单构造一个post请求!


<form action="test" method="post">
        <input type="text" name="name">
        <input type="password" name="password">
        <input type="submit" name="post请求" value="post请求">
</form>

image.png

客户端:服务器


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/test")
public class Test extends HttpServlet {
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       resp.getWriter().write("hello world!");
   }
   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       String nameValue = req.getParameter("name");
       String passwordValue = req.getParameter("password");
       resp.getWriter().write("name:"+nameValue+" password:"+passwordValue);
   }
}

image.png

将响应输入到了浏览器上!


HttpServletResquest

我们知道HttpServletResquest类就是我们收到的请求,通过这个类将接收到了http的请求信息然后转换了对应http协议格式的字符串!我们通过该类提供的一些方法就可以获取到请求的报头信息还有内容了!


核心方法

image.png


上述方法,我们根据其英文意思和之前对http协议报头的学习就可以大概得出什么功能!

我们们通过上述方法就是为了得到一个http请求的报头和内容!

所以我们多使用上述方法就知道使用场景如何了!

代码示例


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
@WebServlet("/request")
public class HttpServletRequest extends HttpServlet {
    @Override
    protected void doGet(javax.servlet.http.HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String Protocolname = req.getProtocol();//返回协议名称和版本!
        String method = req.getMethod();//返回请求方法
        String url = req.getRequestURI();
        String QueuryString = 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>");
        }
    }
}

浏览器返回的结果!

image.png

这就是HttpServletRequest类中核心方法的使用!


HttpServletRespondse

这个类就是我们服务器用来返回响应的类!

我们Servlet处理请求的doxx方法,我们根据请求计算出响应,我们可以根据请求将响应信息通过该类中的方法构造好,然后该类方法的对象通过http协议格式,转化成一个字符串,并通过socket写会给浏览器!

核心方法:

image.png

代码示例


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: hold on
 * Date: 2022-06-30
 * Time: 13:03
 */
@WebServlet("/response")
public class HttpResponse extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");//设置响应内容类型,和编码方式!
        resp.setHeader("name","bug郭");//设置响应头部分,传入键值对信息!
        resp.setHeader("password","666666");
        resp.setStatus(404);//设置响应状态码!
        resp.getWriter().write("收到响应!");
    }
}

浏览器获取到的结果:

image.png


fiddler抓包获取到的响应

可以看到我们自己构造的响应头部分,出现了中文乱码!!!

但是我们刚刚不是已经设置过了编码方式utf8嘛?

为啥还这样呢?

image.pngx

我们看到这里的setContentType只是设置响应内容,就是body部分的格式和字符编码!我们的响应头部分中的属性一般都是已有的属性,一般没有中文,所以并不能设置!


resp.setStatus(304);//设置响应状态码重定向!(也可以省略)
  resp.sendRedirect("https://www.bilibili.com/");//重定向后跳转的网页

请求

image.png

重定向

image.png

fiddler抓取到的响应!

image.png

目录
相关文章
|
13天前
|
JavaScript Java 容器
servlet过滤器Filter简要回顾-过滤请求字符编码,/和/*和/**的区别
本文简要回顾了Servlet过滤器Filter的概念和使用,通过实例演示了如何创建过滤器以过滤请求字符编码,并解释了在web.xml中配置过滤器时使用`/`、`/*`和`/**`的区别。
servlet过滤器Filter简要回顾-过滤请求字符编码,/和/*和/**的区别
|
13天前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
13天前
|
API Python
4. salt-api请求salt-minion执行任务 tornado超时报错
4. salt-api请求salt-minion执行任务 tornado超时报错
|
19天前
|
JSON 资源调度 JavaScript
Vue框架中Ajax请求的实现方式:使用axios库或fetch API
选择 `axios`还是 `fetch`取决于项目需求和个人偏好。`axios`提供了更丰富的API和更灵活的错误处理方式,适用于需要复杂请求配置的场景。而 `fetch`作为现代浏览器的原生API,使用起来更为简洁,但在旧浏览器兼容性和某些高级特性上可能略显不足。无论选择哪种方式,它们都能有效地在Vue应用中实现Ajax请求的功能。
18 4
|
1天前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
7 0
|
2月前
|
API 开发者 Python
API接口:原理、实现及应用
本文详细介绍了API接口在现代软件开发中的重要性及其工作原理。API接口作为应用程序间通信的桥梁,通过预定义的方法和协议实现数据和服务的共享。文章首先解释了API接口的概念,接着通过Python Flask框架示例展示了API的设计与实现过程,并强调了安全性的重要性。最后,本文还讨论了API接口在Web服务和移动应用程序等领域的广泛应用场景。
|
2月前
|
存储 Kubernetes API
【APIM】Azure API Management Self-Host Gateway是否可以把请求的日志发送到Application Insights呢?让它和使用Azure上托管的 Gateway一样呢?
【APIM】Azure API Management Self-Host Gateway是否可以把请求的日志发送到Application Insights呢?让它和使用Azure上托管的 Gateway一样呢?
|
2月前
|
API C#
【Azure API 管理】APIM如何实现对部分固定IP进行访问次数限制呢?如60秒10次请求
【Azure API 管理】APIM如何实现对部分固定IP进行访问次数限制呢?如60秒10次请求
|
2月前
|
机器人 API Python
智能对话机器人(通义版)会话接口API使用Quick Start
本文主要演示了如何使用python脚本快速调用智能对话机器人API接口,在参数获取的部分给出了具体的获取位置截图,这部分容易出错,第一次使用务必仔细参考接入参数获取的位置。
131 1
|
18天前
|
安全 API 开发者
Web 开发新风尚!Python RESTful API 设计与实现,让你的接口更懂开发者心!
在当前的Web开发中,Python因能构建高效简洁的RESTful API而备受青睐,大大提升了开发效率和用户体验。本文将介绍RESTful API的基本原则及其在Python中的实现方法。以Flask为例,演示了如何通过不同的HTTP方法(如GET、POST、PUT、DELETE)来创建、读取、更新和删除用户信息。此示例还包括了基本的路由设置及操作,为开发者提供了清晰的API交互指南。
72 6