JavaSec - Java Web - 三大核心组件 - Servlet

JavaSec - Java Web - 三大核心组件 - Servlet

本篇作为JavaSec的基础内容学习中,Java web中的三大核心组件介绍的第一篇Servlet,主要分享学习Servlet在Java web工程中的所承担的职责,以及开始了解Java web工程,以便之后更好的了解漏洞所处的工程环境

Figure 1: Servlet Summary

Figure 1: Servlet Summary

什么是Servlet

“A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.” – Java EE API

根据Java EE的文档说明,Servlet是运行在Web服务器上的一个Java程序,主要负责接收和处理从Web客户端(浏览器)发出的请求(POST & GET),一般通过HTTP。

我们知道在PHP当中,当用户填写表达并提交,一般就会将相应的请求发送到服务端对应的php脚本上,然后php脚本会根据 $_GET 以及 $_POST 数组(以及其他请求)来获取请求的内容并进行处理。

Servlet就是Java中用来处理来自客户端的GET以及POST请求(以及其他请求)的一个程序。

Servlet的生命周期

“This interface defines methods to initialize a servlet, to service requests, and to remove a servlet from the server. These are known as life-cycle methods and are called in the following sequence:

The servlet is constructed, then initialized with the init method. Any calls from clients to the service method are handled. The servlet is taken out of service, then destroyed with the destroy method, then garbage collected and finalized.”

既然我们之前说Servlet作为一个程序,那么它就一定有一个生命周期,那么根据文档,Servlet程序的生命周期如下:

  1. 加载,当Tomcat第一次访问Servlet资源时,执行 Servlet 构造器方法,创建Servlet实例
  2. 初始化:执行 init() 初始化方法
    • 当Servlet程序第一次收到客户端请求时,会依次执行构造器方法和 init() 方法
    • 之后就不用再次执行
  3. 执行 service() 方法 每次收到来自客户端的请求时都会执行
  4. 执行 destroy() 销毁方法 当 web 工程停止的时候时执行,释放内存

如何使用Servlet

“To implement this interface, you can write a generic servlet that extends javax.servlet.GenericServlet or an HTTP servlet that extends javax.servlet.http.HttpServlet.”

接着我们就要知道,具体要如何实现并使用Servlet来帮助web程序来处理来自客户端的请求呢?

一般来说,我们都是通过直接继承 HttpServlet 这个类,并且重写 doGet() 以及 doPost() 这两个方法来实现对于不同请求的处理

Figure 2: Servlet的继承关系

Figure 2: Servlet的继承关系

记得我们之前说过service()方法是用来处理客户端的各种请求的,下面的代码就很好的说明了 service() 方法是如何区分各种请求,并且与对应的 doGet(), doPost() 等方法配合的。

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        this.service(request, response);
    } else {
        throw new ServletException("non-HTTP request or response");
    }
}

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String method = req.getMethod();
    long lastModified;
    if (method.equals("GET")) {
        lastModified = this.getLastModified(req);
        if (lastModified == -1L) {
            this.doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader("If-Modified-Since");
            if (ifModifiedSince < lastModified) {
                this.maybeSetLastModified(resp, lastModified);
                this.doGet(req, resp);
            } else {
                resp.setStatus(304);
            }
        }
    } else if (method.equals("HEAD")) {
        lastModified = this.getLastModified(req);
        this.maybeSetLastModified(resp, lastModified);
        this.doHead(req, resp);
    } else if (method.equals("POST")) {
        this.doPost(req, resp);
    } else if (method.equals("PUT")) {
        this.doPut(req, resp);
    } else if (method.equals("DELETE")) {
        this.doDelete(req, resp);
    } else if (method.equals("OPTIONS")) {
        this.doOptions(req, resp);
    } else if (method.equals("TRACE")) {
        this.doTrace(req, resp);
    } else {
        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[]{method};
        errMsg = MessageFormat.format(errMsg, errArgs);
        resp.sendError(501, errMsg);
    }

}

ServletConfig 配置

