- 任务一:Tomcat 系统架构与原理剖析
- 1. 浏览器访问服务器的流程
- 2. Tomcat 连接器组件 Coyote
- 3. Tomcat Servlet 容器 Catalina
- 4. Tomcat 服务器核心配置详解
- 任务二:手写实现迷你版 Tomcat
- 1. 手写实现迷你版 Tomcat
- 2. Tomcat 类加载机制剖析
- JVM 的类加载机制
- 双亲委派机制
- Tomcat 的类加载机制
- 3. Tomcat 对 Https 的支持及 Tomcat 配置调优策略
- Tomcat 对 Https 的支持
- Tomcat 配置调优
- 任务三:Nginx
- 1. Nginx简介
- 2. Nginx范例
顺利完成模块四的学习,正式进入第二阶段模块一。
1. 浏览器访问服务器的流程
注意:浏览器访问服务器使⽤的是Http协议,Http是应⽤层协议,⽤于定义数据通信的格式,具体的数据传输使⽤的是TCP/IP协议。
当⽤户请求某个URL资源时:
1)HTTP服务器会把请求信息使⽤ServletRequest对象封装起来
2)进⼀步去调⽤Servlet容器中某个具体的Servlet
3)在 2)中,Servlet容器拿到请求后,根据URL和Servlet的映射关系,找到相应的Servlet
4)如果Servlet还没有被加载,就⽤反射机制创建这个Servlet,并调⽤Servlet的init⽅法来完成初始化
5)接着调⽤这个具体Servlet的service⽅法来处理请求,请求处理结果使⽤ServletResponse对象封装
6)把ServletResponse对象返回给HTTP服务器,HTTP服务器会把响应发送给客户端
Tomcat 设计了两个核⼼组件连接器(Connector)和容器(Container)来完成 Tomcat 的两⼤核⼼功能。
连接器,负责对外交流: 处理Socket连接,负责⽹络字节流与Request和Response对象的转化;
容器,负责内部处理:加载和管理Servlet,以及具体处理Request请求;
Coyote 是Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过Coyote与服务器建⽴连接、发送请求并接受响应 。
(1)Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)
(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦
(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流
(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容
Tomcat是⼀个由⼀系列可配置(conf/server.xml)的组件构成的Web容器,⽽Catalina是Tomcat的servlet容器。
从另⼀个⻆度来说,Tomcat 本质上就是⼀款 Servlet 容器, 因为 Catalina 才是 Tomcat 的核⼼ , 其他模块都是为Catalina 提供⽀撑的。 ⽐如 : 通过 Coyote 模块提供链接通信,Jasper 模块提供 JSP 引擎,Naming 提供JNDI 服务,Juli 提供⽇志服务。
Container组件下有⼏种具体的组件,分别是Engine、Host、Context和Wrapper。这4种组件(容器)是⽗⼦关系。Tomcat通过⼀种分层的架构,使得Servlet容器具有很好的灵活性。
- Engine表示整个Catalina的Servlet引擎,⽤来管理多个虚拟站点,⼀个Service最多只能有⼀个Engine,但是⼀个引擎可包含多个Host
- Host代表⼀个虚拟主机,或者说⼀个站点,可以给Tomcat配置多个虚拟主机地址,⽽⼀个虚拟主机下可包含多个Context
- Context表示⼀个Web应⽤程序, ⼀个Web应⽤可包含多个Wrapper
- Wrapper表示⼀个Servlet,Wrapper 作为容器中的最底层,不能包含⼦容器上述组件的配置其实就体现在conf/server.xml中。
Server 标签
...
Service 标签
...
Executor 标签
Connector 标签
Engine 标签
...
Host 标签
...
Context 标签
任务二:手写实现迷你版 Tomcat 1. 手写实现迷你版 Tomcat
Bootstrap 启动类
package server;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
public class Bootstrap {
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public void start() throws Exception {
// 加载解析相关的配置,web.xml
loadServlet();
// 定义⼀个线程池
int corePoolSize = 10;
int maximumPoolSize =50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new
ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("=====>>>Minicat start on port:" + port);
System.out.println("=========>>>>>>使⽤线程池进⾏多线程改造");
while(true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new
RequestProcessor(socket,servletMap);
//requestProcessor.start();
threadPoolExecutor.execute(requestProcessor);
}
}
private Map servletMap = new
HashMap();
private void loadServlet() {
InputStream resourceAsStream =
this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List selectNodes =
rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// lagou
Element servletnameElement = (Element)
element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// server.LagouServlet
Element servletclassElement = (Element)
element.selectSingleNode("servlet-class");
String servletClass =
servletclassElement.getStringValue();
// 根据servlet-name的值找到url-pattern
Element servletMapping = (Element)
rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" +
servletName + "']");
// /lagou
String urlPattern = servletMapping.selectSingleNode("url-
pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet)
Class.forName(servletClass).newInstance());
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Http协议⼯具类
package server;
public class HttpProtocolUtil {
public static String getHttpHeader200(long contentLength) {
return "HTTP/1.1 200 OK n" +
"Content-Type: text/html n" +
"Content-Length: " + contentLength + " n" +
"rn";
}
public static String getHttpHeader404() {
String str404 = "404 not found";
return "HTTP/1.1 404 NOT Found n" +
"Content-Type: text/html n" +
"Content-Length: " + str404.getBytes().length + " n" +
"rn" + str404;
}
}
Request封装类
package server;
import java.io.IOException;
import java.io.InputStream;
public class Request {
private String method; // 请求⽅式,⽐如GET/POST
private String url; // 例如 /,/index.html
private InputStream inputStream; // 输⼊流,其他属性从输⼊流中解析出来
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public Request() {
}
// 构造器,输⼊流传⼊
public Request(InputStream inputStream) throws IOException {
this.inputStream = inputStream;
// 从输⼊流中获取请求信息
int count = 0;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputStr = new String(bytes);
// 获取第⼀⾏请求头信息
String firstLineStr = inputStr.split("\n")[0]; // GET / HTTP/1.1
String[] strings = firstLineStr.split(" ");
this.method = strings[0];
this.url = strings[1];
System.out.println("=====>>method:" + method);
System.out.println("=====>>url:" + url);
}
}
Response封装类
package server;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Response {
private OutputStream outputStream;
public Response() {
}
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
// 使⽤输出流输出指定字符串
public void output(String content) throws IOException {
outputStream.write(content.getBytes());
}
public void outputHtml(String path) throws IOException {
// 获取静态资源⽂件的绝对路径
String absoluteResourcePath =
StaticResourceUtil.getAbsolutePath(path);
// 输⼊静态资源⽂件
File file = new File(absoluteResourcePath);
if(file.exists() && file.isFile()) {
// 读取静态资源⽂件,输出静态资源
StaticResourceUtil.outputStaticResource(new
FileInputStream(file),outputStream);
}else{
// 输出404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
静态资源请求处理⼯具类
package server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StaticResourceUtil {
public static String getAbsolutePath(String path) {
String absolutePath =
StaticResourceUtil.class.getResource("/").getPath();
return absolutePath.replaceAll("\\","/") + path;
}
public static void outputStaticResource(InputStream inputStream,
OutputStream outputStream) throws IOException {
int count = 0;
while(count == 0) {
count = inputStream.available();
}
int resourceSize = count;
// 输出http请求头,然后再输出具体内容
outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getByt
es());
// 读取内容输出
long written = 0 ;// 已经读取的内容⻓度
int byteSize = 1024; // 计划每次缓冲的⻓度
byte[] bytes = new byte[byteSize];
while(written < resourceSize) {
if(written + byteSize > resourceSize) { // 说明剩余未读取⼤⼩不⾜⼀个1024⻓度,那就按真实⻓度处理
byteSize = (int) (resourceSize - written); // 剩余的⽂件内容⻓度
bytes = new byte[byteSize];
}
inputStream.read(bytes);
outputStream.write(bytes);
outputStream.flush();
written+=byteSize;
}
}
}
业务类Servlet定义LagouServlet
package server;
import java.io.IOException;
public class LagouServlet extends HttpServlet {
@Override
public void doGet(Request request, Response response) {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String content = "LagouServlet get";
try {
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes()
.length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String content = "LagouServlet post";
try {
多线程改造封装的RequestProcessor类
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes()
.length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void init() throws Exception {
}
@Override
public void destory() throws Exception {
}
}
多线程改造封装的RequestProcessor类
package server;
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;
public class RequestProcessor extends Thread {
private Socket socket;
private Map servletMap;
public RequestProcessor(Socket socket, Map
servletMap) {
this.socket = socket;
this.servletMap = servletMap;
}
@Override
public void run() {
try{
InputStream inputStream = socket.getInputStream();
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
// 静态资源处理
if(servletMap.get(request.getUrl()) == null) {
response.outputHtml(request.getUrl());
}else{
// 动态资源servlet请求
HttpServlet httpServlet =
servletMap.get(request.getUrl());
httpServlet.service(request,response);
}
socket.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
2. Tomcat 类加载机制剖析
Java类(.java)—> 字节码⽂件(.class) —> 字节码⽂件需要被加载到jvm内存当中(这个过程就是⼀个类加载的过程)
类加载器(ClassLoader,说⽩了也是⼀个类,jvm启动的时候先把类加载器读取到内存当中去,再用类加载器加载其他的类(⽐如各种jar中的字节码⽂件,⾃⼰开发的代码编译之后的.class⽂件等等))
要说 Tomcat 的类加载机制,⾸先需要来看看 Jvm 的类加载机制,因为 Tomcat 类加载机制是在 Jvm 类加载机制基础之上进⾏了⼀些变动。
JVM 的类加载机制中有⼀个⾮常重要的⻆⾊叫做类加载器(ClassLoader),类加载器有⾃⼰的体系,JVM内置了⼏种类加载器,包括:引导类加载器、扩展类加载器、系统类加载器,他们之间形成父子关系,通过 Parent 属性来定义这种关系,最终可以形成树形结构。
- 双亲委派机制:当某个类加载器需要加载某个.class⽂件时,它⾸先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,⾃⼰才会去加载这个类。
- 作用:防⽌重复加载同⼀个.class。通过委托去向上⾯问⼀问,加载过了,就不⽤再加载⼀遍,保证数据安全。保证核⼼.class不能被篡改。通过委托⽅式,不会去篡改核⼼.class,即使篡改也不会去加载,即使加载也不会是同⼀个.class对象了。不同的加载器加载同⼀个.class也不是同⼀个.class对象。这样保证了class执⾏安全(如果⼦类加载器先加载,那么我们可以写⼀些与java.lang包中基础类同名的类, 然后再定义⼀个⼦类加载器,这样整个应⽤使⽤的基础类就都变成我们⾃⼰定义的类了。)
- Object类 -----> ⾃定义类加载器(会出现问题的,那么真正的Object类就可能被篡改了)
- 引导类加载器 和 扩展类加载器 的作⽤不变
- 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使⽤该变量,⽽是加载tomcat启动的类,⽐如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下
- Common 通⽤类加载器加载Tomcat使⽤以及应⽤通⽤的⼀些类,位于CATALINA_HOME/lib下,⽐如servlet-api.jar
- Catalina ClassLoader ⽤于加载服务器内部可⻅类,这些类应⽤程序不能访问
- Shared ClassLoader ⽤于加载应⽤程序共享类,这些类服务器不会依赖
- Webapp ClassLoader,每个应⽤程序都会有⼀个独⼀⽆⼆的Webapp ClassLoader,他⽤来加载本应⽤程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类
Tomcat 配置调优 任务三:Nginx 1. Nginx简介
- Nginx 简介:Nginx 是⼀个⾼性能的HTTP和反向代理web服务器,核⼼特点是占有内存少,并发能⼒强。
- 正向代理:在浏览器中配置代理服务器的相关信息,通过代理服务器访问⽬标⽹站,代理服务器收到⽬标⽹站的响应之后,会把响应信息返回给我们⾃⼰的浏览器客户端。
- 反向代理:浏览器客户端发送请求到反向代理服务器(⽐如Nginx),由反向代理服务器选择原始服务器提供服务获取结果响应,最终再返回给客户端浏览器。
- Nginx的核⼼配置⽂件conf/nginx.conf包含三块内容:全局块、events块、http块。
- 全局块:从配置⽂件开始到events块之间的内容,此处的配置影响nginx服务器整体的运⾏,⽐如worker进程的数量、错误⽇志的位置等。
- events块:events块主要影响nginx服务器与⽤户的⽹络连接,⽐如worker_connections 1024,标识每个workerprocess⽀持的最⼤连接数为1024
- http块:http块是配置最频繁的部分,虚拟主机的配置,监听端⼝的配置,请求转发、反向代理、负载均衡等
- 9003端口转8080端口
- /abc转8080端口,/def转8081端口



