整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Tomcat进阶学习上篇

、Tomcat原理理论介绍

1.1 Tomcat简介

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应HTML(标准通用标记语言下的一个应用)页面的访问请求。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。

1.1.1Tomcat目录结构

目录说明bin主要存放编译后的代码的地方,startup.bat、shutdown.bat分别对应windows版本的启动和关闭conf主要存放配置文件目录,context.xml 存放上下文的配置信息,logging.properties存放日志配置信息,server.xml指定服务端的一些配置信息比如端口号,web.xml指定一些Servlet的配置文件lib存储一些依赖的jar包(tomcat也是java开发的)Logs存放日志相关的包temp存放一些临时文件的包webapps主要存放解压之后的war的项目信息Work工作相关的信息

1.2 请求处理流程

1.2.1计算机的网络通信模型



应用层

不同计算机上应用之间的通信,来解决通信双方数据传输的问题。或者说不同计算机上对应的应用进程之间的通信。支持的协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS DHCP等。(协议)

表示层

用来规定传输的数据格式以及加解密,格式有,JPEG、ASCll、DECOIC、加密格式等。【在四层模型里面已经合并到了应用层】

会话层

用来规定开始、控制和终止一个会话。对应主机的进程,指本地主机与远程主机正在进行的会话。【在四层模型里面已经合并到了应用层】

传输层

规定传输数据的协议端口号,以及流控和差错校验。支持的协议有:TCP UDP等,数据包一旦离开网卡即进入网络传输层。指定IO模型 BIO NIO AIO等

网络层

进行逻辑地址寻址,实现不同网络之间的路径选择。(路由选择) 协议有:ICMP IGMP IP(IPV4 IPV6) ARP RARP。

数据链路层

建立逻辑连接、进行硬件地址寻址、差错校验等功能。(由底层网络定义协议,ATM,FDDI等)将比特组合成字节进而组合成帧,用MAC地址访问介质,能错误发现但不能纠正。

物理层

建立、维护、断开物理连接。(由底层网络定义协议,RJ45,802.3等)【在四层模型里面已经合并到了数据链路层】

1.2.2浏览器访问服务器流程

http请求处理过程



第2步是先建立连接,进行3次握手。

1.3 Tomcat 系统总体架构

1.3.1Tomcat处理请求大致流程

因为Tomcat可以处理Http请求,因此称Tomcat为Http服务器。

如果我们直接从浏览器发送请求,Tomcat直接接收请求,并将请求转发到对应的java程序的话,也可以实现请求处理,但是耦合度非常高,因此Tomcat的做法是在接收到http请求之后,将请求转发给servlet容器,由servlet容器在进行处理并转发到对应的java程序。

因此在Tomcat内部也实现了Servlet规范。(Servlet接⼝和Servlet容器这⼀整套内容叫作Servlet规范。

因此Tomcat有两个身份:

1、Http服务器

2、Servlet容器



1.3.2Tomcat Servlet容器处理过程


步骤说明:

1)Tomcat中Http服务器模块会接收到原始的请求参数Request,封装ServletRequest准备请求Servlet容器

2)将请求转发到Servlet容器中定位Servlet(URL和Servlet的映射关系,找到相应的Servlet)

3)如果Servlet还未加载,利用反射机制创建Servlet,并调用Servlet的init初始化方法

4)获取到具体的Servlet实例之后直接调用对应的业务处理方法执行业务

5)将处理好的响应结果封装成ServletResponse

6)Http服务器在将ServletResponse对象封装成原生的Response相应到浏览器

1.3.3Tomcat系统总体架构总结


Tomcat中包括两大核心组件:

连接器(Connector)组件,容器(Container)组件。(还包括其他组件)

连接器(Connector)组件主要作用:处理Socket连接,负责⽹络字节流与Request和Response对象的转化。【与客户端交互】

容器(Container)组件主要作用:加载和管理Servlet,处理Request请求;

1.4Tomcat组件

1.4.1连接器组件 Coyote

1)Coyote简介

Coyote是Tomcat中连接器的组件名称,是对应的接口。客户端通过Coyote与服务器建立连接、发送请求并接受响应。

2)主要职责

1)Coyote封装了底层网络通信(Socket请求及相应处理)

