第一步
<mirror>
<id>maven-default-http-blockerid>
<mirrorOf>external:http:*mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.name>
<url>http://0.0.0.0/url>
<blocked>trueblocked>
mirror>
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.zzwgroupId>
<artifactId>zzwtomcatartifactId>
<packaging>warpackaging>
<version>1.0-SNAPSHOTversion>
<name>zzwtomcat Maven Webappname>
<url>http://maven.apache.orgurl>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>3.8.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.0.1version>
<scope>providedscope>
dependency>
dependencies>
<build>
<finalName>zzwtomcatfinalName>
build>
project>
Tomcat有三种运行模式(BIO, NIO, APR), 采用BIO线程模型来模拟Tomcat如何接收客户端请求, 解析请求, 调用Servlet, 并返回结果的机制流程
Content-Type: text/html;charset=gbk 给浏览器响应时设置成gbk
/**
* @author 赵志伟
* @version 1.0
* 这是第一个版本的tomcat, 可以完成接收浏览器的请求, 并返回信息
*/
@SuppressWarnings({"all"})
public class ZzwTomcatVersion1 {
public static void main(String[] args) throws IOException {
//1.在服务端监听8080端口
ServerSocket serverSocket = new ServerSocket(8080);
while (!serverSocket.isClosed()) {
System.out.println("服务端Tomcat在 8080端口 等待连接");
//如果有连接, 就创建一个socket, 这个socket是服务端和客户端的连接通道
Socket socket = serverSocket.accept();
System.out.println("接收浏览器发送的数据");
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String message = "";
while ((message = bufferedReader.readLine()) != null) {
//判断长度是否为0
if (message.length() == 0) {
break;
}
System.out.println(message);
}
//我们的Tomcat以 http协议方式 回送数据给浏览器
OutputStream outputStream = socket.getOutputStream();
//构建一个http响应的消息头
// \r\n代表换行
// 响应头和响应体之间有个空行
String responseHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=gbk\r\n\r\n";
String response = responseHeader + "你好, 世界 521";
System.out.println("\ntomcat响应给浏览器的数据\n" + response);
outputStream.write(response.getBytes());//因为是字节输出流, 所以要按照字节的方式返回
//关闭流
outputStream.flush();
outputStream.close();
bufferedReader.close();
socket.close();
}
}
}
/**
* @author 赵志伟
* @version 1.0
* ZzwRequestHandler 是一个线程对象
* 用来处理 http请求
*/
@SuppressWarnings({"all"})
public class ZzwRequestHandler implements Runnable {
private Socket socket;
public ZzwRequestHandler(Socket socket) {
this.socket = socket;
}
public void run() {
//对客户端和浏览器进行IO操作
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
BufferedReader bufferedReader//inputStream->bufferedReader 方便按行读取
= new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
System.out.println("当前线程=" + Thread.currentThread().getId());
System.out.println("==============Tomcat Version2 接收到的数据如下==============");
String message = "";
while ((message = bufferedReader.readLine()) != null) {
if (message.length() == 0) {
break;
}
System.out.println(message);
}
//构建一个http响应头
//响应头和响应体之间有两个换行 \r\n\r\n
String responseHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=gbk\r\n\r\n";
String response = responseHeader + "你好,世界
";
System.out.println("==============Tomcat Version2 返回的数据如下==============");
System.out.println(response);
//得到输出流,将数据封装成 http响应格式 返回给浏览器/客户端
outputStream = socket.getOutputStream();
outputStream.write(response.getBytes());//这个方法把字符串转成字节数组
socket.close();//一定要确保socket关闭
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
outputStream.flush();
outputStream.close();
inputStream.close();
if (socket != null) {
socket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* @author 赵志伟
* @version 1.0
* 这是第二个版本的tomcat, 可以调用线程
*/
@SuppressWarnings({"all"})
public class ZzwTomcatVersion2 {
public static void main(String[] args) throws IOException {
//1.在服务端监听8080端口
ServerSocket serverSocket = new ServerSocket(8080);
//只有servletSocket没有关闭, 就一直等待 浏览器/客户端 连接
while (!serverSocket.isClosed()) {
System.out.println("服务端Tomcat version2 在 8080端口 等待连接");
//如果有连接, 就创建一个socket, 这个socket是服务端和客户端的连接通道
Socket socket = serverSocket.accept();
//不能直接调用run方法, 要调用start
ZzwRequestHandler zzwRequestHandler = new ZzwRequestHandler(socket);
new Thread(zzwRequestHandler).start();
}
}
}
- ZzwRequest对象
/**
* @author 赵志伟
* @version 1.0
* 1.ZzwRequest作用 封装http请求的数据
* get /zzwCalServlet?num1=12&num2=21
* 2.比如 请求方法method(get/post), uri(/zzwCalServlet), 参数(num1=12&num2=21)
* 3.ZzwRequest等价于原生Servlet中的 HttpServletRequest
* 4.这里只考虑get请求
*/
@SuppressWarnings({"all"})
public class ZzwRequest {
private String method;
private String uri;
//存放参数列表 参数名-参数值 => 数据结构HashMap
private HashMap<String, String> argsMapping = new HashMap<String, String>();
//inputStream 是和对应http请求的socket关联
public ZzwRequest(InputStream inputStream) {
init(inputStream);
}
public void init(InputStream inputStream) {
//inputStream->bufferedReader
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
/**读取第一行(读取请求行)
* GET /calServlet?num1=-87&num2=89 HTTP/1.1
*/
String line1 = bufferedReader.readLine();
String[] line1s = line1.split(" ");
//获取method
method = line1s[0];
//获取uri
int index = line1s[1].indexOf("?");
if (index == -1) {//说没后面没有参数列表
uri = line1s[1];
} else {//这里有参数情况
uri = line1s[1].substring(0, index);
//args=>num1=-87&num2=89
String args = line1s[1].substring(index + 1);//直接截取到最后
//argsPair => ["num1=-87","num2=89"]
String[] argsPair = args.split("&");
for (String argPair : argsPair) {
String[] argVal = argPair.split("=");
if (argVal.length == 2) {
//放入到argsMapping
argsMapping.put(argVal[0], argVal[1]);
}
}
}
//这里inputStream和socket关联, 不能在这里关闭
//inputStream.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getParameter(String name) {
if (argsMapping.containsKey(name)) {
return argsMapping.get(name);
} else {
return "";
}
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
@Override
public String toString() {
return "ZzwRequest{" +
"method='" + method + '\'' +
", uri='" + uri + '\'' +
", argsMapping=" + argsMapping +
'}';
}
}
- ZzwResponse对象
/**
* @author 赵志伟
* @version 1.0
* 1.ZzwResponse对象 可以封装OutputStream(和socket关联)
* 2.可以通过ZzwResponse对象 返回HTTP响应给客户端
* 3.ZzwResponse对象的作用等价于原生的Servlet的HttpServletResponse
*/
@SuppressWarnings({"all"})
public class ZzwResponse {
private OutputStream outputStream;
//设置一个http响应头
private static final String responseHeader = "HTTP/1.1 200\r\n" +
"Content-Type: text/html;charset=gbk\r\n\r\n";
private String response;
public ZzwResponse(OutputStream outputStream) {
this.outputStream = outputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
public void setOutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
}
public String getResponseHeader() {
return responseHeader;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = responseHeader + response;
}
}
- ZzwRequestHandler改进
public class ZzwRequestHandler implements Runnable {
private Socket socket;
public ZzwRequestHandler(Socket socket) {
this.socket = socket;
}
public void run() {
//对客户端和浏览器进行IO操作
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
ZzwRequest zzwRequest = new ZzwRequest(inputStream);
String num1 = zzwRequest.getParameter("num1");
String num2 = zzwRequest.getParameter("num2");
System.out.println("num1=" + num1);
System.out.println("num2=" + num2);
System.out.println("zzwRequest=" + zzwRequest);
ZzwResponse zzwResponse = new ZzwResponse(socket.getOutputStream());
zzwResponse.setResponse("刀剑神域
");
outputStream = zzwResponse.getOutputStream();
outputStream.write(zzwResponse.getResponse().getBytes());
socket.close();//一定要确保socket关闭
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
outputStream.flush();
outputStream.close();
inputStream.close();
if (socket != null) {
socket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
- ZzwServlet接口
/**
* @author 赵志伟
* @version 1.0
* 搭建结构, 由实现类写内容
*/
public interface ZzwServlet {
void init() throws Exception;
void service(ZzwRequest request, ZzwResponse response) throws IOException;
void destroy();
}
- ZzwHttpServlet类
/**
* @author 赵志伟
* @version 1.0
*/
public abstract class ZzwHttpServlet implements ZzwServlet {
public void service(ZzwRequest request, ZzwResponse response) throws IOException {
//equalsIgnoreCase 比较字符串内容并忽略大小写
if (request.getMethod().equalsIgnoreCase("GET")) {
this.doGET(request, response);
} else if (request.getMethod().equalsIgnoreCase("POST")) {
this.doPost(request, response);
}
}
//这里是模板设计模式,让 ZzwHttpServlet的子类来实现
public abstract void doGET(ZzwRequest request, ZzwResponse response);
public abstract void doPost(ZzwRequest request, ZzwResponse response);
}
- ZzwCalServlet实现类
public class ZzwCalServlet extends ZzwHttpServlet {
public void doGET(ZzwRequest request, ZzwResponse response) {
doPost(request, response);
}
public void doPost(ZzwRequest request, ZzwResponse response) {
String num1 = request.getParameter("num1");
String num2 = request.getParameter("num2");
int sum = WebUtils.parseInt(num1, 0) + WebUtils.parseInt(num2, 0);
try {
OutputStream outputStream = response.getOutputStream();
response.setResponse(""
+ num1 + "+" + num2 + "=" + sum + " ZzwTomcatVersion3");
outputStream.write(response.getResponse().getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void init() throws Exception {
}
public void destroy() {
}
}
- ZzwRequestHandler改进2
/**
* @author 赵志伟
* @version 1.0
* ZzwRequestHandler 是一个线程对象
* 用来处理 http请求
*/
@SuppressWarnings({"all"})
public class ZzwRequestHandler implements Runnable {
private Socket socket;
public ZzwRequestHandler(Socket socket) {
this.socket = socket;
}
public void run() {
//对客户端和浏览器进行IO操作
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
ZzwRequest zzwRequest = new ZzwRequest(inputStream);
ZzwResponse zzwResponse = new ZzwResponse(outputStream);
//创建ZzwCalServlet对象
ZzwCalServlet zzwCalServlet = new ZzwCalServlet();
zzwCalServlet.doGET(zzwRequest, zzwResponse);
socket.close();//一定要确保socket关闭
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
outputStream.flush();
outputStream.close();
inputStream.close();
if (socket != null) {
socket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
因为我们的Servlet是自己设计的, web.xml检查报红, 直接忽略, 同时要在target/classes目录手动拷贝一份web.xml(平时是自动拷贝)
如果想要取消报红, 那么
- ZzwTomcatVersion3
/**
* @author 赵志伟
* @version 1.0
* 第3版Tomcat, 实现通过xml+反射 初始化容器
*/
@SuppressWarnings({"all"})
public class ZzwTomcatVersion3 {
/*
容器 servletMapping
- ConcurrentHashMap
- HashMap
key - value
ServletName Servlet实例
*/
//因为ZzwHttpServlet是所有业务Servlet的父类, 所以这里可以存放子类Servlet的对象
public static final ConcurrentHashMap<String, ZzwHttpServlet> servletMapping
= new ConcurrentHashMap<String, ZzwHttpServlet>();
/*
容器 servletMapping
- ConcurrentHashMap
- HashMap
key - value
url-pattern ServletName
*/
public static final ConcurrentHashMap<String, String> servletUriMapping
= new ConcurrentHashMap<String, String>();
//直接对两个容器进行初始化
public void init() {
//读取web.xml文件 => dom4j
// 得到web.xml文件[拷贝一份]的路径 => 定位到target/classes
String path = ZzwTomcatVersion3.class.getResource("/").getPath();
//使用dom4j完成xml文件的提取
// 获取解析器
SAXReader reader = new SAXReader();
try {
Document document = reader.read(new File(path + "web.xml"));// 加不加/都行
System.out.println(document);
// 获取rootElement
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements();
// 遍历元素并过滤出 servlet servlet-mapping
for (Element element : elements) {
if ("servlet".equalsIgnoreCase(element.getName())) {
//如果这是一个servlet配置
//System.out.println("servlet\n" + element);
String servletName = element.element("servlet-name").getText();
String servletClass = element.element("servlet-class").getText();
//使用反射将该servlet实例放入到servletMapping集合
servletMapping.put(servletName, (ZzwHttpServlet) Class.forName(servletClass).newInstance());
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
//如果这是一个servlet-mapping配置
//System.out.println("servlet-mapping\n" + element);
Element urlPattern = element.element("url-pattern");
Element serlvetName = element.element("servlet-name");
servletUriMapping.put(urlPattern.getText(), serlvetName.getText());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println(servletMapping);
System.out.println(servletUriMapping);
}
public static void main(String[] args) {
//String path = ZzwTomcatVersion3.class.getResource("/").getPath();
//System.out.println(path);
ZzwTomcatVersion3 zzwTomcatVersion3 = new ZzwTomcatVersion3();
zzwTomcatVersion3.init();
//启动zzwTomcatVersion3容器
zzwTomcatVersion3.run();
}
启动ZzwTomcatVersion3容器, 这只是一个普通方法
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
while (!serverSocket.isClosed()) {
System.out.println("服务端Tomcat version3 在 8080端口 等待连接");
Socket socket = serverSocket.accept();
ZzwRequestHandler zzwRequestHandler = new ZzwRequestHandler(socket);
new Thread(zzwRequestHandler).start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- ZzwRequestHandler改进3
/**
* @author 赵志伟
* @version 1.0
* ZzwRequestHandler 是一个线程对象
* 用来处理 http请求
*/
@SuppressWarnings({"all"})
public class ZzwRequestHandler implements Runnable {
private Socket socket;
public ZzwRequestHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
ZzwRequest zzwRequest = new ZzwRequest(socket.getInputStream());//socket关闭后,这些流也就没有了
ZzwResponse zzwResponse = new ZzwResponse(socket.getOutputStream());
//1.得到uri => servletUriMapping的urlPattern
String uri = zzwRequest.getUri();
String servletName = ZzwTomcatVersion3.servletUriMapping.get(uri);
//2.uri->得到servletName->得到servlet实例, 其真正的运行类型是其子类 ZzwCalServlet
// 细节:这里的servletName可能是空, ConcurrentHashMap的get(空)会报错, HashMap的get(空)不会报错
// 解决方案一: 换成HashMap
// 解决方案二: 如果是空换成空串
servletName = (servletName == null) ? "" : servletName;
ZzwHttpServlet zzwHttpServlet = ZzwTomcatVersion3.servletMapping.get(servletName);
//3.调用service方法, 通过OOP的动态绑定机制 调用到真正运行类型的doGet或者doPost
if (zzwHttpServlet != null) {
zzwHttpServlet.service(zzwRequest, zzwResponse);
} else {
//请求的地址不存在, 返回404
zzwResponse.setResponse("404 Not Found!
");
OutputStream outputStream = zzwResponse.getOutputStream();
outputStream.write(zzwResponse.getResponse().getBytes());
outputStream.flush();
outputStream.close();
}
socket.close();//一定要确保socket关闭
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
在ZzwTomcatHandler类的try代码块的中上位置加入以下代码
if (!WebUtils.isExist(uri.substring(1))) {
OutputStream outputStream = zzwResponse.getOutputStream();
zzwResponse.setResponseBody("404 Not Found!");
outputStream.write(zzwResponse.getResponse().getBytes());
outputStream.flush();
outputStream.close();
socket.close();
return;
}
if (WebUtils.isHtml(uri)) {
String html = WebUtils.readHtml(uri.substring(1));
OutputStream outputStream = zzwResponse.getOutputStream();
zzwResponse.setResponseBody(html);
outputStream.write(zzwResponse.getResponse().getBytes());
outputStream.flush();
outputStream.close();
socket.close();
return;
}
在WebUtils工具类中增加以下代码
public static boolean isExist(String fileName) {
String path = WebUtils.class.getResource("/").getPath();
File file = new File(path + fileName);
return file.exists();
}
public static boolean isHtml(String uri) {
return uri.endsWith(".html");
}
//读取该网页
public static String readHtml(String htmlName) {
String path = WebUtils.class.getResource("/").getPath();
StringBuffer stringBuffer = new StringBuffer();
String line = "";
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(path + htmlName));
while ((line = bufferedReader.readLine()) != null) {
stringBuffer.append(line);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return stringBuffer.toString();
}
```