JAVA手寫tomcat,帶你了解tomcat的原理
1 建立一個簡單的servlet
程式碼示例:
package springmvc;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet{
/**
* 一個最簡單的servlet
*/
private static final long serialVersionUID = 7683475760018217521L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String data="這是我的第一個servlet!";
System.out.println(data);
}
}
web.xml中的標籤確定了servlet的訪問路徑
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>littleServlet</servlet-name>
<servlet-class>springmvc.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>littleServlet</servlet-name>
<url-pattern>/servlet</url-pattern>
</servlet-mapping>
</web-app>
servlet完成之後需要需要將他打包成專案(war/jar),並解壓完成,形成完整的專案目錄,供後續自己寫的tomcat使用。通常情況下,所有的專案都會放入tomcat的webapps目錄下。
開始手動編寫tomcat程式碼程式碼示例
package springboot.tomcat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.Principal;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpUpgradeHandler;
import javax.servlet.http.Part;
import springboot.util.ProjectUtil;
/**
* @author liuhongya328
*
*/
public class TomcatServer {
static int threads = 10;
private static ExecutorService pool = Executors.newCachedThreadPool();
/**
* @param args
*/
public static void main(String[] args) throws Exception{
//載入專案,獲取tomcat需要釋出的所有專案及專案下的所有servlet資訊
final Map<String, ProjectUtil.WebXml> projectIno = ProjectUtil.load();
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("Tomcat 啟動成功");
while(!serverSocket.isClosed()) {
//阻塞獲取新連線
Socket socket = serverSocket.accept();
pool.submit(()->{
//接收資料
InputStream inputStream = socket.getInputStream();;
//請求響應結果
OutputStream outputStream = socket.getOutputStream();
try {
System.out.println("收到請求:-------");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String msg = null;
StringBuilder requestInfo = new StringBuilder();
while((msg = reader.readLine())!=null) {
if(msg.length()==0) {
break;
}
requestInfo.append(msg).append("\\r\\n");
}
System.out.println(requestInfo.toString()+"--------");
//請求需要訪問的servlet,這個servlet相當於業務程式碼的controller,是需要放入tomcat中啟動的專案,用原生的servlet便於從底層程式碼理解
//1.獲取請求方式 及請求路徑 GET /SpringMVC/servlet/littleServlet HTTP/1.1
String firstLine = requestInfo.toString().split("\\r\\n")[0];
String projectName = firstLine.split(" ")[1].split("/")[1];
String servletPath = firstLine.split(" ")[1].replace("/"+projectName, "");
//找到servlet對應的名稱--j2ee規範
String servletName = projectIno.get(projectName).servletMapping.get(servletPath).toString();
//找到servlet對應的例項
Servlet servlet = (Servlet) projectIno.get(projectName).servletInstances.get(servletName);
//將socket的inputsteam 轉成 request物件 ,如果想servlet接收和傳遞訊息,需要實現自己的HttpServletRequest/HttpServletResponse即可。
//這裡的建立僅作展示,實際沒有作用,並不會傳遞socket的資訊。
HttpServletRequest servletReuqest = createRequest();
HttpServletResponse servletResponse = createResponse();
//servlet的生命週期
servlet.service(servletReuqest, servletResponse);
outputStream.write("HTTP/1.1 200 OK\\r\\n".getBytes());
outputStream.write("Content-Length: 12\\r\\n\\r\\n".getBytes());
outputStream.write("Hello World!".getBytes());
outputStream.flush();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
socket.close();
return null;
});
}
serverSocket.close();
}
private static HttpServletRequest createRequest() {
return new HttpServletRequest() {
@Override
public Object getAttribute(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getAttributeNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
// TODO Auto-generated method stub
}
@Override
public int getContentLength() {
// TODO Auto-generated method stub
return 0;
}
@Override
public long getContentLengthLong() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletInputStream getInputStream() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String getParameter(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getParameterNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public String[] getParameterValues(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, String[]> getParameterMap() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getProtocol() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getScheme() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getServerName() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getServerPort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public BufferedReader getReader() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteAddr() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteHost() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setAttribute(String name, Object o) {
// TODO Auto-generated method stub
}
@Override
public void removeAttribute(String name) {
// TODO Auto-generated method stub
}
@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<Locale> getLocales() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isSecure() {
// TODO Auto-generated method stub
return false;
}
@Override
public RequestDispatcher getRequestDispatcher(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRealPath(String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public int getRemotePort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getLocalName() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getLocalAddr() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getLocalPort() {
// TODO Auto-generated method stub
return 0;
}
@Override
public ServletContext getServletContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public AsyncContext startAsync() throws IllegalStateException {
// TODO Auto-generated method stub
return null;
}
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws IllegalStateException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isAsyncStarted() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isAsyncSupported() {
// TODO Auto-generated method stub
return false;
}
@Override
public AsyncContext getAsyncContext() {
// TODO Auto-generated method stub
return null;
}
@Override
public DispatcherType getDispatcherType() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getAuthType() {
// TODO Auto-generated method stub
return null;
}
@Override
public Cookie[] getCookies() {
// TODO Auto-generated method stub
return null;
}
@Override
public long getDateHeader(String name) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getHeader(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getHeaders(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Enumeration<String> getHeaderNames() {
// TODO Auto-generated method stub
return null;
}
@Override
public int getIntHeader(String name) {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getMethod() {
//示例
return "GET";
}
@Override
public String getPathInfo() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getPathTranslated() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getContextPath() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getQueryString() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRemoteUser() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isUserInRole(String role) {
// TODO Auto-generated method stub
return false;
}
@Override
public Principal getUserPrincipal() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRequestedSessionId() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRequestURI() {
// TODO Auto-generated method stub
return null;
}
@Override
public StringBuffer getRequestURL() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getServletPath() {
// TODO Auto-generated method stub
return null;
}
@Override
public HttpSession getSession(boolean create) {
// TODO Auto-generated method stub
return null;
}
@Override
public HttpSession getSession() {
// TODO Auto-generated method stub
return null;
}
@Override
public String changeSessionId() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isRequestedSessionIdValid() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRequestedSessionIdFromCookie() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRequestedSessionIdFromURL() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isRequestedSessionIdFromUrl() {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
// TODO Auto-generated method stub
return false;
}
@Override
public void login(String username, String password) throws ServletException {
// TODO Auto-generated method stub
}
@Override
public void logout() throws ServletException {
// TODO Auto-generated method stub
}
@Override
public Collection<Part> getParts() throws IOException, ServletException {
// TODO Auto-generated method stub
return null;
}
@Override
public Part getPart(String name) throws IOException, ServletException {
// TODO Auto-generated method stub
return null;
}
@Override
public <T extends HttpUpgradeHandler> T upgrade(Class<T> httpUpgradeHandlerClass)
throws IOException, ServletException {
// TODO Auto-generated method stub
return null;
}
};
}
private static HttpServletResponse createResponse() {
return new HttpServletResponse() {
@Override
public String getCharacterEncoding() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getContentType() {
// TODO Auto-generated method stub
return null;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return null;
}
@Override
public PrintWriter getWriter() throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setCharacterEncoding(String charset) {
// TODO Auto-generated method stub
}
@Override
public void setContentLength(int len) {
// TODO Auto-generated method stub
}
@Override
public void setContentLengthLong(long length) {
// TODO Auto-generated method stub
}
@Override
public void setContentType(String type) {
// TODO Auto-generated method stub
}
@Override
public void setBufferSize(int size) {
// TODO Auto-generated method stub
}
@Override
public int getBufferSize() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void flushBuffer() throws IOException {
// TODO Auto-generated method stub
}
@Override
public void resetBuffer() {
// TODO Auto-generated method stub
}
@Override
public boolean isCommitted() {
// TODO Auto-generated method stub
return false;
}
@Override
public void reset() {
// TODO Auto-generated method stub
}
@Override
public void setLocale(Locale loc) {
// TODO Auto-generated method stub
}
@Override
public Locale getLocale() {
// TODO Auto-generated method stub
return null;
}
@Override
public void addCookie(Cookie cookie) {
// TODO Auto-generated method stub
}
@Override
public boolean containsHeader(String name) {
// TODO Auto-generated method stub
return false;
}
@Override
public String encodeURL(String url) {
// TODO Auto-generated method stub
return null;
}
@Override
public String encodeRedirectURL(String url) {
// TODO Auto-generated method stub
return null;
}
@Override
public String encodeUrl(String url) {
// TODO Auto-generated method stub
return null;
}
@Override
public String encodeRedirectUrl(String url) {
// TODO Auto-generated method stub
return null;
}
@Override
public void sendError(int sc, String msg) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void sendError(int sc) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void sendRedirect(String location) throws IOException {
// TODO Auto-generated method stub
}
@Override
public void setDateHeader(String name, long date) {
// TODO Auto-generated method stub
}
@Override
public void addDateHeader(String name, long date) {
// TODO Auto-generated method stub
}
@Override
public void setHeader(String name, String value) {
// TODO Auto-generated method stub
}
@Override
public void addHeader(String name, String value) {
// TODO Auto-generated method stub
}
@Override
public void setIntHeader(String name, int value) {
// TODO Auto-generated method stub
}
@Override
public void addIntHeader(String name, int value) {
// TODO Auto-generated method stub
}
@Override
public void setStatus(int sc) {
// TODO Auto-generated method stub
}
@Override
public void setStatus(int sc, String sm) {
// TODO Auto-generated method stub
}
@Override
public int getStatus() {
// TODO Auto-generated method stub
return 0;
}
@Override
public String getHeader(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Collection<String> getHeaders(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public Collection<String> getHeaderNames() {
// TODO Auto-generated method stub
return null;
}
};
}
}
獲取專案資訊的工具類
package springboot.util;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.Servlet;
/**
*
* 載入專案資訊
*
* */
public class ProjectUtil {
public static Map<String,WebXml> load() throws Exception{
final Map<String,WebXml> projetInfo = new HashMap<String,WebXml>();
//j2ee定義的是tomcat下的webapp目錄,本地我取自己打包的war包目錄
String webapps = "E:\\\\learning\\\\SpringMVC\\\\SpringMVC\\\\webapps";
//讀取tomcat釋出的所有的專案
File[] projects = new File(webapps).listFiles(projectName -> projectName.isDirectory());
for(File project : projects) {
//根據每個專案的web.xml讀取servlet資訊
WebXml webXml = new XMLConfigUtil().load(project.getPath()+"\\\\WEB-INF\\\\web.xml");
webXml.projectPath = project.getPath();
//類載入,載入class檔案-----war包或者jar包中解壓的class檔案
webXml.loadServlet();
projetInfo.put(project.getName(), webXml);
}
return projetInfo;
}
public class WebXml{
public String projectPath = null;
//查詢專案中所有的servlet
public Map<String,Object> servlets = new HashMap<String,Object>();
public Map<String,Object> servletMapping = new HashMap<String,Object>();
//例項物件
public Map<String,Object> servletInstances = new HashMap<String,Object>();
public void loadServlet() throws Exception {
//jvm載入class檔案
URL url = new URL("file:"+projectPath+"\\\\WEB-INF\\\\classes\\\\");
URLClassLoader classLoader = new URLClassLoader(new URL[] {url});
//載入servlet,建立servlet物件
for(Entry<String,Object> entry : servlets.entrySet()) {
//servlet的名稱
String servletName = entry.getKey();
//servlet類的完整路徑
String servletClassName = entry.getValue().toString();
//載入
Class<?> clazz = classLoader.loadClass(servletClassName);
//反射建立物件,這就是為什麼j2ee規範 servlet要繼承HttpServlet,
Servlet servlet = (Servlet) clazz.newInstance();
this.servletInstances.put(servletName, servlet);
}
}
}
}
解析web.xml的工具類
說明package springboot.util;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import springboot.util.ProjectUtil.WebXml;
public class XMLConfigUtil extends DefaultHandler {
// 查詢專案中所有的servlet
public Map<String, Object> servlets = new HashMap<String, Object>();
public Map<String, Object> servletMapping = new HashMap<String, Object>();
// 例項物件
public Map<String, Object> servletInstances = new HashMap<String, Object>();
private String tag;// 儲存操作的標籤
private boolean isMapping = false;
private String currentServlet;
private String currentServletMapping;
public WebXml load(String path) throws SAXException, IOException, ParserConfigurationException {
// SAX解析
// 1、獲取SAX解析工廠
SAXParserFactory factory = SAXParserFactory.newInstance();
// 2、從解析工廠中獲取解析器
SAXParser parse = factory.newSAXParser();
XMLConfigUtil handler = new XMLConfigUtil();
// 5、解析
parse.parse(path, this);
// 6、獲取資料
ProjectUtil.WebXml webXMl = new ProjectUtil().new WebXml();
webXMl.servlets = this.servlets;
webXMl.servletMapping = this.servletMapping;
return webXMl;
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (null != qName) {
tag = qName;// 儲存標籤名
if (tag.equals("servlet")) {
isMapping = false;
} else if (tag.equals("servlet-mapping")) {
isMapping = true;
}
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
String contents = new String(ch, start, length).trim();
if (null != contents) {// 處理空的問題
if (isMapping) {// 操作servlet-mapping
if (tag.equals("servlet-name")) {
currentServletMapping = contents;
} else if (tag.equals("url-pattern")) {
String urlPattern = contents;
servletMapping.put(urlPattern, currentServlet);
}
} else {// 操作servlet
if (tag.equals("servlet-name")) {
currentServlet = contents;
currentServletMapping = contents;
} else if (tag.equals("servlet-class")) {
String servletClass = contents;
servlets.put(currentServlet, servletClass);
}
}
}
}
}
本次程式碼沒有實現request和response是的互動,返回都是寫死的outputStream.write(“Hello World!”.getBytes())。
如果需要實現,需要寫自己的request和response。
原文連結:/file/2019/11/20/20191120164709_2943.jpg