JavaWeb

Web服务器

服务器

服务器通常进行一些被动操作,比如当用户发来请求时,服务器会对请求进行处理,并给用户以响应

访问网站时,机器为我们做的事:

  1. 用户在浏览器输入一个url地址,按下回车
  2. 计算机检查本机的host配置文件,看是否有该url中域名的映射
    • 有:直接访问对应的ip地址
    • 没有:去DNS服务器寻找对应的ip地址
  3. 计算机将服务器的响应返回给用户

Tomcat

  • Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。
  • Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。
  • 另外,Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。不过,Tomcat处理静态HTML的能力不如Apache服务器。

Tomcat安装

  • 在官网下载合适的tomcat压缩包
  • 解压到合适的路径,tomcat就可以使用了,前提是机器配置好了jdk环境
  • 配置环境变量(可选)

Tomcat启动与关闭

  • 启动:bin目录下,点击startup.bat
  • 关闭:bin目录下,点击shutdown.bat
  • 测试:localhost:8080

Tomcat配置

tomcat服务器的核心配置文件是conf文件下的server.xml

文件详解如下:

  • <connector port=”8080” protocol=”HTTP/1.1”…:指定了tomcat所占的端口号
  • <Host name=”localhost”…:这里的localhost是对C:\Windows\System32\drivers\etc\host里localhost的映射
  • appBase=”webapps”:网站应用存放的路径

Tomcat发布一个网络应用

  1. 在tomcat的webapps路径下,建立一个文件夹(如app1),在该文件夹下建立一个WEB-INF目录,WEB-INF下建立一个web.xml

  2. web.xml内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                          http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
      version="4.0"
      metadata-complete="true">
       
    </web-app>
       
    
  3. app1路径下,创建一些静态资源如:html、css、js等

  4. 启动tomcat,在浏览器输入localhost:8080/app1/xx,即可访问到xx资源

一个网络应用应有的结构:

-webapps
	-ROOT
	-app : 网站的目录名
		-WEB-INF
			-classes : 放.class文件
			-lib : 放依赖的jar包
			-web.xml : 应用配置文件
		-index.html : 应用的首页
		-xx.html
		-static : 放静态资源的地方
			-css
			-js
			-img

HTTP

什么是HTTP

  • 超文本传输协议,本质是一个简单的请求-响应协议,通常运行在TCP协议之上
  • 超文本指除文本之外,还可以传输图片、音乐、视频等不同格式的文件
  • 该协议所占用的默认端口号是80

HTTPS

  • 加密的,安全的HTTP
  • 默认占用端口号:443

HTTP的版本

  • HTTP1.0:

    客户端与服务端建立连接后,只能获得一个资源,然后断开连接

  • HTTP1.1:

    客户端与服务端建立连接后,可以获得多个资源

HTTP请求

一个HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成

  • 请求行分为三个部分:请求方法、请求地址URL和HTTP协议版本,它们之间用空格分割。例如 :

    GET /waston/sky.html HTTP/1.1 
    
  • 一个HTTP的请求头内容如下:

    Host: localhost:8080 
    Connection: keep-alive 
    Cache-Control: max-age=0 
    Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 
    Sec-Fetch-Dest: document 
    Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 
    Sec-Fetch-Site: none 
    Sec-Fetch-Mode: navigate 
    Sec-Fetch-User: ?1 
    Accept-Encoding: gzip, deflate, br 
    Accept-Language: zh-CN,zh;q=0.9 
    If-None-Match: W/"2120-1598344459070" 
    If-Modified-Since: Tue, 25 Aug 2020 08:34:19 GMT 
    
  • 请求数据不在GET方法中使用,而在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的最长使用的请求头部是Cntent-Type和Content-Length

HTTP响应

