簡單的Tomcat實現--2.2Tomcat的內建物件之engine
阿新 • • 發佈:2020-08-05
Engine
-
Engine在Tomcat中表示Servlet引擎,用來處理servlet 的請求
-
首先在server.xml中增加Engine節點,一個Engine節點可能對應多個Host,一個host也可能對應著多個Context
-
<?xml version="1.0" encoding="UTF-8"?> <Server> <Engine defaultHost="localhost"> <Host name="localhost"> <Context path="/b" docBase="d:/Java/JavaProject/Jerrymice/webapps/b" /> </Host> </Engine> </Server>
-
注意Engine節點有一個屬性值defaultHost指定了預設的host
-
-
ServerXmlUtil從配置檔案中解析出預設的host以及engine對應的多個host列表
-
public static String getEngineDefaultHost(){ String defaultHost = ""; try{ Document document = Jsoup.parse(Constant.serverXmlFile, "utf-8"); Element engine = document.select("Engine").first(); defaultHost = engine.attr("defaultHost"); }catch(IOException e){ LogFactory.get().info(e); } return defaultHost; } public static List<Host> getHosts(Engine engine) { List<Host> hosts = new ArrayList<>(); try { Document document = Jsoup.parse(Constant.serverXmlFile, "utf-8"); Elements elements = document.select("Host"); for (Element element : elements) { String name = element.attr("name"); Host host = new Host(name, engine); hosts.add(host); } }catch (IOException e){ LogFactory.get().info(e); } return hosts; }
-
-
建立Engine類,這個類包含裡兩個欄位,一個是其指定的預設host,另外一個是它對應的多個host
-
package jerrymice.catalina; import cn.hutool.log.LogFactory; import jerrymice.util.ServerXmlUtil; import java.util.List; /** * @author :xiaosong * @description:TODO * @date :2020/8/4 22:59 */ public class Engine { private String defaultHost; private List<Host> hosts; public Engine(){ this.defaultHost = ServerXmlUtil.getEngineDefaultHost(); this.hosts = ServerXmlUtil.getHosts(this); checkDefault(); } /** * 檢查時候存在預設的host,如果不存在的話丟擲異常 */ private void checkDefault(){ if (getDefaultHost() == null) { LogFactory.get().error("the default host does not exist!"); throw new RuntimeException("the default host does not exist!"); } } public Host getDefaultHost(){ for (Host host : hosts) { if (defaultHost.equals(host.getName())){ return host; } } return null; } }
-
-
將Request類中的Host欄位改為Engine,用engine的預設host作為原本request搜尋應用路徑的依據。
-
private Engine engine;
-
public void parseContext(){ String path = StrUtil.subBetween(uri, "/", "/"); if (null == path) { // 如果uri = /timeConsume.html,那麼path = null, 經過此處之後path=/ path = "/"; } else { // uri = /dir1/1.html, 那麼path= dir1, 經過此處之後path=/dir1 path = "/" + path; } // 根據獲取到的path去掃描得到的對映中去尋找這個資料夾 context = engine.getDefaultHost().getContext(path); if (context == null) { // 如果沒有獲取到這個context物件,那麼說明目錄中根本就沒有這個應用,或者本身就在根目錄下 context = engine.getDefaultHost().getContext("/"); } }
-
將Engine放到構造方法中接收
-
public Request(Socket socket, Engine engine) throws IOException { this.socket = socket; this.engine = engine; parseHttpRequest(); if (StrUtil.isEmpty(requestString)){ return; } parseUri(); parseContext(); // 比如 uri 是 /a/index.html, 獲取出來的 Context路徑不是 "/”, 那麼要修正 uri 為 /index.html if (!"/".equals(context.getPath())){ uri = StrUtil.removePrefix(uri, context.getPath()); } }
-
Request物件接收一個socket,從socket中獲取瀏覽器的請求資訊,接收一個engine,從engine中獲取伺服器中的應用資訊。
-
-
在Host類中新增engine,並在構造方法中接收
-
public Host(String name, Engine engine){ this.contextMap = new HashMap<>(); this.name = name; this.engine = engine; scanContextsOnWebAppsFolder(); scanContextsInServerXML(); }
-
構造方法接受兩個,一個是host本身的name,一個是host所屬的engine。然後host會獲取屬於他的Context物件,也就是伺服器上部署的應用。
-
-
Bootstrap中把Host的初始化去掉,改為Engine的初始化。
-
package jerrymice; import jerrymice.catalina.Context; import cn.hutool.core.io.FileUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.LogFactory; import cn.hutool.system.SystemUtil; import jerrymice.catalina.Engine; import jerrymice.catalina.Host; import jerrymice.http.Request; import jerrymice.http.Response; import jerrymice.util.Constant; import jerrymice.util.ServerXmlUtil; import sun.awt.windows.WPrinterJob; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.*; /** * @author :xiaosong * @description:專案的啟動類 * @date :2020/7/28 20:41 */ public class Bootstrap { /** * 定義伺服器的埠號 */ final static int PORT = 10086; public static void main(String[] args) { try { // 列印jvm資訊 logJvm(); // 在port埠上新建serverSocket ServerSocket serverSocket = new ServerSocket(PORT); // 建立host物件,host物件對於一個配置檔案來說是唯一的,所以在迴圈外建立 Engine engine = new Engine(); // 外部使用一個while迴圈,當處理完一個Socket的連結請求之後,再處理下一個連結請求 while (true) { Socket socket = serverSocket.accept(); // 使用lambda表示式代替Runnable Runnable runnable = () -> { try { // 獲取輸入流,這個輸入流表示的是收到一個瀏覽器客戶端的請求 Request request = new Request(socket, engine); System.out.println("瀏覽器的輸入資訊: \r\n" + request.getRequestString()); Response response = new Response(); // 先將html資訊寫入到response的Writer的StringWriter中 String uri; uri = request.getUri(); if (uri == null) { return; } Context context = request.getContext(); if ("/".equals(uri)) { String html = "Hello JerryMice"; response.getWriter().println(html); } else { // removePrefix()方法可以去掉字串指定的字首 String fileName = StrUtil.removePrefix(uri, "/"); File file = FileUtil.file(context.getDocBase(), fileName); if (file.exists()) { //如果檔案存在,那就去試圖訪問 String fileContent = FileUtil.readUtf8String(file); // 寫入到response中 response.getWriter().println(fileContent); // 判斷是否是模擬的耗時任務 if ("timeConsume.html".equals(fileName)) { ThreadUtil.sleep(1000); } } else { System.out.println("File not found!"); } } System.out.println(uri); // 開啟輸出流,準備給客戶端輸出資訊 handle200(socket, response); } catch (IOException e) { LogFactory.get().error(e); } }; jerrymice.util.ThreadUtil.run(runnable); } } catch (IOException e) { LogFactory.get().error(e); } } private static void logJvm() { // 建立一個Map用於儲存各種資訊 Map<String, String> infoMap = new LinkedHashMap<>(); infoMap.put("Server version", "JerryMice 1.0.0"); infoMap.put("Server build", "2020-08-03"); infoMap.put("OS:\t", SystemUtil.get("os.name")); infoMap.put("OS version", SystemUtil.get("os.version")); infoMap.put("Architecture", SystemUtil.get("os.arch")); infoMap.put("Java Home", SystemUtil.get("java.home")); infoMap.put("JSM Version", SystemUtil.get("java.runtime.version")); infoMap.put("JVM Vendor", SystemUtil.get("java.vm.specification.vendor")); Set<String> keys = infoMap.keySet(); for (String key : keys) { // 呼叫hutool的LogFactory工廠函式獲取logger,logger會自動根據log4j.properties來對Log4j的Logger進行配置 LogFactory.get().info(key + ":\t\t" + infoMap.get(key)); } } /** * @param socket: * @param response:Response物件,伺服器對瀏覽器請求的響應,可以通過response的getBody()獲取儲存在其中的html文字 * @throws IOException */ private static void handle200(Socket socket, Response response) throws IOException { // 獲取型別 String contentType = response.getContentType(); String headText = Constant.responseHead200; headText = StrUtil.format(headText, contentType); byte[] head = headText.getBytes(); // 獲取response中的html文字,這個html文字是通過writer寫到stringWriter字元流上的 byte[] body = response.getBody(); byte[] responseBytes = new byte[head.length + body.length]; ArrayUtil.copy(head, 0, responseBytes, 0, head.length); ArrayUtil.copy(body, 0, responseBytes, head.length, body.length); OutputStream outputStream = socket.getOutputStream(); outputStream.write(responseBytes); socket.close(); } }
-