理解tomcat之搭建簡易http服務器

   作過java web的同窗都對tomcat很是熟悉。咱們在使用tomcat帶來的便利的同時,是否想過tomcat是如何工做的呢?tomcat本質是一個http服務器,本篇文章將搭建一個簡單的http服務器。html

1 Catalina模型

   首先咱們先了解一下tomcat的大體工做原理。tomcat的核心是servlet容器,咱們稱它爲Catalina(爲何叫這個名字?我也不知道 ̄へ ̄)。模型圖如1.1前端

     

                                        圖1.1java

    Connector是用來「鏈接」容器裏邊的請求的。它的工做是爲接收到每個 HTTP 請求構造一個 request 和 response 對象。而後它把流程傳遞給容器。容器從鏈接器接收到 requset 和 response 對象以後調用 servlet 的 service 方法用於響應。謹記,這個描述僅僅是冰山一角而已。這裏容器作了至關多事情。例如,在它調用 servlet 的 service 方法以前,它必須加載這個 servlet,驗證用戶(假如須要的話),更新用戶會話等等。以此爲思路,咱們就開始咱們的構造http服務器之旅吧。web

2 服務器搭建

  首先咱們明確一下咱們的服務器的功能點。數組

  1. 須要有一個類去接收http請求;瀏覽器

  2. 須要一個自定義Request類和Response類,把接收到的請求構形成這兩個類;tomcat

  3. 根據請求的格式來肯定處理方式:返回靜態資源 or 進入Servlet ?服務器

  4. 須要一個Servlet類執行業務邏輯app

  UML圖以下2.1socket

    

                                     圖2.1

2.1 HttpServer 

首先構造HttpServer類

 1 public class HttpServer {
 2 
 3 
 4     private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
 5     private static boolean shutdown = false;
 6 
 7     public static void main(String[] args) {
 8         HttpServer server = new HttpServer();
 9         server.await();
10     }
11 
12     public static void await() {
13         ServerSocket serverSocket = null;
14         int port = 8080;
15         try {
16             serverSocket = new ServerSocket(port, 1,
17                     InetAddress.getByName("127.0.0.1"));
18         } catch (IOException e) {
19             e.printStackTrace();
20             System.exit(1);
21         }
22         // Loop waiting for a request
23         while (!shutdown) {
24             Socket socket = null;
25             InputStream input = null;
26             OutputStream output = null;
27             try {
28                 socket = serverSocket.accept();
29                 input = socket.getInputStream();
30                 output = socket.getOutputStream();
31                 // create Request object and parse
32                 Request request = new Request(input);
33                 request.parseUrl();
34                 // create Response object
35                 Response response = new Response(output);
36                 response.setRequest(request);
37 
38                 if (request.getUri().startsWith("/v2/")) {
39                     ServletProcessor processor = new ServletProcessor();
40                     processor.process(request, response);
41                 }
42                 else {
43                     StaticResourceProcessor processor =
44                             new StaticResourceProcessor();
45                     processor.process(request, response);
46                 }
47                 // Close the socket
48                 socket.close();
49                 //check if the previous URI is a shutdown command
50                 shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
51             } catch (Exception e) {
52                 e.printStackTrace();
53                 System.exit(1);
54             }
55         }
56     }
57 }
View Code

  咱們的服務器啓動入口放在了HttpServer裏面。await()方法負責接收Socket鏈接,只有當用戶輸入了表明shutdown的URL時,服務器纔會中止運行。Request類提供瞭解析請求的功能,根據請求的url,來決定是返回靜態資源,或者進入對應的servlet類執行service邏輯。

    這裏咱們須要關注一下ServerSocket這個類的用法。Socket 類表明一個客戶端套接字,即任什麼時候候你想鏈接到一個遠程服務器應用的時候,你都會第一時間想到這個類。而ServerSocket 和 Socket 不一樣,服務器套接字的角色是等待來自客戶端的鏈接請求。一旦服 務器套接字得到一個鏈接請求,它建立一個 Socket 實例來與客戶端進行通訊。 ServletSocket套接字的其中一個構造函數爲

