文章目录
- 1.Tomcat整体架构分析
- 自己理解
- 2.第一阶段
- 1.实现功能
- 2.代码
- 1.TomcatV1.java
- 3.调试阶段
- 1.阻塞在readLine导致无法返回结果
- 4.结果演示
- 3.第二阶段
- 1.实现功能
- 2.代码
- 1.RequestHander.java
- 2.TomcatV2.java
- 3.调试阶段
- 1.发现每次按回车会接受到两次请求
- 4.结果演示
- 4.第三阶段
- 1.实现功能
- 2.总体框架
- 3.代码实现
- ===1.封装Request
- 1.Request.java
- 2.RequestHander.java
- 单元测试报错Sock is closed
- 原因
- 修改之后
- ===2.封装Response
- 3.Response.java
- 4.RequestHander.java
- 单元测试无误
- ===3.设计servlet规范
- 5.Servlet.java
- 6.HttpServlet.java
- 7.CalServlet.java
- 8.WebUtils.java
- 9.RequestHander.java
- 单元测试无误
- ===4.xml + 反射初始化容器
- 10.web.xml
- 11.TomcatV3.java
- 12.RequestHander.java
- 4.总体调试阶段
- 1.空指针异常
- 5.结果演示
- 5.课后作业
- 1.更新WebUtils.java
- 添加方法
- 2.修改RequestHander.java
- 3.cal.html
- 4.调试阶段
- 5.结果展示
1.Tomcat整体架构分析
自己理解
pdf下载
2.第一阶段
1.实现功能
编写自己的Tomcat能接受浏览器的请求并返回结果
2.代码
1.TomcatV1.java
package Tomcat;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
* 第一个版本的,可以接收浏览器的请求并返回信息
*/
public class TomcatV1 {
public static void main(String[] args) throws Exception {
//在8080端口监听
ServerSocket serverSocket = new ServerSocket(8081);
System.out.println("Tomcat在8080端口监听");
//只要不是手动关闭服务,则循环获取连接
while (!serverSocket.isClosed()) {
//获取连接
Socket accept = serverSocket.accept();
//获取输入流
InputStream inputStream = accept.getInputStream();
//使用转换流转为bufferedreader可以读取一行
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
//字符串缓冲区
String buff = null;
while ((buff = bufferedReader.readLine()) != null) {
//readline()在网络编程中,只有客户端的连接关闭了才会返回null,所以如果不设置别的判定条件退出,则会阻塞
//由于请求信息最后有一个\r\n,读到这个\r\n会返回一个"",就可以退出了
if (buff.equals("")) {
break;
}
System.out.println(buff);
}
//tomcat向浏览器发送http响应,\r\n是换行
String respHead =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n"; //响应头下面要空一行才能写响应体所以两个换行
String resp = respHead + "<h1>hello world!</h1>";
//获取输出流
OutputStream outputStream = accept.getOutputStream();
//输出
outputStream.write(resp.getBytes());
//关闭
outputStream.flush();
outputStream.close();
inputStream.close();
accept.close();
}
}
}
3.调试阶段
1.阻塞在readLine导致无法返回结果
- 最开始我并没有添加,如果readLine()读到""就退出循环的逻辑
- 后来发现readLine()在网络编程中,只有浏览器端关闭连接才会返回null
- 当读到了请求的
\r\n
的时候就会返回一个""字符串,然后阻塞在这里等待输入 - 所以在循环中添加当读到
\r\n
即返回""的时候退出即可
4.结果演示
3.第二阶段
1.实现功能
BIO线程模型支持多线程
2.代码
1.RequestHander.java
package Tomcat.handler;
import java.io.*;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
* 处理http请求的线程类
*/
public class RequestHander implements Runnable{
//定义一个socket属性
private Socket socket = null;
public RequestHander(Socket socket) {
this.socket = socket;
}
public void run() {
//可以对客户端或浏览器进行交互
try {
//获取输入流
InputStream inputStream = socket.getInputStream();
//转换成BufferedReader,方便按行读取
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
System.out.println("tomcatV2接受到浏览器的数据如下:");
//接受数据
//设置缓冲
String buff = null;
//循环读取
while ((buff = bufferedReader.readLine()) != null) {
//判断是否读取到了\r\n
if (buff.equals("")) {
break; //读取完毕则退出循环,避免readLine阻塞
}
System.out.println(buff);
}
//获取输出流,返回信息到浏览器
OutputStream outputStream = socket.getOutputStream();
String respHead =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n"; //响应头下面要空一行才能写响应体所以两个换行
String resp = respHead + "<h1>hello 孙显圣!</h1>";
outputStream.write(resp.getBytes());
//关闭
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//确保关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
2.TomcatV2.java
package Tomcat;
import Tomcat.handler.RequestHander;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
*/
public class TomcatV2 {
public static void main(String[] args) throws IOException {
//在8080端口监听
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("TomcatV2在8080端口监听");
//只要没有手动关闭则服务一直开启,循环监听
while (!serverSocket.isClosed()) {
Socket socket = serverSocket.accept();
//将获取的socket交给线程类来处理
RequestHander requestHander = new RequestHander(socket);
Thread thread = new Thread(requestHander);
thread.start(); //启动线程
}
}
}
3.调试阶段
1.发现每次按回车会接受到两次请求
原因就是每次请求还要请求一下页面的小图标
4.结果演示
4.第三阶段
1.实现功能
2.总体框架
3.代码实现
===1.封装Request
1.Request.java
package Tomcat.http;
/**
* @author 孙显圣
* @version 1.0
*/
import java.io.*;
import java.util.HashMap;
/**
* 1.Request的作用就是封装http请求的数据 GET /tomcatv2?a=9&b=3 HTTP/1.1
* 2.比如method(get), uri(/tomcat/cal),还有参数列表(num1&num2)
* 3.相当于原生的HttpServletRequest
*/
public class Request {
private String method;
private String uri;
//存放参数列表
private HashMap<String, String> parametersMapping = new HashMap<String, String>();
//构造方法获取inputStream来封装信息
public Request(InputStream inputStream) throws IOException {
//转换成BufferedReader
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
inputStream, "utf-8"));
//读取第一行
String s = bufferedReader.readLine();
//将内容封装到属性
String[] split = s.split(" ");
method = split[0];
//判断是否有参数列表
int index = split[1].indexOf("?");
if (index == -1) { //没有参数列表
uri = split[1];
}
else { //有参数列表
uri = split[1].substring(0,index);
String parameters = split[1].substring(index + 1, split[1].length());
String[] split1 = parameters.split("&"); //分割参数
//判断?后面是否有东西
if (split1 != null && !"".equals(split1)) {
//遍历参数
for (String parameterPair : split1) {
String[] split2 = parameterPair.split("=");
parametersMapping.put(split2[0],split2[1]);
}
}
}
//不能关闭,否则socket也就关闭了
// inputStream.close();
}
public String getMethod() {
return method;
}
public String getUri() {
return uri;
}
public String getParameter(String name) {
if (parametersMapping.containsKey(name)) {
return parametersMapping.get(name);
}
else {
return null;
}
}
}
2.RequestHander.java
package Tomcat.handler;
import Tomcat.http.Request;
import java.io.*;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
* 处理http请求的线程类
*/
public class RequestHander implements Runnable {
//定义一个socket属性
private Socket socket = null;
public RequestHander(Socket socket) {
this.socket = socket;
}
public void run() {
//可以对客户端或浏览器进行交互
try {
//获取输入流
InputStream inputStream = socket.getInputStream();
//封装到Request里
Request request = new Request(inputStream);
//获取数据
System.out.println(request.getUri() + " " + request.getMethod());
System.out.println(request.getParameter("num1") + " " + request.getParameter("num2")
+ " " + request.getParameter("num3"));
//获取输出流,返回信息到浏览器
OutputStream outputStream = socket.getOutputStream();
String respHead =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n"; //响应头下面要空一行才能写响应体所以两个换行
String resp = respHead + "<h1>hello 孙显圣!</h1>";
outputStream.write(resp.getBytes());
//关闭
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//确保关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
单元测试报错Sock is closed
原因
在Request.java中获取完参数顺便把inputStream关闭了,导致了Socket也一起关闭了,使得主线程在使用socket的时候出现了已经关闭的错误
修改之后
===2.封装Response
3.Response.java
package Tomcat.http;
/**
* @author 孙显圣
* @version 1.0
*/
import java.io.OutputStream;
/**
* 1.这个response对象可以封装OutputSream
* 2.即可以通过这个对象返回http响应给浏览器
* 3.相当于原生的HttpServletResponse
*/
public class Response {
private OutputStream outputStream = null;
//封装一个响应头
public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html;charset=utf-8\r\n\r\n"; //响应头下面要空一行才能写响应体所以两个换行
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
//以后再Servlet里面获取输出流
public OutputStream getOutputStream() {
return outputStream;
}
}
4.RequestHander.java
package Tomcat.handler;
import Tomcat.http.Request;
import Tomcat.http.Response;
import java.io.*;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
* 处理http请求的线程类
*/
public class RequestHander implements Runnable {
//定义一个socket属性
private Socket socket = null;
public RequestHander(Socket socket) {
this.socket = socket;
}
public void run() {
//可以对客户端或浏览器进行交互
try {
//获取输入流
InputStream inputStream = socket.getInputStream();
//封装到Request里
Request request = new Request(inputStream);
//获取数据
System.out.println(request.getUri() + " " + request.getMethod());
System.out.println(request.getParameter("num1") + " " + request.getParameter("num2")
+ " " + request.getParameter("num3"));
//获取输出流,返回信息到浏览器
OutputStream outputStream = socket.getOutputStream();
//封装到Response对象中
Response response = new Response(outputStream);
//获取输出流输出信息
OutputStream outputStream1 = response.getOutputStream();
outputStream1.write((response.respHeader + "<h1>response响应:你好,孙显圣</h1>").getBytes());
//关闭
outputStream.flush();
outputStream.close();
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//确保关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
单元测试无误
===3.设计servlet规范
5.Servlet.java
package Tomcat.servlet;
import Tomcat.http.Request;
import Tomcat.http.Response;
import java.io.IOException;
/**
* @author 孙显圣
* @version 1.0
* 保留三个核心方法
*/
public interface Servlet {
void init() throws Exception;
void service(Request request, Response response) throws IOException;
void destroy();
}
6.HttpServlet.java
package Tomcat.servlet;
import Tomcat.http.Request;
import Tomcat.http.Response;
import java.io.IOException;
/**
* @author 孙显圣
* @version 1.0
*/
public abstract class HttpServlet implements Servlet {
public void service(Request request, Response response) throws IOException {
//抽象模板设计模式,判断类型来决定调用什么方法
if ("GET".equalsIgnoreCase(request.getMethod())) {
//以后会反射创建子类实例,调用子类的service方法,子类没有,就从父类找,然后再使用动态绑定到子类的doGet方法
this.doGet(request, response);
} else if ("POST".equalsIgnoreCase(request.getMethod())) {
this.doPost(request, response);
}
}
public abstract void doGet(Request request, Response response);
public abstract void doPost(Request request, Response response);
}
7.CalServlet.java
package Tomcat.servlet;
import Tomcat.http.Request;
import Tomcat.http.Response;
import Tomcat.utils.WebUtils;
import java.io.IOException;
import java.io.OutputStream;
/**
* @author 孙显圣
* @version 1.0
*/
public class CalServlet extends HttpServlet {
public void doGet(Request request, Response response) {
//完成计算任务
int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);
int sum = num1 + num2;
//返回计算结果给浏览器
OutputStream outputStream = response.getOutputStream();
String resp = Response.respHeader + "<h1>" + num1 + " + " + num2 + " = " + sum + "</h1>";
try {
outputStream.write(resp.getBytes());
//关闭
outputStream.flush();
outputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void doPost(Request request, Response response) {
this.doGet(request, response);
}
public void init() throws Exception {
}
public void destroy() {
}
}
8.WebUtils.java
package Tomcat.utils;
/**
* @author 孙显圣
* @version 1.0
*/
public class WebUtils {
//把String类型转换成int类型,如果是非整数则返回默认值
public static int parseInt(String str, int defaultVal) {
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
System.out.println("转换失败");
}
return defaultVal;
}
}
9.RequestHander.java
package Tomcat.handler;
import Tomcat.http.Request;
import Tomcat.http.Response;
import Tomcat.servlet.CalServlet;
import java.io.*;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
* 处理http请求的线程类
*/
public class RequestHander implements Runnable {
//定义一个socket属性
private Socket socket = null;
public RequestHander(Socket socket) {
this.socket = socket;
}
public void run() {
//可以对客户端或浏览器进行交互
try {
//获取输入流
InputStream inputStream = socket.getInputStream();
//封装到Request里
Request request = new Request(inputStream);
//获取输出流,返回信息到浏览器
OutputStream outputStream = socket.getOutputStream();
//封装到Response对象中
Response response = new Response(outputStream);
CalServlet calServlet = new CalServlet();
calServlet.service(request,response); //这个会调用他抽象父类的service方法
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//确保关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
单元测试无误
===4.xml + 反射初始化容器
10.web.xml
11.TomcatV3.java
package Tomcat;
import Tomcat.handler.RequestHander;
import Tomcat.servlet.HttpServlet;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author 孙显圣
* @version 1.0
*/
public class TomcatV3 {
//设置两个hashmap容器,在启动的Tomcat的时候就初始化
//存放名字和实例
public static final ConcurrentHashMap<String, HttpServlet>
servletMapping = new ConcurrentHashMap<String, HttpServlet>();
//存放路径和名字
public static final ConcurrentHashMap<String, String>
servletUrlMapping = new ConcurrentHashMap<String, String>();
public static void main(String[] args) throws MalformedURLException, DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
TomcatV3 tomcatV3 = new TomcatV3();
tomcatV3.init();
tomcatV3.run();
}
//启动TomcatV3容器
public void run() {
try {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("TomcatV3在8080端口监听");
while (!serverSocket.isClosed()) { //只要没有手动关闭服务,就循环获取连接
Socket socket = serverSocket.accept();
//将socket交给线程处理
RequestHander requestHander = new RequestHander(socket);
new Thread(requestHander).start(); //启动线程
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void init() throws MalformedURLException, DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//获取该类的路径
String path = TomcatV3.class.getResource("/").getPath();
//使用dom4j读取web.xml文件
//获取解析器
SAXReader saxReader = new SAXReader();
//读取文件
Document read = saxReader.read(new File(path + "web.xml"));
//获取根元素
Element rootElement = read.getRootElement();
//获取所有二级元素
List<Element> servlet = rootElement.elements();
//遍历所有二级元素,根据不同类型做处理
for (Element element : servlet) {
if ("servlet".equalsIgnoreCase(element.getName())) {
//获取名字和全类名
Element servletName = element.element("servlet-name");
Element servletClass = element.element("servlet-class");
//通过反射创建实例
Class<?> aClass = Class.forName(servletClass.getText().trim()); //trim是清除空格
HttpServlet httpServlet = (HttpServlet) aClass.newInstance();
//将其放到容器中去
servletMapping.put(servletName.getText(), httpServlet);
} else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
//获取名字和url
Element servletName = element.element("servlet-name");
Element servletUrl = element.element("url-pattern");
//将其放到容器中去
servletUrlMapping.put("/tomcat" + servletUrl.getText(), servletName.getText());
}
}
}
}
12.RequestHander.java
package Tomcat.handler;
import Tomcat.TomcatV3;
import Tomcat.http.Request;
import Tomcat.http.Response;
import Tomcat.servlet.CalServlet;
import Tomcat.servlet.HttpServlet;
import java.io.*;
import java.net.Socket;
/**
* @author 孙显圣
* @version 1.0
* 处理http请求的线程类
*/
public class RequestHander implements Runnable {
//定义一个socket属性
private Socket socket = null;
public RequestHander(Socket socket) {
this.socket = socket;
}
public void run() {
//可以对客户端或浏览器进行交互
try {
//获取输入流
InputStream inputStream = socket.getInputStream();
//封装到Request里
Request request = new Request(inputStream);
//获取输出流,返回信息到浏览器
OutputStream outputStream = socket.getOutputStream();
//封装到Response对象中
Response response = new Response(outputStream);
String uri = request.getUri();
String servletName = TomcatV3.servletUrlMapping.get(uri);
if (servletName == null) {
servletName = "";
}
HttpServlet httpServlet = TomcatV3.servletMapping.get(servletName);
//判断是否得到了这个对象
if (httpServlet != null) {
httpServlet.service(request, response);
} else {
//没有得到则返回404
String resp = Response.respHeader + "<h1>404 not found</h1>";
response.getOutputStream().write(resp.getBytes());
}
inputStream.close();
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
//确保关闭
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
4.总体调试阶段
1.空指针异常
5.结果演示
5.课后作业
1.更新WebUtils.java
添加方法
//判断是不是html格式的文件,如果是就直接读取文件内容并且返回true
public static boolean isHtml(String uri, Response response) {
//使用正则表达式匹配html文件
String regStr = "(/.*)*/(.*\\.html)";
Pattern compile = Pattern.compile(regStr);
Matcher matcher = compile.matcher(uri);
if (!matcher.find()) { //没匹配到就直接返回false
return false;
}
//得到html文件的路径
String path = "D:\\Intelij IDEA Project\\java_web\\tomcat\\target\\classes\\" + matcher.group(2);
System.out.println(path);
//根据路径读取文件并存放到StringBuilder中
StringBuilder stringBuilder = new StringBuilder();
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
String buf = null;
while ((buf = bufferedReader.readLine()) != null) {
stringBuilder.append(buf); //存放到stringBuilder中
}
//将stringBuilder的内容响应给浏览器
String resp = Response.respHeader + stringBuilder.toString();
response.getOutputStream().write(resp.getBytes());
}catch (Exception e) {
System.out.println("文件找不到!");
return false; //返回false之后就会继续进行原来的逻辑,弹出404
}
//如果不出异常则说明响应成功
return true;
}
2.修改RequestHander.java
3.cal.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/tomcat/CalServlet" method="get">
num1:<input type="text" name="num1">
num2:<input type="text" name="num2">
<input type="submit" value="提交">
</form>
</body>
</html>
4.调试阶段
- 一直显示我的cal.html文件找不到,调了半个小时
- 原因是
String path = TomcatV3.class.getResource("/").getPath();
使用这个来获取的路径没有空格,而我的资源路径是这个D:\\Intelij IDEA Project\\java_web\\tomcat\\target\\classes\\
,中间带了空格,真的是醉了,深刻体会到文件夹起名不要带空格的重要性了