在,我们已经充分了解了 HTTP 和 Socket 的关系,也了解了 HTTP 报文的格式,为了让小伙伴能够加深对这两个概念的理解,本文我们来看看如何利用 Socket 模拟 HTTP 请求。如果小伙伴们对 HTTP 和 Socket 的关系、HTTP 报文格式尚不熟悉的话,可以参考前面的文章 Http 和 Socket 到底是哪门子亲戚?。
由于 HTTP 是基于 TCP 协议的应用层协议,因此我们可以用更为底层的方式来访问 HTTP 服务,即直接使用 Socket 完成 HTTP 的请求和响应。我们前面说过,HTTP 的任务就是完成数据的包装, Socket 提供了网络的传输能力,所以我们只需要按照 HTTP 报文的格式来组装数据,然后利用 Socket 将数据发送出去,就能得到回应。
假设我现在有一个数据接口 http://localhost/hello,该接口每次接收一个参数 name ,调用成功之后返回给用户一个 hello:name 字符串,那我们用 Socket 来实现这样一个 HTTP 请求。
首先,我们要先组装出 HTTP 的请求头,如下(如果小伙伴对下面这个请求头有疑问,请复习 Http 和 Socket 到底是哪门子亲戚?一文):
POST /hello HTTP/1.1
Accept:text/html
Accept-Language:zh-cn
Host:localhost
name=张三
我这里为了简单,只添加了三个请求头,然后我们通过 Socket 将上面这个字符串发送出去:
Socket socket=new Socket(InetAddress.getByName("localhost"), 80);
OutputStream os=socket.getOutputStream();
String data="name=张三";
int dataLen=data.getBytes().length;
String contentType="Content-Length:" + dataLen+"\r\n";
os.write("POST /hello HTTP/1.1\r\n".getBytes());
os.write("Accept:text/html\r\n".getBytes());
os.write("Accept-Language:zh-cn\r\n".getBytes());
os.write(contentType.getBytes());
os.write("Host:localhost\r\n".getBytes());
os.write("\r\n".getBytes());
os.write(data.getBytes());
os.write("\r\n".getBytes());
os.flush();
我在 Serlvet 中接收这个请求并作简单处理,如下:
BufferedReader br=new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
StringBuffer sb=new StringBuffer();
String str;
while ((str=br.readLine()) !=null) {
sb.append(str).append("\r\n");
}
System.out.println("sb:"+sb.toString());
resp.setContentType("text/html;charset=utf-8");
PrintWriter out=resp.getWriter();
out.write(sb.toString());
out.flush();
out.close();
然后通过 Socket 中的输入流我就能拿到响应结果,如下:
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
StringBuffer sb=new StringBuffer();
String str;
while ((str=br.readLine()) !=null) {
sb.append(str).append("\r\n");
}
System.out.println(sb.toString());
响应结果如下:
HTTP/1.1 200
Content-Type: text/html;charset=utf-8
Transfer-Encoding: chunked
Date: Sun, 03 Dec 2017 10:46:52 GMT
name=张三
这是一个简单的通过 POST 请求下载文本的案例。接下来我们再来一个 GET 请求下载图片的案例,来加深对 Socket 的理解。
这个实际上也不难,但是要实现图片的下载需要我们首先熟悉HTTP响应的数据格式,不熟悉的小伙伴可以阅读 Http 和 Socket 到底是哪门子亲戚?一文。
下载图片,响应头是文本文件,响应数据是二进制文件,我们要想办法通过空行将这两块数据分开,分别处理。为了解决这个问题,我首先提供一个工具类,这个工具类用来实现一行一行的解析字节流,如下:
public class BufferedLineInputStream {
private InputStream is;
public BufferedLineInputStream(InputStream is) {
this.is=is;
}
/**
* @param buf 将数据读入到byte数组中
* @param offset 数组偏移量
* @param len 数组长度
* @return 返回值表示读取到的数据长度 -1表示数据读取完毕,0表示其他异常情况
*/
public int readLine(byte[] buf, int offset, int len) throws IOException {
if (len < 1) {
return 0;
}
//count用来统计已经向数组中存储了多少数据
int count=0, c;
while ((c=is.read()) !=-1) {
buf[offset++]=(byte) c;
count++;
//如果一行已经读完或者数组已满
if (c=='\n' || count==len) {
break;
}
}
return count > 0 ? count : -1;
}
}
然后将响应中的头信息和图片分别保存在不同的文件中,数据解析的核心思路就是一行一行读取响应数据,当遇到 \r\n 表示头信息已经读取完了,要开始读取二进制数据了,二进制数据读取到之后,将之保存成图片即可。核心代码如下:
fos=new FileOutputStream("E:\333.png");
pw=new PrintWriter(new OutputStreamWriter(new FileOutputStream("E:\222.txt")));
socket=new Socket(InetAddress.getByName("localhost"), 80);
OutputStream out=socket.getOutputStream();
out.write("GET /1.png HTTP/1.1\r\n".getBytes());
out.write("Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n".getBytes());
out.write("Accept-Language:zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3\r\n".getBytes());
out.write("Host:localhost\r\n".getBytes());
out.write("\r\n".getBytes());
BufferedLineInputStream blis=new BufferedLineInputStream(socket.getInputStream());
int len;
byte[] buf=new byte[1024];
while ((len=blis.readLine(buf, 0, buf.length)) !=-1) {
String s=new String(buf, 0, len);
System.out.println(s);
if (s.equals("\r\n")) {//表示头信息读取完毕
break;
}
}
//开始解析二进制的图片数据
while ((len=blis.readLine(buf, 0, buf.length)) !=-1) {
fos.write(buf, 0, len);
}
OK,Socket 模拟 HTTP 请求我们就先说到这里,两个案例,希望能够加深小伙伴对 Socket 和 HTTP 的理解。
HTTPS (HyperText Transfer Protocol Secure) 是一种通过 SSL/TLS 加密保护数据传输安全的 HTTP 协议。HTTPS 的加密机制是保证数据在传输过程中不会被窃取、篡改或冒充的关键。
本文将详细介绍 HTTPS 的加密过程及其工作原理。
HTTPS 的加密过程可以分为以下步骤:
下面我们将详细介绍每个步骤的细节。
当客户端需要从服务器获取数据时,它会向服务器发送一个 HTTPS 请求。这个请求包括请求的 URL、HTTP 请求头和请求体。例如:
GET / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
当服务器接收到 HTTPS 请求后,它会将公钥证书发送给客户端。公钥证书中包含了服务器的公钥、服务器的域名、证书颁发机构、证书有效期等信息。
客户端接收到证书后,会从中提取出服务器的公钥。
客户端接收到服务器的证书后,会对其进行验证,以确保该证书是由可信任的证书颁发机构颁发的,并且证书中的域名和服务器的实际域名一致。
如果证书验证失败,客户端会中断连接。如果验证通过,客户端会生成一个用于会话的对称密钥。
客户端生成一个用于会话的对称密钥。对称密钥是一种加密方式,它使用相同的密钥进行加密和解密。这个密钥只存在于客户端和服务器之间,因此被称为“对称”。
客户端使用服务器的公钥对对称密钥进行加密,并将加密后的密钥发送给服务器。在这个过程中,客户端和服务器都知道对称密钥,但是只有客户端知道对称密钥的值。
服务器使用私钥对客户端发送的加密密钥进行解密,得到对称密钥。由于私钥只在服务器端保存,因此只有服务器才能解密客户端发送的加密密钥,并得到对称密钥的值。
服务器和客户端使用对称密钥进行加密和解密数据传输。这个对称密钥只存在于客户端和服务器之间,因此对数据的加密和解密只有客户端和服务器可以进行。
HTTPS 的加密过程基于公钥密码学和对称密钥密码学。
在 HTTPS 的加密过程中,公钥密码学用于保护对称密钥的安全传输,而对称密钥密码学用于加密和解密数据传输。
公钥密码学是一种密码学技术,它使用一对密钥(公钥和私钥)来加密和解密数据。公钥可以被任何人获得并用于加密数据,但是只有私钥的拥有者才能解密数据。
在 HTTPS 的加密过程中,服务器使用公钥加密对称密钥,客户端使用私钥解密对称密钥。这样就可以保证对称密钥在传输过程中不会被窃取、篡改或冒充。
对称密钥密码学是一种密码学技术,它使用相同的密钥进行加密和解密数据。在 HTTPS 的加密过程中,客户端和服务器都知道对称密钥的值,因此可以使用对称密钥对数据进行加密和解密。
对称密钥密码学的优点是加密和解密速度快,但是对称密钥的安全传输是一个问题。因此,在 HTTPS 的加密过程中,公钥密码学用于保护对称密钥的安全传输,保证数据在传输过程中不会被窃取、篡改或冒充。
HTTPS 加密是保证数据传输安全的关键。在 HTTPS 的加密过程中,公钥密码学用于保护对称密钥的安全传输,对称密钥密码学用于加密和解密数据传输。
HTTPS 加密的过程中,客户端和服务器之间建立了一个安全的通信通道。在这个通道中,数据被加密传输,确保了数据在传输过程中的机密性、完整性和真实性。
在今天的互联网时代,数据安全越来越受到重视。HTTPS 加密作为一种保证数据传输安全的技术,已经成为现代互联网通信的标准之一。通过了解 HTTPS 加密的工作原理,我们可以更好地理解互联网通信的安全性,并且能够更好地保护自己的数据安全。
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应HTML(标准通用标记语言下的一个应用)页面的访问请求。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。
目录说明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工作相关的信息
不同计算机上应用之间的通信,来解决通信双方数据传输的问题。或者说不同计算机上对应的应用进程之间的通信。支持的协议有: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等)【在四层模型里面已经合并到了数据链路层】
http请求处理过程
第2步是先建立连接,进行3次握手。
因为Tomcat可以处理Http请求,因此称Tomcat为Http服务器。
如果我们直接从浏览器发送请求,Tomcat直接接收请求,并将请求转发到对应的java程序的话,也可以实现请求处理,但是耦合度非常高,因此Tomcat的做法是在接收到http请求之后,将请求转发给servlet容器,由servlet容器在进行处理并转发到对应的java程序。
因此在Tomcat内部也实现了Servlet规范。(Servlet接?和Servlet容器这?整套内容叫作Servlet规范。)
因此Tomcat有两个身份:
1、Http服务器
2、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相应到浏览器
Tomcat中包括两大核心组件:
连接器(Connector)组件,容器(Container)组件。(还包括其他组件)
连接器(Connector)组件主要作用:处理Socket连接,负责?络字节流与Request和Response对象的转化。【与客户端交互】
容器(Container)组件主要作用:加载和管理Servlet,处理Request请求;
Coyote是Tomcat中连接器的组件名称,是对应的接口。客户端通过Coyote与服务器建立连接、发送请求并接受响应。
1)Coyote封装了底层网络通信(Socket请求及相应处理)
2)Coyote使Container容器组件与具体的请求协议及IO操作?式完全解耦
3)Coyote将Socket输入转换封装为Request对象,并进一步封装成ServletRequest交给Container容器进行处理,处理完成后返回ServletResponse给Coyote,Coyote将对象转换成Response对象将结果写入输出流。
4)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的性能。
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。
本来Catalina组件只是Servlet容器的一个组件,而Tomcat是由一些列组件组成,组件可以在conf/server.xml文件中配置。Catalina在Tomcat中的地位非常的核心,因此经常把tomcat的一个实例当作一个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?户的模块。
Container组件其中包括Engine、Host、Context、Wrapper4种组件,他们之间是父子关系。Tomcat通过分层的架构,让Servlet容器具有很好的灵活性。
Engine:表示整个Servlet容器的引擎,用来管理多个虚拟站点,一个Service只能有一个Engine。
Host:代表一个虚拟主机,或者说一个站点,可以配置多个虚拟主机地址,一个Engine可以有多个Host。
Context:表示一个Web应用服务器,一个Host下可以包含多个Context。
Wrapper:表示一个Servlet, 一个Context中可以包括多个Wrapper。
思考:
去哪儿配置?->conf/server.xml中
怎么配置?
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>
<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
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
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 name="Catalina" defaultHost="localhost">
...
</Engine>
name属性: ?于指定Engine 的名称, 默认为Catalina
defaultHost:默认使?的虚拟主机名称, 当客户端请求指向的主机?效时, 将交由默认的虚拟主机处
理, 默认为localhost。
<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 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。
手写一个Tomcat名称叫做Minicat,需求:可以接收浏览器的http请求,并进行请求处理,处理之后将结果返回给浏览器客户端。
1)提供服务,接收请求(Socket通信)
2)请求信息封装成Request对象(Response对象)
3)客户端请求资源,资源分为静态资源和动态资源
4)将资源返回给客户端
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响应头信息浏览器才能显示
要求:能够输出静态资源文件。
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对象。
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();
}
}
}
@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);
}
}
@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编程,就是对应输入和输出流进行了封装,截取了需要的信息。
需求,能够接收静态和动态资源,使用线程池接收。
基于V2版本Request和Response进行开发。
<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>
<?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>
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("销毁方法...");
}
}
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();
}
}
}
@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整体的处理思路。
*请认真填写需求信息,我们会在24小时内与您取得联系。