2)Coyote使Container容器组件与具体的请求协议及IO操作⽅式完全解耦

3)Coyote将Socket输入转换封装为Request对象,并进一步封装成ServletRequest交给Container容器进行处理,处理完成后返回ServletResponse给Coyote,Coyote将对象转换成Response对象将结果写入输出流。

4)Coyote负责的是具体协议(应⽤层)和IO(传输层)相关内容。

3)Tomcat Coyote支持的IO模型与协议

支持的协议

应用层应用层描述描述HTTP/1.1大部分Web应用采用的协议【Tomcat8.x默认协议】AJPAJP定向包协议,实现对静态资源的优化以及集群的部署。HTTP/2HTTP2.0大幅度提升了web性能,属于下一代的HTTP协议但是用的很少。

支持的IO

传输层IO模型描述NIO同步非阻塞I/O、采用javaNIO类库实现【Tomcat8默认IO模型】NIO2异步非阻塞I/O、采用jdk7的NIO类库实现APR采用Apache可移植运行库实现,是C/C++编写的本地库,如果使用需要单独安装APR库。

Tomcat在8.0之前默认使用的是BIO。如果使用APR性能可能达到Apache Http Server的性能。

4)Coyote内部组件



Coyote组件其中包括EndPoint组件、Processor组件、Adapter组件。

EndPoint:EndPoint 是 Coyote 通信端点,即通信监听的接⼝,是具体Socket接收和发送处理器,是对传输层的抽象,因此EndPoint⽤来实现TCP/IP协议的。

Processor:Processor 是Coyote 协议处理接⼝,如果说EndPoint是⽤来实现TCP/IP协议的,那么Processor⽤来实现HTTP协议,Processor接收来⾃EndPoint的Socket,读取字节流解析成Tomcat的 Request和Response原生对象。

Adapter:Tomcat Request对象不是标准的ServletRequest,不能⽤Tomcat Request作为参数来调⽤容器。Tomcat设计者的解决⽅案是引⼊CoyoteAdapter,将参数转换成ServlerRequest对象。


ProtocolHandler:由Endpoint 和 Processor组成,Tomcat 按照协议和I/O 提供了6个实现类 : AjpNioProtocol,AjpAprProtocol,AjpNio2Protocol,Http11NioProtocol,Http11Nio2Protocol,Http11AprProtocol。

1.4.2Tomcat Catalina组件


1)Catalina组件地位

本来Catalina组件只是Servlet容器的一个组件,而Tomcat是由一些列组件组成,组件可以在conf/server.xml文件中配置。Catalina在Tomcat中的地位非常的核心,因此经常把tomcat的一个实例当作一个Catalina实例。



2)Catalina结构



整个Tomcat就相当于一个Catalina实例,Tomcat启动的时候会先初始化这个实例Catalina,Catalina实例对象通过加载server.xml完成其他实例的创建,创建并管理⼀个Server,Server创建并管理多个服务,每个服务⼜可以有多个Connector和⼀个Container。


对应关系:一个Tomcat对应一个Catalina,对应一个Server,对应多个Service。每一个Service实例有多个Connector和一个Container实例。

Catalina:负责解析Tomcat的配置⽂件(server.xml) , 以此来创建服务器Server组件并进⾏管理

Server:Server是整个Catalina Servlet容器以及其它组件,作用负责组装并启动Servlaet引擎,Tomcat连接器。Server通过实现Lifecycle接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式。

Service:Service是Server内部的组件,⼀个Server包含多个Service。它将若⼲个Connector组件绑定到⼀个Container。

Container:容器,负责处理⽤户的servlet请求,并返回对象给web⽤户的模块。


3)Container 组件介绍

Container组件其中包括Engine、Host、Context、Wrapper4种组件,他们之间是父子关系。Tomcat通过分层的架构,让Servlet容器具有很好的灵活性。



Engine:表示整个Servlet容器的引擎,用来管理多个虚拟站点,一个Service只能有一个Engine。

Host:代表一个虚拟主机,或者说一个站点,可以配置多个虚拟主机地址,一个Engine可以有多个Host。

Context:表示一个Web应用服务器,一个Host下可以包含多个Context。

Wrapper:表示一个Servlet, 一个Context中可以包括多个Wrapper。

2、Tomcat核心配置

思考:

去哪儿配置?->conf/server.xml中

怎么配置?

2.1server.xml配置详解

2.1.1server配置文件概述

server.xml中包括Server根标签,Listener,GlobalNamingResources,Service

Server标签:主要用来创建一个server实体对象

Listener标签:定义监听器

GlobalNamingResources 标签:定义服务器的全局JNDI(Java Naming and Directory Interface 标准的Java命名系统接口)资源。

Service标签:定义⼀个Service服务,⼀个Server标签可以有多个Service服务实例


Server.xml整体结构

<?xml version="1.0" encoding="UTF-8"?>
<!--
port:关闭服务器的监听端⼝
shutdown:关闭服务器的指令字符串
-->
<Server port="8005" shutdown="SHUTDOWN">
    <!-- 以⽇志形式输出服务器 、操作系统、JVM的版本信息 -->
    <Listener className="org.apache.catalina.startup.VersionLoggerListener" />
    <!-- Security listener. Documentation at /docs/config/listeners.html
    <Listener className="org.apache.catalina.security.SecurityListener" />
    -->
    <!--APR library loader. Documentation at /docs/apr.html -->
    <!-- 加载(服务器启动) 和 销毁 (服务器停⽌) APR。 如果找不到APR库, 则会输出⽇志, 并
    不影响 Tomcat启动 -->
    <Listener className="org.apache.catalina.core.AprLifecycleListener"
              SSLEngine="on" />
    <!-- Prevent memory leaks due to use of particular java/javax APIs-->
    <!-- 避免JRE内存泄漏问题 -->
    <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
    <!-- 加载(服务器启动) 和 销毁(服务器停⽌) 全局命名服务 -->
    <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
    <!-- 在Context停⽌时重建 Executor 池中的线程, 以避免ThreadLocal 相关的内存泄漏 -->
    <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
    <!-- Global JNDI resources
    Documentation at /docs/jndi-resources-howto.html
    GlobalNamingResources 中定义了全局命名服务
    -->
    <GlobalNamingResources>
        <!-- Editable user database that can also be used by
        UserDatabaseRealm to authenticate users
        -->
        <Resource name="UserDatabase" auth="Container"
                  type="org.apache.catalina.UserDatabase"
                  description="User database that can be updated and saved"
                  factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
                  pathname="conf/tomcat-users.xml" />
    </GlobalNamingResources>
    <!-- A "Service" is a collection of one or more "Connectors" that share
    a single "Container" Note: A "Service" is not itself a "Container",
    so you may not define subcomponents such as "Valves" at this level.
    Documentation at /docs/config/service.html
    -->
    <Service name="Catalina">
        ...
    </Service>
</Server>


2.1.2Service标签

<Server port="8005" shutdown="SHUTDOWN">
   ....
    <Service name="Catalina">
        <!--为Connector创建一个线程池-->
        <!--
        <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
            maxThreads="150" minSpareThreads="4"/>
        -->
        <!--创建一个监听8080的连接器组件 -->
        <Connector port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        <!-- 创建连接池的连接器 -->
        <!--
        <Connector executor="tomcatThreadPool"
                   port="8080" protocol="HTTP/1.1"
                   connectionTimeout="20000"
                   redirectPort="8443" />
        -->

        <!-- 创建一个监听8009的ajp的connector -->
        <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

        <!--创建引擎 管理 多个Host-->
        <Engine name="Catalina" defaultHost="localhost">
            <!--配置集群-->
            <!--
            <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
            -->
            <Realm className="org.apache.catalina.realm.LockOutRealm">
                <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
                       resourceName="UserDatabase"/>
            </Realm>

            <!--host来管理各个Servlet-->
            <Host name="localhost"  appBase="webapps"
                  unpackWARs="true" autoDeploy="true">
                <!--servlet-->
                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                       prefix="localhost_access_log" suffix=".txt"
                       pattern="%h %l %u %t "%r" %s %b" />

            </Host>
        </Engine>
    </Service>
</Server>

Service标签包括Executor 、Connector、Engine、Host等子标签。


