9-java安全基础——Servlet

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了9-java安全基础——Servlet相关的知识,希望对你有一定的参考价值。

Servlet程序

Servlet是Servlet Applet的简称,翻译过来大概就是一个运行在服务端的小程序,用于处理服务器的请求,什么是服务器程序?

我们知道一般在Web应用程序中都是通过浏览器来访问web服务器资源的,通常浏览器要访问一个网页时会发送一个http请求给web服务器,然后web服务器接收到该请求会进行相应的处理,然后把处理的结果再返回给浏览器,因此不难看出Servlet是用于处理服务器的请求(业务逻辑)

如何编写一个Servlet程序

servlet是Sun公司提供了一种开发动态web资源的一种技术,并在其API提供了一个servlet接口,如果开发者想要开发一个Servlet程序需要实现以下几个步骤:

  1. 自定义一个java类并实现Servlet接口
  2. 实现servlet接口中的抽象方法
  3. 配置Servlet访问规则

自定义ServletTest2类实现Servlet接口并重写接口中的抽象方法:

import javax.servlet.*;
import java.io.IOException;

/**
 * @auther songly_
 * @data 2021/8/1 16:47
 */
public class ServletTest2 implements Servlet {

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("service方法执行了......");
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

在web.xml文件中配置ServletTest2的访问规则:

    <servlet>
        <servlet-name>test2</servlet-name>
        <servlet-class>ServletTest2</servlet-class>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>test2</servlet-name>
        <url-pattern>/test2</url-pattern>
    </servlet-mapping>

启动tomcat容器,在浏览器中访问:http://localhost:8080/ServletTest2_war_exploded/test2 ,当访问/test2时,ServletTest2中的service方法就就会被调用。

Servlet执行原理

接下来我们通过Servlet执行原理来分析ServletTest2中的service方法是如何被调用的

1. 用户在浏览器中输入http://localhost:8080/ServletTest2_war_exploded/test2时,会发送一个http请求

2. 当tomcat服务器接受到客户端浏览器的http请求后,会解析该请求中的资源文件rui路径(/test2),然后解析web.xml文件,查找<servlet-mapping>标签中的<url-pattern>标签体的uri路径是否为/test2,如果有继续根据<servlet-name>标签查找对应的<servlet-class>全类名,tomcat容器会根据ServletTest2的全类名找到该类加载到内存生成class文件,并通过反射创建Servlet实例对象

3. tomcat会创建一个ServletRequest对象和ServletResponse对象,然后将用户浏览器的http请求消息封装到ServletRequest对象中,再将这两个对象传给Servlet,然后调用Servle对象的Service方法

通过Servlet执行流程中不难看出,Servlet主要是负责接收请求,调用Service方法,然后响应数据。

Servlet生命周期

Servlet接口中有5个方法,如下所示:

init方法:当客户端发送请求时,Servlet容器就会调用init方法初始化一个Servlet对象,需要注意的是init方法只会在第一次请求时被调用

service方法:提供服务方法,每一次访问Servlet对象时都会被调用

destory方法:当要销毁Servlet之前会调用该方法,例如当要关闭服务时Servlet容器就会调用destory方法销毁Servlet对象

getServletInfo方法:这个方法会返回Servlet的描述信息,可以返回一段字符串

getServletConfig方法:这个方法会返回由Servlet容器传给ini方法的ServletConfig对象

关于init方法

这里想tomcat服务器发送了三次请求,可以看到init方法只会在第一次访问时被调用,说明一个Servlet在内存中只存在一个对象,Servlet是单例的,多个用户同时访问时,可能存在线程安全问题。

Servlet什么时候会被创建?web.xml文件中指定Servlet的创建时机:<load-on-startup>的值为负数表示第一次被访问时才创建Servlet,如果值为0或正整数表示在服务器启动时创建Servlet。

    <servlet>
        <servlet-name>test2</servlet-name>
        <servlet-class>com.test.ServletTest2</servlet-class>
        <!-- 配置Servlet创建时机 -->
        <load-on-startup>0</load-on-startup>
    </servlet>

关于service方法

浏览器每一次发送请求时都会调用service方法,但是我们知道Servlet是一个接口不能实例化,那么Servlet对象调用service方法实际上是调用了谁的service方法?

 通过分析Servlet接口的结构体系发现,Servlet接口有一个实现类GenericServlet,但是GenericServlet是一个抽象类无法实例化,service方法也是一个抽象方法,如下所示:

public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

但是HttpServlet类继承了GenericServlet抽象类,并重写了service抽象方法

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        //http请求对象
        HttpServletRequest  request;
        //http响应对象
        HttpServletResponse response;

