程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 理解tomcat之搭建簡易http服務器,tomcat搭建服務器

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

編輯:JAVA綜合教程

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


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

1 Catalina模型

   首先我們先了解一下tomcat的大致工作原理。tomcat的核心是servlet容器,我們稱它為Catalina(為什麼叫這個名字?我也不知道 ̄へ ̄)。模型圖如1.1

                                             圖1.1

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

2 服務器搭建

  首先我們明確一下我們的服務器的功能點。

  1. 需要有一個類去接收http請求;

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

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

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

  UML圖如下2.1

    

                                     圖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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved