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 {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("=====>>>Minicat start on port:" + port);
while(true) {
Socket socket = serverSocket.accept();
// 有了socket,接收到请求,获取输出流
OutputStream outputStream = socket.getOutputStream();
String data = "Hello Minicat!";
//调用封装好的HttpProtocolUtil类的方法,输出响应头
String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
outputStream.write(responseText.getBytes());
socket.close();
}
}
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 创建HttpProtocolUtil类
创建该类的目的旨在向浏览器输出时,按照浏览器的解析格式输出文件头信息。分别为响应码200级响应码404提供请求头信息。
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;
}
}
3. 测试
浏览器中输入:localhost:8080/ 即可,浏览器中输出“Hello Minicat!”
(二)V2.0 版本 1. Minicat的主类(启动类)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 {
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("=====>>>Minicat start on port:" + port);
while(true) {
//监听浏览器发送过来的请求
Socket socket = serverSocket.accept();
//接收浏览器发送过来的请求内容,这些内容就是要刷选后封装成request对象的内容。
InputStream inputStream = socket.getInputStream();
// 封装Request对象和Response对象
Request request = new Request(inputStream);
Response response = new Response(socket.getOutputStream());
response.outputHtml(request.getUrl());
socket.close();
}
}
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2. 创建Request类
用Request封装socket.getInputStream();
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;
//在读写操作前先得知数据流里有多少个字节可以读取
//这里循环读,直到count读到内容
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
//将输入流中的字节读到数组bytes中
inputStream.read(bytes);
String inputStr = new String(bytes);
// 获取第一行请求头信息
// 以换行符进行分割
String firstLineStr = inputStr.split("\n")[0]; // GET / HTTP/1.1
//把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);
}
}
3. 创建Response类
用response封装socket.getInputStream()
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 {
// 获取静态资源文件的绝对路径,自定义一个StaticResourceUtil工具类
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
// 输出静态资源文件
// 将绝对路径中的静态资源转换成文件
File file = new File(absoluteResourcePath);
// 判断文件是否存在
if(file.exists() && file.isFile()) {
// 读取静态资源文件,输出静态资源,自定义一个StaticResourceUtil工具类
StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
}else{
// 输出404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
4. 创建StaticResourceUtil类
目的是:读取静态资源文件,输出静态资源
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();
// 再将以上的绝对路径拼接传进来的路径
// absolutePath.replaceAll("\\","/")表示统一的把系统分割符转换成“/”
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).getBytes());
// 读取内容输出
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;
}
}
}
5. 创建静态资源文件index.html放在工程的resources目录下
static resouce
Hello Minicat-static resouce!
6. 测试
v2.0版本只改动以上代码,其余未改动代码参照V1.0
浏览器中输入浏览器中输入:localhost:8080/ 即可,浏览器中返回index.html静态资源。
(三)V3.0 版本 1. Minicat的主类(启动类)loadServlet()方法用来加载web.xml并解析web.xml,将server.LagouServlet类实例化
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();
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("=====>>>Minicat start on port:" + port);
while(true) {
Socket socket = serverSocket.accept();
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();
}
}
private void loadServlet() {
//将配置文件读取成输入流
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
//开始解析web.xml
SAXReader saxReader = new SAXReader();
try {
//生成文档对象
document document = saxReader.read(resourceAsStream);
//获取web.xml中的根节点
Element rootElement = document.getRootElement();
List selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// lagou
// 拿到servlet-name这个节点
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
// 通过节点获取该节点的值,也就是“lagou”
String servletName = servletnameElement.getStringValue();
// server.LagouServlet
// 拿到servlet-class这个节点
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
// 通过节点获取该节点的值,也就是“server.LagouServlet”
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();
}
}
}
2. 创建Servlet接口
创建Servlet接口,旨在建立Servlet规范,创建三个方法用来继承,其中service方法用来选择是否走doGet还是doPost。
package server;
public interface Servlet {
void init() throws Exception;
void destory() throws Exception;
void service(Request request,Response response) throws Exception;
}
3. 创建HttpServlet抽象类继承Servlet接口
该类创建doGet及doPost抽象方法,重写接口的service方法
package server;
public abstract class HttpServlet implements Servlet{
public abstract void doGet(Request request,Response response);
public abstract void doPost(Request request,Response response);
@Override
public void service(Request request, Response response) throws Exception {
if("GET".equalsIgnoreCase(request.getMethod())) {
doGet(request,response);
}else{
doPost(request,response);
}
}
}
4. 创建LagouServlet类继承HttpServlet抽象类
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 {
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 {
}
}
5. 创建web.xml
指定LagouServlet的全路径
指定访问路径。
6. 测试v3.0版本只展示改动的代码,未改动部分参照V1.0及V2.0
浏览器中输入浏览器中输入:localhost:8080/lagou
(四)多线程改造(使用线程池) 1. Minicat的主类(启动类)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;
}
// 定义一个线程池
int corePoolSize = 10;
int maximumPoolSize =50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
//这里使用队列的一个实现类ArrayBlockingQueue,长度设置成50
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 // 线程的拒绝策略
);
public void start() throws Exception {
// 加载解析相关的配置,web.xml
loadServlet();
System.out.println("=========>>>>>>使用线程池进行多线程改造");
while(true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
//requestProcessor.start();
threadPoolExecutor.execute(requestProcessor);
}
}
private void loadServlet() {
//将配置文件读取成输入流
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
//开始解析web.xml
SAXReader saxReader = new SAXReader();
try {
//生成文档对象
document document = saxReader.read(resourceAsStream);
//获取web.xml中的根节点
Element rootElement = document.getRootElement();
List selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// lagou
// 拿到servlet-name这个节点
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
// 通过节点获取该节点的值,也就是“lagou”
String servletName = servletnameElement.getStringValue();
// server.LagouServlet
// 拿到servlet-class这个节点
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
// 通过节点获取该节点的值,也就是“server.LagouServlet”
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();
}
}
}
2. 创建RequestProcessor类
创建线程,将浏览器的访问Request的请求塞进线程
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();
}
}
}
(五)完整代码展示
迷你版Tomcat完整代码展示
三. Tomcat 源码构建 (一)下载源码apache-tomcat-8.5.50-src
(二)源码导⼊IDE之前准备⼯作解压 tar.gz 压缩包,得到⽬录 apache-tomcat-8.5.50-src
进⼊ apache-tomcat-8.5.50-src ⽬录,创建⼀个pom.xml⽂件,⽂件内容如下
在 apache-tomcat-8.5.50-src ⽬录中创建 source ⽂件夹 将 conf 、 webapps ⽬录移动到刚刚创建的 source ⽂件夹中 (三) 导⼊源码⼯程到IDE并进⾏配置 将源码⼯程导⼊到 IDEA 中 给 tomcat 的源码程序启动类 Bootstrap 配置 VM 参数,因为 tomcat 源码运⾏也需要加载配置⽂ 件等。4.0.0 org.apache.tomcat apache-tomcat-8.5.50-srcTomcat8.5 8.5 Tomcat8.5 java java org.apache.maven.plugins maven-compiler-plugin3.1 UTF-8 11 11 org.easymock easymock3.4 ant ant1.7.0 wsdl4j wsdl4j1.6.2 javax.xml jaxrpc1.1 org.eclipse.jdt.core.compiler ecj4.5.1 javax.xml.soap javax.xml.soap-api1.4.0
-Dcatalina.home = /Users/yingdian/workspace/servers/apache-tomcat-8.5.50- src/source -Dcatalina.base = /Users/yingdian/workspace/servers/apache-tomcat-8.5.50- src/source -Djava.util.logging.manager = org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file = /Users/yingdian/workspace/servers/apache tomcat-8.5.50-src/source/conf/logging.properties运⾏ Bootstrap 类的 main 函数,此时就启动了 tomcat ,启动时候会去加载所配置的 conf ⽬录下 的 server.xml 等配置⽂件,所以访问 8080 端⼝即可,但此时我们会遇到如下的⼀个错误 原因是 Jsp 引擎 Jasper 没有被初始化,从⽽⽆法编译 JSP ,我们需要在 tomcat 的源码 ContextConfifig 类中的 confifigureStart ⽅法中增加⼀⾏代码将 Jsp 引擎初始化,如下:
四. 核⼼流程源码剖析
Tomcat核⼼流程源码剖析_舞鹤白沙编码日志-CSDN博客
五. Tomcat 类加载机制剖析Tomcat 类加载机制剖析_舞鹤白沙编码日志-CSDN博客
六. Tomcat 对 Https 的⽀持 (一)HTTPS 简介Http 超⽂本传输协议,明⽂传输 ,传输不安全, https 在传输数据的时候会对数据进⾏加密 ssl 协议 TLS(transport layer security) 协议 HTTPS 和 HTTP 的主要区别
HTTPS协议使⽤时需要到电⼦商务认证授权机构(CA)申请SSL证书 HTTP默认使⽤8080端⼝,HTTPS默认使⽤8443端⼝ HTTPS则是具有SSL加密的安全性传输协议,对数据的传输进⾏加密,效果上相当于HTTP的升级 版 HTTP的连接是⽆状态的,不安全的;HTTPS协议是由SSL+HTTP协议构建的可进⾏加密传输、身份认证的⽹络协议,⽐HTTP协议安全
HTTPS⼯作原理
(二)Tomcat 对 HTTPS 的⽀持 1. 使⽤ JDK 中的 keytool ⼯具⽣成免费的秘钥库⽂件(证书)。keytool -genkey -alias lagou -keyalg RSA -keystore lagou.keystore2. 配置conf/server.xml
3. 使⽤https协议访问8443端⼝(https://localhost:8443)。 七. Tomcat 性能优化策略
系统性能的衡量指标,主要是响应时间和吞吐量。
1 )响应时间:执⾏某个操作的耗时; 2) 吞吐量:系统在给定时间内能够⽀持的事务数量,单位为 TPS ( Transactions PerSecond 的缩写,也就是事务数 / 秒,⼀个事务是指⼀个客户机向服务器发送请求然后服务器做出反应的过程。 Tomcat 优化从两个⽅⾯进⾏ 1 ) JVM 虚拟机优化(优化内存模型) 2 ) Tomcat ⾃身配置的优化(⽐如是否使⽤了共享线程池? IO 模型?) 学习优化的原则提供给⼤家优化思路,没有说有明确的参数值⼤家直接去使⽤,必须根据⾃⼰的真实⽣产环境来进⾏调 整,调优是⼀个过程 JVM优化深度剖析_舞鹤白沙编码日志-CSDN博客