        try {
            request = (HttpServletRequest) req;
            response = (HttpServletResponse) res;
        } catch (ClassCastException e) {
            throw new ServletException("non-HTTP request or response");
        }
        //又调用了一次service方法
        service(request, response);
}

service方法中有两个参数,ServletRequest表示当前http请求,ServletResponse表示当前http响应。在service方法中又调用了一次service方法。

service方法接收了两个参数,HttpServletRequest 是对http请求对象的封装,HttpServletResponse是对http响应对象的封装,service方法会获取http请求的请求方式,并判断http请求的具体请求方式,是否为GET或POST,PUT等其它请求方式,然后调用对应的do前缀方法。

	    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取http请求的方式
        String method = req.getMethod();
        //判断http请求的具体方式
        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)) {
            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);
        }
    }

如果我们想要自己实现一个基于GET或者POST请求的服务逻辑的话,就可以通过自定义一个类继承HttpServlet类并重写doGet和doPost方法就行了,这样就可以不需要实现Servlet接口,还要重写Servlet接口的service方法这么麻烦的方式了。

以GET方式发送请求时就会调用doGet方法,以POST方式发送请求会调用doPost方法。

public class ServletTest3 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet 执行了.......");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost 执行了......");
    }
}

关于HttpServletRequest接口和HttpServletResponse接口

无论是doGet方法还是doPost方法,都会接收两个参数,HttpServletRequest表示当前http请求,HttpServletResponse表示当前http响应,当浏览器发送一个GET方式的http请求时,我们可以使用doGet方法中的req就可以获取http请求消息中的数据,那么大家有没有想过这两个对象从哪里来的呢?

回忆一下Servlet执行原理,发现tomcat已经自动把http请求和http响应抽象成两个接口了。

tomcat为什么要这么做?想象一下,如果tomcat不这么做,这意味着需要我们自己写socket程序,从流中解析http请求了,要知道这是一个很头疼的工作量,无形之中增加了开发复杂度,而tomcat为了简化开发流程已经帮我们完成了这件事,这样我们只需简单的调用这两个接口提供的API接口就可以操作http请求和响应数据了,剩下的把重心放在如何处理业务逻辑就行了。

来看一下HttpServletRequest和HttpServletResponse的结构体系:

 使用HttpServletRequest对象获取http请求消息数据:

package com.test;


import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

/**
 * @auther songly_
 * @data 2021/8/2 14:37
 */

@WebServlet("/test4")
public class ServletTest4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取request运行类型
        System.out.println(request.getClass());
        //请求的协议和版本信息
        String protocol = request.getProtocol();
        //请求的URL地址
        String requestURL = request.getRequestURL().toString();
        //请求的uri资源
        String requestURI = request.getRequestURI();
        //请求的Servlet路径
        String servletPath = request.getServletPath();
        //请求方式
        String method = request.getMethod();
        //发送请求客户端IP地址
        String remoteAddr = request.getRemoteAddr();
        //发送请求的客户端端口
        int remotePort = request.getRemotePort();
        System.out.println("请求的协议和版本信息: " + protocol);
        System.out.println("请求的URL地址: " + requestURL);
        System.out.println("请求的uri资源: " + requestURI);
        System.out.println("请求的Servlet路径: " + servletPath);
        System.out.println("请求方式: " + method);
        System.out.println("发送请求客户端IP地址: " + remoteAddr);
        System.out.println("发送请求的客户端端口: " + remotePort);
    }

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

    }
}

程序执行结果如下:

关于ServletConfig对象和ServletContext对象

在Servlet的web.xml配置文件的<init-param>标签表示servlet配置的初始化参数,tomcat会将这些初始化参数封装到ServletConfig对象中,当Servlet的init方法被调用时要求传入一个ServletConfig参数,init方法会将ServletConfig对象传给servlet。

当创建Servlet对象时,<init-param>标签中的初始化参数会封装到ServletConfig对象中,我们可以通过Servlet对象的getServletConfig方法获取ServletConfig对象,在通过getInitParameterNames方法获取所有初始化参数。

当tomcat服务器启动时会加载web.xml文件,tomcat会为每个web应用程序创建一个ServletContext对象,可以理解为整个web.xml文件就代表ServletContext对象,并且一个web应用通常会有多个Servlet

无论是哪种方式都是获取同一个ServletContext对象,并且在开发过程中可以通过ServletConfig对象的getServletContext方法获得ServletContext对象,通常一个web应用中通常会有多个Servlet,并且这些Servlet共享同一个ServletContext对象,因此可以通过ServletContext对象来实现Servlet之间数据共享。 

以上是关于9-java安全基础——Servlet的主要内容,如果未能解决你的问题,请参考以下文章

Java基础——JSP

JSP基础学习

jsp基础语言-jsp代码段

过滤器监听器上下文servlet线程安全问题

servlet基础

JSP基础--JSP入门