理解tomcat之搭建简易http服务器

来源:互联网 发布:三件套女装秋装淘宝 编辑:程序博客网 时间:2024/05/29 15:36

做过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 request23         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 parse32                 Request request = new Request(input);33                 request.parseUrl();34                 // create Response object35                 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 socket48                 socket.close();49                 //check if the previous URI is a shutdown command50                 shutdown = request.getUri().equals(SHUTDOWN_COMMAND);51             } catch (Exception e) {52                 e.printStackTrace();53                 System.exit(1);54             }55         }56     }57 }
复制代码

  我们的服务器启动入口放在了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     @Override101     public String getProtocol() {102         return null;103     }104 105     @Override106     public String getScheme() {107         return null;108     }109 110     @Override111     public String getServerName() {112         return null;113     }114 115     @Override116     public int getServerPort() {117         return 0;118     }119 120     @Override121     public BufferedReader getReader() throws IOException {122         return null;123     }124 125     @Override126     public String getRemoteAddr() {127         return null;128     }129 130     @Override131     public String getRemoteHost() {132         return null;133     }134 135     @Override136     public void setAttribute(String name, Object o) {137 138     }139 140     @Override141     public void removeAttribute(String name) {142 143     }144 145     @Override146     public Locale getLocale() {147         return null;148     }149 150     @Override151     public Enumeration getLocales() {152         return null;153     }154 155     @Override156     public boolean isSecure() {157         return false;158     }159 160     @Override161     public RequestDispatcher getRequestDispatcher(String path) {162         return null;163     }164 165     @Override166     public String getRealPath(String path) {167         return null;168     }169 170     @Override171     public int getRemotePort() {172         return 0;173     }174 175     @Override176     public String getLocalName() {177         return null;178     }179 180     @Override181     public String getLocalAddr() {182         return null;183     }184 185     @Override186     public int getLocalPort() {187         return 0;188     }189 }
复制代码

     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     @Override100     public boolean isCommitted() {101         return false;102     }103 104     @Override105     public void reset() {106 107     }108 109     @Override110     public void setLocale(Locale loc) {111 112     }113 114     @Override115     public Locale getLocale() {116         return null;117     }118 }
复制代码

     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     @Override14     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     @Override22     public String getServletInfo() {23         return "this is v2 info";24     }25 26     @Override27     public void destroy() {28         System.out.println("destroy");29     }30 }
复制代码

    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 }
复制代码

    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 }
复制代码

  StaticResourceProcessor则简单的调用response的sendStaticResource()方法来返回静态资源。

    最后我们还需要建一个辅助类Constants指定静态资源的存放路径

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

 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芋头
出处:http://www.cnblogs.com/maypattis/
本博客中未标明转载的文章归作者mayday芋头和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


转载自: http://www.cnblogs.com/maypattis/p/5549613.html

原创粉丝点击