HTTP响应报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成

  • 状态行由3部分组成,分别为:协议版本、状态码、状态码扫描

    HTTP/1.1 200 OK
    
  • 一个HTTP的响应头内容如下:

    Date: Thu, 19 Nov 2020 06:48:09 GMT
    Accept-Ranges: bytes
    ETag: W/"2120-1598344459070"
    Last-Modified: Tue, 25 Aug 2020 08:34:19 GMT
    Content-Type: text/html
    Content-Length: 2120
    
  • 响应数据可以是常见的html文件,也可以是各种各样类型的文件

Servlet

Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

  • 在代码层中Servlet是Sun公司提供的一个API接口,开发Servlet程序,只需两个步骤:
    1. 编写一个实现Servlet接口的类
    2. 把这个类部署到web服务器中

Servlet项目的建立

  1. idear中新建maven工程(选择web-app预设),添加依赖如下:

    <?xml version="1.0" encoding="UTF-8"?>
       
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
       
      <groupId>cn.waston</groupId>
      <artifactId>maven-web</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
       
      <name>maven-web Maven Webapp</name>
      <!-- FIXME change it to the project's website -->
      <url>http://www.example.com</url>
       
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
      </properties>
       
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
       
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
          <scope>provided</scope>
        </dependency>
       
        <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>javax.servlet.jsp-api</artifactId>
          <version>2.3.3</version>
        </dependency>
      </dependencies>
       
      <build>
        <finalName>maven-web</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
          <plugins>
            <plugin>
              <artifactId>maven-clean-plugin</artifactId>
              <version>3.1.0</version>
            </plugin>
            <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>3.0.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.0</version>
            </plugin>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>2.22.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-war-plugin</artifactId>
              <version>3.2.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-install-plugin</artifactId>
              <version>2.5.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-deploy-plugin</artifactId>
              <version>2.8.2</version>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </project>
       
    
  2. 建立java包和resources包,并分进行标记(右键->mark directory as)

  3. 将web.xml文件内容,改为如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                          http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0"
             metadata-complete="true">
    </web-app>
    
  4. 编写一个servlet程序

    package cn.waston.myservlet;
       
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
       
    /**
     * @Description:
     * @Author: Waston
     * @Date: 2020/11/19 18:16
     */
    public class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            System.out.println("调用了doGet方法");
            PrintWriter writer = resp.getWriter();//响应流
            writer.print("Hello Servlet");
        }
       
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            super.doGet(req, resp);
        }
    }
       
    
  5. 在web.xml编写Servlet的映射

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                          http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0"
             metadata-complete="true">
         
      <!--注册Servlet-->
      <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>cn.waston.myservlet.HelloServlet</servlet-class>
      </servlet>
      <!--Servlet的请求路径-->
      <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/hello</url-pattern>
      </servlet-mapping>
    </web-app>
    
  6. idear配置tomcat,选择版本、设置访问路径

  7. 启动tomcat,进行测试

Servlet原理

通过分析源码,可以发现,我们写servlet所继承的HttpServlet类继承自GenericServlet,而GenericServlet又继承自Servlet接口

Servlet接口的源码如下:

package javax.servlet;

import java.io.IOException;

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

这5个方法,每个servlet实现类都会依次从上到下执行;我们也常把这5个方法称为servlet的声明周期

GenericServlet接口的源码如下:

package javax.servlet;

import java.io.IOException;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.ResourceBundle;

public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
    private static final String LSTRING_FILE = "javax.servlet.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.LocalStrings");
    private transient ServletConfig config;

    public GenericServlet() {
    }

    public void destroy() {
    }

    public String getInitParameter(String name) {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameter(name);
        }
    }

    public Enumeration<String> getInitParameterNames() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getInitParameterNames();
        }
    }

    public ServletConfig getServletConfig() {
        return this.config;
    }

    public ServletContext getServletContext() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletContext();
        }
    }

    public String getServletInfo() {
        return "";
    }

    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.init();
    }

    public void init() throws ServletException {
    }

    public void log(String msg) {
        this.getServletContext().log(this.getServletName() + ": " + msg);
    }

    public void log(String message, Throwable t) {
        this.getServletContext().log(this.getServletName() + ": " + message, t);
    }

    public abstract void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;

    public String getServletName() {
        ServletConfig sc = this.getServletConfig();
        if (sc == null) {
            throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
        } else {
            return sc.getServletName();
        }
    }
}

