手写Mini版Tomcat服务器
原创: sean9468 程序员文章集锦
原文地址
基本原理
Tomcat服务器是servlet容器,主要用于部署javaWeb项目。Tomcat主要实现了监听socket,获得输入流,输出流,解析http包创建request,response,调用servlet.doFilter和servlet.service 函数填充response信息,写入客户端输出流。
核心代码
详细代码点击原文查看gitee地址。
核心代码:
启动类:BootStrap
连接器:HttpConnector
http处理类:HttpProcessor
HttpRequest
HttpResponse
门面类:RequesFacade
门面类:ResponseFacade
测试代码:Servlet和静态HTML文件。
PrimitiveServle
test.html
Bootstrap
启动类就是一个主函数,整个Web项目的入口,平常开发用的war包是没有项目入口的,其实是在Tomcat的启动类中。即使用spring boot开发也是包含了tomcat的依赖,打包为jar包。也是同样的道理。
public class BootStrap {
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
connector.start();
}
}
连接器
连接器HttpConnector主要目的就是监听端口,这里采用同步阻塞。监听代码是新建一个线程负责。所有的客户端请求都是这同一个线程处理,效率不是最好的,但对于稳定小流量是可以了。关于线程和IO模型也是一个重要的技术面。这里不过多阐述。
public class HttpConnector implements Runnable {
boolean stopped;
private String scheme = "http";
public String getScheme() {
return scheme;
}
public void start() {
Thread thread = new Thread(this);
thread.start();
}
@Override
public void run() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 100, InetAddress.getByName("127.0.0.1"));
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
while (!stopped) {
Socket socket = null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
e.printStackTrace();
continue;
}
HttpProcessor processor = new HttpProcessor();
processor.process(socket);
}
}
}
Http处理类
这是一个核心类,在Tomcat中也属于连接器的职责范围,主要是生成Request 和Response。其中Request中的Header Cookie RequestLine RequestPara参数都一一解析。这部分会对HTTP协议了解的更深入。
process方法
将客户端的InputStream 进一步封装为SocketInputStream ,创建HttpRequest、解析HTTP头信息、创建HttpResponse;这里主要是GET请求。
根据解析出的URI判断是要Servlet处理还是静态文件处理。Servlet会调用我们的写好的测试Servlet代码,静态资源会处理本地静态html文件,如果没有找到就会返回404。
public void process(Socket socket) {
SocketInputStream input = null;
OutputStream output = null;
try {
input = new SocketInputStream(socket.getInputStream(), 2048);
output = socket.getOutputStream();
request = new HttpRequest(input);
response = new HttpResponse(output);
response.setRequest(request);
response.setHeader("Server", "sean servlet container");
parseRequest(input, output);
parseHeaders(input);
if (request.getRequestURI().startsWith("/servlet/")) {
ServletProcessor processor = new ServletProcessor();
processor.process(request, response);
} else {
StaticResourceProcessor processor = new StaticResourceProcessor();
processor.process(request, response);
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
}
}
解析RequestLine
将客户端的InputStream 进一步封装为SocketInputStream ,该类实现了解析HTTP首行的HTTP method 、HTTP URI、HTTP protocol 和version 即封装的RequestLine。然后赋值给Request中的method,URI ,protocol。
注意:解析RequestLine 要优先于解析Header。
input.readRequestLine(requestLine);
String method = new String(requestLine.method, 0, requestLine.methodEnd);
String uri;
String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);
if (method.length() < 1) {
throw new ServletException("Missing Http request method");
} else if (requestLine.uriEnd < 1) {
throw new ServletException("Missing Http request URI");
}
int question = requestLine.indexOf("?");
if (question >= 0) {
request.setQueryString(new String(requestLine.uri, question + 1, requestLine.uriEnd - question - 1));
uri = new String(requestLine.uri, 0, question);
} else {
request.setQueryString(null);
uri = new String(requestLine.uri, 0, requestLine.uriEnd);
}
if (!uri.startsWith("/")) {
int pos = uri.indexOf("://");
if (pos == -1) {
uri = "";
} else {
uri = uri.substring(pos);
}
}
request.setUri(uri);
request.setMethod(method);
request.setProtocol(protocol);
}
解析Headers
Headers信息只有其实是name-value键值对,根据不同name可以提取不同的元素,比如cookies,content-length ,user-agent等。 提取Header是一个循环,每次循环都会创建一个Header直到读取完毕为止。
private void parseHeaders(SocketInputStream input) throws IOException, ServletException {
while (true) {
httpHeader = new HttpHeader();
input.readHeader(httpHeader);
if (httpHeader.nameEnd == 0) {
if (httpHeader.valueEnd == 0) {
return;
} else {
throw new ServletException("parseHeaders exceptions");
}
}
request.addHeader(httpHeader);
if (httpHeader.name.equals("cookie")) {
Cookie cookies[] = RequestUtil.parseCookieHeader(new String(httpHeader.value));
for (int i = 0; i < cookies.length; i++) {
request.addCookie(cookies[i]);
}
} else if (httpHeader.name.equals("content-length")) {
int n = Integer.parseInt(new String(httpHeader.value));
request.setContentLength(n);
}
}
}
Servlet处理类
该Servlet处理类根据URI请求的名字加载Servlet Class 、实例化、调用service函数。 关于service传参其实传递的是Request和Response的门面类。主要目的是为了安全避免不必要的接口暴漏给Servlet业务程序员。
public class ServletProcessor {
public void process(HttpRequest request, HttpResponse response) {
String uri = request.getRequestURI();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
URL[] urls = new URL[1];
URLStreamHandler streamHandler = null;
File classPath = new File(Constants.WEB_ROOT);
String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString();
loader = new URLClassLoader(urls);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Class myClass = null;
try {
servletName = "tomcat.step3.servlet." + servletName;
myClass = loader.loadClass(servletName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
servlet.service(requestFacade, responseFacade);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
HttpResponse
这里将处理静态文件的代码放到了HttpResponse中,sendStaticResource方法中。
public void sendStaticResource() throws IOException {
byte[] bytes = new byte[BUFFER_SIZE];
try {
File file = new File(HttpServer.WEB_ROOT, httpRequest.getRequestURI());
System.out.println(file.getAbsolutePath());
if (file.exists()) {
System.out.println("文件存在");
InputStreamReader reader = new InputStreamReader(new FileInputStream(file));
BufferedReader reader1 = new BufferedReader(reader);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output));
String line = null;
while ((line = reader1.readLine()) != null) {
System.out.println(line);
writer.write(line);
}
writer.flush();
writer.close();
reader.close();
reader1.close();
} else {
String erroMessage = "HTTP/1.1 404 Fie Not Found\r\n" +
"Content-Type: text/html\r\n" +
"Content-length:23\r\n" +
"\r\n" +
"<h1>File Not Found</h1>";
output.write(erroMessage.getBytes());
}
} catch (Exception e) {
System.out.println(e.toString());
} finally {
}
}
调试运行
请求PrimitiveServlet
请求test.html
请求test-notfound.html