1. 程式人生 > 實用技巧 >簡單的Tomcat實現--2.2Tomcat的內建物件之engine

簡單的Tomcat實現--2.2Tomcat的內建物件之engine

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();
          }
      }