GenericServlet类对5个方法除service方法外都进行了实现

HttpServlet类源码如下:

package javax.servlet.http;

import java.io.IOException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.ResourceBundle;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public abstract class HttpServlet extends GenericServlet {
    private static final String METHOD_DELETE = "DELETE";
    private static final String METHOD_HEAD = "HEAD";
    private static final String METHOD_GET = "GET";
    private static final String METHOD_OPTIONS = "OPTIONS";
    private static final String METHOD_POST = "POST";
    private static final String METHOD_PUT = "PUT";
    private static final String METHOD_TRACE = "TRACE";
    private static final String HEADER_IFMODSINCE = "If-Modified-Since";
    private static final String HEADER_LASTMOD = "Last-Modified";
    private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings";
    private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings");

    public HttpServlet() {
    }

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_get_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

    protected long getLastModified(HttpServletRequest req) {
        return -1L;
    }

    protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        NoBodyResponse response = new NoBodyResponse(resp);
        this.doGet(req, response);
        response.setContentLength();
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_post_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_put_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String protocol = req.getProtocol();
        String msg = lStrings.getString("http.method_delete_not_supported");
        if (protocol.endsWith("1.1")) {
            resp.sendError(405, msg);
        } else {
            resp.sendError(400, msg);
        }

    }

    private Method[] getAllDeclaredMethods(Class<? extends HttpServlet> c) {
        Class<?> clazz = c;

        Method[] allMethods;
        for(allMethods = null; !clazz.equals(HttpServlet.class); clazz = clazz.getSuperclass()) {
            Method[] thisMethods = clazz.getDeclaredMethods();
            if (allMethods != null && allMethods.length > 0) {
                Method[] subClassMethods = allMethods;
                allMethods = new Method[thisMethods.length + allMethods.length];
                System.arraycopy(thisMethods, 0, allMethods, 0, thisMethods.length);
                System.arraycopy(subClassMethods, 0, allMethods, thisMethods.length, subClassMethods.length);
            } else {
                allMethods = thisMethods;
            }
        }

        return allMethods != null ? allMethods : new Method[0];
    }

    protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Method[] methods = this.getAllDeclaredMethods(this.getClass());
        boolean ALLOW_GET = false;
        boolean ALLOW_HEAD = false;
        boolean ALLOW_POST = false;
        boolean ALLOW_PUT = false;
        boolean ALLOW_DELETE = false;
        boolean ALLOW_TRACE = true;
        boolean ALLOW_OPTIONS = true;

        for(int i = 0; i < methods.length; ++i) {
            String methodName = methods[i].getName();
            if (methodName.equals("doGet")) {
                ALLOW_GET = true;
                ALLOW_HEAD = true;
            } else if (methodName.equals("doPost")) {
                ALLOW_POST = true;
            } else if (methodName.equals("doPut")) {
                ALLOW_PUT = true;
            } else if (methodName.equals("doDelete")) {
                ALLOW_DELETE = true;
            }
        }

        StringBuilder allow = new StringBuilder();
        if (ALLOW_GET) {
            allow.append("GET");
        }

        if (ALLOW_HEAD) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("HEAD");
        }

        if (ALLOW_POST) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("POST");
        }

        if (ALLOW_PUT) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("PUT");
        }

        if (ALLOW_DELETE) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("DELETE");
        }

        if (ALLOW_TRACE) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("TRACE");
        }

        if (ALLOW_OPTIONS) {
            if (allow.length() > 0) {
                allow.append(", ");
            }

            allow.append("OPTIONS");
        }

        resp.setHeader("Allow", allow.toString());
    }

    protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String CRLF = "\r\n";
        StringBuilder buffer = (new StringBuilder("TRACE ")).append(req.getRequestURI()).append(" ").append(req.getProtocol());
        Enumeration reqHeaderEnum = req.getHeaderNames();

        while(reqHeaderEnum.hasMoreElements()) {
            String headerName = (String)reqHeaderEnum.nextElement();
            buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));
        }

        buffer.append(CRLF);
        int responseLength = buffer.length();
        resp.setContentType("message/http");
        resp.setContentLength(responseLength);
        ServletOutputStream out = resp.getOutputStream();
        out.print(buffer.toString());
    }

    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);
        }

    }

    private void maybeSetLastModified(HttpServletResponse resp, long lastModified) {
        if (!resp.containsHeader("Last-Modified")) {
            if (lastModified >= 0L) {
                resp.setDateHeader("Last-Modified", lastModified);
            }

        }
    }

    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");
        }
    }
}