Executor标签

	<Executor 
          name="commonThreadPool"  <!--指定当前线程的名字 相当于Id-->
          namePrefix="thread-exec-" <!-- namePrefix 给线程指定前缀名字 -->
          maxThreads="200"  <!--指定最大线程数 -->
          minSpareThreads="100" <!-- 指定核心线程数 -->
          maxIdleTime="60000" <!-- 指定线程空闲时间 超过该时间线程被自动销毁单位是毫秒 -->
          maxQueueSize="Integer.MAX_VALUE"  <!-- 阻塞队列的大小最大值 -->
          prestartminSpareThreads="false" <!--确定线程池时是否启动核心线程-->
          threadPriority="5" <!--线程中的优先级默认是5 取值范围是1到10 -->
          className="org.apache.catalina.core.StandardThreadExecutor" <!-- 自定义线程池类路径 -->
         />

Executor主要作用是来创建一个线程池,用于连接器Connector使用。

Connector标签

<Connector 
        port="8080" <!--监听的端口号--> 
        protocol="HTTP/1.1"  
        connectionTimeout="20000" <!--连接超时时间单位毫秒-->
        redirectPort="8443" />

protocol="HTTP/1.1": 当前Connector⽀持的访问协议。 默认为 HTTP/1.1 ,并采⽤⾃动切换机制选择⼀个基于 JAVANIO 的链接器或者基于本地APR的链接器(根据本地是否含有Tomcat的本地库判定)

redirectPort="8443":当前Connector 不⽀持SSL请求, 接收到了⼀个请求, 并且也符合security-constraint 约束,需要SSL传输,Catalina⾃动将请求重定向到指定的端⼝。

URIEncoding="UTF-8":⽤于指定编码URI的字符编码, Tomcat8.x版本默认的编码为 UTF-8 , Tomcat7.x版本默认为ISO-8859-1。

executor="commonThreadPool":指定共享线程池的名称,也可以通过maxThreads、minSpareThreads 等属性配置内部线程池。

内部线程Demo

	<Connector port="8080"
           protocol="HTTP/1.1"
           executor="commonThreadPool"
           maxThreads="1000"
           minSpareThreads="100"
           acceptCount="1000"
           maxConnections="1000"
           connectionTimeout="20000"
           compression="on"  <!--是否压缩数据, on是开启压缩 off关闭-->
           compressionMinSize="2048" <!--压缩的最小容量-->
           disableUploadTimeout="true" <!--是否允许Servlet容器,正在执行使用一个较长的连接超时-->
           redirectPort="8443"
           URIEncoding="UTF-8" />

Engine 标签

	<Engine name="Catalina" defaultHost="localhost">
    ...
	</Engine>

name属性: ⽤于指定Engine 的名称, 默认为Catalina

defaultHost:默认使⽤的虚拟主机名称, 当客户端请求指向的主机⽆效时, 将交由默认的虚拟主机处

理, 默认为localhost。

Host标签

<Host name="localhost2"  appBase="webapps2"
      unpackWARs="true" autoDeploy="true">

     <!--用来指定日志配置文件地址 valve-->
    <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log" suffix=".txt"
           pattern="%h %l %u %t "%r" %s %b" />

</Host>

Host标签属于engine的子标签,主要配置虚拟主机的。默认可以配置多个,appBase假如不配置context默认访问ROOT文件下的应用。

name:用于指定虚拟主机。

appBase:对应的配置路径

unpackWARs:解压war包默认为true

autoDeploye:自动提交


Context 标签

<Context docBase="/Users/demo/web_demo" path="/web3"></Context>

Context属于Host的子标签,主要用来映射应用所在的文件,比如上边的访问uri是web3映射到 /Users/demo/web_demo文件夹下。

docBase:Web应⽤⽬录或者War包的部署路径。可以是绝对路径,也可以是相对于Host appBase的相对路径。

path:Web应⽤的Context 路径。如果我们Host名为localhost, 则该web应⽤访问的根路径为:localhost:8080/web3。

3、手写简易版本Tomcat

3.1分析

手写一个Tomcat名称叫做Minicat,需求:可以接收浏览器的http请求,并进行请求处理,处理之后将结果返回给浏览器客户端。

1)提供服务,接收请求(Socket通信)

2)请求信息封装成Request对象(Response对象)

3)客户端请求资源,资源分为静态资源和动态资源

4)将资源返回给客户端

3.2 V1版本Code

public class Bootstrap {

    //自定义端口号
    public static final int port = 8080;