public ServerSocket(int port, int backLog, InetAddress bindingAddress); 

    port表明端口號,backLog表明這個套接字可支持的最大鏈接數量,bindingAddress表明服務器綁定的地址。一旦你有一個 ServerSocket 實例,你能夠經過調用 ServerSocket 類的 accept 方法j。這個監聽當前地址的當前端口上的請求,方法只會在有鏈接請求時纔會返回,而且返回值是一個 Socket 類的實例。

2.2 Request Response

     servlet 的 service 方法從 servlet 容器中接收一個 javax.servlet.ServletRequest 實例 和一個 javax.servlet.ServletResponse 實例。這就是說對於每個 HTTP 請求,servlet 容器 必須構造一個 ServletRequest 對象和一個 ServletResponse 對象並把它們傳遞給正在服務的 servlet 的 service 方法。 

 

  1 public class Request implements ServletRequest {
  2 
  3     private InputStream input;
  4     private String uri;
  5 
  6     public Request(InputStream input) {
  7         this.input = input;
  8     }
  9 
 10     public String getUri(){
 11         return uri;
 12     }
 13 
 14     public void parseUrl() {
 15         StringBuffer request = new StringBuffer(2048);
 16         int i;
 17         byte[] buffer = new byte[2048];
 18 
 19         try {
 20             i = input.read(buffer);
 21         } catch (IOException e) {
 22             e.printStackTrace();
 23             i = -1;
 24         }
 25 
 26         for (int j = 0; j < i; j++) {
 27             request.append((char) buffer[j]);
 28         }
 29 
 30         System.out.print(request.toString());
 31         uri = parseUri(request.toString());
 32     }
 33 
 34     private static String parseUri(String requestString) {
 35         int index1, index2;
 36         index1 = requestString.indexOf(' ');
 37         if (index1 != -1) {
 38             index2 = requestString.indexOf(' ', index1 + 1);
 39             if (index2 > index1)
 40                 return requestString.substring(index1 + 1, index2);
 41         }
 42         return null;
 43     }
 44 
 45     @Override
 46     public Object getAttribute(String name) {
 47         return null;
 48     }
 49 
 50     @Override
 51     public Enumeration getAttributeNames() {
 52         return null;
 53     }
 54 
 55     @Override
 56     public String getCharacterEncoding() {
 57         return null;
 58     }
 59 
 60     @Override
 61     public void setCharacterEncoding(String env) throws UnsupportedEncodingException {
 62 
 63     }
 64 
 65     @Override
 66     public int getContentLength() {
 67         return 0;
 68     }
 69 
 70     @Override
 71     public String getContentType() {
 72         return null;
 73     }
 74 
 75     @Override
 76     public ServletInputStream getInputStream() throws IOException {
 77         return null;
 78     }
 79 
 80     @Override
 81     public String getParameter(String name) {
 82         return null;
 83     }
 84 
 85     @Override
 86     public Enumeration getParameterNames() {
 87         return null;
 88     }
 89 
 90     @Override
 91     public String[] getParameterValues(String name) {
 92         return new String[0];
 93     }
 94 
 95     @Override
 96     public Map getParameterMap() {
 97         return null;
 98     }
 99 
100     @Override
101     public String getProtocol() {
102         return null;
103     }
104 
105     @Override
106     public String getScheme() {
107         return null;
108     }
109 
110     @Override
111     public String getServerName() {
112         return null;
113     }
114 
115     @Override
116     public int getServerPort() {
117         return 0;
118     }
119 
120     @Override
121     public BufferedReader getReader() throws IOException {
122         return null;
123     }
124 
125     @Override
126     public String getRemoteAddr() {
127         return null;
128     }
129 
130     @Override
131     public String getRemoteHost() {
132         return null;
133     }
134 
135     @Override
136     public void setAttribute(String name, Object o) {
137 
138     }
139 
140     @Override
141     public void removeAttribute(String name) {
142 
143     }
144 
145     @Override
146     public Locale getLocale() {
147         return null;
148     }
149 
150     @Override
151     public Enumeration getLocales() {
152         return null;
153     }
154 
155     @Override
156     public boolean isSecure() {
157         return false;
158     }
159 
160     @Override
161     public RequestDispatcher getRequestDispatcher(String path) {
162         return null;
163     }
164 
165     @Override
166     public String getRealPath(String path) {
167         return null;
168     }
169 
170     @Override
171     public int getRemotePort() {
172         return 0;
173     }
174 
175     @Override
176     public String getLocalName() {
177         return null;
178     }
179 
180     @Override
181     public String getLocalAddr() {
182         return null;
183     }
184 
185     @Override
186     public int getLocalPort() {
187         return 0;
188     }
189 }
View Code

     Request類表明一個 request 對象並被傳遞給 servlet 的 service 方法。就自己而言,它必須實現 javax.servlet.ServletRequest 接口。這個類必須提供這個接口全部方法的實現。不過,咱們想要讓它很是簡單而且僅僅提供實現其中一些方法,好比解析url的功能。在Request初始化時初始化成員變量inputStream,而且用parseUrl()方法建立了一個字節數組來讀入輸入流,並轉化爲成一個StringBuffer對象,進而解析url。

 1 public class Response implements ServletResponse {  2 
 3     private static final int BUFFER_SIZE = 1024;  4  Request request;  5  OutputStream output;  6  PrintWriter writer;  7 
 8     public Response(OutputStream output) {  9         this.output = output;  10  }  11 
 12     public void setRequest(Request request) {  13         this.request = request;  14  }  15 
 16     public void sendStaticResource() throws IOException {  17         byte[] bytes = new byte[BUFFER_SIZE];  18         FileInputStream fis = null;  19         try {  20             File file = new File(HttpServer.WEB_ROOT, request.getUri());  21             if (file.exists()) {  22                 fis = new FileInputStream(file);  23                 int ch = fis.read(bytes, 0, BUFFER_SIZE);  24                 while (ch != -1) {  25                     output.write(bytes, 0, ch);  26                     ch = fis.read(bytes, 0, BUFFER_SIZE);  27  }  28             } else {  29                 String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
 30                         "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" +
 31                         "<h1>File Not Found</h1>";  32  output.write(errorMessage.getBytes());  33  }  34         } catch (Exception e) {  35  System.out.println(e.toString());  36         } finally {  37             if (fis != null)  38  fis.close();  39  }  40  }  41 
 42  @Override  43     public String getCharacterEncoding() {  44         return null;  45  }  46 
 47  @Override  48     public String getContentType() {  49         return null;  50  }  51 
 52  @Override  53     public ServletOutputStream getOutputStream() throws IOException {  54         return null;  55  }  56 
 57  @Override  58     public PrintWriter getWriter() throws IOException {  59         writer = new PrintWriter(output, true);  60         return writer;  61 
 62  }  63 
 64  @Override  65     public void setCharacterEncoding(String charset) {  66 
 67  }  68 
 69  @Override  70     public void setContentLength(int len) {  71 
 72  }  73 
 74  @Override  75     public void setContentType(String type) {  76 
 77  }  78 
 79  @Override  80     public void setBufferSize(int size) {  81 
 82  }  83 
 84  @Override  85     public int getBufferSize() {  86         return 0;  87  }  88 
 89  @Override  90     public void flushBuffer() throws IOException {  91 
 92  }  93 
 94  @Override  95     public void resetBuffer() {  96 
 97  }  98 
 99  @Override 100     public boolean isCommitted() { 101         return false; 102  } 103 
104  @Override 105     public void reset() { 106 
107  } 108 
109  @Override 110     public void setLocale(Locale loc) { 111 
112  } 113 
114  @Override 115     public Locale getLocale() { 116         return null; 117  } 118 }
View Code

     Response類則提供了發送靜態資源的功能。sendStaticResource()方法根據request內解析過的url,在本地尋找指定文件。若是找獲得,把文件內容讀出到瀏覽器,若是找不到,那麼返回404的錯誤碼。

