Tomcat
照着大佬视频敲了一遍......
Tomcat是什么
是一个Http服务器+Servlet容器,直接屏蔽了应用层的协议解析和网络通信细节,为我们提供了标准的Request和Responsse对象,从而只需要关注业务逻辑。
启动类:
package com.ljw.MyTomcat;
import jakarta.servlet.Servlet;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyTomcat {
// tomcat的上下文,封装每一个app的上下文
private Map<String,Context> contextMap = new HashMap<>();
public Map<String, Context> getContextMap() {
return contextMap;
}
public static void main(String[] args) {
MyTomcat tomcat = new MyTomcat();
// 部署一个tomcat中的每一个app
tomcat.deployApps();
// 启动
tomcat.start();
}
private void start() {
try {
// 使用线程池:如果只是使用单个线程,那么在处理一个请求时,如果有其他请求过来,将会被阻塞到第一个请求被处理完
// 主线程负责建立链接,交由线程池进行处理请求
// 实际tomcat并不是这样的简单处理
ExecutorService service = Executors.newFixedThreadPool(20);
ServerSocket socket = new ServerSocket(8090);
while (true) {
Socket accept = socket.accept();
service.execute(new SocketHandler(accept,this));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void deployApps() {
File file = new File(System.getProperty("user.dir"), "webapps");
for (String app : file.list()) {
deployApp(file,app);
}
}
private void deployApp(File webapps, String app) {
// 有哪些app
File appDirectory = new File(webapps, app);
File classesDirectory = new File(appDirectory, "classes");
List<File> files = getAllFilePath(classesDirectory);
for (File file : files) {
// 项目的servlet上下文:封装每一个项目的servlet
Context context = new Context();
String name = file.getPath();
// 去掉工作目录前缀
name = name.replace(classesDirectory.getPath()+"\\","");
// 去掉.class后缀
name = name.replace(".class","");
// 将目录格式com\ljw 转换成 com.ljw 的形式
name = name.replace("\\",".");
try {
WebAppClassLoader classLoader = new WebAppClassLoader(new URL[]{classesDirectory.toURL()});
Class<?> servletClass = classLoader.loadClass(name);
// 判断 servletClass 的父类是否是 HttpServlet
if (HttpServlet.class.isAssignableFrom(servletClass)){
// 是否被@WebServlet 标记
if (servletClass.isAnnotationPresent(WebServlet.class)) {
// 获取路径的值
WebServlet annotation = servletClass.getAnnotation(WebServlet.class);
String[] urlPatterns = annotation.urlPatterns();
// 每一个路径对应一个servlet
for (String urlPattern : urlPatterns) {
context.addUrlPatternMapping(urlPattern,(Servlet) servletClass.newInstance());
}
}
contextMap.put(app,context);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* 递归获取目录下所有文件
* @param srcFile
* @return
*/
public List<File> getAllFilePath(File srcFile){
List<File> list = new ArrayList<>();
File[] files = srcFile.listFiles();
if (files != null){
for (File file : files) {
if (file.isDirectory()){
list.addAll(getAllFilePath(file));
}else {
list.add(file);
}
}
}
return list;
}
}
协议解析、业务处理:
package com.ljw.MyTomcat;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* socket连接处理
*/
public class SocketHandler implements Runnable {
private Socket socket;
public SocketHandler(Socket socket) {
this.socket = socket;
}
public SocketHandler(Socket socket,MyTomcat tomcat) {
this.socket = socket;
this.tomcat = tomcat;
}
@Override
public void run() {
handleSocket(socket);
}
private MyTomcat tomcat= new MyTomcat();
/**
* http协议格式
* 请求方法 空格 URL 空格 协议版本 回车符 换行符
* 头部字段名 : 值 回车符 换行符 -|
* ... |- 请求头
* 头部字段名 : 值 回车符 换行符 -|
* 回车符 换行符
* 请求数据
*
* @param socket
*/
private void handleSocket(Socket socket) {
// 处理socket连接
try {
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
inputStream.read(bytes);
// 解析字节内容
for (byte b : bytes) {
/**
* 打印内容:
* GET /test HTTP/1.1
* Host: localhost:8090
* Connection: keep-alive
* sec-ch-ua: "Chromium";v="136", "Microsoft Edge";v="136", "Not.A/Brand";v="99"
* sec-ch-ua-mobile: ?0
* sec-ch-ua-platform: "Windows"
* Upgrade-Insecure-Requests: 1
* User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0
* Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,q = 0.8, application / signed - exchange;v = b3;q = 0.7
* ...
*/
// System.out.print((char) b);
}
int pos = 0;
int begin = 0,end = 0;
// 解析请求类型
for (; pos < bytes.length; pos++,end++) {
if (bytes[pos] == ' ') break;
}
StringBuilder method = new StringBuilder();
for (;begin<end;begin++){
method.append((char) bytes[begin]);
}
// System.out.println(method);
// 解析URL
pos++;begin++;end++;
for (; pos < bytes.length; pos++,end++) {
if (bytes[pos] == ' ') break;
}
StringBuilder url = new StringBuilder();
for (;begin<end;begin++){
url.append((char) bytes[begin]);
}
// System.out.println(url);
// 解析协议版本
pos++;begin++;end++;
for (; pos < bytes.length; pos++,end++) {
if (bytes[pos] == '\r') break;
}
StringBuilder protocl = new StringBuilder();
for (;begin<end;begin++){
protocl.append((char) bytes[begin]);
}
// System.out.println(protocl);
// 构建请求
Request request = new Request(method.toString(),url.toString(),protocl.toString(),socket);
// 匹配Servlet:doGet doPost
// TestServlet testServlet = new TestServlet();
Response response = new Response(request);
// testServlet.service(request,response);
String requestUrl = request.getRequestURI().toString();
// 去掉请求的第一个斜杠:/hello/test
requestUrl = requestUrl.substring(1);// hello/test
String[] split = requestUrl.split("/");
String appName = split[0];
// 获取项目的上下文
Context context = tomcat.getContextMap().get(appName);
if (context == null){
System.out.println(appName);
}
if (split.length > 1){
Servlet servlet = context.getByUrlPattern(split[1]);// /test
if (servlet == null){
// 默认返回处理
DefaultServlet defaultServlet = new DefaultServlet();
defaultServlet.service(request,response);
response.complete();
}else {
// 业务处理
servlet.service(request,response);
// 发送响应
response.complete();
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ServletException e) {
throw new RuntimeException(e);
}
}
}
请求对象抽象类:
package com.ljw.MyTomcat;
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
public class AbstractRequest implements HttpServletRequest {
@Override
public String getAuthType() {
return null;
}
@Override
public Cookie[] getCookies() {
return new Cookie[0];
}
@Override
public long getDateHeader(String s) {
return 0;
}
@Override
public String getHeader(String s) {
return null;
}
@Override
public Enumeration<String> getHeaders(String s) {
return null;
}
@Override
public Enumeration<String> getHeaderNames() {
return null;
}
@Override
public int getIntHeader(String s) {
return 0;
}
@Override
public String getMethod() {
return null;
}
@Override
public String getPathInfo() {
return null;
}
@Override
public String getPathTranslated() {
return null;
}
@Override
public String getContextPath() {
return null;
}
@Override
public String getQueryString() {
return null;
}
@Override
public String getRemoteUser() {
return null;
}
@Override
public boolean isUserInRole(String s) {
return false;
}
@Override
public Principal getUserPrincipal() {
return null;
}
@Override
public String getRequestedSessionId() {
return null;
}
@Override
public String getRequestURI() {
return null;
}
@Override
public StringBuffer getRequestURL() {
return null;
}
@Override
public String getServletPath() {
return null;
}
@Override
public HttpSession getSession(boolean b) {
return null;
}
@Override
public HttpSession getSession() {
return null;
}
@Override
public String changeSessionId() {
return null;
}
@Override
public boolean isRequestedSessionIdValid() {
return false;
}
@Override
public boolean isRequestedSessionIdFromCookie() {
return false;
}
@Override
public boolean isRequestedSessionIdFromURL() {
return false;
}
@Override
public boolean isRequestedSessionIdFromUrl() {
return false;
}
@Override
public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException {
return false;
}
@Override
public void login(String s, String s1) throws ServletException {
}
@Override
public void logout() throws ServletException {
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
return null;
}
@Override
public Part getPart(String s) throws IOException, ServletException {
return null;
}
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> aClass) throws IOException, ServletException {
return null;
}
@Override
public Object getAttribute(String s) {
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
return null;
}
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public void setCharacterEncoding(String s) throws UnsupportedEncodingException {
}
@Override
public int getContentLength() {
return 0;
}
@Override
public long getContentLengthLong() {
return 0;
}
@Override
public String getContentType() {
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return null;
}
@Override
public String getParameter(String s) {
return null;
}
@Override
public Enumeration<String> getParameterNames() {
return null;
}
@Override
public String[] getParameterValues(String s) {
return new String[0];
}
@Override
public Map<String, String[]> getParameterMap() {
return null;
}
@Override
public String getProtocol() {
return null;
}
@Override
public String getScheme() {
return null;
}
@Override
public String getServerName() {
return null;
}
@Override
public int getServerPort() {
return 0;
}
@Override
public BufferedReader getReader() throws IOException {
return null;
}
@Override
public String getRemoteAddr() {
return null;
}
@Override
public String getRemoteHost() {
return null;
}
@Override
public void setAttribute(String s, Object o) {
}
@Override
public void removeAttribute(String s) {
}
@Override
public Locale getLocale() {
return null;
}
@Override
public Enumeration<Locale> getLocales() {
return null;
}
@Override
public boolean isSecure() {
return false;
}
@Override
public RequestDispatcher getRequestDispatcher(String s) {
return null;
}
@Override
public String getRealPath(String s) {
return null;
}
@Override
public int getRemotePort() {
return 0;
}
@Override
public String getLocalName() {
return null;
}
@Override
public String getLocalAddr() {
return null;
}
@Override
public int getLocalPort() {
return 0;
}
@Override
public ServletContext getServletContext() {
return null;
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
return null;
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException {
return null;
}
@Override
public boolean isAsyncStarted() {
return false;
}
@Override
public boolean isAsyncSupported() {
return false;
}
@Override
public AsyncContext getAsyncContext() {
return null;
}
@Override
public DispatcherType getDispatcherType() {
return null;
}
}
请求对象:
package com.ljw.MyTomcat;
import java.net.Socket;
public class Request extends AbstractRequest {
private String method;
private String url;
private String protocol;
// 需要绑定当前请求的socket连接对象,用于获取outputsream进行响应
private Socket socket;
public Request(String method, String url, String protocol,Socket socket) {
this.method = method;
this.url = url;
this.protocol = protocol;
this.socket = socket;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getRequestURI() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
}
响应抽象类:
package com.ljw.MyTomcat;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Locale;
public class AbstractResponse implements HttpServletResponse {
@Override
public void addCookie(Cookie cookie) {
}
@Override
public boolean containsHeader(String s) {
return false;
}
@Override
public String encodeURL(String s) {
return null;
}
@Override
public String encodeRedirectURL(String s) {
return null;
}
@Override
public String encodeUrl(String s) {
return null;
}
@Override
public String encodeRedirectUrl(String s) {
return null;
}
@Override
public void sendError(int i, String s) throws IOException {
}
@Override
public void sendError(int i) throws IOException {
}
@Override
public void sendRedirect(String s) throws IOException {
}
@Override
public void setDateHeader(String s, long l) {
}
@Override
public void addDateHeader(String s, long l) {
}
@Override
public void setHeader(String s, String s1) {
}
@Override
public void addHeader(String s, String s1) {
}
@Override
public void setIntHeader(String s, int i) {
}
@Override
public void addIntHeader(String s, int i) {
}
@Override
public void setStatus(int i) {
}
@Override
public void setStatus(int i, String s) {
}
@Override
public int getStatus() {
return 0;
}
@Override
public String getHeader(String s) {
return null;
}
@Override
public Collection<String> getHeaders(String s) {
return null;
}
@Override
public Collection<String> getHeaderNames() {
return null;
}
@Override
public String getCharacterEncoding() {
return null;
}
@Override
public String getContentType() {
return null;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return null;
}
@Override
public PrintWriter getWriter() throws IOException {
return null;
}
@Override
public void setCharacterEncoding(String s) {
}
@Override
public void setContentLength(int i) {
}
@Override
public void setContentLengthLong(long l) {
}
@Override
public void setContentType(String s) {
}
@Override
public void setBufferSize(int i) {
}
@Override
public int getBufferSize() {
return 0;
}
@Override
public void flushBuffer() throws IOException {
}
@Override
public void resetBuffer() {
}
@Override
public boolean isCommitted() {
return false;
}
@Override
public void reset() {
}
@Override
public void setLocale(Locale locale) {
}
@Override
public Locale getLocale() {
return null;
}
}
响应对象:
package com.ljw.MyTomcat;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
/**
* 响应对象
*/
public class Response extends AbstractResponse{
// 状态码
private int status = 200;
// 返回信息
private String message = "OK";
// 请求头
private Map<String,String> headers = new HashMap<>();
// 请求对象
private Request request;
private OutputStream socketOutputStream;
// 响应输出流
private ResponseServletOutputStream responseServletOutputStream = new ResponseServletOutputStream();
@Override
public int getStatus() {
return status;
}
@Override
public void setStatus(int status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Map<String, String> getHeaders() {
return headers;
}
public void setHeaders(Map<String, String> headers) {
this.headers = headers;
}
@Override
public void addHeader(String s, String s1) {
headers.put(s,s1);
}
@Override
public ResponseServletOutputStream getOutputStream() throws IOException {
return responseServletOutputStream;
}
public Response(Request request){
this.request = request;
try {
this.socketOutputStream = request.getSocket().getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void complete() throws IOException {
// 响应行
sendResponseLine();
// 响应头
sendHeader();
// 响应数据
sendResponseBody();
}
private void sendResponseBody() throws IOException {
socketOutputStream.write(getOutputStream().getBytes());
}
private byte SP = ' ';
private byte CR = '\r';
private byte LF = '\n';
private void sendResponseLine() throws IOException {
socketOutputStream.write(request.getProtocol().getBytes());
socketOutputStream.write(SP);
socketOutputStream.write(status);
socketOutputStream.write(SP);
socketOutputStream.write(message.getBytes());
socketOutputStream.write(CR);
socketOutputStream.write(LF);
}
private void sendHeader() throws IOException {
// 设置响应格式
if (!headers.containsKey("Content-Length")){
addHeader("Content-Length",String.valueOf(getOutputStream().getPos()));
}
if (!headers.containsKey("Content-Type")){
addHeader("Content-Type","");
}
// 遍历map
for (Map.Entry<String, String> entry : headers.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
socketOutputStream.write(key.getBytes());
socketOutputStream.write(":".getBytes());
socketOutputStream.write(value.getBytes());
socketOutputStream.write(CR);
socketOutputStream.write(LF);
}
socketOutputStream.write(CR);
socketOutputStream.write(LF);
}
}
响应输出流:
package com.ljw.MyTomcat;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import java.io.IOException;
public class ResponseServletOutputStream extends ServletOutputStream {
// 一个响应可能会写多次,所以需要将响应数据暂存起来
private byte[] bytes = new byte[1024];
// 实际的数据长度
private int pos = 0;
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
bytes[pos] = (byte) b;
pos++;
}
public byte[] getBytes() {
return bytes;
}
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
public int getPos() {
return pos;
}
public void setPos(int pos) {
this.pos = pos;
}
}
项目app上下文:
package com.ljw.MyTomcat;
import jakarta.servlet.Servlet;
import java.util.HashMap;
import java.util.Map;
/**
* 每一个项目的上下文
*/
public class Context {
private String appName;
private Map<String, Servlet> urlPatternMap = new HashMap<>();
public String getAppName() {
return appName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public void addUrlPatternMapping(String urlPattern,Servlet servlet){
urlPatternMap.put(urlPattern,servlet);
}
public Servlet getByUrlPattern(String urlPattern){
for (String s : urlPatternMap.keySet()) {
if (s.contains("/"+urlPattern)){
return urlPatternMap.get(s);
}
}
return null;
}
}
自定义类加载器:
package com.ljw.MyTomcat;
import java.net.URL;
import java.net.URLClassLoader;
public class WebAppClassLoader extends URLClassLoader {
public WebAppClassLoader(URL[] urls) {
super(urls);
}
}
默认处理Servlet:
package com.ljw.MyTomcat;
import jakarta.servlet.http.HttpServlet;
public class DefaultServlet extends HttpServlet {
}
Servlet.class:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.ljw.MyTomcat;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(
urlPatterns = {"/test"}
)
public class TestServlet extends HttpServlet {
public TestServlet() {
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(req.getMethod());
resp.setContentType("text/plain;charset=utf-8");
resp.getOutputStream().write("hello world".getBytes());
}
}
目录结构:

本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 bjlm