    //启动方法
    private void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("启动成功,端口号:" + port);
        while (true) {
            Socket accept = serverSocket.accept();

            OutputStream outputStream = accept.getOutputStream();
            String content = "Hello Word !";
            String retResult = HttpProtocolUtil.getHeader200(content.getBytes().length) + content;
            outputStream.write(retResult.getBytes());

            outputStream.close();
        }

    }

    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


封装相应头工具类

public class HttpProtocolUtil {
 /**
     * 获取200响应结果
     */
 public static String getHeader200(int size){
        return "HTTP/1.1 200 OK \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + size + " \n" +
                "\r\n";
    }

 /**
     * 获取404响应结果
     */
 public static String getHeader404(){
        String str404 = "<p1>404 Not Found</p1>";
        return "HTTP/1.1 404 Not Found \n" +
                "Content-Type: text/html \n" +
                "Content-Length: " + str404.length() + " \n" +
                "\r\n" + str404;
    }
}

总结:V1版本的代码比较简单,就是简单在页面上写输入一个8080返回一个Hello Minicat但是需要注意在返回结果中必须添加上html响应头信息浏览器才能显示

3.3 V2版本Code

要求:能够输出静态资源文件。

3.3.1 观察请求参数

private void start() throws IOException {
    ServerSocket serverSocket = new ServerSocket(port);
    while (true){
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();

        //防止网络波动读取不到参数
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }

        byte[] bytes = new byte[count];
        inputStream.read(bytes);
        System.out.println(new String(bytes));

        socket.close();
    }
}

打印的参数

GET / HTTP/1.1

Host: localhost:8080

Connection: keep-alive

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36

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

Sec-Fetch-Dest: document

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

只提取一些有用的请求信息,比如GET、/ 路径信息、参数信息。来封装成Request对象和Response对象。

3.3.2完成V2版本的Code

Bootstrap启动类

public class Bootstrap {
    private static final int port = 8080;

    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 /**
     * V2.0版本
     */
 private void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("启动成功");

        while (true){
            Socket socket = serverSocket.accept();
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            //获取到输入输出对象
            Request request = new Request(inputStream);
            Response response = new Response(outputStream);

            //将结果写到输出流中
            response.outputHtml(request.getUrl());

            //一定要把socket关闭
            socket.close();
        }
    }
}

Request对象

@Data
@NoArgsConstructor
public class Request {
    //请求方式GET、POST
    private String methodType;
    //请求URL
    private String url;
    //输入流
    private InputStream inputStream;


 /**
     * 构造器
     */
 public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;
        parseInputParam(inputStream);
    }

    //解析参数
    private void parseInputParam(InputStream inputStream) throws IOException {
        //只提取第一行
        int length = 0;
        while (length == 0){
            length = inputStream.available();
        }
        byte[] bytes = new byte[length];
        inputStream.read(bytes);
        String inputStr = new String(bytes);
        //截取出来 GET / HTTP/1.1
        String[] split = inputStr.split("\\n");
        String[] infoArr = split[0].split(" ");
        this.methodType = infoArr[0];
        this.url = infoArr[1];

        System.out.println("=====>>method:" + methodType);
        System.out.println("=====>>url:" + url);
    }
}

Response响应对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Response {
    private OutputStream outputStream;

    //V2版本只输出静态资源文件
    public void outputHtml(String path) throws IOException {
        //获取到文件的绝对路径
        String absolutePath = StaticResourceUtil.getAbsolutePath(path);
        File file = new File(absolutePath);
        if (file.exists() && file.isFile()) {

            //调用工具类输出文件
            StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
        }else{
            //404 Not Found
            output(HttpProtocolUtil.getHeader404());
        }
    }

    //输出文件
    private void output(String path) throws IOException {
        outputStream.write(path.getBytes());
    }
}


静态资源处理工具

public class StaticResourceUtil {

 /**
     * 获取到文件的绝对路径并且替换\\为/方便linux识别
     */
 public static String getAbsolutePath(String path) {
        //获取到当前的绝对路径
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return (absolutePath + path).replaceAll("\\\\", "/");
    }