注意其中的service方法,它根据客户端请求的类型,去回调对应的方法,如doGet、doPost等

Servlet在容器中的执行过程:

  1. 服务器启动时,会把我们编写的servlet类生成对象,并开始它的生命周期(执行init()方法)…
  2. 客户端(浏览器)在首次访问服务器(servlet容器)时,服务器会分别生成ServletRequestServletResponse这两个接口的实现类的一个对象
  3. 服务器得到url的值,根据逻辑(web.xml),开启一个线程,找到对应的servlet,执行service()方法时,把request和response作为参数传入
  4. service()方法执行过程中,负责向客户端响应的response对象写入向客户端输出数据
  5. service()方法执行完后,服务器从response对象中取出数据,构建一个http响应,返回给客户端

用时序图描述该过程如下:

servlet执行过程

  • 在容器中的每个Servlet原则上只有一个实例
  • 每个请求对应一个线程
  • 多个线程可作用于同一个Servlet(这是造成Servlet线程不安全的根本原因)
  • 每个线程一旦执行完任务,就被销毁或放在线程池中等待回收

ServletContext

Web容器在启动的时候,会为每个web程序都创建一个对应的ServletContext对象,它代表了当前的web应用

ServletContext的应用:

  • 共享数据:在一个servlet中保存的数据,可以在另一个servlet中拿到
  • 配置一些web应用的初始化参数
  • 请求转发(请求路径不变)
  • 获取项目资源文件里的内容

利用ServletContext共享数据:

  1. SetServlet中调出ServletContext对象,并在放入键和值

    public class SetServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       
            ServletContext context = this.getServletContext();
            context.setAttribute("username","waston");
        }
       
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req, resp);
        }
    }
    
  2. GetServlet中调出ServletContext对象,获取该键的值

    public class GetServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            ServletContext context = this.getServletContext();
            Object username = context.getAttribute("username");
       
            resp.setContentType("text/html");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().print("name" + username);
        }
       
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req, resp);
        }
    }
    

利用ServletContext配置应用初始化:

  1. web.xml中配置一些信息

    <context-param>
      <param-name>url</param-name>
      <param-value>jdbc:mysql://localhost:3306/test</param-value>
    </context-param>
    
  2. servlet中通过ServletContext获取该信息

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        String url = context.getInitParameter("url");
       
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().print(url);
    }
    

利用ServletContext请求转发:

  • protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletContext context = this.getServletContext();
        RequestDispatcher requestDispatcher = context.getRequestDispatcher("/hello");
        requestDispatcher.forward(req, resp);
    }
    

利用ServletContext读取资源文件:

  • protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        InputStream resourceAsStream = this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
      
        Properties properties = new Properties();
        properties.load(resourceAsStream);
        String name = properties.getProperty("name");
        String password = properties.getProperty("password");
      
        resp.getWriter().print("name:" + name +",password:" + password);
    }
    

Response

当服务器接收到请求时,会生成一个继承于HttpServletResponse的对象,主要用于记录需要向客户端返回的数据

