手动写一个迷你版的tomcat,了解一下tomcat的工作流程。 minicat需要实现的功能是,装载servlet,接收http转发给servelt处理,并返回结果。
手动创建一个项目,结构如下
1、web.xml。 指定/lagou由LagouServlet处理
<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
<servlet>
<servlet-name>lagou</servlet-name>
<servlet-class>server.LagouServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>lagou</servlet-name>
<url-pattern>/lagou</url-pattern>
</servlet-mapping>
</web-app>
2、LagouServlet
一个处理请求的类,实现了HttpServelt(也是自己写的),有doGet和doPost方法
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 = "<h1>LagouServlet get</h1>";
try {
response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void doPost(Request request, Response response) {
String content = "<h1>LagouServlet post</h1>";
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 {
}
}
HttpServlet也是自己写的
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);
}
}
}
Servlet
public interface Servlet {
void init() throws Exception;
void destory() throws Exception;
void service(Request request,Response response) throws Exception;
}
另外,在LagouServlet中涉及到的Request Response,http协议工具类等,都是自己定义的
import java.io.IOException;
import java.io.InputStream;
/**
* 把请求信息封装为Request对象(根据InputSteam输入流封装)
*/
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;
while (count == 0) {
count = inputStream.available();
}
byte[] bytes = new byte[count];
inputStream.read(bytes);
String inputStr = new String(bytes);
// 获取第一行请求头信息
String firstLineStr = inputStr.split("\\n")[0]; // 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);
}
}
Response
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* 封装Response对象,需要依赖于OutputStream
*
* 该对象需要提供核心方法,输出html
*/
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());
}
/**
*
* @param path url,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过
* 输出流输出
* /-----> classes
*/
public void outputHtml(String path) throws IOException {
// 获取静态资源文件的绝对路径
String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
// 输入静态资源文件
File file = new File(absoluteResourcePath);
if(file.exists() && file.isFile()) {
// 读取静态资源文件,输出静态资源
StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);
}else{
// 输出404
output(HttpProtocolUtil.getHttpHeader404());
}
}
}
HttpProtocoUtil-提供响应头信息和状态码
/**
* http协议工具类,主要是提供响应头信息,这里我们只提供200和404的情况
*/
public class HttpProtocolUtil {
/**
* 为响应码200提供请求头信息
* @return
*/
public static String getHttpHeader200(long contentLength) {
return "HTTP/1.1 200 OK \n" +
"Content-Type: text/html \n" +
"Content-Length: " + contentLength + " \n" +
"\r\n";
}
/**
* 为响应码404提供请求头信息(此处也包含了数据内容)
* @return
*/
public static String getHttpHeader404() {
String str404 = "<h1>404 not found</h1>";
return "HTTP/1.1 404 NOT Found \n" +
"Content-Type: text/html \n" +
"Content-Length: " + str404.getBytes().length + " \n" +
"\r\n" + str404;
}
}
有了上面这些基础类后,看看怎样写一个tomcat的功能,来使用这些基础类,达到获取url,并执行相关的业务方法,最终返回信息。
tomcat核心类:Bootstrap
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.*;
/**
* Minicat的主类
*/
public class Bootstrap {
/**定义socket监听的端口号*/
private int port = 8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
* Minicat启动需要初始化展开的一些操作
*/
public void start() throws Exception {
// 加载解析相关的配置,web.xml
loadServlet();
// 定义一个线程池 (多线程接受多个请求)
int corePoolSize = 10;
int maximumPoolSize =50;
long keepAliveTime = 100L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
//使用Socket建立连接
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("=====>>>Minicat start on port:" + port);
/**
* 使用线程池
*/
while(true) {
Socket socket = serverSocket.accept();
//RequestProcessor多线程,run方法是具体逻辑
RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);
//requestProcessor.start();
threadPoolExecutor.execute(requestProcessor);
}
}
/**
* 存放url和Servlet的关系
*/
private Map<String,HttpServlet> servletMap = new HashMap<String,HttpServlet>();
/**
* 加载解析web.xml,初始化Servlet
*/
private void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//servlet");
for (int i = 0; i < selectNodes.size(); i++) {
Element element = selectNodes.get(i);
// <servlet-name>lagou</servlet-name>
Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletnameElement.getStringValue();
// <servlet-class>server.LagouServlet</servlet-class>
Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletclassElement.getStringValue();
// 根据servlet-name的值找到url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
// /lagou 然后放到map中,通过该路径就能找到LagouServlet
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();
}
}
/**
* Minicat 的程序启动入口
* @param args
*/
public static void main(String[] args) {
Bootstrap bootstrap = new Bootstrap();
try {
// 启动Minicat
bootstrap.start();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
看看多线程RequestProcessor的具体实现逻辑:
import java.io.InputStream;
import java.net.Socket;
import java.util.Map;
public class RequestProcessor extends Thread {
private Socket socket;
private Map<String,HttpServlet> servletMap;
public RequestProcessor(Socket socket, Map<String, HttpServlet> 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();
}
}
}
这就是模仿web应用的简写tomcat,启动后加载url和servlet,最终解析执行方法,返回信息。
当然,对于http,socket等知识,后续讲解。