In addition to the life-cycle methods, this interface provides the getServletConfig method, which the servlet can use to get any startup information, and the getServletInfo method, which allows the servlet to return basic information about itself, such as author, version, and copyright.

Servlet程序有两种配置形式,一种是使用在项目目录下 WEB-INF 下的 web.xml 文件中使用 <servlet> 系列标签进行配置,另一种是使用注解方式来进行配置(Servelet 3.0之后,Tomcat7+)

web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <servlet-name>HelloServlet</servlet-name>
        <servlet-class>com.example.jsplearn.HelloServlet</servlet-class>
        <init-param>
            <param-name>username</param-name>
            <param-value>root</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>HelloServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>
</web-app>

注解配置

@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
    private String message;

    public void init() {
        message = "Hello World!";
    }
......

可以配置的信息包括启用的Servlet程序所对应的类以及访问的url路径(servlet-name, servlet-class, servlet-mapping, url-paatern),还可以配置一个Servlet在初始化时所初始化的一些变量(init-param)。

回顾一下之前的Servlet继承关系,我们说GenericServlet已经开始定义配置信息了,是因为它实现了 ServletCofnig 接口,因此servlet对象可以通过以下的get方法来获取配置信息,同时还可以同通过 getServletconfig() 方法来返回一个 ServletConfig 对象

Figure 3: ServletConfig 接口

Figure 3: ServletConfig 接口

这里值得一提的是,ServletConfig对象是在一个Servlet程序启动时,在 init() 方法中被创建并保存的,因此如果在重写Servlet类的init()方法时,如果缺失了这一块,就会导致错误。

同时,getSevletConfig只能获取得到当前Servlet对象的ServletConfig对象,不同的Servlet类之间是不相同的

ServletConfig类的三大作用

  1. 获取Servlet程序的别名 servlet-name (web.xml配置中的值)
  2. 获取初始化参数 init-param
  3. 获取ServletContext对象

ServletContext

什么是ServletContext?

  1. ServletContext是一个接口,表示Servlet上下文对象;
  2. 一个web工程,只有一个ServletContext对象实例,在所有的Servlet类中都可以获取到该唯一实例中的所有数据;
  3. ServletContext对象是一个域对象(域对象的作用:保存数据,获取数据,共享数据);
  4. ServletContext在web工程部署启动的时候创建,在web工程停止的时候销毁。

ServletContext的作用

  1. 获取 web.xml 配置文件中的 context-param 参数的值

    • context.getInitParameter(String s)
    • context-param的值保存在 ServletContext 对象中,一个项目下的所有Servlet都可以获取到
    • 以context(上下文)结尾的类或者接口大多都承担存储整一个工程中的一些通用信息的职责
  2. 获取当前的工程路径:/路径

    • context.getContextPath()
    • http(s)//ip:port/[工程路径]
  3. 获取工程部署在服务器硬盘上的绝对路径

    • context.getRealPath("/")
    • 以IDEA举例,工程在部署之后,IDEA会将一个Module模块下的 webapp 目录下的文件以及编译成 .class 的字节码拷贝到一个对应的硬盘位置
    • /表示webapp目录对应的工程部署之后的存储目录
    • 也可以添加其他webapp目录下的其他资源或者目录的路径
  4. 存取数据(向Map一样)

    • void setAttribute(String var1, Object var2);
    • Object getAttribute(String var1);
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    super.service(req, res);
    // 获取 context-para参数的值
    ServletContext context = getServletContext();
    System.out.println("Get context parameters, username: " + context.getInitParameter("username"));
    System.out.println("Get context parameters, username: " + context.getInitParameter("password"));

    // 获取当前的工程路径
    System.out.println("Get project path: " + context.getContextPath());

    // 获取工程部署在服务器硬盘上的绝对路径
    System.out.println("Get project real path on server's disk: " + context.getRealPath("/"));
}

存取数据

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ServletContext context = getServletContext();
    context.setAttribute("key_str", "A");
    context.setAttribute("key_int", 2);
    System.out.println("获取Context中的存储的值, key_str: " + context.getAttribute("key_str"));
    System.out.println("获取Context中的存储的值, key_int: " + context.getAttribute("key_int"));

}