 /**
     * 根据输入流 对文件进行输出
     *
     * @param inputStream  输入流
     * @param outputStream 输出流
     */
 public static void outputStaticResource(InputStream inputStream, 
                                               OutputStream outputStream) throws IOException {
        //缓冲区
        int buffer = 1024;
        int actualOutSize = 0;

        //获取需要输出文件流的长度
        int outputSize = 0;
        while (outputSize == 0){
            outputSize = inputStream.available();
        }

        //输出请求头
        outputStream.write(HttpProtocolUtil.getHeader200(outputSize).getBytes());

        byte[] bytes = new byte[buffer];
        while (actualOutSize < outputSize){
            //如果最后不够一个缓冲区的话需要获取到最后的数据length
            if (actualOutSize + buffer > outputSize) {
                buffer = outputSize - actualOutSize;
                bytes = new byte[buffer];
            }
            //从输入流中读取
            inputStream.read(bytes);
            //写出到输出流
            outputStream.write(bytes);
            //刷新输出流
            outputStream.flush();
            actualOutSize += buffer;
        }
    }
}

总结: 底层使用的是JavaSocket编程,就是对应输入和输出流进行了封装,截取了需要的信息。

3.4 V3版本Code

需求,能够接收静态和动态资源,使用线程池接收。

基于V2版本Request和Response进行开发。

添加Pom Dom4j用来配置servlet

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
    </dependency>

    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>


web.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app>

    <servlet>
        <servlet-name>myServlet</servlet-name>
        <servlet-class>com.tomcat.demo.servlet.MyServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>myServlet</servlet-name>
        <url-pattern>/hello</url-pattern>
    </servlet-mapping>

</web-app>

Servlet

public interface Servlet {
   //初始化
   void init() throws Exception;
   //销毁
   void destroy() throws Exception;
   //执行请求
   void service(Request request, Response response) throws Exception;
}
public abstract class HttpServlet implements Servlet {

    public abstract void doGet(Request request, Response response)throws Exception;
    public abstract void doPost(Request request, Response response)throws Exception;

    @Override
    public void service(Request request, Response response) throws Exception {
        if ("GET".equalsIgnoreCase(request.getMethodType())) {
            doGet(request,response);
        }else {
            doPost(request,response);
        }
    }
}

public class MyServlet extends HttpServlet {

    @Override
    public void doGet(Request request, Response response) throws Exception {
        doPost(request,response);
    }

    @Override
    public void doPost(Request request, Response response) throws Exception {
        String content = "<H1>Hello Servlet</H1>";
        String header200 = HttpProtocolUtil.getHeader200(content.getBytes().length);
        response.output(header200 + content);
    }

    @Override
    public void init() throws Exception {
        System.out.println("初始化方法...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("销毁方法...");

    }
}

BootStrap启动类

public class Bootstrap {

    private static final int port = 8080;
    private Map<String, Servlet> servletMap = new HashMap<>();


    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 /**
     * 启动tomcat
     */
 private void start() throws IOException {
        ServerSocket serverSocket = new ServerSocket(port);

        //解析web.xml
        parseWebXml();

        //创建一个线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                20,//指定核心线程数
                100,//指定最大线程数
                100L,//指定存活时间
                TimeUnit.MILLISECONDS,//指定时间格式
                new LinkedBlockingDeque<>(1000));//设置阻塞队列大小

        while (true){
            //获取到socket
            Socket socket = serverSocket.accept();

            //通过线程池去执行
            threadPool.execute(new RequestProcessor(socket, servletMap));
        }
    }

