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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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

目录
相关文章
|
21天前
|
缓存 监控 API
抖音抖店 API 请求获取宝贝详情数据的调用频率限制如何调整?
抖音抖店API请求获取宝贝详情数据的调用频率受限,需遵循平台规则。开发者可通过提升账号等级、申请更高配额、优化业务逻辑(如缓存数据、异步处理、批量请求)及监控调整等方式来应对。
|
23天前
|
缓存 负载均衡 API
抖音抖店API请求获取宝贝详情数据、原价、销量、主图等参数可支持高并发调用接入演示
这是一个使用Python编写的示例代码,用于从抖音抖店API获取商品详情,包括原价、销量和主图等信息。示例展示了如何构建请求、处理响应及提取所需数据。针对高并发场景,建议采用缓存、限流、负载均衡、异步处理及代码优化等策略,以提升性能和稳定性。
|
3天前
|
JSON API 数据格式
携程API接口系列,酒店景点详情请求示例参考
携程API接口系列涵盖了酒店预订、机票预订、旅游度假产品预订、景点门票预订等多个领域,其中酒店和景点详情请求是较为常用的功能。以下提供酒店和景点详情请求的示例参考
|
1月前
|
JavaScript 前端开发 Java
多种语言请求API接口方法
每种语言和库的选择取决于具体需求、项目环境以及个人偏好。了解这些基本方法,开发者就可以根据项目需求选择合适的语言和库来高效地与API交互。
37 1
|
1月前
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
43 4
|
2月前
|
JSON Go API
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
使用Go语言和Gin框架构建RESTful API:GET与POST请求示例
|
1月前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
21 0
|
4天前
|
JSON API 数据格式
淘宝 / 天猫官方商品 / 订单订单 API 接口丨商品上传接口对接步骤
要对接淘宝/天猫官方商品或订单API,需先注册淘宝开放平台账号,创建应用获取App Key和App Secret。之后,详细阅读API文档,了解接口功能及权限要求,编写认证、构建请求、发送请求和处理响应的代码。最后,在沙箱环境中测试与调试,确保API调用的正确性和稳定性。
|
16天前
|
供应链 数据挖掘 API
电商API接口介绍——sku接口概述
商品SKU(Stock Keeping Unit)接口是电商API接口中的一种,专门用于获取商品的SKU信息。SKU是库存量单位,用于区分同一商品的不同规格、颜色、尺寸等属性。通过商品SKU接口,开发者可以获取商品的SKU列表、SKU属性、库存数量等详细信息。
|
17天前
|
JSON API 数据格式
店铺所有商品列表接口json数据格式示例(API接口)
当然,以下是一个示例的JSON数据格式,用于表示一个店铺所有商品列表的API接口响应