HttpServletRequest

接下来我们会来具体地看看Servlet是如何处理来自客户端的请求的。

@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}

首先还是回到doGet以及doPost请求,我们看到他们都有共同的参数 HttpServletRequest以及HttpServletResponse

HttpServletRequest的作用

每当Tomcat服务器收到来自客户端的请求时,服务器都会将用户的HTTP请求封装到 Request的对象中并传递到service方法中供程序员进行处理,我们可以通过该HttpServletRequest对象获取所有请求的信息

HttpServletRequest API

Method Description|
getRequestURI() 获取请求的资源路径
getRequestURL() 获取请求的统一资源定位符(绝对路径)
getRemoteHost() 获取客户端的ip地址
getHeader() 获取请求头
getParameter 获取请求的参数
getParameterValues 获取请求的参数(多个值的时候使用)
getMethod() 获取请求的方式(GET/POST)
setAttribute(key, value) 设置域数据
getAttribute(key) 获取域数据
getRequestDispatcher() 获取请求转发对象
setCharacterEncoding() 设置字符集(防止出现POST请求的中文乱码问题)
注意:设置字符集需要在获取请求参数之前设置才有效

请求转发

当我们需要创建多个Servlet完成复杂的业务操作时,就要使用请求的转发。

首先我们要明确对于每次客户端的请求都只维护唯一个HttpServletRequest对象,这个request对象将在业务流水线上的所有Servlet之间传递。

每个Servlet首先完成自己的业务逻辑,然后需要使用 getRequestDispatcher(str) 获取调度器,进而使用RequestDispatcher forward(request, response)来完成转发

值得注意的是,使用的调度器只能在工程目录下寻找(因为是斜杠打头),并且由于WEB-INF是受保护的目录,因此用户在客户端无法直接访问WEB-INF下的资源

getRequestDispatcher(str) 获取调度器,请求转发到另一个资源(以斜杠打头)
/ 斜杠表示地址为:http://ip:port/%E5%B7%A5%E7%A8%8B%E5%90%8D, 部署后所映射到的web目录
RequestDispatcher forward(request, response) 转发请求

值得注意的是,使用的调度器只能在工程目录下寻找(因为是斜杠打头),并且由于WEB-INF是受保护的目录,因此用户在客户端无法直接访问WEB-INF下的资源

第二点需要关注的是,在使用请求转发以及其他情况下发生了地址栏URL的变化的时候,可能会导致一些相对路径的地址发生跳转错误,这时候可以使用<base href>标签来将一个页面的相对路径固定,从而避免错误

HttpServletResponse

对于每一个用户的HTTP请求,Tomcat服务器都会创建唯一一个Response对象在Servlet之间传递,用于表示所有的响应信息。

两个输出流

那么当Tomcat服务器收到并处理了用户的请求之后,就需要做出响应,通常都需要返回给用户资源,那么根据返回资源类型的不同,Java Web提供了两种输出流

get Method Description
getOutputStream() 回传二进制数据
getWriter() 回传字符串数据

只能获取一个输出流,因此这两个方法不能同时使用,否则会出现报错(服务器错误,500)

如何往客户端回传数据

那么得到了返回流对象之后,还需要使用回传的具体方法来进行输出

  1. write()
  2. printWriter()

结语

通过对于Servlet的学习,我们知道了Servlet是作为Java web中是web服务端用来处理客户端的各种请求的一个中间件,我们一直都说要重视用户的输入,因此其的安全至关重要。

Servelet中各个组件的关系如下图所示:

Figure 4: Servlet Summary

Figure 4: Servlet Summary

我们也开始了解了一些Java web开发中的一些组成结构,也体验了一下实际开发中的一些流程与操作,更加具体的使用这里暂时就不具体展开了。

补充内容

Cookie是服务器通知客户端保存键值对的一种技术 客户端有了Cookie之后,每次请求都发送给服务器 每个cookie的大小不能超过4KB