2.3 PrimitiveServlet類    

 1 public class PrimitiveServlet implements Servlet {
 2 
 3     @Override
 4     public void init(ServletConfig config) throws ServletException {
 5         System.out.println("init");
 6     }
 7 
 8     @Override
 9     public ServletConfig getServletConfig() {
10         return null;
11     }
12 
13     @Override
14     public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
15         System.out.println("from service");
16         PrintWriter out = res.getWriter();
17         out.println("Hello. Roses are red.");
18         out.print("Violets are blue.");
19     }
20 
21     @Override
22     public String getServletInfo() {
23         return "this is v2 info";
24     }
25 
26     @Override
27     public void destroy() {
28         System.out.println("destroy");
29     }
30 }
View Code

    PrimitiveServlet實現了標準的Servlet接口。咱們簡單的實現了Servlet生命週期的其餘方法,並在service()方法咱們作了最簡單的向瀏覽器吐文字的操做。

2.4 ServletProcessor 和 StaticResourceProcessor

 1 public class ServletProcessor {
 2 
 3     public void process(Request request, Response response) {
 4         String uri = request.getUri();
 5         String servletName = uri.substring(uri.lastIndexOf("/") + 1);
 6         Class myClass = null;
 7         try {
 8             myClass = Class.forName("tomcat.v2." + servletName);
 9         } catch (ClassNotFoundException e) {
10             System.out.println(e.toString());
11         }
12         Servlet servlet = null;
13         try {
14             servlet = (Servlet) myClass.newInstance();
15             servlet.service((ServletRequest) request, (ServletResponse) response);
16         } catch (Exception e) {
17             e.printStackTrace();
18         } catch (Throwable e) {
19             e.printStackTrace();
20         }
21     }
22 }
View Code

    ServletProcessor是處理serlvet請求的類。它的做用在於,根據請求的路徑實例化對應的Servlet,而且執行該Servlet的service()方法。

 1 public class StaticResourceProcessor {
 2 
 3     public void process(Request request, Response response){
 4         try{
 5             response.sendStaticResource();
 6         }catch (IOException e){
 7             e.printStackTrace();
 8         }
 9     }
10 }
View Code

  StaticResourceProcessor則簡單的調用response的sendStaticResource()方法來返回靜態資源。

    最後咱們還須要建一個輔助類Constants指定靜態資源的存放路徑

1 public class Constants {
2     public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
3 }
View Code

 3 啓動服務器

    啓動main函數來啓動咱們的http服務器。在瀏覽器輸入http://localhost:8080/test,獲得的結果如圖3.1

       

 

                                               圖3.1

這個url訪問的是個人電腦中的一個文件test,它的存儲路徑爲 /Users/wangyu/Documents/workspace/Tomcat/webroot/test,即當前項目的webroot目錄下。

咱們還能夠輸入一個servlet地址查看效果。在瀏覽器輸入http://localhost:8080/v2/PrimitiveServlet,獲得的結果如圖3.2

        

                           圖3.2  

     這和咱們的PrimitiveServlet的輸出是一致的。

4 結語

    固然,tomcat的功能比咱們的簡易http服務器強大的多。可是經過這個最簡單的http服務器,是否讓你對http服務器有更深的一點了解了呢?

 

參考資料:

1 深刻剖析Tomcat

 

 

做者: mayday芋頭
本博客中未標明轉載的文章歸做者mayday芋頭和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。
相關文章
相關標籤/搜索