    private void parseWebXml() {
        try {
            InputStream resourceAsStream = this.getClass().getResourceAsStream("/web-apps/WEB-INF/web.xml");
            Document document = new SAXReader().read(resourceAsStream);
            Element rootElement = document.getRootElement();
            Element servletElement = (Element) rootElement.selectSingleNode("servlet");
            String servletName = servletElement.selectSingleNode("servlet-name").getStringValue();
            String servletClass = servletElement.selectSingleNode("servlet-class").getStringValue();

            Element servletMapping = (Element) rootElement.selectSingleNode("servlet-mapping[servlet-name = '" + servletName + "']");
            String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();

            servletMap.put(urlPattern, (Servlet) Class.forName(servletClass).newInstance());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

RequestProcessor请求处理类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestProcessor implements Runnable {
    private Socket socket;
    private Map<String, Servlet> servletMap;

    @Override
    public void run() {
        try {
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();

            Request request = new Request(inputStream);
            Response response = new Response(outputStream);

            //动态资源
            if (servletMap.containsKey(request.getUrl())) {
                servletMap.get(request.getUrl()).service(request, response);
            }else {
                //静态资源
                response.outputHtml(request.getUrl());
            }

            //关闭当前socket
 socket.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}


总结:书写Mini版本的Tomcat感受一下Tomcat整体的处理思路。

多数PHP程序都使用HTML表单从用户那里获取数据并计算结果。

HTML表单的一些基本原则

  • 选择适合于收集的数据类型和提供交互方式的控件。
  • 清楚标记每一个控件,这样用户就可以理解其功能。
  • 尽可能将标签对齐。将控件的左边缘对齐。
  • 将相关的标签分组,并且通过设计中使用空白将每一个分组分开。
  • 表单上的控件顺序应该类似于用户操作他们的顺序。

创建基本HTML表单

首先创造一个基本的HTML大纲,包含表单控件;然后将控件进行合并(HTML表单必须包括一个提交按钮,用户单击它可以将表单数据发送到服务器。)一个单独的HTML页面可以包含多个表单。

  • 创建HTML结构

包含表单的HTML结构和和普通的HTML结构一样。

<HTML>
  <HEAD>
  <TITLE>标题放在这</TITLE>
  </HEAD>
<BODY>
  表单页面放在这
  </BODY>
  </HTML>

在包含表单的HTML页面中可以使用任何HTML标签。基本的表单使用FROM标签来说明。该标签中METHOD属性接收GET或POST两个值中的一个。ACTION属性子明PHP脚本的url,该脚本可以收集通过表单收集的数据,可以是绝对路径或者相对路径

<FORM METHOD="method" ACTION="url"> 
  中间可以放置表单控件
  </FORM>
  • 合并控件

两个常用的基本控件:文本框和提交按钮。

文本框:允许用户键入信息以发送给PHP脚本。NAME属性为文本提供名称,PHP脚本可以通过名称准确访问其内容,因此它应该是唯一的且符合PHP变量命名规则(但不需要$符号),单标签。VALUE属性指明出现在提交按钮上面的标题。创建方式如下:

<INPUT TYPE = "TEXT" NAME="text">

提交按钮:允许用户将一个表单的内容发送到服务器,一个HTML表单对应应该有一个提交按钮。

示例:一个完整的HTML表单。

<HTML>
  <HEAD>
  <TITLE>标题</TITLE>
  </HEAD>
<BODY>
<FORM METHOD="POST" ACTION="phpinfo.php">
<INPUT TYPE="TEXT" NAME="user_name">
<BR/>
<BR/>
<INPUT TYPE="TEXT" NAME="user_email">
<BR/>
<BR/>
<INPUT TYPE="SUBMIT" VALUE="Send the Data">
</FORM>
  </BODY>
  </HTML>


  • 使用多个表单

可以在一个HTML页面中包含多个表单,注意下一个表单的FORM开始之前需要结束前一个FORM表单。

<HTML>
  <HEAD>
  <TITLE>标题</TITLE>
  </HEAD>
<BODY>
  
<FORM METHOD="POST" ACTION="phpinfo.php">
<INPUT TYPE="TEXT" NAME="user_name">
<BR/>
<BR/>
<INPUT TYPE="TEXT" NAME="user_email">
<BR/>
<BR/>
<INPUT TYPE="SUBMIT" VALUE="Send the Data">
<BR/>
<BR/>
</FORM>

<FORM METHOD="POST" NAME="phpinfo.php">
<INPUT TYPE="TEXT" NAME="user_name1">
<BR/>
<BR/>
<INPUT TYPE="TEXT" NAME="user_email1">
<BR/>
<BR/>
<INPUT TYPE="SUBMIT" VALUE="Send the Data1">
</FORM>

  </BODY>
  </HTML>


创建表单控件

  • 创建自定义的文本框

文本框的属性中,TYPE和NAME是必须的,其余是可选属性。SIZE属性用于设置文本框的可视大小;MAXLENGTH指明用户键入字符的最大长度;VALUE给出了一个最初显示在文本框中的值。

<input type="text" name="" size="" maxlength="" value="">
  • 创建文本区域

文本区域可以输入多行文本。NAME和ROWS属性是必须的。ROWS属性表明了文本区域内可以看到的文本行数,充满时会滚动。COLS属性指明可见文本列数与行数类似。WRAP属性指明文本区域内单词换行的方式,可以指定如下值。该标签为双标签。

说明

off

禁止单词换行但用户可以输入换行符强制换行

virtual/soft

各行显示为换行,但是换行并没有被发送到服务器

physica/hard

启用了单词换行

<inputarea name="" rows="" cols="" wrap="">
  • 创建密码框

创建密码框的语法与文本框相同,但要将TYPE属性指定为PASSWORD而不是TYPE。

<input type="password" name="" size="" maxlength="" value="">
  • 创建复选框

取两个值中的一个,即二选一。TYPE属性是必须的,checked属性出现,该复选框默认情况会被选定。value属性指定复选框被选定情况下被发送到服务器的值,默认发送on值。法如下:

<input type="checkbox" name="" checked value="">
  • 创建单选按钮

语法与复选框属性含义相同,但是TYPE属性的值必须是RADIO,NAME属性是必须的。

<input type="radio" name="" checked value="">
  • 创建列表框

用户可以选择一个或者多个选项,它是一个滚动菜单。

<select name="" multipile size="">options go here</select>

name属性是必须的,multipile属性指明用户可以通过按下crtl键并单击多个选项来选择它们

列表框的单选行为可作为单选按钮。

<option selected value="text"></options>
  • 创建隐藏域
<input type="hidden" name="text"value="">
  • 实现上传文件的HTML表单
<input type="FILE" name="name" accept="time" value="text">

其中type属性是必须的。格式通过使用MIME码指定。常用的格式如下:


超文本标记语言文本 .html,.html text/html

  普通文本 :txt text/plain

  word文档:application/msword

  RTF文本 :rtf application/rtf

  GIF图形 :gif image/gif

  JPEG图形 :jpeg,

  jpg: image/jpeg

  au声音文件:au audio/basic

  MIDI音乐文件 :mid,.midi audio/midi,audio/x-midi

  RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio

  MPEG文件 .mpg,.mpeg video/mpeg

  AVI文件 .avi video/x-msvideo

  GZIP文件 .gz application/x-gzip

  压缩文件.rar application/octet-stream

  压缩文件.zip application/x-zip-compressed

  TAR文件 .tar application/x-tar


更多提交表单的信息

  • 使用图像提交数据
<input type="image" src="url" name="text" align="align">
  • 创建重置按钮
<input type="reset" value="text">

olang 简介

Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。

golang发展前景

适合云计算、微服务、基础后端软件的功能实现。

go语言优点

go是非常年轻的一门语言,“兼具Python 等动态语言的开发速度和C/C++等编译型语言的性能与安全性”,具备强大的标准库,内置垃圾回收机制,属于静态类型语言,因此具备编译检查和编码规范,特别是并发功能实现简单,但功能强大,可使用goroutine和channel轻松实现高并发。

golang web 优势

语法简单:语法简洁,新手入门简单,开发应用性能高。

便于维护:强类型静态语言,便于项目的重构和维护。

部署方便:编译的可执行二进制文件,在执行时,不需要部署环境,把编译文件上传,就完成了项目的部署。

1 - 构建Web服务

2 - 查看GET请求报文

GET /index HTTP/1.1 //请求行信息 , 方法是GET , 请求地址是/index , 请求版本HTTP/1.1

Host: 127.0.0.1 //以下是请求头信息

Connection: keep-alive

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36

Sec-Fetch-User: ?1

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

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

3 - 查看POST请求报文

构建请求页面

构建请求服务端

查看请求行 , 请求头 , 请求体

4 - 查看相应

5 - 响应状态码

1xx :(临时响应),代表请求已被接受,需要继续处理。

2xx :(成功),代表请求已成功被服务器接收、理解、并接受。

3xx :(重定向),代表需要客户端采取进一步的操作才能完成请求。

4xx :(请求错误),代表客户端看起来可能发生了错误,妨碍了服务器的处理。

5xx :(服务器错误),代表服务器在处理请求的过程中有错误或者异常状态发生。

常见的状态码

200 : 服务器成功返回网页

404 : 请求的网页不存在

503 : 服务不可用