Figure 5: Cookie的创建

Figure 5: Cookie的创建

  • 服务器获取Cookie

    返回数组进行遍历之后才能获取具体的Cookie的值

  • 修改Cookie

    1. 构造新的Cookie对象来覆盖原有Cookie
    2. 查找需要修改的的Cookie对象,并用setValue方法赋予Cookie新的值(注意有一定的格式要求),最后调用response.addCookie()通知客户端保存修改的Cookie
  • Cookie的生命控制

    Cookie的生命控制指的是如何管理Cookie什么时候被销毁(删除)

    由SetMaxAge()方法来控制

    public void setMaxAge(int expiry)

    Sets the maximum age in seconds for this Cookie.

    A positive value indicates that the cookie will expire after that many seconds have passed. Note that the value is the maximum age when the cookie will expire, not the cookie’s current age.

    A negative value means that the cookie is not stored persistently and will be deleted when the Web browser exits.

    A zero value causes the cookie to be deleted. 马上删除

  • Cookie有效路径path的设置

    使用path属性可以有效地过滤哪些Cookie可以发送给服务器,哪些不发

    对于单次的请求来说,只有path满足当前请求地址的cookie,才会被一起发送

  • Cookie免密登陆

    Figure 7: Cookie免密登陆

    Figure 7: Cookie免密登陆

Session

Session在Java中是一个接口,是一个会话,用来维护 一个客户端 和服务器之间的关联技术。

Session会话中,我们经常用来保存用户登陆之后的信息(在服务端)

  • 创建和获取Session

    request.getSession() 第一调用时创建Session会话,之后再调用,都会获取前面创建好的Session会话对象。

    isNew():判断到底是不是刚创建出来的

    每个会话都有一个ID值来唯一标识一个Session getId() 来获取ID

  • Session域的数据存取

    我们知道Session是保存在服务器端保存的用户登陆之后的信息

    既然是域对象,那么API与之间我们见过的也是一致的

    setAttribute(key, value) getAttribute(key)

  • Session生命周期控制

    void setMaxInactiveInterval(int interval)

    Specifies the time, in seconds, between client requests before the servlet container will invalidate this session.

    An interval value of zero or less indicates that the session should never timeout.

    Parameters: interval - An integer specifying the number of seconds


    int getMaxInactiveInterval()

    Returns the maximum time interval, in seconds, that the servlet container will keep this session open between client accesses. After this interval, the servlet container will invalidate the session. The maximum time interval can be set with the setMaxInactiveInterval method.

    A return value of zero or less indicates that the session will never timeout.

    Returns: an integer specifying the number of seconds this session remains open between client requests See Also: setMaxInactiveInterval(int)

    Session的超时的概念指的是,客户端两次请求之间的间隔(Interval),如果超出规定的时间,Session就会失效

    Session的默认的超时时间为30min,可以在web.xml中查看到该配置,如果需要修改统一的默认时间,也可以重写这个配置来定义

    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    

Figure 8: Cookie &amp; Session总结

Figure 8: Cookie & Session总结

  1. 客户端发起请求,服务器接收到请求之后创建新的Session(request.getSession()),并存放到内存中
  2. 服务器在为该客户端创建Session时,还会创建一个Cookie对象来保存JSESSIONID:ID的键值对,并发送给客户端
  3. 客户端收到Set-Cookie请求头之后,保存SessionID,并在之后的每一次请求中都带上这个Cookie
  4. 服务器在每次收到客户端的请求时也会收到SessionID,从而在内存中寻找对应的Session

由于Cookie的默认存活时间设置为Session,即关闭浏览器直接销毁,因此当保存有SessionID的Cookie被销毁之后,原本的Session也就自然而然的失效了。

Reference

【尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版】

Licensed under CC BY-NC-SA 4.0
Last updated on Oct 10, 2022 16:34 CST
comments powered by Disqus
Cogito, ergo sum
Built with Hugo
Theme Stack designed by Jimmy