├── webapps ├── ROOT │ ├── b │ │ ├── a.html │ │ └── 1.jpg │ ├── index.html │ ├── WEB-INF │ │ └── web.xml │ └── form.html └── test │ ├── b │ ├── a.html │ └── 1.jpg │ ├── index.htm │ └── WEB-INF │ └── web.xml ├── lib ├── jspc_all.jar ├── junit-4.9.jar ├── jsoup-1.12.1.jar ├── log4j-1.2.17.jar ├── servlet-api.jar └── hutool-all-4.3.1.jar ├── static ├── HttpRequestProcess.png └── TinyWebServerArchitecture.png ├── .idea ├── vcs.xml ├── .gitignore ├── misc.xml ├── modules.xml ├── inspectionProfiles │ └── Project_Default.xml └── uiDesigner.xml ├── src └── com │ └── yzb │ ├── exception │ ├── LifecycleException.java │ ├── URLMismatchedExpection.java │ └── ParseHttpRequestException.java │ ├── TinyWebServer.java │ ├── core │ ├── Host.java │ ├── Context.java │ ├── StandardServletConfig.java │ ├── Engine.java │ ├── StandardContainer.java │ ├── StandardConnector.java │ ├── StandardService.java │ ├── StandardServletContext.java │ └── StandardServer.java │ ├── servlets │ ├── TestServlet3.java │ ├── TestServlet.java │ ├── TestServlet2.java │ ├── SessionServlet1.java │ ├── SessionServlet2.java │ ├── SessionServlet3.java │ ├── HelloServlet2.java │ ├── HelloServlet.java │ └── DefaultServlet.java │ ├── common │ ├── CommonThreadPool.java │ ├── Container.java │ ├── ServerContext.java │ ├── Service.java │ ├── Server.java │ ├── Connector.java │ ├── Response.java │ ├── Request.java │ └── Lifecycle.java │ ├── classcloader │ └── WebappClassLoader.java │ ├── http │ ├── ApplicationRequestDispatcher.java │ ├── HttpProcessor.java │ ├── StandardSession.java │ ├── Dispatcher.java │ ├── SessionManager.java │ ├── HttpConnector.java │ ├── HttpContant.java │ ├── HttpResponse.java │ ├── ApplicationContext.java │ └── HttpRequest.java │ └── util │ └── ServerXMLParser.java ├── .gitignore ├── conf ├── log4j.properties ├── context.xml └── server.xml ├── README.md ├── TinyWebServer.iml └── test └── com └── yzb ├── ServerTest.java ├── Client.java └── CommonTest.java /webapps/ROOT/b/a.html: -------------------------------------------------------------------------------- 1 | this is a.html under b dir -------------------------------------------------------------------------------- /webapps/test/b/a.html: -------------------------------------------------------------------------------- 1 | this is a.html under b dir -------------------------------------------------------------------------------- /lib/jspc_all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/lib/jspc_all.jar -------------------------------------------------------------------------------- /lib/junit-4.9.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/lib/junit-4.9.jar -------------------------------------------------------------------------------- /lib/jsoup-1.12.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/lib/jsoup-1.12.1.jar -------------------------------------------------------------------------------- /lib/log4j-1.2.17.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/lib/log4j-1.2.17.jar -------------------------------------------------------------------------------- /lib/servlet-api.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/lib/servlet-api.jar -------------------------------------------------------------------------------- /webapps/ROOT/b/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/webapps/ROOT/b/1.jpg -------------------------------------------------------------------------------- /webapps/test/b/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/webapps/test/b/1.jpg -------------------------------------------------------------------------------- /lib/hutool-all-4.3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/lib/hutool-all-4.3.1.jar -------------------------------------------------------------------------------- /static/HttpRequestProcess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/static/HttpRequestProcess.png -------------------------------------------------------------------------------- /static/TinyWebServerArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangzebin001/TinyWebServer/HEAD/static/TinyWebServerArchitecture.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /webapps/ROOT/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | This is ROOT index.html. 9 | 10 | -------------------------------------------------------------------------------- /webapps/test/index.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | This is test index.html. 9 | 10 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../:\yzb\TinyWebServer\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/com/yzb/exception/LifecycleException.java: -------------------------------------------------------------------------------- 1 | package com.yzb.exception; 2 | 3 | /** 4 | * @Description 5 | * @Date 2021/1/28 下午10:00 6 | * @Creater BeckoninGshy 7 | */ 8 | public class LifecycleException extends Exception { 9 | 10 | public LifecycleException() { 11 | } 12 | 13 | public LifecycleException(String message) { 14 | super(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/com/yzb/exception/URLMismatchedExpection.java: -------------------------------------------------------------------------------- 1 | package com.yzb.exception; 2 | 3 | /** 4 | * @Description 5 | * @Date 2021/2/5 下午10:01 6 | * @Creater BeckoninGshy 7 | */ 8 | public class URLMismatchedExpection extends Exception{ 9 | 10 | public URLMismatchedExpection() { 11 | } 12 | 13 | public URLMismatchedExpection(String message) { 14 | super(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | *.pdf 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | 26 | webapps/*.pdf 27 | 28 | logs/ 29 | 30 | out/ 31 | 32 | -------------------------------------------------------------------------------- /src/com/yzb/exception/ParseHttpRequestException.java: -------------------------------------------------------------------------------- 1 | package com.yzb.exception; 2 | 3 | /** 4 | * @description: 解析HTTP请求异常类 5 | * @author: BeckoninGshy 6 | * @create: 2021/1/24 16:33 7 | */ 8 | public class ParseHttpRequestException extends Exception { 9 | private static final long serialVersionUID = 54839657436794L; 10 | 11 | public ParseHttpRequestException() { 12 | } 13 | 14 | public ParseHttpRequestException(String message) { 15 | super(message); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/yzb/TinyWebServer.java: -------------------------------------------------------------------------------- 1 | package com.yzb; 2 | 3 | import com.yzb.common.Server; 4 | import com.yzb.exception.LifecycleException; 5 | import com.yzb.util.ServerXMLParser; 6 | 7 | /** 8 | * @Description TinyWebServer app starter 9 | * @Date 2021/1/26 下午9:20 10 | * @Creater BeckoninGshy 11 | */ 12 | public class TinyWebServer { 13 | public static void main(String[] args) throws LifecycleException { 14 | Server server = ServerXMLParser.getServerWithAutoPack(); 15 | server.init(); 16 | server.start(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /conf/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger = info,stdout,R 2 | 3 | log4j.appender.stdout = org.apache.log4j.ConsoleAppender 4 | log4j.appender.stdout.layout = org.apache.log4j.PatternLayout 5 | log4j.appender.stdout.layout.ConversionPattern = %d{MM dd,yyyy HH:mm:ss a}%c%M%n%-5p:%m%n 6 | 7 | log4j.appender.R = org.apache.log4j.DailyRollingFileAppender 8 | log4j.appender.R.File = logs/log 9 | log4j.appender.R.DatePattern = ','yyyy-MM-dd'.log' 10 | log4j.appender.R.layout = org.apache.log4j.PatternLayout 11 | log4j.appender.R.layout.ConversionPattern = %d{MM dd,yyyy HH:mm:ss a} %c %M%n%-5p: %m%n -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyWebServer 2 | 一个小型的web服务器 3 | 4 | ### 项目架构图 5 | ![Architecture](static/TinyWebServerArchitecture.png) 6 | 7 | ### 请求执行流程图 8 | ![HttpRequestProcess](static/HttpRequestProcess.png) 9 | 10 | ### feature: 11 | 12 | - Multi-application deployment 13 | - Java servlet API support 14 | - Application-Context manager 15 | - Url Dispatcher 16 | - HTTP/1.1 protocol support 17 | - HTTP request and response 18 | - Cookie and Session 19 | - Request Dispatcher and Response SendRedirect 20 | - 404 and 500 code status 21 | - MIME type and welcome file support 22 | - Log management 23 | 24 | > default character set is UTF-8 25 | -------------------------------------------------------------------------------- /src/com/yzb/core/Host.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import com.yzb.common.ServerContext; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * @Description 9 | * @Date 2021/1/30 下午9:36 10 | * @Creater BeckoninGshy 11 | */ 12 | public class Host extends StandardContainer { 13 | 14 | private String appBase; 15 | 16 | public String getAppBase() { 17 | return appBase; 18 | } 19 | 20 | public void setAppBase(String appBase) { 21 | if(appBase.startsWith(File.separator)) 22 | this.appBase = ServerContext.serverBasePath + appBase; 23 | else this.appBase = ServerContext.serverBasePath + File.separator + appBase; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/TestServlet3.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | import java.io.IOException; 7 | 8 | /** 9 | * @Description 10 | * @Date 2021/2/6 下午8:58 11 | * @Creater BeckoninGshy 12 | */ 13 | public class TestServlet3 extends HelloServlet{ 14 | @Override 15 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 16 | resp.getWriter().println("this is test3 response."); // should be useless. 17 | req.getRequestDispatcher("/test1").forward(req, resp); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/com/yzb/common/CommonThreadPool.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | import java.util.concurrent.LinkedBlockingDeque; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * @Description handle Connector Thread Pool 9 | * @Date 2021/1/27 下午12:07 10 | * @Creater BeckoninGshy 11 | */ 12 | public class CommonThreadPool { 13 | private static ThreadPoolExecutor tpe = new ThreadPoolExecutor(20,100,60,TimeUnit.SECONDS,new LinkedBlockingDeque<>()); 14 | public static void run(Runnable runnable){ 15 | tpe.execute(runnable); 16 | } 17 | 18 | public static void shutdown() { 19 | tpe.shutdown(); 20 | while(!tpe.isShutdown()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/TestServlet.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServlet; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | 10 | /** 11 | * @Description 12 | * @Date 2021/2/1 下午9:35 13 | * @Creater BeckoninGshy 14 | */ 15 | public class TestServlet extends HttpServlet { 16 | @Override 17 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 18 | PrintWriter writer = resp.getWriter(); 19 | writer.println("this is TestSerlvet response"); 20 | writer.close(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/TestServlet2.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServletRequest; 5 | import javax.servlet.http.HttpServletResponse; 6 | import java.io.IOException; 7 | import java.io.PrintWriter; 8 | 9 | /** 10 | * @Description 11 | * @Date 2021/2/6 下午8:58 12 | * @Creater BeckoninGshy 13 | */ 14 | public class TestServlet2 extends HelloServlet{ 15 | @Override 16 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 17 | // req.getRequestDispatcher("/test1").forward(req, resp); 18 | req.getServletContext().getContext("/") 19 | .getRequestDispatcher("/b/a.html").forward(req,resp); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/com/yzb/common/Container.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | /** 4 | * @Description 5 | * @Date 2021/1/28 下午10:18 6 | * @Creater BeckoninGshy 7 | */ 8 | public interface Container extends Lifecycle { 9 | 10 | public String getName(); 11 | 12 | public void setName(String name); 13 | 14 | public Service getService(); 15 | 16 | public void setService(Service service); 17 | 18 | public Container getParent(); 19 | 20 | public void setParent(Container parent); 21 | 22 | public ClassLoader getParentClassLoader(); 23 | 24 | public void setParentClassLoader(ClassLoader classLoader); 25 | 26 | public void addChild(Container container); 27 | 28 | public Container findChild(String container); 29 | 30 | public Container[] findChildren(); 31 | 32 | public void removeChild(Container container); 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/com/yzb/common/ServerContext.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * @Description 7 | * @Date 2021/1/30 下午10:01 8 | * @Creater BeckoninGshy 9 | */ 10 | public class ServerContext { 11 | 12 | public static final String serverName = "TinyWebServer"; 13 | 14 | public static final String version = "0.0.1"; 15 | 16 | public static final String serverBasePath = System.getProperty("user.dir").toString(); 17 | 18 | public static final String serverConfigDir = serverBasePath + File.separator + "conf"; 19 | 20 | public static final String serverXMLPath = serverConfigDir + File.separator + "server.xml"; 21 | 22 | public static final String webXMLPath = serverConfigDir+ File.separator + "web.xml"; 23 | 24 | public static final String servletLoadClassDir = serverConfigDir+ File.separator + "servlets"; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/com/yzb/common/Service.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | 4 | /** 5 | * @Description 6 | * @Date 2021/1/28 下午10:07 7 | * @Creater BeckoninGshy 8 | */ 9 | public interface Service extends Lifecycle { 10 | 11 | 12 | public Container getContainer(); 13 | 14 | public void setContainer(Container container); 15 | 16 | public String getInfo(); 17 | 18 | public String getName(); 19 | 20 | public void setName(String name); 21 | 22 | public Server getServer(); 23 | 24 | public void setServer(Server server); 25 | 26 | public void addConnector(Connector connector); 27 | 28 | public Connector findConnector(String name); 29 | 30 | public String[] getConnectorNames(); 31 | 32 | public Connector[] findConnectors(); 33 | 34 | public void removeConnector(Connector connector); 35 | 36 | public ClassLoader getParentClassLoader(); 37 | 38 | public void setParentClassLoader(ClassLoader classLoader); 39 | } 40 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/SessionServlet1.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServlet; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import javax.servlet.http.HttpSession; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | 11 | /** 12 | * @Description 13 | * @Date 2021/2/10 下午10:30 14 | * @Creater BeckoninGshy 15 | */ 16 | public class SessionServlet1 extends HttpServlet { 17 | @Override 18 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 19 | HttpSession session = req.getSession(); 20 | session.setAttribute("username", "lisi"); 21 | System.out.println(session.isNew()); 22 | PrintWriter writer = resp.getWriter(); 23 | writer.println("this is SessionServlet1 response"); 24 | writer.close(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/com/yzb/common/Server.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | /** 4 | * @Description 5 | * @Date 2021/1/28 下午10:31 6 | * @Creater BeckoninGshy 7 | */ 8 | public interface Server extends Lifecycle { 9 | 10 | public String getName(); 11 | 12 | public void setName(String name); 13 | 14 | public int getPort(); 15 | 16 | public void setPort(int port); 17 | 18 | public String getAddress(); 19 | 20 | public void setAddress(String address); 21 | 22 | public String getShutdown(); 23 | 24 | public void setShutdown(String shutdown); 25 | 26 | public void addService(Service service); 27 | 28 | public Service findService(String service); 29 | 30 | public Service[] findServices(); 31 | 32 | public String[] getServiceNames(); 33 | 34 | public void removeService(Service service); 35 | 36 | public ClassLoader getParentClassLoader(); 37 | 38 | public void setParentClassLoader(ClassLoader classLoader); 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/SessionServlet2.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServlet; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import javax.servlet.http.HttpSession; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | 11 | /** 12 | * @Description 13 | * @Date 2021/2/10 下午10:30 14 | * @Creater BeckoninGshy 15 | */ 16 | public class SessionServlet2 extends HttpServlet { 17 | @Override 18 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 19 | HttpSession session = req.getSession(); 20 | String username = (String) session.getAttribute("username"); 21 | System.out.println(username); 22 | PrintWriter writer = resp.getWriter(); 23 | writer.println("this is SessionServlet2 response"); 24 | writer.close(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/com/yzb/servlets/SessionServlet3.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletException; 4 | import javax.servlet.http.HttpServlet; 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import javax.servlet.http.HttpSession; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | 11 | /** 12 | * @Description 13 | * @Date 2021/2/10 下午10:30 14 | * @Creater BeckoninGshy 15 | */ 16 | public class SessionServlet3 extends HttpServlet { 17 | @Override 18 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 19 | HttpSession session = req.getSession(); 20 | session.removeAttribute("username"); 21 | // session.invalidate(); 22 | System.out.println(session.isNew()); 23 | PrintWriter writer = resp.getWriter(); 24 | writer.println("this is SessionServlet3 response"); 25 | writer.close(); 26 | } 27 | } -------------------------------------------------------------------------------- /TinyWebServer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /webapps/ROOT/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HelloServlet 5 | com.yzb.servlets.HelloServlet 6 | 7 | username 8 | zhangsan 9 | 10 | 11 | password 12 | 123 13 | 14 | 1 15 | 16 | 17 | 18 | HelloServlet2 19 | com.yzb.servlets.HelloServlet2 20 | 21 | 22 | 23 | HelloServlet 24 | /hello 25 | 26 | 27 | 28 | HelloServlet2 29 | /hello2.do 30 | 31 | -------------------------------------------------------------------------------- /src/com/yzb/core/Context.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import java.io.File; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | /** 8 | * @Description The Context element represents a web application under webapps folder. 9 | * @Date 2021/1/30 下午9:37 10 | * @Creater BeckoninGshy 11 | */ 12 | public class Context extends StandardContainer { 13 | 14 | private Map attributes = new ConcurrentHashMap<>(); 15 | private String path; 16 | 17 | public Context(){} 18 | 19 | public String getPath() { 20 | return path; 21 | } 22 | 23 | public void setPath(String path) { 24 | this.path = path; 25 | } 26 | 27 | public String getRealPath(){ 28 | if(getPath().equals("/")) return ((Host)parent).getAppBase() + File.separator + "ROOT"; 29 | return ((Host)parent).getAppBase() + getPath(); 30 | } 31 | 32 | public Object getAttribute(String key) { 33 | return attributes.get(key); 34 | } 35 | 36 | public void setAttribute(String key, Object value){ 37 | attributes.put(key, value); 38 | } 39 | 40 | public void removeAttribute(String key){ 41 | attributes.remove(key); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /webapps/ROOT/form.html: -------------------------------------------------------------------------------- 1 | 2 | tiny web standardServer 3 | 4 |
5 | 姓名:
6 | 密码:
7 | 性别:男 8 |
9 | I have a bike 10 |
11 | I have a car 12 | 城市:
18 |
21 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /src/com/yzb/common/Connector.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | import java.net.Socket; 4 | 5 | /** 6 | * @Description 7 | * @Date 2021/1/28 下午10:17 8 | * @Creater BeckoninGshy 9 | */ 10 | public interface Connector extends Lifecycle { 11 | 12 | public String getName(); 13 | 14 | public void setName(String name); 15 | 16 | public String getProperty(String property); 17 | 18 | public boolean setProperty(String key, String value); 19 | 20 | public Service getService(); 21 | 22 | public void setService(Service service); 23 | 24 | public int getPort(); 25 | 26 | public void setPort(int port); 27 | 28 | public String getProtocol(); 29 | 30 | public void setProtocol(String protocol); 31 | 32 | public void setConnectionTimeout(long timeout); 33 | 34 | public long getConnectionTimeout(); 35 | 36 | public String getScheme(); 37 | 38 | public void setScheme(String scheme); 39 | 40 | public String getURIEncoding(); 41 | 42 | public void setURIEncoding(String uriEncoding); 43 | 44 | public boolean getUseBodyEncodingForURI(); 45 | 46 | public void setUseBodyEncodingForURI(boolean isUse); 47 | 48 | public Request createRequest(Socket socket, Connector connector); 49 | 50 | public Response createResponse(Socket socket); 51 | 52 | public Server getServer(); 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/com/yzb/core/StandardServletConfig.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import javax.servlet.ServletConfig; 4 | import javax.servlet.ServletContext; 5 | import java.util.Collections; 6 | import java.util.Enumeration; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Description The implementation class of ServletConfig 12 | * @Date 2021/2/4 下午9:31 13 | * @Creater BeckoninGshy 14 | */ 15 | public class StandardServletConfig implements ServletConfig { 16 | private Map initParameters; 17 | private ServletContext servletContext; 18 | private String servletName; 19 | 20 | public StandardServletConfig(ServletContext servletContext,String servletName,Map initParameters){ 21 | this.servletContext = servletContext; 22 | this.initParameters = initParameters; 23 | this.servletName = servletName; 24 | } 25 | 26 | @Override 27 | public String getServletName() { 28 | return servletName; 29 | } 30 | 31 | @Override 32 | public ServletContext getServletContext() { 33 | return servletContext; 34 | } 35 | 36 | @Override 37 | public String getInitParameter(String s) { 38 | return initParameters.get(s); 39 | } 40 | 41 | @Override 42 | public Enumeration getInitParameterNames() { 43 | return Collections.enumeration(initParameters.keySet()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/HelloServlet2.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletConfig; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.http.HttpServlet; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | import java.util.Enumeration; 11 | 12 | /** 13 | * @Description 14 | * @Date 2021/2/2 下午5:41 15 | * @Creater BeckoninGshy 16 | */ 17 | public class HelloServlet2 extends HttpServlet { 18 | 19 | @Override 20 | public void destroy() { 21 | System.out.println("hello servlet2 is destroyed"); 22 | } 23 | 24 | @Override 25 | public void init(ServletConfig sc) throws ServletException { 26 | System.out.println(sc.getServletName()); 27 | Enumeration initParameterNames = sc.getInitParameterNames(); 28 | while(initParameterNames.hasMoreElements()){ 29 | String s = initParameterNames.nextElement(); 30 | System.out.println("init name:" + s + ", value:" + sc.getInitParameter(s)); 31 | } 32 | } 33 | 34 | @Override 35 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 36 | PrintWriter writer = resp.getWriter(); 37 | writer.println("this is HelloSerlvet2 response"); 38 | writer.close(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/com/yzb/classcloader/WebappClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.yzb.classcloader; 2 | 3 | import cn.hutool.core.io.FileUtil; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.net.URL; 8 | import java.net.URLClassLoader; 9 | import java.util.List; 10 | 11 | /** 12 | * @Description 13 | * @Date 2021/2/1 下午8:57 14 | * @Creater BeckoninGshy 15 | */ 16 | public class WebappClassLoader extends URLClassLoader { 17 | 18 | public WebappClassLoader(String docBase, ClassLoader commonClassLoader){ 19 | super(new URL[]{},commonClassLoader); 20 | 21 | try { 22 | File webinfFolder = new File(docBase,"WEB-INF"); 23 | File classFoler = new File(webinfFolder,"classes"); 24 | File libFolder = new File(webinfFolder,"lib"); 25 | URL url; 26 | url = new URL("file:"+classFoler.getAbsolutePath()+"/"); 27 | this.addURL(url); 28 | List jarFile = FileUtil.loopFiles(libFolder); 29 | for (File file:jarFile){ 30 | url = new URL("file:"+file.getAbsolutePath()); 31 | this.addURL(url); 32 | } 33 | }catch (Exception e){ 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | public void stop(){ 39 | try { 40 | close(); 41 | }catch (IOException e){ 42 | e.printStackTrace(); 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/HelloServlet.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletConfig; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.http.HttpServlet; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | import java.util.Enumeration; 11 | 12 | /** 13 | * @Description 14 | * @Date 2021/2/2 下午5:41 15 | * @Creater BeckoninGshy 16 | */ 17 | public class HelloServlet extends HttpServlet { 18 | 19 | @Override 20 | public void destroy() { 21 | System.out.println("hello servlet is destroyed"); 22 | } 23 | 24 | @Override 25 | public void init(ServletConfig sc) throws ServletException { 26 | System.out.println(sc.getServletName()); 27 | Enumeration initParameterNames = sc.getInitParameterNames(); 28 | while(initParameterNames.hasMoreElements()){ 29 | String s = initParameterNames.nextElement(); 30 | System.out.println("init name:" + s + ", value:" + sc.getInitParameter(s)); 31 | } 32 | } 33 | 34 | @Override 35 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 36 | // int i = 1/0; 37 | PrintWriter writer = resp.getWriter(); 38 | writer.println("this is HelloSerlvet response"); 39 | writer.close(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/com/yzb/servlets/DefaultServlet.java: -------------------------------------------------------------------------------- 1 | package com.yzb.servlets; 2 | 3 | import javax.servlet.ServletConfig; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.http.HttpServlet; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | import java.util.Enumeration; 11 | 12 | /** 13 | * @Description 14 | * @Date 2021/2/2 下午8:40 15 | * @Creater BeckoninGshy 16 | */ 17 | public class DefaultServlet extends HttpServlet { 18 | 19 | @Override 20 | public void init(ServletConfig sc) throws ServletException { 21 | System.out.println(sc.getServletName()); 22 | Enumeration initParameterNames = sc.getInitParameterNames(); 23 | while(initParameterNames.hasMoreElements()){ 24 | String s = initParameterNames.nextElement(); 25 | System.out.println("init name:" + s + ", value:" + sc.getInitParameter(s)); 26 | } 27 | } 28 | 29 | @Override 30 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 31 | PrintWriter writer = resp.getWriter(); 32 | writer.println("this is default page"); 33 | writer.close(); 34 | } 35 | 36 | @Override 37 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 38 | doGet(req, resp); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /conf/context.xml: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 21 | 22 | WEB-INF/web.xml 23 | 24 | 25 | 28 | 29 | 31 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | -------------------------------------------------------------------------------- /conf/server.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 20 | 23 | 24 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/com/yzb/http/ApplicationRequestDispatcher.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import javax.servlet.RequestDispatcher; 4 | import javax.servlet.ServletException; 5 | import javax.servlet.ServletRequest; 6 | import javax.servlet.ServletResponse; 7 | import java.io.IOException; 8 | 9 | /** 10 | * @Description Defines an object that receives requests from the client and sends them to any resource (relative ) 11 | * @Date 2021/2/6 下午7:57 12 | * @Creater BeckoninGshy 13 | */ 14 | public class ApplicationRequestDispatcher implements RequestDispatcher { 15 | 16 | private String uri; 17 | private ApplicationContext appContext = null; 18 | 19 | public ApplicationRequestDispatcher(String uri){ 20 | if(uri.startsWith("/")) 21 | this.uri = uri; 22 | else this.uri = "/" + uri; 23 | } 24 | 25 | public ApplicationRequestDispatcher(String uri, ApplicationContext appContext){ 26 | this(uri); 27 | this.appContext = appContext; 28 | } 29 | 30 | @Override 31 | public void forward(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { 32 | HttpRequest req= (HttpRequest) servletRequest; 33 | HttpResponse resp = (HttpResponse) servletResponse; 34 | HttpProcessor hp = new HttpProcessor(); 35 | if(appContext != null) req.setServletContext(appContext); 36 | req.setForwardURI(uri); 37 | req.setForwarding(); 38 | resp.resetBuffer(); 39 | hp.execute(req.getSocket(), req, resp); 40 | req.setForwarded(); 41 | } 42 | 43 | @Override 44 | public void include(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/com/yzb/ServerTest.java: -------------------------------------------------------------------------------- 1 | package com.yzb; 2 | 3 | import cn.hutool.core.util.RandomUtil; 4 | import com.yzb.common.Service; 5 | import com.yzb.core.StandardServer; 6 | import com.yzb.core.StandardService; 7 | import org.junit.*; 8 | 9 | /** 10 | * @Description 11 | * @Date 2021/1/29 下午1:37 12 | * @Creater BeckoninGshy 13 | */ 14 | public class ServerTest { 15 | public static StandardServer ss = null; 16 | public static Service[] services = new Service[0]; 17 | 18 | @BeforeClass 19 | public static void beforeClass(){ 20 | ss = StandardServer.getServerInstance(); 21 | services = new Service[100]; 22 | } 23 | 24 | 25 | 26 | @Test 27 | public void addServicesTest(){ 28 | for(int i = 0; i < 100; i++){ 29 | services[i] = new StandardService(); 30 | ss.addService(services[i]); 31 | } 32 | Assert.assertEquals(100,ss.findServices().length); 33 | } 34 | 35 | 36 | @Test 37 | public void findServicesTest(){ 38 | Assert.assertEquals(100,ss.findServices().length); 39 | } 40 | 41 | 42 | @Test 43 | public void removeServicesTest(){ 44 | Assert.assertEquals(100,ss.findServices().length); 45 | for(int i = 0; i < 100; i++){ 46 | ss.removeService(services[i]); 47 | } 48 | Assert.assertEquals(0,ss.findServices().length); 49 | } 50 | 51 | @Test 52 | public void removeServicesRandomTest(){ 53 | for(int i = 0; i < 100; i++){ 54 | services[i] = new StandardService(); 55 | ss.addService(services[i]); 56 | } 57 | int[] cnt = new int[100]; 58 | for(int i = 0; i < 100; i++){ 59 | int t = RandomUtil.randomInt(0,100); 60 | while(cnt[t] != 0) t = RandomUtil.randomInt(0,100); 61 | cnt[t] = 1; 62 | ss.removeService(services[t]); 63 | } 64 | Assert.assertEquals(0,ss.findServices().length); 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /webapps/test/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TestServlet 5 | com.yzb.servlets.TestServlet 6 | 7 | 8 | TestServlet2 9 | com.yzb.servlets.TestServlet2 10 | 11 | 12 | TestServlet3 13 | com.yzb.servlets.TestServlet3 14 | 15 | 16 | SessionServlet1 17 | com.yzb.servlets.SessionServlet1 18 | 19 | 20 | SessionServlet2 21 | com.yzb.servlets.SessionServlet2 22 | 23 | 24 | SessionServlet3 25 | com.yzb.servlets.SessionServlet3 26 | 27 | 28 | 29 | TestServlet 30 | /test1 31 | 32 | 33 | TestServlet2 34 | /test2 35 | 36 | 37 | TestServlet3 38 | /test3 39 | 40 | 41 | SessionServlet1 42 | /session1 43 | 44 | 45 | SessionServlet2 46 | /session2 47 | 48 | 49 | SessionServlet3 50 | /session3 51 | 52 | -------------------------------------------------------------------------------- /src/com/yzb/http/HttpProcessor.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | 4 | import cn.hutool.core.util.StrUtil; 5 | import cn.hutool.log.LogFactory; 6 | import com.yzb.classcloader.WebappClassLoader; 7 | import com.yzb.exception.URLMismatchedExpection; 8 | 9 | import javax.servlet.Servlet; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpSession; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | import java.net.Socket; 15 | 16 | /** 17 | * @Description processor one http request 18 | * @Date 2021/1/26 下午10:00 19 | * @Creater BeckoninGshy 20 | */ 21 | public class HttpProcessor { 22 | 23 | public void execute(Socket socket, HttpRequest request, HttpResponse response){ 24 | try{ 25 | LogFactory.get().info("receiving from {}, request: {}", request.getRemoteAddr(), request.getRequestURI()); 26 | 27 | // prepare session for every connector 28 | prepareSession(request, response); 29 | ApplicationContext appContext = (ApplicationContext) request.getServletContext(); 30 | new Dispatcher().dispatch(request.getRequestURI(), appContext, request, response); 31 | 32 | } catch (URLMismatchedExpection e) { 33 | //404 34 | handle404(request.getRequestURI(), response, e.getMessage()); 35 | } catch (Exception e) { 36 | //500 37 | handle500(request.getRequestURI(), response, e.getMessage()); 38 | } finally { 39 | try { 40 | if(!socket.isClosed()) 41 | socket.close(); 42 | } catch (IOException e) { 43 | } 44 | } 45 | } 46 | 47 | private void prepareSession(HttpRequest request, HttpResponse response){ 48 | String jSessionId = request.getJSessionIdFromCookie(); 49 | HttpSession session = SessionManager.getSession(jSessionId, request, response); 50 | request.setSession(session); 51 | } 52 | 53 | private void handle404(String url, HttpResponse resp, String message) { 54 | LogFactory.get().info("{} is 404", url); 55 | try { 56 | resp.sendError(404, StrUtil.format(HttpContant.textFormat_404, url, message)); 57 | } catch (IOException e) { 58 | } 59 | } 60 | 61 | private void handle500(String url, HttpResponse resp, String message) { 62 | LogFactory.get().info("{} is 500", url); 63 | try { 64 | resp.sendError(500, StrUtil.format(HttpContant.textFormat_500, url, message)); 65 | } catch (IOException e) { 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/com/yzb/http/StandardSession.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import javax.servlet.ServletContext; 4 | import javax.servlet.http.HttpSession; 5 | import javax.servlet.http.HttpSessionContext; 6 | import java.util.Collections; 7 | import java.util.Enumeration; 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * @Description class Session 13 | * @Date 2021/2/10 下午4:26 14 | * @Creater BeckoninGshy 15 | */ 16 | public class StandardSession implements HttpSession { 17 | 18 | private ServletContext servletContext; 19 | private Map attritubes; 20 | private String ID; 21 | private long creationTime; 22 | private long lastAccessedTime; 23 | private int maxInactiveInterval; 24 | 25 | 26 | public StandardSession(String sessionId, ServletContext servletContext){ 27 | this.ID = sessionId; 28 | this.servletContext = servletContext; 29 | this.creationTime = System.currentTimeMillis(); 30 | this.lastAccessedTime = creationTime; 31 | this.attritubes = new ConcurrentHashMap<>(); 32 | } 33 | 34 | 35 | @Override 36 | public long getCreationTime() { 37 | return creationTime; 38 | } 39 | 40 | @Override 41 | public String getId() { 42 | return ID; 43 | } 44 | 45 | @Override 46 | public long getLastAccessedTime() { 47 | return lastAccessedTime; 48 | } 49 | 50 | public void setLastAccessedTime(long lastAccessedTime){ 51 | this.lastAccessedTime = lastAccessedTime; 52 | } 53 | 54 | @Override 55 | public ServletContext getServletContext() { 56 | return servletContext; 57 | } 58 | 59 | @Override 60 | public void setMaxInactiveInterval(int i) { 61 | this.maxInactiveInterval = i; 62 | } 63 | 64 | @Override 65 | public int getMaxInactiveInterval() { 66 | return maxInactiveInterval; 67 | } 68 | 69 | @Override 70 | public HttpSessionContext getSessionContext() { 71 | return null; 72 | } 73 | 74 | 75 | @Override 76 | public Object getAttribute(String s) { 77 | return attritubes.get(s); 78 | } 79 | 80 | @Override 81 | public Object getValue(String s) { 82 | return null; 83 | } 84 | 85 | @Override 86 | public Enumeration getAttributeNames() { 87 | return Collections.enumeration(attritubes.keySet()); 88 | } 89 | 90 | @Override 91 | public String[] getValueNames() { 92 | return new String[0]; 93 | } 94 | 95 | @Override 96 | public void setAttribute(String s, Object o) { 97 | attritubes.put(s,o); 98 | } 99 | 100 | @Override 101 | public void putValue(String s, Object o) { 102 | 103 | } 104 | 105 | @Override 106 | public void removeAttribute(String s) { 107 | attritubes.remove(s); 108 | } 109 | 110 | @Override 111 | public void removeValue(String s) { 112 | 113 | } 114 | 115 | @Override 116 | public void invalidate() { 117 | attritubes.clear(); 118 | long now = System.currentTimeMillis(); 119 | setMaxInactiveInterval(0); 120 | } 121 | 122 | @Override 123 | public boolean isNew() { 124 | return creationTime == lastAccessedTime; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/com/yzb/core/Engine.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import com.yzb.common.Container; 4 | import com.yzb.common.ServerContext; 5 | import com.yzb.exception.LifecycleException; 6 | import com.yzb.http.ApplicationContext; 7 | 8 | import java.io.File; 9 | import java.io.FileNotFoundException; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * @Description 15 | * @Date 2021/1/30 下午9:34 16 | * @Creater BeckoninGshy 17 | */ 18 | public class Engine extends StandardContainer { 19 | private String defaultHostName; 20 | 21 | public String getDefaultHostName() { 22 | return defaultHostName; 23 | } 24 | 25 | public Host getDefaultHost(){ 26 | if(defaultHostName == null) return null; 27 | for(Container child : children){ 28 | if(child instanceof Host && defaultHostName.equals(child.getName())){ 29 | return (Host)child; 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | public void setDefaultHostName(String defaultHostName) { 36 | this.defaultHostName = defaultHostName; 37 | 38 | } 39 | 40 | @Override 41 | public void init() throws LifecycleException { 42 | ApplicationContext defaultContext = null; 43 | try { 44 | defaultContext = loadDefaultContext(); 45 | } catch (FileNotFoundException e) { 46 | e.printStackTrace(); 47 | stop(); 48 | } 49 | loadUserContext(defaultContext); 50 | super.init(); 51 | } 52 | 53 | private ApplicationContext loadDefaultContext() throws FileNotFoundException { 54 | String contextsDirectory = ServerContext.webXMLPath; 55 | File contextFile = new File(contextsDirectory); 56 | if(!contextFile.exists()) 57 | throw new FileNotFoundException("There is no web.xml in conf dir."); 58 | 59 | ApplicationContext defaultContext = new ApplicationContext(ServerContext.servletLoadClassDir,true); 60 | defaultContext.setPath(""); 61 | defaultContext.setParent(getDefaultHost()); 62 | getDefaultHost().addChild(defaultContext); 63 | defaultContext.setService(getService()); 64 | return defaultContext; 65 | } 66 | 67 | 68 | private void loadUserContext(ApplicationContext defaultContext){ 69 | String contextsDirectory = getDefaultHost().getAppBase(); 70 | File contexts = new File(contextsDirectory); 71 | if(!contexts.exists() || !contexts.isDirectory()) return; 72 | File[] files = contexts.listFiles(); 73 | List contextList = new ArrayList<>(); 74 | for(File file : files){ 75 | if(file.isDirectory()){ 76 | ApplicationContext t = new ApplicationContext(file.getAbsolutePath(),false); 77 | if("ROOT".equals(file.getName())){ 78 | t.setPath("/"); 79 | }else{ 80 | t.setPath("/" + file.getName()); 81 | } 82 | t.setName(file.getName()); 83 | contextList.add(t); 84 | } 85 | } 86 | for(ApplicationContext servletContext : contextList){ 87 | servletContext.setDefaultContext(defaultContext); 88 | servletContext.setParent(getDefaultHost()); 89 | getDefaultHost().addChild(servletContext); 90 | servletContext.setService(getService()); 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/com/yzb/http/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.yzb.classcloader.WebappClassLoader; 5 | import com.yzb.exception.URLMismatchedExpection; 6 | 7 | import javax.servlet.Servlet; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.ServletOutputStream; 10 | import java.io.*; 11 | 12 | /** 13 | * @Description Parse the url and pass it to the appropriate component, such as a servlet or file 14 | * @Date 2021/2/2 下午5:15 15 | * @Creater BeckoninGshy 16 | */ 17 | public class Dispatcher { 18 | 19 | 20 | public void dispatch(String url, ApplicationContext appContext, HttpRequest req, HttpResponse resp) throws ClassNotFoundException, IOException, ServletException, IllegalAccessException, InstantiationException, URLMismatchedExpection { 21 | // Reduce context path 22 | if(!appContext.getPath().equals("/")) 23 | url = url.substring(appContext.getPath().length()); 24 | 25 | // is app root url, match welcome-file-list config; 26 | if(url.length() == 0 || url.equals("/")){ // like "/test" or "/test/" --> "" test is an app dir 27 | InputStream is = appContext.getWelcomFile(); 28 | if(is != null){ 29 | writeFileToResponse(is, resp); 30 | return; 31 | } 32 | } 33 | 34 | // is servlet url 35 | if(url.startsWith("/")){ 36 | // app context --> default context 37 | ApplicationContext curContext = appContext; 38 | if(appContext.getServletURLToClass(url) == null) { 39 | curContext = curContext.getDefaultContext(); 40 | } 41 | 42 | if(curContext.getServletURLToClass(url) != null) { 43 | WebappClassLoader webappClassLoader = curContext.getWebappClassLoader(); 44 | Class clazz = webappClassLoader.loadClass(curContext.getServletURLToClass(url)); 45 | //get instance servlet from context 46 | Servlet servlet = curContext.getServlet(clazz); 47 | //execute service method (auto call method like doGet,doPost etc)process request 48 | servlet.service(req,resp); 49 | return; 50 | } 51 | } 52 | // is mime type? 53 | if (StrUtil.contains(url, '.')){ 54 | String ext = StrUtil.subAfter(url, '.', false); 55 | String type = appContext.getMimeType(ext); 56 | InputStream is = appContext.getResourceAsStream(url); 57 | if(type != null && is != null) { 58 | //get resource succeeded and server can handle this mimetype 59 | resp.setContentType(type); 60 | //default charset: utf-8 61 | if(type.startsWith("text")) { 62 | resp.setCharacterEncoding("utf-8"); 63 | } 64 | writeFileToResponse(is, resp); 65 | return; 66 | } 67 | } 68 | // is mismatched. 69 | throw new URLMismatchedExpection(url); 70 | } 71 | 72 | 73 | private void writeFileToResponse(InputStream is, HttpResponse resp) throws IOException { 74 | ServletOutputStream outputStream = resp.getOutputStream(); 75 | byte[] buffer = new byte[1024]; 76 | int len = 0; 77 | while((len = is.read(buffer,0,buffer.length)) != -1){ 78 | outputStream.write(buffer,0,len); 79 | } 80 | outputStream.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/com/yzb/core/StandardContainer.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import com.yzb.common.Container; 4 | import com.yzb.common.Service; 5 | import com.yzb.exception.LifecycleException; 6 | 7 | import java.io.FileNotFoundException; 8 | 9 | /** 10 | * @Description 11 | * @Date 2021/1/29 下午1:17 12 | * @Creater BeckoninGshy 13 | */ 14 | public class StandardContainer implements Container { 15 | protected String name; 16 | protected Service service; 17 | protected Container parent; 18 | private ClassLoader parentClassLoader; 19 | protected Container[] children = new Container[0]; 20 | 21 | @Override 22 | public String getName() { 23 | return name; 24 | } 25 | 26 | @Override 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | @Override 32 | public Service getService() { 33 | return service; 34 | } 35 | 36 | @Override 37 | public void setService(Service service) { 38 | this.service = service; 39 | } 40 | 41 | @Override 42 | public Container getParent() { 43 | return parent; 44 | } 45 | 46 | @Override 47 | public void setParent(Container parent) { 48 | this.parent = parent; 49 | } 50 | 51 | @Override 52 | public ClassLoader getParentClassLoader() { 53 | return parentClassLoader; 54 | } 55 | 56 | @Override 57 | public void setParentClassLoader(ClassLoader classLoader) { 58 | this.parentClassLoader = classLoader; 59 | } 60 | 61 | @Override 62 | public void addChild(Container container) { 63 | int len = children.length; 64 | Container[] newChildren = new Container[len+1]; 65 | System.arraycopy(children,0,newChildren,0,len); 66 | newChildren[len] = container; 67 | children = newChildren; 68 | } 69 | 70 | @Override 71 | public Container findChild(String container) { 72 | for (Container value : children) { 73 | if (value.getName().equals(name)) return value; 74 | } 75 | return null; 76 | } 77 | 78 | @Override 79 | public Container[] findChildren() { 80 | return children; 81 | } 82 | 83 | @Override 84 | public void removeChild(Container container) { 85 | int idx = 0, len = children.length; 86 | while(idx < len && children[idx] != container) idx++; 87 | if(idx == len) return; 88 | try { 89 | children[idx].stop(); 90 | } catch (LifecycleException e) { 91 | //noting to do. 92 | } 93 | Container[] newContainers = new Container[len-1]; 94 | System.arraycopy(children,0,newContainers,0,idx); 95 | System.arraycopy(children,idx+1,newContainers,idx,len-idx-1); 96 | children = newContainers; 97 | } 98 | 99 | @Override 100 | public void init() throws LifecycleException { 101 | for(Container child : children){ 102 | child.init(); 103 | } 104 | } 105 | 106 | @Override 107 | public void start() throws LifecycleException { 108 | for(Container child : children){ 109 | child.start(); 110 | } 111 | } 112 | 113 | @Override 114 | public void stop() throws LifecycleException { 115 | for(Container child : children){ 116 | child.stop(); 117 | } 118 | } 119 | 120 | @Override 121 | public void destroy() throws LifecycleException { 122 | for(Container child : children){ 123 | child.destroy(); 124 | } 125 | } 126 | 127 | @Override 128 | public String getState() { 129 | return null; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/com/yzb/core/StandardConnector.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import com.yzb.common.*; 4 | import com.yzb.exception.LifecycleException; 5 | 6 | import java.net.ServerSocket; 7 | import java.net.Socket; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Description 12 | * @Date 2021/1/29 下午1:17 13 | * @Creater BeckoninGshy 14 | */ 15 | public class StandardConnector implements Connector { 16 | 17 | protected String name; 18 | protected Map properties; 19 | protected Service service; 20 | protected int port = -1; 21 | protected String protocol; 22 | private String scheme; 23 | protected long connectionTimeout = 0; 24 | private String uriEncoding; 25 | private boolean useBodyEncodingForURI; 26 | protected static ServerSocket serverSocket; 27 | 28 | @Override 29 | public String getName() { 30 | return name; 31 | } 32 | 33 | @Override 34 | public void setName(String name) { 35 | this.name = name; 36 | } 37 | 38 | @Override 39 | public String getProperty(String property) { 40 | return properties.get(property); 41 | } 42 | 43 | @Override 44 | public boolean setProperty(String key, String value) { 45 | properties.put(key,value); 46 | return true; 47 | } 48 | 49 | @Override 50 | public Service getService() { 51 | return service; 52 | } 53 | 54 | @Override 55 | public void setService(Service service) { 56 | this.service = service; 57 | } 58 | 59 | @Override 60 | public int getPort() { 61 | return port; 62 | } 63 | 64 | @Override 65 | public void setPort(int port) { 66 | this.port = port; 67 | } 68 | 69 | @Override 70 | public String getProtocol() { 71 | return protocol; 72 | } 73 | 74 | @Override 75 | public void setProtocol(String protocol) { 76 | this.protocol = protocol; 77 | } 78 | 79 | @Override 80 | public void setConnectionTimeout(long timeout) { 81 | this.connectionTimeout = timeout; 82 | } 83 | 84 | @Override 85 | public long getConnectionTimeout() { 86 | return connectionTimeout; 87 | } 88 | 89 | @Override 90 | public String getScheme() { 91 | return scheme; 92 | } 93 | 94 | @Override 95 | public void setScheme(String scheme) { 96 | this.scheme = scheme; 97 | } 98 | 99 | @Override 100 | public String getURIEncoding() { 101 | return uriEncoding; 102 | } 103 | 104 | @Override 105 | public void setURIEncoding(String uriEncoding) { 106 | this.uriEncoding = uriEncoding; 107 | } 108 | 109 | @Override 110 | public boolean getUseBodyEncodingForURI() { 111 | return useBodyEncodingForURI; 112 | } 113 | 114 | @Override 115 | public void setUseBodyEncodingForURI(boolean isUse) { 116 | this.useBodyEncodingForURI = isUse; 117 | } 118 | 119 | @Override 120 | public Request createRequest(Socket socket, Connector connector) { 121 | return null; 122 | } 123 | 124 | @Override 125 | public Response createResponse(Socket socket) { 126 | return null; 127 | } 128 | 129 | @Override 130 | public Server getServer() { 131 | return service.getServer(); 132 | } 133 | 134 | @Override 135 | public void init() throws LifecycleException { 136 | 137 | 138 | } 139 | 140 | @Override 141 | public void start() throws LifecycleException { 142 | 143 | } 144 | 145 | @Override 146 | public void stop() throws LifecycleException { 147 | 148 | } 149 | 150 | @Override 151 | public void destroy() throws LifecycleException { 152 | 153 | } 154 | 155 | @Override 156 | public String getState() { 157 | return null; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /test/com/yzb/Client.java: -------------------------------------------------------------------------------- 1 | package com.yzb; 2 | 3 | import cn.hutool.core.lang.Console; 4 | import cn.hutool.http.Header; 5 | import cn.hutool.http.HttpRequest; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.net.InetSocketAddress; 14 | import java.net.Socket; 15 | import java.nio.charset.StandardCharsets; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public class Client { 20 | Client client; 21 | @Before 22 | public void before(){ 23 | client = new Client(); 24 | } 25 | 26 | @Test 27 | public void RequestByUtil(){ 28 | String body = HttpRequest.get("127.0.0.1:9090") 29 | .header(Header.USER_AGENT, "yzb's client") 30 | .header(Header.REFERER, "http://www.baidu.com") 31 | .body("username=张三") 32 | .body("password=123") 33 | .execute().body(); 34 | Console.log(body); 35 | } 36 | 37 | @Test 38 | public void RequestPostByUtil(){ 39 | Map formData = new HashMap<>(); 40 | formData.put("username","zhangsan"); 41 | formData.put("password","password"); 42 | String body = HttpRequest.post("127.0.0.1:9090") 43 | .header(Header.USER_AGENT, "yzb's client") 44 | .form(formData)//表单内容 45 | .timeout(20000)//超时,毫秒 46 | .execute().body(); 47 | Console.log(body); 48 | } 49 | 50 | @Test 51 | public void RequestBySocket() throws IOException { 52 | InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 9090); 53 | Socket socket = new Socket(); 54 | socket.connect(serverAddress); 55 | OutputStream outputStream = socket.getOutputStream(); 56 | String sendMessage = "hello Server!"; 57 | outputStream.write(sendMessage.getBytes(StandardCharsets.UTF_8)); 58 | 59 | InputStream inputStream = socket.getInputStream(); 60 | int bufferSize = 1024; 61 | byte[] buffer = new byte[1024]; 62 | ByteArrayOutputStream baso = new ByteArrayOutputStream(); 63 | while(true){ 64 | int len = inputStream.read(buffer); 65 | if(len == -1) break; 66 | baso.write(buffer, 0 , len); 67 | if(len != bufferSize){ 68 | break; 69 | } 70 | } 71 | System.out.println(baso.toString()); 72 | baso.close(); 73 | socket.close(); 74 | } 75 | 76 | @Test 77 | public void RequestPostBySocket() throws IOException { 78 | InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 9090); 79 | Socket socket = new Socket(); 80 | socket.connect(serverAddress); 81 | OutputStream outputStream = socket.getOutputStream(); 82 | String sendMessage = "POST / HTTP/1.1\r\n" 83 | + "User-Agent: yzb's client\r\n" 84 | + "Content-Length: 26\r\n" 85 | + "\r\n" 86 | + "username=张三&password=123"; 87 | outputStream.write(sendMessage.getBytes(StandardCharsets.UTF_8)); 88 | 89 | InputStream inputStream = socket.getInputStream(); 90 | int bufferSize = 1024; 91 | byte[] buffer = new byte[1024]; 92 | ByteArrayOutputStream baso = new ByteArrayOutputStream(); 93 | while(true){ 94 | int len = inputStream.read(buffer); 95 | if(len == -1) break; 96 | baso.write(buffer, 0 , len); 97 | if(len != bufferSize){ 98 | break; 99 | } 100 | } 101 | System.out.println(baso.toString()); 102 | baso.close(); 103 | socket.close(); 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/com/yzb/common/Response.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | import javax.servlet.ServletOutputStream; 4 | import javax.servlet.http.Cookie; 5 | import javax.servlet.http.HttpServletResponse; 6 | import java.io.IOException; 7 | import java.io.PrintWriter; 8 | import java.util.Collection; 9 | import java.util.Locale; 10 | 11 | public class Response implements HttpServletResponse { 12 | @Override 13 | public void addCookie(Cookie cookie) { 14 | 15 | } 16 | 17 | @Override 18 | public boolean containsHeader(String s) { 19 | return false; 20 | } 21 | 22 | @Override 23 | public String encodeURL(String s) { 24 | return null; 25 | } 26 | 27 | @Override 28 | public String encodeRedirectURL(String s) { 29 | return null; 30 | } 31 | 32 | @Override 33 | public String encodeUrl(String s) { 34 | return null; 35 | } 36 | 37 | @Override 38 | public String encodeRedirectUrl(String s) { 39 | return null; 40 | } 41 | 42 | @Override 43 | public void sendError(int i, String s) throws IOException { 44 | 45 | } 46 | 47 | @Override 48 | public void sendError(int i) throws IOException { 49 | 50 | } 51 | 52 | @Override 53 | public void sendRedirect(String s) throws IOException { 54 | 55 | } 56 | 57 | @Override 58 | public void setDateHeader(String s, long l) { 59 | 60 | } 61 | 62 | @Override 63 | public void addDateHeader(String s, long l) { 64 | 65 | } 66 | 67 | @Override 68 | public void setHeader(String s, String s1) { 69 | 70 | } 71 | 72 | @Override 73 | public void addHeader(String s, String s1) { 74 | 75 | } 76 | 77 | @Override 78 | public void setIntHeader(String s, int i) { 79 | 80 | } 81 | 82 | @Override 83 | public void addIntHeader(String s, int i) { 84 | 85 | } 86 | 87 | @Override 88 | public void setStatus(int i) { 89 | 90 | } 91 | 92 | @Override 93 | public void setStatus(int i, String s) { 94 | 95 | } 96 | 97 | @Override 98 | public int getStatus() { 99 | return 0; 100 | } 101 | 102 | @Override 103 | public String getHeader(String s) { 104 | return null; 105 | } 106 | 107 | @Override 108 | public Collection getHeaders(String s) { 109 | return null; 110 | } 111 | 112 | @Override 113 | public Collection getHeaderNames() { 114 | return null; 115 | } 116 | 117 | @Override 118 | public String getCharacterEncoding() { 119 | return null; 120 | } 121 | 122 | @Override 123 | public String getContentType() { 124 | return null; 125 | } 126 | 127 | @Override 128 | public ServletOutputStream getOutputStream() throws IOException { 129 | return null; 130 | } 131 | 132 | @Override 133 | public PrintWriter getWriter() throws IOException { 134 | return null; 135 | } 136 | 137 | @Override 138 | public void setCharacterEncoding(String s) { 139 | 140 | } 141 | 142 | @Override 143 | public void setContentLength(int i) { 144 | 145 | } 146 | 147 | @Override 148 | public void setContentType(String s) { 149 | 150 | } 151 | 152 | @Override 153 | public void setBufferSize(int i) { 154 | 155 | } 156 | 157 | @Override 158 | public int getBufferSize() { 159 | return 0; 160 | } 161 | 162 | @Override 163 | public void flushBuffer() throws IOException { 164 | 165 | } 166 | 167 | @Override 168 | public void resetBuffer() { 169 | 170 | } 171 | 172 | @Override 173 | public boolean isCommitted() { 174 | return false; 175 | } 176 | 177 | @Override 178 | public void reset() { 179 | 180 | } 181 | 182 | @Override 183 | public void setLocale(Locale locale) { 184 | 185 | } 186 | 187 | @Override 188 | public Locale getLocale() { 189 | return null; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/com/yzb/core/StandardService.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import cn.hutool.log.LogFactory; 4 | import com.yzb.common.Connector; 5 | import com.yzb.common.Container; 6 | import com.yzb.common.Server; 7 | import com.yzb.common.Service; 8 | import com.yzb.exception.LifecycleException; 9 | 10 | /** 11 | * @Description 12 | * @Date 2021/1/29 下午1:15 13 | * @Creater BeckoninGshy 14 | */ 15 | public class StandardService implements Service { 16 | 17 | private Container container; 18 | private String name = "StandardService"; 19 | private Server server; 20 | private Connector[] connectors = new Connector[0]; 21 | private final Object connectorsLock = new Object(); 22 | private ClassLoader parentClassLoader; 23 | 24 | @Override 25 | public void init() throws LifecycleException { 26 | container.init(); 27 | synchronized (connectorsLock) { 28 | for(Connector connector : connectors){ 29 | connector.init(); 30 | } 31 | } 32 | } 33 | 34 | @Override 35 | public void start() throws LifecycleException { 36 | container.start(); 37 | 38 | synchronized (connectorsLock) { 39 | for(Connector connector : connectors){ 40 | connector.start(); 41 | } 42 | } 43 | 44 | LogFactory.get().info("stared service {}", getName()); 45 | } 46 | 47 | @Override 48 | public void stop() throws LifecycleException { 49 | for(Connector connector : connectors){ 50 | connector.stop(); 51 | } 52 | container.stop(); 53 | LogFactory.get().info("stopped service {}", getName()); 54 | } 55 | 56 | @Override 57 | public void destroy() throws LifecycleException { 58 | for(Connector connector : connectors){ 59 | connector.destroy(); 60 | } 61 | container.destroy(); 62 | LogFactory.get().info("destroyed service {}", getName()); 63 | } 64 | 65 | @Override 66 | public String getState() { 67 | return null; 68 | } 69 | 70 | @Override 71 | public Container getContainer() { 72 | return container; 73 | } 74 | 75 | @Override 76 | public void setContainer(Container container) { 77 | this.container = container; 78 | } 79 | 80 | @Override 81 | public String getInfo() { 82 | return null; 83 | } 84 | 85 | @Override 86 | public String getName() { 87 | return name; 88 | } 89 | 90 | @Override 91 | public void setName(String name) { 92 | this.name = name; 93 | } 94 | 95 | @Override 96 | public Server getServer() { 97 | return server; 98 | } 99 | 100 | @Override 101 | public void setServer(Server server) { 102 | this.server = server; 103 | } 104 | 105 | @Override 106 | public void addConnector(Connector connector) { 107 | synchronized (connectorsLock) { 108 | int len = connectors.length; 109 | Connector[] newConnectors = new Connector[len+1]; 110 | System.arraycopy(connectors,0,newConnectors,0,len); 111 | newConnectors[len] = connector; 112 | connectors = newConnectors; 113 | } 114 | } 115 | 116 | @Override 117 | public Connector findConnector(String name) { 118 | int len = connectors.length; 119 | for (Connector value : connectors) { 120 | if (value.getName().equals(name)) return value; 121 | } 122 | return null; 123 | } 124 | 125 | @Override 126 | public String[] getConnectorNames() { 127 | int len = connectors.length; 128 | String[] names = new String[len]; 129 | for(int i = 0; i < len; i++){ 130 | names[i] = connectors[i].getName(); 131 | } 132 | return names; 133 | } 134 | 135 | @Override 136 | public Connector[] findConnectors() { 137 | return connectors; 138 | } 139 | 140 | @Override 141 | public void removeConnector(Connector connector) { 142 | synchronized (connectorsLock) { 143 | 144 | int idx = 0, len = connectors.length; 145 | while(idx < len && connectors[idx] != connector) idx++; 146 | if(idx == len) return; 147 | try { 148 | connectors[idx].stop(); 149 | } catch (LifecycleException e) { 150 | //noting to do. 151 | } 152 | 153 | Connector[] newConnectors = new Connector[len-1]; 154 | System.arraycopy(connectors,0,newConnectors,0,idx); 155 | System.arraycopy(connectors,idx+1,newConnectors,idx,len-idx-1); 156 | connectors = newConnectors; 157 | } 158 | } 159 | 160 | @Override 161 | public ClassLoader getParentClassLoader() { 162 | return parentClassLoader; 163 | } 164 | 165 | @Override 166 | public void setParentClassLoader(ClassLoader classLoader) { 167 | this.parentClassLoader = classLoader; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/com/yzb/http/SessionManager.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import cn.hutool.core.convert.Convert; 4 | import cn.hutool.core.io.FileUtil; 5 | import cn.hutool.core.thread.ThreadUtil; 6 | import cn.hutool.core.util.RandomUtil; 7 | import cn.hutool.crypto.SecureUtil; 8 | import com.yzb.common.Request; 9 | import com.yzb.common.Response; 10 | import com.yzb.common.ServerContext; 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.nodes.Document; 13 | import org.jsoup.select.Elements; 14 | 15 | import javax.print.Doc; 16 | import javax.servlet.ServletContext; 17 | import javax.servlet.http.Cookie; 18 | import javax.servlet.http.HttpSession; 19 | import java.io.File; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Set; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | 26 | /** 27 | * @Description 28 | * @Date 2021/2/10 下午4:40 29 | * @Creater BeckoninGshy 30 | */ 31 | public class SessionManager { 32 | private static Map sessionMap = new ConcurrentHashMap<>(); 33 | 34 | private static int defaultTimeout = getTimeout(); 35 | private static Thread checkoutThread; 36 | 37 | static{ 38 | startCheckOutSessionOutDateThread(); 39 | } 40 | 41 | //启动线程,每隔5s调用一次checkOutDateSession方法 42 | private static void startCheckOutSessionOutDateThread() { 43 | checkoutThread = new Thread(){ 44 | public void run(){ 45 | while(true){ 46 | checkOutDateSession(); 47 | ThreadUtil.sleep(1000*5); 48 | } 49 | } 50 | }; 51 | checkoutThread.start(); 52 | } 53 | 54 | private static void checkOutDateSession() { 55 | Set sessionId = sessionMap.keySet(); 56 | List outdateJessionIds = new ArrayList<>(); 57 | for(String id : sessionId){ 58 | StandardSession standardSession = sessionMap.get(id); 59 | long interval = System.currentTimeMillis() - standardSession.getLastAccessedTime(); 60 | if(standardSession.getMaxInactiveInterval() == -1) continue; 61 | //getMaxInactiveInterval --> s 62 | //interval --> ms 63 | if(interval > standardSession.getMaxInactiveInterval() * 1000L){ 64 | outdateJessionIds.add(id); 65 | } 66 | } 67 | for(String id : outdateJessionIds){ 68 | sessionMap.remove(id); 69 | } 70 | } 71 | 72 | 73 | private static int getTimeout(){ 74 | int defautlTime = 30*60; 75 | Document document = Jsoup.parse(FileUtil.readUtf8String(ServerContext.webXMLPath)); 76 | Elements select = document.select("session-config session-timeout"); 77 | if(select.isEmpty()) return defautlTime; 78 | return Convert.toInt(select.get(0).text())*60; 79 | } 80 | 81 | 82 | public static void destroy(){ 83 | checkoutThread.interrupt(); 84 | try { 85 | checkoutThread.join(); 86 | } catch (InterruptedException e) { 87 | } 88 | } 89 | 90 | // 如果浏览器没有传jsessionid过来,就创建一个新的session 91 | // 如果浏览器传递过来的jsessionid无效,那么也创建一个新的sessionid 92 | // 否则就使用现成的session,并修改他的lastAccessedTime,以及创建对应的cookie 93 | public static HttpSession getSession(String jsessionid, Request request, Response response) { 94 | if (null == jsessionid) { 95 | return newSession(request, response); 96 | } else { 97 | StandardSession currentSession = sessionMap.get(jsessionid); 98 | if (null == currentSession) { 99 | return newSession(request, response); 100 | } else { 101 | currentSession.setLastAccessedTime(System.currentTimeMillis()); 102 | // createCookieBySession(currentSession, request, response); 103 | return currentSession; 104 | } 105 | } 106 | } 107 | 108 | 109 | private static void createCookieBySession(HttpSession session, Request request, Response response) { 110 | Cookie cookie = new Cookie("JSESSIONID", session.getId()); 111 | cookie.setMaxAge(session.getMaxInactiveInterval()); 112 | cookie.setPath(request.getServletContext().getContextPath()); 113 | response.addCookie(cookie); 114 | } 115 | 116 | 117 | public static StandardSession newSession(Request request, Response response){ 118 | ServletContext servletContext = request.getServletContext(); 119 | String sessionId = generateSessionId(); 120 | StandardSession standardSession = new StandardSession(sessionId, servletContext); 121 | standardSession.setMaxInactiveInterval(defaultTimeout); 122 | sessionMap.put(sessionId,standardSession); 123 | //create cookie 124 | createCookieBySession(standardSession,request,response); 125 | return standardSession; 126 | } 127 | 128 | 129 | public static synchronized String generateSessionId() { 130 | String result = null; 131 | byte[] bytes = RandomUtil.randomBytes(16); 132 | result = new String(bytes); 133 | result = SecureUtil.md5(result); 134 | result = result.toUpperCase(); 135 | return result; 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /src/com/yzb/http/HttpConnector.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import cn.hutool.log.LogFactory; 4 | import com.yzb.common.*; 5 | import com.yzb.core.Engine; 6 | import com.yzb.core.StandardConnector; 7 | import com.yzb.exception.LifecycleException; 8 | import com.yzb.exception.ParseHttpRequestException; 9 | 10 | import java.io.IOException; 11 | import java.net.ServerSocket; 12 | import java.net.Socket; 13 | 14 | /** 15 | * @Description 16 | * @Date 2021/1/29 下午10:13 17 | * @Creater BeckoninGshy 18 | */ 19 | public class HttpConnector extends StandardConnector implements Runnable { 20 | 21 | private Thread workThread; 22 | 23 | 24 | 25 | 26 | @Override 27 | public void init() throws LifecycleException { 28 | super.init(); 29 | try { 30 | serverSocket = new ServerSocket(port); 31 | } catch (IOException e) { 32 | LogFactory.get().error("init Connector{} failed, port {} is used", getName(), getPort()); 33 | throw new LifecycleException(); 34 | } 35 | } 36 | 37 | @Override 38 | public void start() throws LifecycleException { 39 | super.start(); 40 | // start a thread to handle connecting 41 | workThread = new Thread(this); 42 | workThread.start(); 43 | } 44 | 45 | @Override 46 | public void stop() throws LifecycleException { 47 | super.stop(); 48 | workThread.interrupt(); 49 | try{ 50 | workThread.join(1000); 51 | } catch (InterruptedException e) { 52 | } 53 | } 54 | 55 | @Override 56 | public void destroy() throws LifecycleException { 57 | super.destroy(); 58 | CommonThreadPool.shutdown(); 59 | } 60 | 61 | @Override 62 | public HttpRequest createRequest(Socket socket, Connector connector) { 63 | try { 64 | return new HttpRequest(socket, connector); 65 | } catch (ParseHttpRequestException e) { 66 | //bad request 67 | LogFactory.get().warn("create request failed! maybe is a bad http request"); 68 | e.printStackTrace(); 69 | } 70 | return null; 71 | } 72 | 73 | @Override 74 | public HttpResponse createResponse(Socket socket) { 75 | return new HttpResponse(socket); 76 | } 77 | 78 | @Override 79 | public void run() { 80 | while(true){ 81 | Socket socket = null; 82 | try { 83 | socket = serverSocket.accept(); 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | LogFactory.get().warn("accept failed!"); 87 | break; 88 | } 89 | 90 | Socket finalSocket = socket; 91 | HttpConnector cn = new HttpConnector(); 92 | Runnable runnable = () -> { 93 | HttpRequest request = createRequest(finalSocket, this); 94 | HttpResponse response = createResponse(finalSocket); 95 | 96 | HttpProcessor httpProcessor = new HttpProcessor(); 97 | httpProcessor.execute(finalSocket, request, response); 98 | }; 99 | //add connector to thread pool 100 | CommonThreadPool.run(runnable); 101 | } 102 | } 103 | 104 | 105 | // find the ServletContext that matched URI, if all ServletContext mismatched URI, default use "/" context 106 | public ApplicationContext getServletContext(String URI) { 107 | if(URI == null) return null; 108 | Service service = getService(); 109 | Container container = service.getContainer(); 110 | Container[] servletContexts = null; 111 | Container rootContext = null; 112 | if(container instanceof Engine){ 113 | servletContexts = ((Engine) container).getDefaultHost().findChildren(); 114 | } 115 | assert servletContexts != null; 116 | for(Container servletContext : servletContexts){ 117 | if(! (servletContext instanceof ApplicationContext)) continue; 118 | if(((ApplicationContext) servletContext).isDefaultContext()) continue; 119 | if(((ApplicationContext) servletContext).getPath().equals("/")) rootContext = servletContext; 120 | else if(URI.startsWith(((ApplicationContext) servletContext).getPath())) return (ApplicationContext) servletContext; 121 | } 122 | return (ApplicationContext) rootContext; 123 | } 124 | 125 | public ApplicationContext getDefaultServletContext() { 126 | Service service = getService(); 127 | Container container = service.getContainer(); 128 | Container[] servletContexts = null; 129 | Container defaultContext = null; 130 | if(container instanceof Engine){ 131 | servletContexts = ((Engine) container).getDefaultHost().findChildren(); 132 | } 133 | assert servletContexts != null; 134 | for(Container servletContext : servletContexts){ 135 | if(! (servletContext instanceof ApplicationContext)) continue; 136 | if(((ApplicationContext) servletContext).isDefaultContext()) { 137 | defaultContext = servletContext; 138 | break; 139 | } 140 | } 141 | return (ApplicationContext) defaultContext; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/com/yzb/util/ServerXMLParser.java: -------------------------------------------------------------------------------- 1 | package com.yzb.util; 2 | 3 | import cn.hutool.core.convert.Convert; 4 | import cn.hutool.core.io.FileUtil; 5 | import com.yzb.common.*; 6 | import com.yzb.core.*; 7 | import com.yzb.http.HttpConnector; 8 | import org.jsoup.Jsoup; 9 | import org.jsoup.nodes.Document; 10 | import org.jsoup.nodes.Element; 11 | import org.jsoup.select.Elements; 12 | 13 | /** 14 | * @Description 15 | * @Date 2021/1/30 下午9:58 16 | * @Creater BeckoninGshy 17 | */ 18 | public class ServerXMLParser { 19 | private static String xml = FileUtil.readUtf8String(ServerContext.serverXMLPath); 20 | public static Server getServer(){ 21 | StandardServer server = StandardServer.getServerInstance(); 22 | Document document = Jsoup.parse(xml); 23 | Element element = document.select("Server").first(); 24 | int port = Convert.toInt(element.attr("port")); 25 | String shutdown = element.attr("shutdown"); 26 | server.setPort(port); 27 | server.setShutdown(shutdown); 28 | server.setName(ServerContext.serverName); 29 | return server; 30 | } 31 | 32 | public static Service[] getServices(){ 33 | Document document = Jsoup.parse(xml); 34 | Elements elements = document.select("Service"); 35 | StandardService[] service = new StandardService[elements.size()]; 36 | for(int i = 0; i < elements.size(); i++){ 37 | String name = elements.get(i).attr("name"); 38 | service[i] = new StandardService(); 39 | service[i].setName(name); 40 | } 41 | return service; 42 | } 43 | 44 | public static Connector[] getConnectors(String serviceName){ 45 | Document document = Jsoup.parse(xml); 46 | Elements elements = document.select("Service"); 47 | 48 | StandardService[] service = new StandardService[elements.size()]; 49 | StandardConnector[] connectors = null; 50 | for(Element element : elements){ 51 | String name = element.attr("name"); 52 | if (name.equals(serviceName)) { 53 | Elements connectorEles = element.select("Connector"); 54 | connectors = new StandardConnector[connectorEles.size()]; 55 | for(int i = 0; i < connectorEles.size(); i++){ 56 | 57 | int port = Convert.toInt(connectorEles.get(i).attr("port")); 58 | String protocol = connectorEles.get(i).attr("protocol"); 59 | long connectionTimeout = Convert.toLong(connectorEles.get(i).attr("connectionTimeout")); 60 | 61 | //to do better. 62 | if("HTTP/1.1".equals(protocol)){ 63 | connectors[i] = new HttpConnector(); 64 | }else{ 65 | connectors[i] = new StandardConnector(); 66 | } 67 | 68 | 69 | connectors[i].setPort(port); 70 | connectors[i].setProtocol(protocol); 71 | connectors[i].setConnectionTimeout(connectionTimeout); 72 | } 73 | break; 74 | } 75 | } 76 | return connectors; 77 | } 78 | public static Engine getEngine(String serviceName){ 79 | Document document = Jsoup.parse(xml); 80 | Elements elements = document.select("Service"); 81 | StandardService[] service = new StandardService[elements.size()]; 82 | Engine engine = null; 83 | for (Element element : elements) { 84 | String name = element.attr("name"); 85 | if (name.equals(serviceName)) { 86 | Element engineEle = element.select("Engine").first(); 87 | String engineName = engineEle.attr("name"); 88 | String defaultHost = engineEle.attr("defaultHost"); 89 | engine = new Engine(); 90 | engine.setName(engineName); 91 | engine.setDefaultHostName(defaultHost); 92 | break; 93 | } 94 | } 95 | return engine; 96 | } 97 | 98 | 99 | public static Host[] getHost(String engineName){ 100 | Document document = Jsoup.parse(xml); 101 | Elements elements = document.select("Engine"); 102 | Host[] hosts = null; 103 | for (Element element: elements){ 104 | String name = element.attr("name"); 105 | if (name.equals(engineName)) { 106 | Elements hostEles = element.select("Host"); 107 | hosts = new Host[hostEles.size()]; 108 | for(int i = 0; i < hostEles.size(); i++){ 109 | String hostName = hostEles.get(i).attr("name"); 110 | String appBase = hostEles.get(i).attr("appBase"); 111 | System.out.println(appBase); 112 | hosts[i] = new Host(); 113 | hosts[i].setName(hostName); 114 | hosts[i].setAppBase(appBase); 115 | } 116 | break; 117 | } 118 | } 119 | return hosts; 120 | } 121 | 122 | 123 | public static Server getServerWithAutoPack(){ 124 | Server server = getServer(); 125 | Service[] services = getServices(); 126 | for(Service service : services){ 127 | service.setServer(server); 128 | server.addService(service); 129 | Connector[] connectors = getConnectors(service.getName()); 130 | for(Connector connector : connectors){ 131 | connector.setService(service); 132 | service.addConnector(connector); 133 | } 134 | Engine engine = getEngine(service.getName()); 135 | engine.setService(service); 136 | service.setContainer(engine); 137 | 138 | Host[] hosts = getHost(engine.getName()); 139 | for(Host host : hosts){ 140 | host.setService(service); 141 | host.setParent(engine); 142 | 143 | engine.addChild(host); 144 | } 145 | } 146 | 147 | return server; 148 | 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/com/yzb/core/StandardServletContext.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import com.yzb.core.Context; 4 | 5 | import javax.servlet.*; 6 | import javax.servlet.descriptor.JspConfigDescriptor; 7 | import java.io.InputStream; 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | import java.util.Enumeration; 11 | import java.util.EventListener; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | /** 16 | * @Description 17 | * @Date 2021/2/1 下午1:29 18 | * @Creater BeckoninGshy 19 | */ 20 | public class StandardServletContext extends Context implements ServletContext { 21 | 22 | @Override 23 | public ServletContext getContext(String s) { 24 | return null; 25 | } 26 | 27 | @Override 28 | public String getContextPath() { 29 | return null; 30 | } 31 | 32 | @Override 33 | public int getMajorVersion() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public int getMinorVersion() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public int getEffectiveMajorVersion() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public int getEffectiveMinorVersion() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public String getMimeType(String s) { 54 | return null; 55 | } 56 | 57 | @Override 58 | public Set getResourcePaths(String s) { 59 | return null; 60 | } 61 | 62 | @Override 63 | public URL getResource(String s) throws MalformedURLException { 64 | return null; 65 | } 66 | 67 | @Override 68 | public InputStream getResourceAsStream(String s) { 69 | return null; 70 | } 71 | 72 | @Override 73 | public RequestDispatcher getRequestDispatcher(String s) { 74 | return null; 75 | } 76 | 77 | @Override 78 | public RequestDispatcher getNamedDispatcher(String s) { 79 | return null; 80 | } 81 | 82 | @Override 83 | public Servlet getServlet(String s) throws ServletException { 84 | return null; 85 | } 86 | 87 | @Override 88 | public Enumeration getServlets() { 89 | return null; 90 | } 91 | 92 | @Override 93 | public Enumeration getServletNames() { 94 | return null; 95 | } 96 | 97 | @Override 98 | public void log(String s) { 99 | 100 | } 101 | 102 | @Override 103 | public void log(Exception e, String s) { 104 | 105 | } 106 | 107 | @Override 108 | public void log(String s, Throwable throwable) { 109 | 110 | } 111 | 112 | @Override 113 | public String getRealPath(String s) { 114 | return null; 115 | } 116 | 117 | @Override 118 | public String getServerInfo() { 119 | return null; 120 | } 121 | 122 | @Override 123 | public String getInitParameter(String s) { 124 | return null; 125 | } 126 | 127 | @Override 128 | public Enumeration getInitParameterNames() { 129 | return null; 130 | } 131 | 132 | @Override 133 | public boolean setInitParameter(String s, String s1) { 134 | return false; 135 | } 136 | 137 | @Override 138 | public Enumeration getAttributeNames() { 139 | return null; 140 | } 141 | 142 | @Override 143 | public String getServletContextName() { 144 | return null; 145 | } 146 | 147 | @Override 148 | public ServletRegistration.Dynamic addServlet(String s, String s1) { 149 | return null; 150 | } 151 | 152 | @Override 153 | public ServletRegistration.Dynamic addServlet(String s, Servlet servlet) { 154 | return null; 155 | } 156 | 157 | @Override 158 | public ServletRegistration.Dynamic addServlet(String s, Class aClass) { 159 | return null; 160 | } 161 | 162 | @Override 163 | public T createServlet(Class aClass) throws ServletException { 164 | return null; 165 | } 166 | 167 | @Override 168 | public ServletRegistration getServletRegistration(String s) { 169 | return null; 170 | } 171 | 172 | @Override 173 | public Map getServletRegistrations() { 174 | return null; 175 | } 176 | 177 | @Override 178 | public FilterRegistration.Dynamic addFilter(String s, String s1) { 179 | return null; 180 | } 181 | 182 | @Override 183 | public FilterRegistration.Dynamic addFilter(String s, Filter filter) { 184 | return null; 185 | } 186 | 187 | @Override 188 | public FilterRegistration.Dynamic addFilter(String s, Class aClass) { 189 | return null; 190 | } 191 | 192 | @Override 193 | public T createFilter(Class aClass) throws ServletException { 194 | return null; 195 | } 196 | 197 | @Override 198 | public FilterRegistration getFilterRegistration(String s) { 199 | return null; 200 | } 201 | 202 | @Override 203 | public Map getFilterRegistrations() { 204 | return null; 205 | } 206 | 207 | @Override 208 | public SessionCookieConfig getSessionCookieConfig() { 209 | return null; 210 | } 211 | 212 | @Override 213 | public void setSessionTrackingModes(Set set) throws IllegalStateException, IllegalArgumentException { 214 | 215 | } 216 | 217 | @Override 218 | public Set getDefaultSessionTrackingModes() { 219 | return null; 220 | } 221 | 222 | @Override 223 | public Set getEffectiveSessionTrackingModes() { 224 | return null; 225 | } 226 | 227 | @Override 228 | public void addListener(String s) { 229 | 230 | } 231 | 232 | @Override 233 | public void addListener(T t) { 234 | 235 | } 236 | 237 | @Override 238 | public void addListener(Class aClass) { 239 | 240 | } 241 | 242 | @Override 243 | public T createListener(Class aClass) throws ServletException { 244 | return null; 245 | } 246 | 247 | @Override 248 | public void declareRoles(String... strings) { 249 | 250 | } 251 | 252 | @Override 253 | public ClassLoader getClassLoader() { 254 | return null; 255 | } 256 | 257 | @Override 258 | public JspConfigDescriptor getJspConfigDescriptor() { 259 | return null; 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/com/yzb/core/StandardServer.java: -------------------------------------------------------------------------------- 1 | package com.yzb.core; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import cn.hutool.core.date.TimeInterval; 5 | import cn.hutool.log.LogFactory; 6 | import cn.hutool.system.SystemUtil; 7 | import com.yzb.common.Server; 8 | import com.yzb.common.ServerContext; 9 | import com.yzb.common.Service; 10 | import com.yzb.exception.LifecycleException; 11 | 12 | import java.io.*; 13 | import java.net.ServerSocket; 14 | import java.net.Socket; 15 | import java.util.LinkedHashMap; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | public class StandardServer implements Server, Runnable { 20 | private volatile static StandardServer standardServerInstance; 21 | private int port = -1; 22 | private String name = "TinyWebServer"; 23 | private String shutdown = "shutdown"; 24 | private String address = "localhost"; 25 | private ClassLoader parentClassLoader; 26 | 27 | private Service[] services = new Service[0]; 28 | private final Object servicesLock = new Object(); 29 | 30 | private static ServerSocket serverSocket; 31 | 32 | public StandardServer() { 33 | } 34 | 35 | public static StandardServer getServerInstance() { 36 | if (standardServerInstance == null) { 37 | synchronized (StandardServer.class) { 38 | if (standardServerInstance == null) { 39 | standardServerInstance = new StandardServer(); 40 | } 41 | } 42 | } 43 | return standardServerInstance; 44 | } 45 | 46 | @Override 47 | public void setPort(int port) { 48 | this.port = port; 49 | } 50 | 51 | @Override 52 | public int getPort() { 53 | return port; 54 | } 55 | 56 | @Override 57 | public String getAddress() { 58 | return address; 59 | } 60 | 61 | @Override 62 | public void setAddress(String address) { 63 | this.address = address; 64 | } 65 | 66 | @Override 67 | public String getShutdown() { 68 | return shutdown; 69 | } 70 | 71 | @Override 72 | public void setShutdown(String shutdown) { 73 | this.shutdown = shutdown; 74 | } 75 | 76 | @Override 77 | public void addService(Service service) { 78 | int len = services.length; 79 | Service[] newServices = new Service[len+1]; 80 | System.arraycopy(services,0,newServices,0,len); 81 | newServices[len] = service; 82 | services = newServices; 83 | } 84 | 85 | @Override 86 | public Service findService(String service) { 87 | int len = services.length; 88 | for (Service value : services) { 89 | if (value.getName().equals(service)) return value; 90 | } 91 | return null; 92 | } 93 | 94 | @Override 95 | public Service[] findServices() { 96 | return services; 97 | } 98 | 99 | @Override 100 | public String[] getServiceNames() { 101 | int len = services.length; 102 | String[] names = new String[len]; 103 | for(int i = 0; i < len; i++){ 104 | names[i] = services[i].getName(); 105 | } 106 | return names; 107 | } 108 | 109 | @Override 110 | public void removeService(Service service) { 111 | int idx = 0, len = services.length; 112 | while(idx < len && services[idx] != service) idx++; 113 | if(idx == len) return; 114 | try { 115 | services[idx].stop(); 116 | } catch (LifecycleException e) { 117 | //noting to do. 118 | } 119 | Service[] newServices = new Service[len-1]; 120 | System.arraycopy(services,0,newServices,0,idx); 121 | System.arraycopy(services,idx+1,newServices,idx,len-idx-1); 122 | services = newServices; 123 | } 124 | 125 | @Override 126 | public ClassLoader getParentClassLoader() { 127 | return parentClassLoader; 128 | } 129 | 130 | @Override 131 | public void setParentClassLoader(ClassLoader classLoader) { 132 | this.parentClassLoader = classLoader; 133 | } 134 | 135 | @Override 136 | public String getName() { 137 | return name; 138 | } 139 | 140 | @Override 141 | public void setName(String name) { 142 | this.name = name; 143 | } 144 | 145 | @Override 146 | public void start() { 147 | TimeInterval startTimer = DateUtil.timer(); 148 | synchronized (servicesLock) { 149 | for(Service service : services){ 150 | try { 151 | service.start(); 152 | } catch (LifecycleException e) { 153 | e.printStackTrace(); 154 | LogFactory.get().error("Start service{} failed", service.getName()); 155 | } 156 | } 157 | } 158 | LogFactory.get().info("Server startup in {} ms", startTimer.intervalMs()); 159 | } 160 | 161 | @Override 162 | public void stop() throws LifecycleException { 163 | for (Service service : services){ 164 | try { 165 | service.stop(); 166 | } catch (LifecycleException e) { 167 | } 168 | } 169 | LogFactory.get().info("Stopped server{}", getName()); 170 | } 171 | 172 | @Override 173 | public void destroy() throws LifecycleException { 174 | for (Service service : services){ 175 | try { 176 | service.destroy(); 177 | } catch (LifecycleException e) { 178 | } 179 | } 180 | LogFactory.get().info("destroyed Server{}", getName()); 181 | } 182 | 183 | @Override 184 | public String getState() { 185 | return null; 186 | } 187 | 188 | @Override 189 | public void init(){ 190 | logJVM(); 191 | synchronized (servicesLock) { 192 | for (Service service : services){ 193 | try { 194 | service.init(); 195 | } catch (LifecycleException e) { 196 | LogFactory.get().error("Init service {} failed",service.getName()); 197 | } 198 | } 199 | LogFactory.get().info("Init server {}", getName()); 200 | } 201 | } 202 | 203 | private static void logJVM() { 204 | Map infos = new LinkedHashMap<>(); 205 | infos.put("Server Name", ServerContext.serverName); 206 | infos.put("Server built", DateUtil.now()); 207 | infos.put("Server version", ServerContext.version); 208 | infos.put("OS Name\t", SystemUtil.get("os.name")); 209 | infos.put("OS version", SystemUtil.get("os.version")); 210 | infos.put("Architecture", SystemUtil.get("java.home")); 211 | infos.put("Java Home", SystemUtil.get("java home")); 212 | infos.put("JVM Version", SystemUtil.get("java.runtime.version")); 213 | infos.put("JVM Vendor", SystemUtil.get("java.vm.specification.vendor")); 214 | Set keys = infos.keySet(); 215 | for (String key : keys) { 216 | LogFactory.get().info(key + ":\t\t" + infos.get(key)); 217 | } 218 | } 219 | 220 | @Override 221 | public void run() { 222 | 223 | try { 224 | serverSocket = new ServerSocket(port); 225 | 226 | while (true) { 227 | Socket socket = serverSocket.accept(); 228 | 229 | } 230 | } catch (IOException e) { 231 | // connector failed 232 | e.printStackTrace(); 233 | } 234 | } 235 | 236 | } -------------------------------------------------------------------------------- /src/com/yzb/common/Request.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | import javax.servlet.*; 4 | import javax.servlet.http.*; 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.UnsupportedEncodingException; 8 | import java.security.Principal; 9 | import java.util.Collection; 10 | import java.util.Enumeration; 11 | import java.util.Locale; 12 | import java.util.Map; 13 | 14 | 15 | /** 16 | * @description: 实现HttpServletRequest接口的基础类 17 | * @author: BeckoningGshy 18 | * @create: 2021-01-24 16:16 19 | * */ 20 | public class Request implements HttpServletRequest { 21 | @Override 22 | public String getAuthType() { 23 | return null; 24 | } 25 | 26 | @Override 27 | public Cookie[] getCookies() { 28 | return new Cookie[0]; 29 | } 30 | 31 | @Override 32 | public long getDateHeader(String s) { 33 | return 0; 34 | } 35 | 36 | @Override 37 | public String getHeader(String s) { 38 | return null; 39 | } 40 | 41 | @Override 42 | public Enumeration getHeaders(String s) { 43 | return null; 44 | } 45 | 46 | @Override 47 | public Enumeration getHeaderNames() { 48 | return null; 49 | } 50 | 51 | @Override 52 | public int getIntHeader(String s) { 53 | return 0; 54 | } 55 | 56 | @Override 57 | public String getMethod() { 58 | return null; 59 | } 60 | 61 | @Override 62 | public String getPathInfo() { 63 | return null; 64 | } 65 | 66 | @Override 67 | public String getPathTranslated() { 68 | return null; 69 | } 70 | 71 | @Override 72 | public String getContextPath() { 73 | return null; 74 | } 75 | 76 | @Override 77 | public String getQueryString() { 78 | return null; 79 | } 80 | 81 | @Override 82 | public String getRemoteUser() { 83 | return null; 84 | } 85 | 86 | @Override 87 | public boolean isUserInRole(String s) { 88 | return false; 89 | } 90 | 91 | @Override 92 | public Principal getUserPrincipal() { 93 | return null; 94 | } 95 | 96 | @Override 97 | public String getRequestedSessionId() { 98 | return null; 99 | } 100 | 101 | @Override 102 | public String getRequestURI() { 103 | return null; 104 | } 105 | 106 | @Override 107 | public StringBuffer getRequestURL() { 108 | return null; 109 | } 110 | 111 | @Override 112 | public String getServletPath() { 113 | return null; 114 | } 115 | 116 | @Override 117 | public HttpSession getSession(boolean b) { 118 | return null; 119 | } 120 | 121 | @Override 122 | public HttpSession getSession() { 123 | return null; 124 | } 125 | 126 | @Override 127 | public boolean isRequestedSessionIdValid() { 128 | return false; 129 | } 130 | 131 | @Override 132 | public boolean isRequestedSessionIdFromCookie() { 133 | return false; 134 | } 135 | 136 | @Override 137 | public boolean isRequestedSessionIdFromURL() { 138 | return false; 139 | } 140 | 141 | @Override 142 | public boolean isRequestedSessionIdFromUrl() { 143 | return false; 144 | } 145 | 146 | @Override 147 | public boolean authenticate(HttpServletResponse httpServletResponse) throws IOException, ServletException { 148 | return false; 149 | } 150 | 151 | @Override 152 | public void login(String s, String s1) throws ServletException { 153 | 154 | } 155 | 156 | @Override 157 | public void logout() throws ServletException { 158 | 159 | } 160 | 161 | @Override 162 | public Collection getParts() throws IOException, IllegalStateException, ServletException { 163 | return null; 164 | } 165 | 166 | @Override 167 | public Part getPart(String s) throws IOException, IllegalStateException, ServletException { 168 | return null; 169 | } 170 | 171 | @Override 172 | public Object getAttribute(String s) { 173 | return null; 174 | } 175 | 176 | @Override 177 | public Enumeration getAttributeNames() { 178 | return null; 179 | } 180 | 181 | @Override 182 | public String getCharacterEncoding() { 183 | return null; 184 | } 185 | 186 | @Override 187 | public void setCharacterEncoding(String s) throws UnsupportedEncodingException { 188 | 189 | } 190 | 191 | @Override 192 | public int getContentLength() { 193 | return 0; 194 | } 195 | 196 | @Override 197 | public String getContentType() { 198 | return null; 199 | } 200 | 201 | @Override 202 | public ServletInputStream getInputStream() throws IOException { 203 | return null; 204 | } 205 | 206 | @Override 207 | public String getParameter(String s) { 208 | return null; 209 | } 210 | 211 | @Override 212 | public Enumeration getParameterNames() { 213 | return null; 214 | } 215 | 216 | @Override 217 | public String[] getParameterValues(String s) { 218 | return new String[0]; 219 | } 220 | 221 | @Override 222 | public Map getParameterMap() { 223 | return null; 224 | } 225 | 226 | @Override 227 | public String getProtocol() { 228 | return null; 229 | } 230 | 231 | @Override 232 | public String getScheme() { 233 | return null; 234 | } 235 | 236 | @Override 237 | public String getServerName() { 238 | return null; 239 | } 240 | 241 | @Override 242 | public int getServerPort() { 243 | return 0; 244 | } 245 | 246 | @Override 247 | public BufferedReader getReader() throws IOException { 248 | return null; 249 | } 250 | 251 | @Override 252 | public String getRemoteAddr() { 253 | return null; 254 | } 255 | 256 | @Override 257 | public String getRemoteHost() { 258 | return null; 259 | } 260 | 261 | @Override 262 | public void setAttribute(String s, Object o) { 263 | 264 | } 265 | 266 | @Override 267 | public void removeAttribute(String s) { 268 | 269 | } 270 | 271 | @Override 272 | public Locale getLocale() { 273 | return null; 274 | } 275 | 276 | @Override 277 | public Enumeration getLocales() { 278 | return null; 279 | } 280 | 281 | @Override 282 | public boolean isSecure() { 283 | return false; 284 | } 285 | 286 | @Override 287 | public RequestDispatcher getRequestDispatcher(String s) { 288 | return null; 289 | } 290 | 291 | @Override 292 | public String getRealPath(String s) { 293 | return null; 294 | } 295 | 296 | @Override 297 | public int getRemotePort() { 298 | return 0; 299 | } 300 | 301 | @Override 302 | public String getLocalName() { 303 | return null; 304 | } 305 | 306 | @Override 307 | public String getLocalAddr() { 308 | return null; 309 | } 310 | 311 | @Override 312 | public int getLocalPort() { 313 | return 0; 314 | } 315 | 316 | @Override 317 | public ServletContext getServletContext() { 318 | return null; 319 | } 320 | 321 | @Override 322 | public AsyncContext startAsync() { 323 | return null; 324 | } 325 | 326 | @Override 327 | public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) { 328 | return null; 329 | } 330 | 331 | @Override 332 | public boolean isAsyncStarted() { 333 | return false; 334 | } 335 | 336 | @Override 337 | public boolean isAsyncSupported() { 338 | return false; 339 | } 340 | 341 | @Override 342 | public AsyncContext getAsyncContext() { 343 | return null; 344 | } 345 | 346 | @Override 347 | public DispatcherType getDispatcherType() { 348 | return null; 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /test/com/yzb/CommonTest.java: -------------------------------------------------------------------------------- 1 | package com.yzb; 2 | 3 | import cn.hutool.core.date.DateTime; 4 | import cn.hutool.core.date.DateUtil; 5 | import cn.hutool.core.lang.Console; 6 | import cn.hutool.core.util.StrUtil; 7 | import com.yzb.common.ServerContext; 8 | import com.yzb.exception.ParseHttpRequestException; 9 | import org.junit.Test; 10 | 11 | import java.io.*; 12 | import java.text.SimpleDateFormat; 13 | import java.util.*; 14 | 15 | /** 16 | * @description: use to some common test 17 | * @author: BeckoninGshy 18 | * @create: 2021/1/24 20:41 19 | */ 20 | public class CommonTest { 21 | @Test 22 | public void test1(){ 23 | String outlineMessage1 = "GET /index.html HTTP/1.1"; 24 | String outlineMessage2 = "POST / HTTP/1.1"; 25 | String method1 = StrUtil.subBefore(outlineMessage1, " ", false); 26 | String method2 = StrUtil.subBefore(outlineMessage2, " ", false); 27 | Console.log(method1); 28 | Console.log(method2); 29 | } 30 | @Test 31 | public void test2(){ 32 | String outlineMessage1 = "GET /index.html HTTP/1.1"; 33 | String outlineMessage2 = "POST / HTTP/1.1"; 34 | String queryString1 = StrUtil.subBetween(outlineMessage1, " ", " "); 35 | String queryString2 = StrUtil.subBetween(outlineMessage2, " ", " "); 36 | Console.log(queryString1); 37 | Console.log(queryString2); 38 | } 39 | 40 | @Test 41 | public void test3(){ 42 | String outlineMessage1 = "GET /index.html HTTP/1.1"; 43 | String outlineMessage2 = "POST / HTTP/1.1"; 44 | String schema1 = outlineMessage1.substring(outlineMessage1.lastIndexOf(' ')+1); 45 | String schema2 = outlineMessage2.substring(outlineMessage2.lastIndexOf(' ')+1); 46 | Console.log(schema1); 47 | Console.log(schema2); 48 | } 49 | 50 | @Test 51 | public void test4(){ 52 | String outlineMessage = "HTTP/1.1"; 53 | String schema1 = StrUtil.subBefore(outlineMessage, "/", false); 54 | 55 | Console.log(schema1.toLowerCase(Locale.ROOT)); 56 | } 57 | 58 | @Test 59 | public void test5(){ 60 | String outlineMessage1 = "/index.html"; 61 | String outlineMessage2 = "/index.html?username=lisi&password=123"; 62 | String outlineMessage3 = "/"; 63 | String schema1 = StrUtil.subAfter(outlineMessage1, "?", false); 64 | String schema2 = StrUtil.subAfter(outlineMessage2, "?", false); 65 | String schema3 = StrUtil.subAfter(outlineMessage3, "?", false); 66 | Console.log(schema1); 67 | Console.log(schema2); 68 | Console.log(schema3); 69 | } 70 | 71 | @Test 72 | public void test6(){ 73 | String outlineMessage1 = "/index.html"; 74 | String outlineMessage2 = "/index.html?username=lisi&password=123"; 75 | String outlineMessage3 = "/"; 76 | String schema1 = StrUtil.subBefore(outlineMessage1, "?", false); 77 | String schema2 = StrUtil.subBefore(outlineMessage2, "?", false); 78 | String schema3 = StrUtil.subBefore(outlineMessage3, "?", false); 79 | Console.log(schema1); 80 | Console.log(schema2); 81 | Console.log(schema3); 82 | } 83 | @Test 84 | public void test7() throws ParseHttpRequestException { 85 | String parameterString = "username=zhangsan&password=123&sex=male&vehicle=Bike&vehicle=Car&city=v_shanghai&message=++++++++++++The+cat+was+playing+in+the+garden.%0D%0A++++++++++++"; 86 | String[] parameters = parameterString.split("&"); 87 | Map> parameterKVs = new HashMap<>(); 88 | for(int i = 0, n = parameters.length; i < n; i++){ 89 | String[] entries = parameters[i].split("="); 90 | if(entries.length != 2) { 91 | throw new ParseHttpRequestException("解析请求参数错误!"); 92 | } 93 | List values = parameterKVs.getOrDefault(entries[0], new ArrayList<>()); 94 | values.add(entries[1]); 95 | parameterKVs.put(entries[0], values); 96 | } 97 | 98 | Map ans = new HashMap<>(); 99 | ans.put("vehicle",new String[]{"BMW"}); 100 | for(Map.Entry> entry : parameterKVs.entrySet()){ 101 | String[] origin = ans.getOrDefault(entry.getKey(), new String[0]); 102 | String[] add = entry.getValue().toArray(new String[0]); 103 | int len = origin.length + add.length; 104 | String[] result = Arrays.copyOf(origin, len); 105 | System.arraycopy(add,0,result,origin.length,add.length); 106 | ans.put(entry.getKey(),result); 107 | } 108 | for(Map.Entry entry : ans.entrySet()){ 109 | System.out.println(entry.getKey() + "===" + Arrays.toString(entry.getValue())); 110 | } 111 | System.out.println(ans.getOrDefault("123",new String[1])[0]); 112 | } 113 | 114 | @Test 115 | public void test8(){ 116 | String sendMessage1 = "POST / HTTP/1.1\r\n" 117 | + "User-Agent: yzb's client\r\n" 118 | + "Content-Length: 26\r\n" 119 | + "\r\n" 120 | + "username=lisi&password=123\r\n"; 121 | String sendMessage2 = "POST / HTTP/1.1\r\n" 122 | + "User-Agent: yzb's client\r\n" 123 | + "Content-Length: 26\r\n" 124 | + "\r\n"; 125 | // System.out.println(StrUtil.subBefore(sendMessage1, "\r\n\r\n", false)); 126 | // System.out.println(StrUtil.subBefore(sendMessage2, "\r\n\r\n", false)); 127 | // System.out.println(StrUtil.subAfter(sendMessage1, "\r\n\r\n", false)); 128 | System.out.println(StrUtil.subAfter(sendMessage2, "\r\n\r\n", false)); 129 | } 130 | 131 | @Test 132 | public void test9(){ 133 | String language = "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2"; 134 | System.out.println(StrUtil.splitTrim(language, ",")); 135 | } 136 | 137 | @Test 138 | public void test10(){ 139 | String ifum = "Wd, 21 Oct 2015 07:28:00 GMT"; 140 | Date date = null; 141 | try{ 142 | date = new Date("-1"); 143 | } catch (IllegalArgumentException e){ 144 | System.out.println(-1); 145 | } 146 | 147 | System.out.println(date.getTime()); 148 | } 149 | 150 | @Test 151 | public void test11(){ 152 | // System.out.println(new Date(0).toString()); 153 | TimeZone timeZone = TimeZone.getTimeZone("UTC"); 154 | Calendar calendar = Calendar.getInstance(timeZone); 155 | calendar.setTimeInMillis(0); 156 | SimpleDateFormat simpleDateFormat = 157 | new SimpleDateFormat("EE MMM dd HH:mm:ss zzz yyyy", Locale.US); 158 | simpleDateFormat.setTimeZone(timeZone); 159 | System.out.println("UTC: " + simpleDateFormat.format(calendar.getTime())); 160 | } 161 | 162 | @Test 163 | public void test12(){ 164 | String characterEncoding = "utf-8"; 165 | String ContentType = "multipart/form-data; boundary=something"; 166 | Map header = new HashMap<>(); 167 | header.put("ContentType", ContentType); 168 | String contentType = header.getOrDefault("ContentType", ""); 169 | if(StrUtil.indexOfIgnoreCase(contentType,"charset=") == -1) { 170 | System.out.println(-1); 171 | return; 172 | } 173 | String ans = StrUtil.subBefore(contentType,"charset=", false) + "charset=" + characterEncoding; 174 | System.out.println(ans); 175 | } 176 | 177 | @Test 178 | public void test13(){ 179 | 180 | String contentType = "multipart/form-data; boundary=something"; 181 | String ans = StrUtil.subAfter(contentType,"charset=", false); 182 | System.out.println(ans); 183 | } 184 | 185 | @Test 186 | public void test14() throws IOException { 187 | BufferedReader bufferedReader = new BufferedReader(new FileReader(new File("webapps/form.html"))); 188 | System.out.println(bufferedReader.readLine()); 189 | 190 | } 191 | 192 | @Test 193 | public void test15(){ 194 | System.out.println(ServerContext.serverBasePath); 195 | System.out.println(ServerContext.serverXMLPath); 196 | } 197 | 198 | @Test 199 | public void test16(){ 200 | String contextsDirectory = ServerContext.serverBasePath+ File.separator + "webapps"; 201 | File contexts = new File(contextsDirectory); 202 | System.out.println(contexts.getName()); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/com/yzb/http/HttpContant.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import com.yzb.common.ServerContext; 4 | 5 | /** 6 | * @description: 7 | * @author: BeckoninGshy 8 | * @create: 2021/1/25 12:03 9 | */ 10 | public class HttpContant { 11 | 12 | public static final String SEPARATOR_LINES = "\r\n\r\n"; 13 | public static final String LINE_TERMINATOR = "\r\n"; 14 | public static final String LINE_SEPARATOR = ": "; 15 | public static final String HEADER_SEPARATOR = ","; 16 | public static final String PARAMATERS_SEPARATOR = "&"; 17 | public static final String PARAMATERSKV_SEPARATOR = "="; 18 | 19 | 20 | public static final String REQUEST_METHOD_GET = "GET"; 21 | public static final String REQUEST_METHOD_POST = "POST"; 22 | public static final String REQUEST_METHOD_DELETE = "DELETE"; 23 | public static final String REQUEST_METHOD_HEAD = "HEAD"; 24 | public static final String REQUEST_METHOD_CONNECT = "CONNECT"; 25 | public static final String REQUEST_METHOD_OPTIONS = "OPTIONS"; 26 | public static final String REQUEST_METHOD_TRACE = "TRACE"; 27 | public static final String REQUEST_METHOD_PATCH = "PATCH"; 28 | 29 | public static final String HEADER_CONTENT_LENGTH = "Content-Length"; 30 | public static final String HEADER_CONTENT_TYPE = "Content-Type"; 31 | public static final String HEADER_HOST = "Host"; 32 | public static final String HEADER_SERVER = "Server"; 33 | 34 | public static final String DEFAULT_CONTENT_TYPE = "text/html; charset=UTF-8"; 35 | public static final String DEFAULT_CHARACTERENCODING = "UTF-8"; 36 | public static final String CONTENT_TYPE_SEPARATOR = "charset="; 37 | 38 | 39 | public static final int RESPONSE_SC_ACCEPTED = 202; 40 | public static final int RESPONSE_SC_BAD_GATEWAY = 502; 41 | public static final int RESPONSE_SC_BAD_REQUEST = 400; 42 | public static final int RESPONSE_SC_CONFLICT = 409; 43 | public static final int RESPONSE_SC_CONTINUE = 100; 44 | public static final int RESPONSE_SC_CREATED = 201; 45 | public static final int RESPONSE_SC_EXPECTATION_FAILED = 417; 46 | public static final int RESPONSE_SC_FORBIDDEN = 403; 47 | public static final int RESPONSE_SC_FOUND = 302; 48 | public static final int RESPONSE_SC_GATEWAY_TIMEOUT = 504; 49 | public static final int RESPONSE_SC_GONE = 410; 50 | public static final int RESPONSE_SC_HTTP_VERSION_NOT_SUPPORTED = 505; 51 | public static final int RESPONSE_SC_INTERNAL_SERVER_ERROR = 500; 52 | public static final int RESPONSE_SC_LENGTH_REQUIRED = 411; 53 | public static final int RESPONSE_SC_METHOD_NOT_ALLOWED = 405; 54 | public static final int RESPONSE_SC_MOVED_PERMANENTLY = 301; 55 | public static final int RESPONSE_SC_MOVED_TEMPORARILY = 302; 56 | public static final int RESPONSE_SC_MULTIPLE_CHOICES = 300; 57 | public static final int RESPONSE_SC_NO_CONTENT = 204; 58 | public static final int RESPONSE_SC_NON_AUTHORITATIVE_INFORMATION = 203; 59 | public static final int RESPONSE_SC_NOT_ACCEPTABLE = 406; 60 | public static final int RESPONSE_SC_NOT_FOUND = 404; 61 | public static final int RESPONSE_SC_NOT_IMPLEMENTED = 501; 62 | public static final int RESPONSE_SC_NOT_MODIFIED = 304; 63 | public static final int RESPONSE_SC_OK = 200; 64 | public static final int RESPONSE_SC_PARTIAL_CONTENT = 206; 65 | public static final int RESPONSE_SC_PAYMENT_REQUIRED = 402; 66 | public static final int RESPONSE_SC_PRECONDITION_FAILED = 412; 67 | public static final int RESPONSE_SC_PROXY_AUTHENTICATION_REQUIRED = 407; 68 | public static final int RESPONSE_SC_REQUEST_ENTITY_TOO_LARGE = 413; 69 | public static final int RESPONSE_SC_REQUEST_TIMEOUT = 408; 70 | public static final int RESPONSE_SC_REQUEST_URI_TOO_LONG = 414; 71 | public static final int RESPONSE_SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416; 72 | public static final int RESPONSE_SC_RESET_CONTENT = 205; 73 | public static final int RESPONSE_SC_SEE_OTHER = 303; 74 | public static final int RESPONSE_SC_SERVICE_UNAVAILABLE = 503; 75 | public static final int RESPONSE_SC_SWITCHING_PROTOCOLS = 101; 76 | public static final int RESPONSE_SC_TEMPORARY_REDIRECT = 307; 77 | public static final int RESPONSE_SC_UNAUTHORIZED = 401; 78 | public static final int RESPONSE_SC_UNSUPPORTED_MEDIA_TYPE = 415; 79 | public static final int RESPONSE_SC_USE_PROXY = 305; 80 | 81 | public static final String textFormat_normal = 82 | "" +ServerContext.serverName+ "/"+ServerContext.version+" - Error report " + 90 | "

HTTP Status {}

" + 91 | "

type Status report

message

description " + 92 | "


"+ ServerContext.serverName +" "+ServerContext.version+"

" + 93 | ""; 94 | 95 | public static final String textFormat_404 = 96 | "" +ServerContext.serverName+ "/"+ServerContext.version+" - Error report " + 104 | "

HTTP Status 404 - {}

" + 105 | "

type Status report

message {}

description " + 106 | "The requested resource is not available.


"+ ServerContext.serverName +" "+ServerContext.version+"

" + 107 | ""; 108 | 109 | public static final String textFormat_500 = 110 | "" +ServerContext.serverName+ "/"+ServerContext.version+" - Error report " 118 | + "

HTTP Status 500 - An exception occurred processing {}

" 119 | + "

type Exception report

message An exception occurred processing {}

description " 120 | + "The server encountered an internal error that prevented it from fulfilling this request.

" 121 | + "

Stacktrace:

" + "
{}
" + "

"+ ServerContext.serverName +" "+ServerContext.version+"

" 122 | + ""; 123 | 124 | 125 | 126 | public static final String DEFAULT_PROTOCOL = "HTTP/1.1"; 127 | 128 | 129 | public static String getResponseHead(int statusCode){ 130 | if(statusCode == 200){ 131 | return " OK"; 132 | }else if(statusCode == 302){ 133 | return " Found"; 134 | }else if(statusCode == 404){ 135 | return " Not Found"; 136 | }else if(statusCode == 500){ 137 | return " Internal Server Error"; 138 | } 139 | return ""; 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/com/yzb/http/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import cn.hutool.core.date.DateField; 4 | import cn.hutool.core.date.DateUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import cn.hutool.log.LogFactory; 7 | import com.yzb.common.Response; 8 | 9 | import javax.servlet.ServletOutputStream; 10 | import javax.servlet.http.Cookie; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.IOException; 13 | import java.io.PrintWriter; 14 | import java.net.Socket; 15 | import java.nio.charset.StandardCharsets; 16 | import java.text.SimpleDateFormat; 17 | import java.util.*; 18 | 19 | /** 20 | * @Description Handle HTTP response 21 | * @Date 2021/1/26 上午11:15 22 | * @Creater BeckoninGsy 23 | */ 24 | public class HttpResponse extends Response { 25 | 26 | private Socket socket; 27 | 28 | private Map headers; 29 | private List cookieList; 30 | private int statusCode; 31 | private boolean commited; 32 | private boolean callGetOutputStream = false; 33 | private boolean callGetWriter = false; 34 | private ByteArrayOutputStream baos; 35 | 36 | 37 | public HttpResponse(Socket socket){ 38 | headers = new HashMap<>(); 39 | cookieList = new ArrayList<>(); 40 | statusCode = HttpContant.RESPONSE_SC_OK; 41 | commited = false; 42 | this.socket = socket; 43 | baos = new ByteArrayOutputStream(){ 44 | @Override 45 | public void flush() throws IOException { 46 | super.flush(); 47 | HttpResponse.this.commitResponse(); 48 | } 49 | 50 | @Override 51 | public void close() throws IOException { 52 | if(!isCommitted()) flush(); 53 | super.close(); 54 | 55 | } 56 | }; 57 | } 58 | 59 | @Override 60 | public void addCookie(Cookie cookie) { 61 | cookieList.add(cookie); 62 | } 63 | 64 | public List getCookies(){ 65 | return this.cookieList; 66 | } 67 | 68 | 69 | private String getCookiesHeader(){ 70 | if(cookieList == null || cookieList.size() == 0) return ""; 71 | String pattern = "EEE, d MMM yyyy HH:mm:ss 'GMT'"; 72 | SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.ENGLISH); 73 | StringBuilder sb = new StringBuilder(); 74 | for(Cookie cookie : getCookies()){ 75 | sb.append("Set-Cookie: "); 76 | sb.append(cookie.getName()); 77 | sb.append("="); 78 | sb.append(cookie.getValue()); 79 | if (-1 != cookie.getMaxAge()){ 80 | sb.append("; Expires="); 81 | Date now = new Date(); 82 | Date expire = DateUtil.offset(now, DateField.SECOND,cookie.getMaxAge()); 83 | sb.append(sdf.format(expire)); 84 | } 85 | if(null != cookie.getPath()){ 86 | sb.append("; Path="+cookie.getPath()); 87 | } 88 | 89 | sb.append(";"); 90 | sb.append(HttpContant.LINE_TERMINATOR); 91 | LogFactory.get().info(cookie.getName()+"="+cookie.getValue()+";"); 92 | } 93 | return sb.toString(); 94 | } 95 | 96 | @Override 97 | public void setHeader(String s, String s1) { 98 | headers.put(s,s1); 99 | } 100 | 101 | @Override 102 | public void addHeader(String s, String s1) { 103 | setHeader(s,s1); 104 | } 105 | 106 | @Override 107 | public void setIntHeader(String s, int i) { 108 | setHeader(s,Integer.toString(i)); 109 | } 110 | 111 | @Override 112 | public void addIntHeader(String s, int i) { 113 | setHeader(s,Integer.toString(i)); 114 | } 115 | 116 | @Override 117 | public void setDateHeader(String s, long l) { 118 | TimeZone timeZone = TimeZone.getTimeZone("UTC"); 119 | Calendar calendar = Calendar.getInstance(timeZone); 120 | calendar.setTimeInMillis(l); 121 | SimpleDateFormat simpleDateFormat = 122 | new SimpleDateFormat("EE MMM dd HH:mm:ss zzz yyyy", Locale.US); 123 | simpleDateFormat.setTimeZone(timeZone); 124 | setHeader(s,simpleDateFormat.format(calendar.getTime())); 125 | } 126 | 127 | @Override 128 | public void addDateHeader(String s, long l) { 129 | setDateHeader(s, l); 130 | } 131 | 132 | @Override 133 | public boolean containsHeader(String s) { 134 | return headers.containsKey(s); 135 | } 136 | 137 | @Override 138 | public void setStatus(int i) { 139 | statusCode = i; 140 | } 141 | 142 | @Override 143 | public int getStatus() { 144 | return statusCode; 145 | } 146 | 147 | @Override 148 | public String getHeader(String s) { 149 | return headers.get(s); 150 | } 151 | 152 | @Override 153 | public Collection getHeaders(String s) { 154 | return StrUtil.splitTrim(getHeader(s), HttpContant.HEADER_SEPARATOR); 155 | } 156 | 157 | @Override 158 | public Collection getHeaderNames() { 159 | return headers.keySet(); 160 | } 161 | 162 | 163 | @Override 164 | public boolean isCommitted() { 165 | return commited; 166 | } 167 | 168 | 169 | @Override 170 | public void setCharacterEncoding(String s) { 171 | String contentType = headers.getOrDefault(HttpContant.HEADER_CONTENT_TYPE, HttpContant.DEFAULT_CONTENT_TYPE); 172 | if(StrUtil.indexOfIgnoreCase(contentType,HttpContant.CONTENT_TYPE_SEPARATOR) == -1) { 173 | setContentType(contentType +"; " + HttpContant.CONTENT_TYPE_SEPARATOR + s); 174 | } else { 175 | setContentType(StrUtil.subBefore(contentType,HttpContant.CONTENT_TYPE_SEPARATOR, false) + HttpContant.CONTENT_TYPE_SEPARATOR + s); 176 | } 177 | } 178 | 179 | @Override 180 | public void setContentLength(int i) { 181 | setHeader(HttpContant.HEADER_CONTENT_LENGTH, Integer.toString(i)); 182 | } 183 | 184 | @Override 185 | public void setContentType(String s) { 186 | setHeader(HttpContant.HEADER_CONTENT_TYPE, s); 187 | } 188 | 189 | @Override 190 | public String getCharacterEncoding() { 191 | String contentType = getContentType(); 192 | if(contentType == null) return HttpContant.DEFAULT_CHARACTERENCODING; 193 | return StrUtil.subAfter(contentType, HttpContant.CONTENT_TYPE_SEPARATOR, false); 194 | } 195 | 196 | @Override 197 | public String getContentType() { 198 | return getHeader(HttpContant.HEADER_CONTENT_TYPE); 199 | } 200 | 201 | 202 | @Override 203 | public void setBufferSize(int i) { 204 | if(isCommitted()) throw new IllegalStateException("can not set buffer size, because it has been commited!"); 205 | baos = new ByteArrayOutputStream(i){ 206 | @Override 207 | public void flush() throws IOException { 208 | super.flush(); 209 | HttpResponse.this.commitResponse(); 210 | } 211 | 212 | @Override 213 | public void close() throws IOException { 214 | if(!isCommitted()) flush(); 215 | super.close(); 216 | 217 | } 218 | }; 219 | } 220 | 221 | @Override 222 | public int getBufferSize() { 223 | return baos.size(); 224 | } 225 | 226 | @Override 227 | public void flushBuffer() throws IOException { 228 | baos.flush(); 229 | } 230 | 231 | @Override 232 | public void resetBuffer() { 233 | if(isCommitted()) throw new IllegalStateException("can not reset buffer, because it has been commited!"); 234 | baos.reset(); 235 | } 236 | 237 | @Override 238 | public void reset() { 239 | if(isCommitted()) throw new IllegalStateException("can not reset response, because it has been commited!"); 240 | baos.reset(); 241 | headers.clear(); 242 | statusCode = HttpContant.RESPONSE_SC_OK; 243 | callGetOutputStream = false; 244 | callGetWriter = false; 245 | } 246 | 247 | 248 | @Override 249 | public ServletOutputStream getOutputStream() throws IOException { 250 | if(callGetWriter) { 251 | throw new IllegalStateException("already call getOutputStream!"); 252 | } 253 | return new ServletOutputStream() { 254 | @Override 255 | public void write(int b) throws IOException { 256 | baos.write(b); 257 | } 258 | 259 | @Override 260 | public void flush() throws IOException { 261 | baos.flush(); 262 | } 263 | 264 | @Override 265 | public void close() throws IOException { 266 | baos.close(); 267 | } 268 | } ; 269 | } 270 | 271 | @Override 272 | public PrintWriter getWriter() throws IOException { 273 | if(callGetOutputStream) { 274 | throw new IllegalStateException("already call getOutputStream!"); 275 | } 276 | callGetWriter = true; 277 | return new PrintWriter(baos); 278 | } 279 | 280 | @Override 281 | public void sendRedirect(String s) throws IOException { 282 | if (callGetWriter || callGetOutputStream) { 283 | throw new IllegalStateException("already call getWriter or getOutputStream!"); 284 | } 285 | setStatus(HttpContant.RESPONSE_SC_MOVED_TEMPORARILY); 286 | setHeader("Location", s); 287 | commitRespnseOnlyWithHeaders(); 288 | } 289 | 290 | 291 | @Override 292 | public void sendError(int code, String s) throws IOException { 293 | if(isCommitted()) throw new IllegalStateException("can not send error, because it has been commited!"); 294 | setStatus(code); 295 | setContentType(HttpContant.DEFAULT_CONTENT_TYPE); 296 | PrintWriter writer = getWriter(); 297 | writer.println(s); 298 | writer.close(); 299 | } 300 | 301 | @Override 302 | public void sendError(int code) throws IOException { 303 | sendError(code, StrUtil.format(HttpContant.textFormat_normal, code)); 304 | } 305 | 306 | 307 | private String generateResponseHeader(){ 308 | StringBuilder sb = new StringBuilder(64); 309 | sb.append(HttpContant.DEFAULT_PROTOCOL); 310 | sb.append(" "); 311 | sb.append(getStatus()); 312 | sb.append(HttpContant.getResponseHead(getStatus())); 313 | sb.append(HttpContant.LINE_TERMINATOR); 314 | for (Map.Entry entry : headers.entrySet()){ 315 | sb.append(entry.getKey()); 316 | sb.append(HttpContant.LINE_SEPARATOR); 317 | sb.append(entry.getValue()); 318 | sb.append(HttpContant.LINE_TERMINATOR); 319 | } 320 | sb.append(getCookiesHeader()); 321 | sb.append(HttpContant.LINE_TERMINATOR); 322 | return sb.toString(); 323 | } 324 | 325 | private void commitRespnseOnlyWithHeaders() throws IOException { 326 | String header = generateResponseHeader(); 327 | socket.getOutputStream().write(header.getBytes(StandardCharsets.UTF_8)); 328 | socket.getOutputStream().flush(); 329 | commited = true; 330 | } 331 | 332 | private void commitResponse() throws IOException { 333 | setContentLength(baos.size()); 334 | String header = generateResponseHeader(); 335 | socket.getOutputStream().write(header.getBytes(StandardCharsets.UTF_8)); 336 | socket.getOutputStream().write(baos.toByteArray()); 337 | socket.getOutputStream().flush(); 338 | commited = true; 339 | } 340 | 341 | 342 | } 343 | -------------------------------------------------------------------------------- /src/com/yzb/common/Lifecycle.java: -------------------------------------------------------------------------------- 1 | package com.yzb.common; 2 | 3 | /** 4 | * @Description 5 | * @Date 2021/1/28 下午9:58 6 | * @Creater BeckoninGshy 7 | */ 8 | 9 | import com.yzb.exception.LifecycleException; 10 | 11 | import java.io.FileNotFoundException; 12 | 13 | /** 14 | * Common interface for component life cycle methods. Catalina components 15 | * may implement this interface (as well as the appropriate interface(s) for 16 | * the functionality they support) in order to provide a consistent mechanism 17 | * to start and stop the component. 18 | *
19 | * The valid state transitions for components that support {@link Lifecycle} 20 | * are: 21 | *
 22 |  *            start()
 23 |  *  -----------------------------
 24 |  *  |                           |
 25 |  *  | init()                    |
 26 |  * NEW -»-- INITIALIZING        |
 27 |  * | |           |              |     ------------------«-----------------------
 28 |  * | |           |auto          |     |                                        |
 29 |  * | |          \|/    start() \|/   \|/     auto          auto         stop() |
 30 |  * | |      INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»---  |
 31 |  * | |         |                                                            |  |
 32 |  * | |destroy()|                                                            |  |
 33 |  * | --»-----«--    ------------------------«--------------------------------  ^
 34 |  * |     |          |                                                          |
 35 |  * |     |         \|/          auto                 auto              start() |
 36 |  * |     |     STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----
 37 |  * |    \|/                               ^                     |  ^
 38 |  * |     |               stop()           |                     |  |
 39 |  * |     |       --------------------------                     |  |
 40 |  * |     |       |                                              |  |
 41 |  * |     |       |    destroy()                       destroy() |  |
 42 |  * |     |    FAILED ----»------ DESTROYING ---«-----------------  |
 43 |  * |     |                        ^     |                          |
 44 |  * |     |     destroy()          |     |auto                      |
 45 |  * |     --------»-----------------    \|/                         |
 46 |  * |                                 DESTROYED                     |
 47 |  * |                                                               |
 48 |  * |                            stop()                             |
 49 |  * ----»-----------------------------»------------------------------
 50 |  *
 51 |  * Any state can transition to FAILED.
 52 |  *
 53 |  * Calling start() while a component is in states STARTING_PREP, STARTING or
 54 |  * STARTED has no effect.
 55 |  *
 56 |  * Calling start() while a component is in state NEW will cause init() to be
 57 |  * called immediately after the start() method is entered.
 58 |  *
 59 |  * Calling stop() while a component is in states STOPPING_PREP, STOPPING or
 60 |  * STOPPED has no effect.
 61 |  *
 62 |  * Calling stop() while a component is in state NEW transitions the component
 63 |  * to STOPPED. This is typically encountered when a component fails to start and
 64 |  * does not start all its sub-components. When the component is stopped, it will
 65 |  * try to stop all sub-components - even those it didn't start.
 66 |  *
 67 |  * Attempting any other transition will throw {@link LifecycleException}.
 68 |  *
 69 |  * 
70 | * The {@link LifecycleEvent}s fired during state changes are defined in the 71 | * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the 72 | * attempted transition is not valid. 73 | * 74 | * @author Craig R. McClanahan 75 | */ 76 | public interface Lifecycle { 77 | 78 | 79 | // ----------------------------------------------------- Manifest Constants 80 | 81 | 82 | /** 83 | * The LifecycleEvent type for the "component before init" event. 84 | */ 85 | public static final String BEFORE_INIT_EVENT = "before_init"; 86 | 87 | 88 | /** 89 | * The LifecycleEvent type for the "component after init" event. 90 | */ 91 | public static final String AFTER_INIT_EVENT = "after_init"; 92 | 93 | 94 | /** 95 | * The LifecycleEvent type for the "component start" event. 96 | */ 97 | public static final String START_EVENT = "start"; 98 | 99 | 100 | /** 101 | * The LifecycleEvent type for the "component before start" event. 102 | */ 103 | public static final String BEFORE_START_EVENT = "before_start"; 104 | 105 | 106 | /** 107 | * The LifecycleEvent type for the "component after start" event. 108 | */ 109 | public static final String AFTER_START_EVENT = "after_start"; 110 | 111 | 112 | /** 113 | * The LifecycleEvent type for the "component stop" event. 114 | */ 115 | public static final String STOP_EVENT = "stop"; 116 | 117 | 118 | /** 119 | * The LifecycleEvent type for the "component before stop" event. 120 | */ 121 | public static final String BEFORE_STOP_EVENT = "before_stop"; 122 | 123 | 124 | /** 125 | * The LifecycleEvent type for the "component after stop" event. 126 | */ 127 | public static final String AFTER_STOP_EVENT = "after_stop"; 128 | 129 | 130 | /** 131 | * The LifecycleEvent type for the "component after destroy" event. 132 | */ 133 | public static final String AFTER_DESTROY_EVENT = "after_destroy"; 134 | 135 | 136 | /** 137 | * The LifecycleEvent type for the "component before destroy" event. 138 | */ 139 | public static final String BEFORE_DESTROY_EVENT = "before_destroy"; 140 | 141 | 142 | /** 143 | * The LifecycleEvent type for the "periodic" event. 144 | */ 145 | public static final String PERIODIC_EVENT = "periodic"; 146 | 147 | 148 | /** 149 | * The LifecycleEvent type for the "configure_start" event. Used by those 150 | * components that use a separate component to perform configuration and 151 | * need to signal when configuration should be performed - usually after 152 | * {@link #BEFORE_START_EVENT} and before {@link #START_EVENT}. 153 | */ 154 | public static final String CONFIGURE_START_EVENT = "configure_start"; 155 | 156 | 157 | /** 158 | * The LifecycleEvent type for the "configure_stop" event. Used by those 159 | * components that use a separate component to perform configuration and 160 | * need to signal when de-configuration should be performed - usually after 161 | * {@link #STOP_EVENT} and before {@link #AFTER_STOP_EVENT}. 162 | */ 163 | public static final String CONFIGURE_STOP_EVENT = "configure_stop"; 164 | 165 | 166 | // --------------------------------------------------------- Public Methods 167 | 168 | /** 169 | * Prepare the component for starting. This method should perform any 170 | * initialization required post object creation. The following 171 | * {@link LifecycleEvent}s will be fired in the following order: 172 | *
    173 | *
  1. INIT_EVENT: On the successful completion of component 174 | * initialization.
  2. 175 | *
176 | * 177 | * @exception LifecycleException if this component detects a fatal error 178 | * that prevents this component from being used 179 | */ 180 | public void init() throws LifecycleException; 181 | 182 | /** 183 | * Prepare for the beginning of active use of the public methods other than 184 | * property getters/setters and life cycle methods of this component. This 185 | * method should be called before any of the public methods other than 186 | * property getters/setters and life cycle methods of this component are 187 | * utilized. The following {@link LifecycleEvent}s will be fired in the 188 | * following order: 189 | *
    190 | *
  1. BEFORE_START_EVENT: At the beginning of the method. It is as this 191 | * point the state transitions to 192 | * {@link LifecycleState#STARTING_PREP}.
  2. 193 | *
  3. START_EVENT: During the method once it is safe to call start() for 194 | * any child components. It is at this point that the 195 | * state transitions to {@link LifecycleState#STARTING} 196 | * and that the public methods other than property 197 | * getters/setters and life cycle methods may be 198 | * used.
  4. 199 | *
  5. AFTER_START_EVENT: At the end of the method, immediately before it 200 | * returns. It is at this point that the state 201 | * transitions to {@link LifecycleState#STARTED}. 202 | *
  6. 203 | *
204 | * 205 | * @exception LifecycleException if this component detects a fatal error 206 | * that prevents this component from being used 207 | */ 208 | public void start() throws LifecycleException; 209 | 210 | 211 | /** 212 | * Gracefully terminate the active use of the public methods other than 213 | * property getters/setters and life cycle methods of this component. Once 214 | * the STOP_EVENT is fired, the public methods other than property 215 | * getters/setters and life cycle methods should not be used. The following 216 | * {@link LifecycleEvent}s will be fired in the following order: 217 | *
    218 | *
  1. BEFORE_STOP_EVENT: At the beginning of the method. It is at this 219 | * point that the state transitions to 220 | * {@link LifecycleState#STOPPING_PREP}.
  2. 221 | *
  3. STOP_EVENT: During the method once it is safe to call stop() for 222 | * any child components. It is at this point that the 223 | * state transitions to {@link LifecycleState#STOPPING} 224 | * and that the public methods other than property 225 | * getters/setters and life cycle methods may no longer be 226 | * used.
  4. 227 | *
  5. AFTER_STOP_EVENT: At the end of the method, immediately before it 228 | * returns. It is at this point that the state 229 | * transitions to {@link LifecycleState#STOPPED}. 230 | *
  6. 231 | *
232 | * 233 | * Note that if transitioning from {@link LifecycleState#FAILED} then the 234 | * three events above will be fired but the component will transition 235 | * directly from {@link LifecycleState#FAILED} to 236 | * {@link LifecycleState#STOPPING}, bypassing 237 | * {@link LifecycleState#STOPPING_PREP} 238 | * 239 | * @exception LifecycleException if this component detects a fatal error 240 | * that needs to be reported 241 | */ 242 | public void stop() throws LifecycleException; 243 | 244 | /** 245 | * Prepare to discard the object. The following {@link LifecycleEvent}s will 246 | * be fired in the following order: 247 | *
    248 | *
  1. DESTROY_EVENT: On the successful completion of component 249 | * destruction.
  2. 250 | *
251 | * 252 | * @exception LifecycleException if this component detects a fatal error 253 | * that prevents this component from being used 254 | */ 255 | public void destroy() throws LifecycleException; 256 | 257 | 258 | /** 259 | * Obtain the current state of the source component. 260 | * 261 | * @return The current state of the source component. 262 | */ 263 | public String getState(); 264 | 265 | } 266 | -------------------------------------------------------------------------------- /src/com/yzb/http/ApplicationContext.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import cn.hutool.core.io.FileUtil; 4 | import cn.hutool.log.Log; 5 | import cn.hutool.log.LogFactory; 6 | import com.yzb.classcloader.WebappClassLoader; 7 | import com.yzb.common.Container; 8 | import com.yzb.common.ServerContext; 9 | import com.yzb.common.Service; 10 | import com.yzb.core.Engine; 11 | import com.yzb.core.StandardServletConfig; 12 | import com.yzb.core.StandardServletContext; 13 | import com.yzb.exception.LifecycleException; 14 | import org.jsoup.Jsoup; 15 | import org.jsoup.nodes.Document; 16 | import org.jsoup.nodes.Element; 17 | import org.jsoup.select.Elements; 18 | 19 | import javax.servlet.*; 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.FileNotFoundException; 23 | import java.io.InputStream; 24 | import java.util.*; 25 | 26 | /** 27 | * @Description 28 | * @Date 2021/2/1 下午8:08 29 | * @Creater BeckoninGshy 30 | */ 31 | public class ApplicationContext extends StandardServletContext { 32 | 33 | private Map servletNameToClass = new HashMap<>(); 34 | private Map servletClassToName = new HashMap<>(); 35 | private Map servletNameToURL = new HashMap<>(); 36 | private Map servletURLToName = new HashMap<>(); 37 | private Map mimeMapping = new HashMap<>(); 38 | private Map initParameters = new HashMap<>(); 39 | private Map> servletClassToInitParameters = new HashMap<>(); 40 | private List loadOnStartupServletClassNames = new LinkedList<>(); 41 | private List welcomeFileNames = new LinkedList<>(); 42 | private Map, Servlet> servletPool = new HashMap<>(); 43 | private WebappClassLoader webappClassLoader; 44 | private boolean isDefault = false; 45 | private ApplicationContext defaultContext = null; 46 | 47 | 48 | 49 | public ApplicationContext(String docBase, Boolean isDefault){ 50 | ClassLoader commonClassLoader = Thread.currentThread().getContextClassLoader(); 51 | this.webappClassLoader = new WebappClassLoader(docBase, commonClassLoader); 52 | this.isDefault = isDefault; 53 | } 54 | 55 | public WebappClassLoader getWebappClassLoader() { 56 | return webappClassLoader; 57 | } 58 | 59 | public String getServletURLToName(String url){ 60 | return servletURLToName.get(url); 61 | } 62 | 63 | public String getServletNameToClass(String name){ 64 | return servletNameToClass.get(name); 65 | } 66 | 67 | public String getServletURLToClass(String url) { 68 | return getServletNameToClass(getServletURLToName(url)); 69 | } 70 | 71 | public String getServletClassToName(Class clazz){ 72 | return servletClassToName.get(clazz.getName()); 73 | } 74 | 75 | @Override 76 | public ServletContext getContext(String URI) { 77 | Service service = getService(); 78 | Container container = service.getContainer(); 79 | Container[] servletContexts = null; 80 | Container rootContext = null; 81 | if(container instanceof Engine){ 82 | servletContexts = ((Engine) container).getDefaultHost().findChildren(); 83 | } 84 | assert servletContexts != null; 85 | for(Container servletContext : servletContexts){ 86 | if(! (servletContext instanceof ApplicationContext)) continue; 87 | if(((ApplicationContext) servletContext).isDefaultContext()) continue; 88 | if(((ApplicationContext) servletContext).getPath().equals("/")) rootContext = servletContext; 89 | else if(URI.startsWith(((ApplicationContext) servletContext).getPath())) return (ApplicationContext) servletContext; 90 | } 91 | return (ApplicationContext) rootContext; 92 | } 93 | 94 | @Override 95 | public String getMimeType(String extension){ 96 | if(mimeMapping.get(extension) == null) return getDefaultContext().getMimeType(extension); 97 | return mimeMapping.get(extension); 98 | } 99 | 100 | @Override 101 | public String getContextPath() { 102 | return getPath(); 103 | } 104 | 105 | @Override 106 | public InputStream getResourceAsStream(String path) { 107 | String filePath = getRealPath() + path; 108 | File file = new File(filePath); 109 | InputStream is; 110 | try { 111 | is = new FileInputStream(file); 112 | } catch (FileNotFoundException e) { 113 | LogFactory.get().warn("get Resource failed, file is not found. path: {} ", path); 114 | return null; 115 | } 116 | return is; 117 | } 118 | 119 | @Override 120 | public String getServletContextName() { 121 | return getName(); 122 | } 123 | 124 | @Override 125 | public void init() throws LifecycleException { 126 | try { 127 | File webInf = null; 128 | if(isDefault){ 129 | webInf = new File(ServerContext.serverConfigDir); 130 | }else{ 131 | webInf = new File(getRealPath(), "WEB-INF"); 132 | if(!webInf.exists()) throw new FileNotFoundException("cannot find WEB-INF dir in current application directory"); 133 | } 134 | 135 | File webXML = new File(webInf, "web.xml"); 136 | String xml = FileUtil.readUtf8String(webXML); 137 | Document document = Jsoup.parse(xml); 138 | 139 | parseServlets(document); 140 | parseMimeAndMapping(document); 141 | parseLoadOnStartupServlet(document); 142 | parseWelcomeFileList(document); 143 | 144 | handleLoadOnStartupServlet(); 145 | } catch (FileNotFoundException e) { 146 | LogFactory.get().error(e.getMessage()); 147 | stop(); 148 | return; 149 | } 150 | super.init(); 151 | } 152 | 153 | @Override 154 | public void destroy() throws LifecycleException { 155 | for(Servlet s : servletPool.values()){ 156 | s.destroy(); 157 | } 158 | super.destroy(); 159 | } 160 | 161 | @Override 162 | public Servlet getServlet(String s) throws ServletException { 163 | try { 164 | return getServlet(webappClassLoader.loadClass(getServletNameToClass(s))); 165 | } catch (IllegalAccessException | InstantiationException | ClassNotFoundException e) { 166 | LogFactory.get().error("get servlet {} failed!", s); 167 | } 168 | return null; 169 | } 170 | 171 | public synchronized Servlet getServlet(Class clazz) throws ServletException, IllegalAccessException, InstantiationException { 172 | Servlet s = servletPool.get(clazz); 173 | if(s == null){ 174 | s = (Servlet) clazz.newInstance(); 175 | ServletConfig sc = new StandardServletConfig(this, getServletClassToName(clazz), servletClassToInitParameters.getOrDefault(clazz.getName(), new HashMap<>())); 176 | s.init(sc); 177 | servletPool.put(clazz, s); 178 | } 179 | return s; 180 | } 181 | 182 | @Override 183 | public String getInitParameter(String s) { 184 | return initParameters.get(s); 185 | } 186 | 187 | @Override 188 | public Enumeration getInitParameterNames() { 189 | return Collections.enumeration(initParameters.keySet()); 190 | } 191 | 192 | @Override 193 | public boolean setInitParameter(String key, String value) { 194 | initParameters.put(key, value); 195 | return true; 196 | } 197 | 198 | @Override 199 | public RequestDispatcher getRequestDispatcher(String uri) { 200 | return new ApplicationRequestDispatcher(uri, this); 201 | } 202 | 203 | public boolean isDefaultContext(){ 204 | return isDefault; 205 | } 206 | 207 | public void setDefaultContext(ApplicationContext defaultContext){ 208 | this.defaultContext = defaultContext; 209 | } 210 | 211 | public ApplicationContext getDefaultContext(){ 212 | return defaultContext; 213 | } 214 | 215 | public List getWelcomeFileNames(){ 216 | return welcomeFileNames; 217 | } 218 | 219 | public InputStream getWelcomFile(){ 220 | for(String name: welcomeFileNames){ 221 | InputStream is = getResourceAsStream("/" + name); 222 | if(is != null) return is; 223 | } 224 | if(!isDefault) { 225 | for(String name: getDefaultContext().getWelcomeFileNames()){ 226 | InputStream is = getResourceAsStream("/" + name); 227 | if(is != null) return is; 228 | } 229 | } 230 | return null; 231 | } 232 | 233 | 234 | private void parseServlets(Document document) throws FileNotFoundException { 235 | 236 | Elements servlets = document.select("servlet"); 237 | for(Element servlet : servlets){ 238 | String name = servlet.select("servlet-name").text(); 239 | String clazz = servlet.select("servlet-class").text(); 240 | assert name != null; 241 | assert clazz != null; 242 | servletNameToClass.put(name, clazz); 243 | servletClassToName.put(clazz, name); 244 | 245 | // process init-param 246 | Elements initParams = servlet.select("init-param"); 247 | if(initParams == null || initParams.size() == 0) continue; 248 | Map inits = new HashMap<>(); 249 | for(Element initParam : initParams){ 250 | String initParamValue = initParam.select("param-value").text(); 251 | String initParamName = initParam.select("param-name").text(); 252 | inits.put(initParamName,initParamValue); 253 | } 254 | servletClassToInitParameters.put(clazz, inits); 255 | } 256 | 257 | Elements servletMappings = document.select("servlet-mapping"); 258 | for(Element servlet : servletMappings){ 259 | String name = servlet.select("servlet-name").text(); 260 | String url = servlet.select("url-pattern").text(); 261 | assert name != null; 262 | assert url != null; 263 | servletNameToURL.put(name, url); 264 | servletURLToName.put(url, name); 265 | } 266 | } 267 | 268 | private void parseMimeAndMapping(Document document) throws FileNotFoundException { 269 | Elements mimeMappings = document.select("mime-mapping"); 270 | for(Element mime : mimeMappings){ 271 | String extension = mime.select("extension").text(); 272 | String mimeType = mime.select("mime-type").text(); 273 | mimeMapping.put(extension, mimeType); 274 | } 275 | 276 | } 277 | 278 | private void parseLoadOnStartupServlet(Document document){ 279 | Elements loadOnStartups = document.select("load-on-startup"); 280 | for (Element loadOnStartup : loadOnStartups){ 281 | Elements servletName = loadOnStartup.parent().select("servlet-name"); 282 | loadOnStartupServletClassNames.add(servletName.text()); 283 | } 284 | } 285 | 286 | private void parseWelcomeFileList(Document document){ 287 | Elements welcomeFiles = document.select("welcome-file-list welcome-file"); 288 | for (Element welcomeFile : welcomeFiles){ 289 | welcomeFileNames.add(welcomeFile.text()); 290 | } 291 | } 292 | 293 | private void handleLoadOnStartupServlet(){ 294 | for(String name: loadOnStartupServletClassNames){ 295 | try { 296 | getServlet(name); 297 | } catch (ServletException e) { 298 | LogFactory.get().error("get servlet {} failed at load on startup", name); 299 | } 300 | } 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/com/yzb/http/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package com.yzb.http; 2 | 3 | import cn.hutool.core.io.IoUtil; 4 | import cn.hutool.core.util.ArrayUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import com.yzb.common.Connector; 7 | import com.yzb.exception.ParseHttpRequestException; 8 | import com.yzb.common.Request; 9 | 10 | import javax.servlet.RequestDispatcher; 11 | import javax.servlet.ServletContext; 12 | import javax.servlet.ServletInputStream; 13 | import javax.servlet.http.Cookie; 14 | import javax.servlet.http.HttpSession; 15 | import java.io.*; 16 | import java.net.Socket; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.*; 19 | 20 | /** 21 | * @description: 处理HTTP请求的实现类 22 | * @author: BeckoninGshy 23 | * @create: 2021/1/24 16:17 24 | */ 25 | public class HttpRequest extends Request { 26 | 27 | private String requestContent; 28 | private String outlineMessage; 29 | private String forwardURI; 30 | 31 | private String charsetName = StandardCharsets.UTF_8.name(); //默认编码为utf-8 32 | 33 | private final Map headers; 34 | private final Map parameterMap; 35 | private Cookie[] cookies; 36 | private HttpSession session; 37 | 38 | private final Socket socket; 39 | private final Connector connector; 40 | private ApplicationContext servletContext; 41 | 42 | private boolean forwarding = false; 43 | 44 | public HttpRequest(Socket socket, Connector connector) throws ParseHttpRequestException { 45 | this.socket = socket; 46 | this.connector = connector; 47 | 48 | headers = new HashMap<>(); 49 | parameterMap = new HashMap<>(); 50 | cookies = new Cookie[0]; 51 | 52 | parseHttpRequestContent(); 53 | parseHttpRequestHeader(); 54 | parseHttpRequestParameter(); 55 | parseCookies(); 56 | 57 | if(connector instanceof HttpConnector){ 58 | this.servletContext = ((HttpConnector) connector).getServletContext(getRequestURI()); 59 | if(null == this.servletContext) throw new ParseHttpRequestException("get servlet context failed"); 60 | } 61 | 62 | } 63 | 64 | @Override 65 | public Cookie[] getCookies() { 66 | return cookies; 67 | } 68 | 69 | public String getJSessionIdFromCookie(){ 70 | if (null==cookies){ 71 | return null; 72 | } 73 | for (Cookie cookie:cookies){ 74 | if ("JSESSIONID".equals(cookie.getName())){ 75 | return cookie.getValue(); 76 | } 77 | } 78 | return null; 79 | } 80 | 81 | @Override 82 | public HttpSession getSession() { 83 | return session; 84 | } 85 | 86 | public void setSession(HttpSession session){ 87 | this.session = session; 88 | } 89 | 90 | @Override 91 | public long getDateHeader(String s) { 92 | if(headers.containsKey(s)) return -1; 93 | Date date = null; 94 | try{ 95 | date = new Date(headers.get(s).trim()); 96 | } catch (IllegalArgumentException e){ 97 | return -1; 98 | } 99 | return date.getTime(); 100 | } 101 | 102 | @Override 103 | public String getHeader(String s) { 104 | return headers.get(s); 105 | } 106 | 107 | @Override 108 | public Enumeration getHeaders(String s) { 109 | return Collections.enumeration(StrUtil.splitTrim(headers.get(s), HttpContant.HEADER_SEPARATOR)); 110 | } 111 | 112 | @Override 113 | public Enumeration getHeaderNames() { 114 | return Collections.enumeration(headers.keySet()); 115 | } 116 | 117 | @Override 118 | public int getIntHeader(String s) { 119 | return Integer.parseInt(headers.get(s)); 120 | } 121 | 122 | public String getRequestContent(){ 123 | return requestContent; 124 | } 125 | 126 | public String getOutlineMessage(){ 127 | return outlineMessage; 128 | } 129 | 130 | @Override 131 | public String getMethod(){ 132 | return StrUtil.subBefore(outlineMessage, " ", false); 133 | } 134 | 135 | @Override 136 | public String getProtocol() { 137 | return outlineMessage.substring(outlineMessage.lastIndexOf(' ')+1); 138 | } 139 | 140 | @Override 141 | public String getScheme() { 142 | return StrUtil.subBefore(getProtocol(), "/", false).toLowerCase(); 143 | } 144 | 145 | 146 | private String getWholeRequestURL(){ 147 | return StrUtil.subBetween(outlineMessage, " ", " "); 148 | } 149 | 150 | @Override 151 | public String getQueryString() { 152 | return StrUtil.subAfter(getWholeRequestURL(), "?", false); 153 | } 154 | 155 | public void setForwardURI(String uri){ 156 | this.forwardURI = servletContext.getContextPath() + uri; 157 | } 158 | 159 | public void setForwarding(){ 160 | forwarding = true; 161 | } 162 | 163 | public void setForwarded(){ 164 | forwarding = false; 165 | } 166 | 167 | public boolean isForwarding(){ 168 | return forwarding; 169 | } 170 | 171 | @Override 172 | public String getRequestURI() { 173 | if(isForwarding()) return forwardURI; 174 | return StrUtil.subBefore(getWholeRequestURL(), "?", false); 175 | } 176 | 177 | @Override 178 | public StringBuffer getRequestURL() { 179 | StringBuffer sb = new StringBuffer(); 180 | sb.append(getScheme()); 181 | sb.append("://"); 182 | sb.append(getLocalName()); 183 | sb.append(":"); 184 | sb.append(getLocalPort()); 185 | sb.append(getRequestURI()); 186 | return sb; 187 | } 188 | 189 | @Override 190 | public RequestDispatcher getRequestDispatcher(String s) { 191 | return new ApplicationRequestDispatcher(s); 192 | } 193 | 194 | @Override 195 | public String getParameter(String s) { 196 | if(getParameterValues(s) == null) return null; 197 | return getParameterValues(s)[0]; 198 | } 199 | 200 | @Override 201 | public Enumeration getParameterNames() { 202 | return Collections.enumeration(parameterMap.keySet()); 203 | } 204 | 205 | @Override 206 | public String[] getParameterValues(String s) { 207 | return parameterMap.getOrDefault(s,null); 208 | } 209 | 210 | @Override 211 | public Map getParameterMap() { 212 | return parameterMap; 213 | } 214 | 215 | @Override 216 | public String getCharacterEncoding() { 217 | return charsetName; 218 | } 219 | 220 | @Override 221 | public void setCharacterEncoding(String s) throws UnsupportedEncodingException { 222 | charsetName = s; 223 | requestContent = new String( s.getBytes(StandardCharsets.UTF_8.name()) , charsetName); 224 | } 225 | 226 | @Override 227 | public int getContentLength() { 228 | String len = null; 229 | if((len = getHeader(HttpContant.HEADER_CONTENT_LENGTH)) == null) return -1; 230 | return Integer.parseInt(len); 231 | } 232 | 233 | @Override 234 | public String getContentType() { 235 | return getHeader(HttpContant.HEADER_CONTENT_TYPE); 236 | } 237 | 238 | @Override 239 | public ServletInputStream getInputStream() throws IOException { 240 | byte[] bytes = getHttpRequestBodyString().getBytes(charsetName); 241 | ByteArrayInputStream byteArrayInputStream = IoUtil.toStream(bytes); 242 | return new ServletInputStream(){ 243 | public int read() { 244 | return byteArrayInputStream.read(); 245 | } 246 | }; 247 | } 248 | 249 | @Override 250 | public BufferedReader getReader() throws IOException { 251 | return new BufferedReader(new InputStreamReader(getInputStream(),charsetName)); 252 | } 253 | 254 | 255 | @Override 256 | public String getRemoteAddr() { 257 | return socket.getInetAddress().getHostAddress(); 258 | } 259 | 260 | @Override 261 | public String getRemoteHost() { 262 | return socket.getInetAddress().getHostName(); 263 | } 264 | 265 | @Override 266 | public int getRemotePort() { 267 | return socket.getPort(); 268 | } 269 | 270 | @Override 271 | public String getLocalName() { 272 | return socket.getLocalAddress().getHostName(); 273 | } 274 | 275 | @Override 276 | public String getLocalAddr() { 277 | return socket.getLocalAddress().getHostAddress(); 278 | } 279 | 280 | @Override 281 | public int getLocalPort() { 282 | return socket.getLocalPort(); 283 | } 284 | 285 | 286 | @Override 287 | public String getServerName() { 288 | return connector.getServer().getName(); 289 | } 290 | 291 | @Override 292 | public int getServerPort() { 293 | return connector.getPort(); 294 | } 295 | 296 | @Override 297 | public ServletContext getServletContext() { 298 | return servletContext; 299 | } 300 | 301 | public void setServletContext(ServletContext servletContext){ 302 | this.servletContext = (ApplicationContext) servletContext; 303 | } 304 | 305 | 306 | public Socket getSocket() { 307 | return socket; 308 | } 309 | 310 | private void parseHttpRequestContent() throws ParseHttpRequestException { 311 | 312 | ByteArrayOutputStream baso = null; 313 | try { 314 | InputStream inputStream = socket.getInputStream(); 315 | int bufferSize = 1024; 316 | byte[] buffer = new byte[1024]; 317 | baso = new ByteArrayOutputStream(); 318 | while (true) { 319 | int len = inputStream.read(buffer); 320 | if (len == -1) break; 321 | baso.write(buffer, 0, len); 322 | if (len != bufferSize) break; 323 | } 324 | requestContent = baso.toString(charsetName); 325 | } catch (IOException e) { 326 | throw new ParseHttpRequestException("解析Http请求错误:读取socket输入流错误"); 327 | } finally { 328 | try { 329 | baso.close(); 330 | } catch (IOException e) { 331 | e.printStackTrace(); 332 | } 333 | } 334 | } 335 | 336 | private void parseHttpRequestHeader() throws ParseHttpRequestException { 337 | String[] headerStrings = getHttpRequestHeaderString().split(HttpContant.LINE_TERMINATOR); 338 | outlineMessage = headerStrings[0]; 339 | for(int i = 1, n = headerStrings.length; i < n; i++){ 340 | String[] line = headerStrings[i].split(HttpContant.LINE_SEPARATOR); 341 | if(line.length != 2) 342 | throw new ParseHttpRequestException("解析请求头部错误!"); 343 | headers.put(line[0].trim(), line[1].trim()); 344 | } 345 | } 346 | 347 | private void parseHttpRequestParameter() throws ParseHttpRequestException { 348 | String parameterString = ""; 349 | if (HttpContant.REQUEST_METHOD_GET.equals(getMethod())) { 350 | parameterString = getQueryString(); 351 | }else if(HttpContant.REQUEST_METHOD_POST.equals(getMethod())) { //仅支持单次请求。例如表单提交 352 | parameterString = getHttpRequestBodyString(); 353 | } 354 | 355 | if(StrUtil.isEmpty(parameterString)) return; 356 | 357 | String[] parameters = parameterString.split(HttpContant.PARAMATERS_SEPARATOR); 358 | 359 | Map> parameterKVs = new HashMap<>(); 360 | for(int i = 0, n = parameters.length; i < n; i++){ 361 | String[] entries = parameters[i].split(HttpContant.PARAMATERSKV_SEPARATOR); 362 | if(entries.length != 2) 363 | throw new ParseHttpRequestException("解析请求参数错误!"); 364 | List values = parameterKVs.getOrDefault(entries[0], new ArrayList<>()); 365 | values.add(entries[1]); 366 | parameterKVs.put(entries[0], values); 367 | } 368 | // add values to origin map 369 | for(Map.Entry> entry : parameterKVs.entrySet()) { 370 | String[] origin = parameterMap.getOrDefault(entry.getKey(), new String[0]); 371 | String[] add = entry.getValue().toArray(new String[0]); 372 | int len = origin.length + add.length; 373 | String[] result = Arrays.copyOf(origin, len); 374 | System.arraycopy(add, 0, result, origin.length, add.length); 375 | parameterMap.put(entry.getKey(), result); 376 | } 377 | } 378 | 379 | private void parseCookies(){ 380 | String cookiesStr = headers.get("Cookie"); 381 | if(null == cookiesStr) return; 382 | List cookieList = new ArrayList<>(); 383 | String[] pairs = StrUtil.split(cookiesStr,";"); 384 | for(String pair : pairs){ 385 | if(StrUtil.isBlank(pair)){ 386 | continue; 387 | } 388 | String[] cookieKeyAndValues = StrUtil.split(pair,"="); 389 | String key = cookieKeyAndValues[0].trim(); 390 | String value = cookieKeyAndValues[1].trim(); 391 | Cookie cookie = new Cookie(key, value); 392 | cookieList.add(cookie); 393 | } 394 | this.cookies = ArrayUtil.toArray(cookieList, Cookie.class); 395 | } 396 | 397 | private String getHttpRequestHeaderString(){ 398 | return StrUtil.subBefore(requestContent, HttpContant.SEPARATOR_LINES,false); 399 | } 400 | 401 | private String getHttpRequestBodyString(){ 402 | return StrUtil.subAfter(requestContent, HttpContant.SEPARATOR_LINES,false); 403 | } 404 | } 405 | --------------------------------------------------------------------------------