主要方法

  • 负责向浏览器发送数据的方法:

    ServletOutputStream getOutputStream() throws IOException;
      
        PrintWriter getWriter() throws IOException;getOutputStream()
    getWriter()
    
  • 负责向浏览器发送响应头的方法:

    void setDateHeader(String var1, long var2);
      
        void addDateHeader(String var1, long var2);
      
        void setHeader(String var1, String var2);
      
        void addHeader(String var1, String var2);
      
        void setIntHeader(String var1, int var2);
      
        void addIntHeader(String var1, int var2);
    

主要应用:

  • 暂存向客户端返回的数据
  • 使客户端能够下载文件(资源)
  • 验证码功能
  • 重定向

使客户端能够下载文件:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 获取要下载文件的路径
    String realPath = "C:/Users/Administrator/Desktop/test.png";
    // 获取文件名
    String fileName = realPath.substring(realPath.lastIndexOf("\\") + 1);
    // 设置响应头,让浏览器实施下载、URLEncoder
    resp.setHeader("Content-Disposition", "attachment;filename" + URLEncoder.encode(fileName, "UTF-8"));
    // 创建下载文件的输入流
    FileInputStream inputStream = new FileInputStream(realPath);
    // 创建缓冲区
    int len = 0;
    byte[] buffer = new byte[1024];
    // 创建OutputStream对象
    ServletOutputStream outputStream = resp.getOutputStream();
    // 将FileOutputStream流写入到buffer缓冲区,使用OutputStream将缓冲区中的数据输出到客户端
    while ((len= inputStream.read()) > 0){
        outputStream.write(buffer, 0, len);
    }

    inputStream.close();
    outputStream.close();
}

验证码功能实现:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 使浏览器3秒刷新一次
    resp.setHeader("refresh", "3");

    // 创建一张图片
    BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics = (Graphics2D) image.getGraphics();
    graphics.setColor(Color.white);
    graphics.fillRect(0 ,0 ,80, 20);

    // 在图片上写东西
    graphics.setColor(Color.blue);
    graphics.setFont(new Font(null, Font.BOLD, 20));
    graphics.drawString(makeNum(), 0 ,20);

    // 设置响应
    resp.setContentType("image/jpeg");
    resp.setDateHeader("expires", -1);
    resp.setHeader("Cache-Control", "no-cache");
    resp.setHeader("Pragma", "no-cache");

    JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(resp.getOutputStream());
    encoder.encode(image);
}

//生成7位随机数
private String makeNum(){
    Random random = new Random();
    String num = random.nextInt(9999999) + "";
    StringBuffer sb = new StringBuffer();
    for(int i = 0; i < 7 - num.length(); i++){
        sb.append("0");
    }
    num = sb.toString() + num;
    return num;
}

重定向实现:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 重定向
    resp.sendRedirect("/waston/code");
}

如下这样也能达到sendRedirect的效果

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setHeader("Location", "/waston/code");
    resp.setStatus(302);
}

前后端联动:

index.jsp

<html>
<body>
<h2>Hello World!</h2>
<%--pageContext.request.contextPath代表当前项目--%>
<form action="${pageContext.request.contextPath}/redirect" method="get">
    用户名:<input type="text" name="username"> <br>
    密码:<input type="password" name="password"> <br>
    <input type="submit">
</form>
</body>
</html>

servlet

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    String username = req.getParameter("username");
    String password = req.getParameter("password");

    System.out.println(username + password);
    resp.sendRedirect("/waston/success.jsp");
}

success.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>成功</title>
</head>
<body>
<h1>Success!</h1>
</body>
</html>

Request

当服务器接收到请求时,会生成一个继承于HttpServletRequest的对象,主要用于从客户端的请求中获取一些数据

主要应用:

  • 根据参数名获取前端传过来的参数值

    //username与前端元素的name值相对应
    String username = req.getParameter("username");
    //获取多个值,如复选框中的值
    String[] items = req.getParameterValues("items");
    
  • 请求转发

    req.getRequestDispatcher("success.jsp").forward(req, resp);
    

Session

Session的翻译为会话

  • 会话:用户打开浏览器,访问一些web资源,提交一些表单,然后关闭浏览器,这一过程称之为“会话”
  • 会话状态:服务端与客户端在会话过程中产生的状态信息,借助会话状态,服务端能够把属于同一会话中的一系列的请求和响应过程关联起来
  • 在Servlet规范中,常用Cookie和Session两种机制完成会话跟踪 ,以实现有状态的会话
  • 服务器会为每一个客户端自动创建一个seesion,并创建一个name为JSESSIONID,value为id值的cookie给客户端;客户端再次访问该应用时,会把这个cookie带上

Session的应用:

  • 客户端在切换某一应用的不同页面或不同站点时,登陆信息在服务端保存;用户不用频繁登陆、验证
  • 不同servlet之间共享信息,根ServletContext类似

测试Session:

  1. Session1Servlet.java

    public class Session1Servlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.setCharacterEncoding("utf-8");
            resp.setHeader("content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
       
            //从request对象中获取session
            HttpSession session = req.getSession();
            //session中存东西
            session.setAttribute("name", "waston");
        }
    }
    
  2. Session2Servlet.java

    public class Session2Servlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            req.setCharacterEncoding("utf-8");
            resp.setHeader("content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
       
            //从request对象中获取session
            HttpSession session = req.getSession();
            //从session中
            String name = (String) session.getAttribute("name");
            System.out.println(name);
       
            String id = session.getId();
            //判断session是不是新生成的
            if(session.isNew()){
                resp.getWriter().write("session创建成功,id:" + id);
            } else {
                resp.getWriter().write("session已在服务器中存在,id:" + id);
            }
        }
    }
    
  3. 在浏览器先后访问1、2两个servlet,在控制台可以看到在1中存入的信息;在浏览器看到JSESSIONID的id

Session的其他方法:

// 移除session的属性
session.removeAttribute("name");
// session注销
session.invalidate();

web.xml中设置session:

<!--设置session的存活时间为15分钟-->
<session-config>
  <session-timeout>15</session-timeout>
</session-config>

客户端在首次访问服务端时,服务端给客户端一个cookie;下次客户端访问时带着cookie,服务端看到了cookie,会省去一些验证,让客户端更快速的访问自己的资源

Cookie的使用:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.setCharacterEncoding("utf-8");
    resp.setHeader("content-type", "text/html;charset=UTF-8");
    resp.setCharacterEncoding("utf-8");

    PrintWriter out = resp.getWriter();
    // 通过request对象获得cookie,cookie可以是多个
    Cookie[] cookies = req.getCookies();
    if(cookies != null){
        for(Cookie cookie : cookies){
            if(cookie.getName().equals("lastLoginTime")){
                long lastLoginTime = Long.parseLong(cookie.getValue());
                Date date = new Date(lastLoginTime);
                out.write("您上次的访问时间是:" + date.toLocaleString());
            }
        }
    }else {
        out.write("这是您第一次访问");
    }

    //给客户端一个cookie
    Cookie cookie = new Cookie("lastLoginTime", System.currentTimeMillis() + "");
    resp.addCookie(cookie);
}

cookie设置保存时间(有效期):

//设置cookie有效期为1天 关闭浏览器后cookie还在
cookie.setMaxAge(24*60*60);
  • 若不设置有效期,关闭浏览器,cookie即失效
  • 若参数为0,则响应完成后,cookie立即失效

cookie特点:

  • 一个Cookie只能保存一个信息
  • 一个web应用可以给客户端发送多个cookie,最多为20个
  • 浏览器可能最多支持存放300个cookie
  • 一个cookie内容不能超过4kb

Session与Cookie的联系与区别

  • Cookie和Session两种机制都用于完成会话跟踪 ,以实现有状态的会话
  • Cookie是把用户的数据写给用户的浏览器,由浏览器保存
  • Session是把用户的数据写到用户独占的Session中,在服务端保存;客户端则保存cookie形式的Session的id
  • Cookie可以保存多个;Session只有一个,一般用于保存重要信息