├── .classpath ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── README.md ├── lib ├── commons-beanutils.jar ├── commons-collections.jar ├── commons-daemon.jar ├── commons-digester.jar ├── commons-logging.jar ├── commons-modeler.jar ├── jaas.jar ├── jakarta-regexp-1.2.jar ├── jcert.jar ├── jnet.jar ├── jsse.jar ├── mx4j.jar ├── naming-common.jar ├── naming-factory.jar ├── naming-resources.jar ├── servlet.jar ├── tomcat-util.jar ├── xercesImpl.jar └── xmlParserAPIs.jar ├── src ├── ex01 │ └── WebServer │ │ ├── HttpServer.java │ │ ├── Request.java │ │ └── Response.java ├── ex02 │ ├── ServletContainer1 │ │ ├── Constants.java │ │ ├── HttpServer.java │ │ ├── Request.java │ │ ├── Response.java │ │ ├── ServletProcessor.java │ │ ├── StaticResourceProcessor.java │ │ └── servletContainer.md │ └── ServletContainer2 │ │ ├── Constants.java │ │ ├── HttpServer2.java │ │ ├── Request.java │ │ ├── RequestFacade.java │ │ ├── Response.java │ │ ├── ResponseFacade.java │ │ ├── ServletProcessor2.java │ │ └── StaticResourceProcessor.java └── ex03 │ └── connector │ ├── RequestStream.java │ ├── ResponseStream.java │ ├── ResponseWriter.java │ ├── ServletProcessor.java │ ├── StaticResourceProcessor.java │ ├── StringManager.java │ ├── http │ ├── Constants.java │ ├── HttpConnector.java │ ├── HttpHeader.java │ ├── HttpProcessor.java │ ├── HttpRequest.java │ ├── HttpRequestLine.java │ ├── HttpResponse.java │ ├── RequestFacade.java │ ├── ResponseFacade.java │ └── SocketInputStream.java │ └── startup │ └── Boostrap.java └── webroot ├── PrimitiveServlet.class ├── PrimitiveServlet.java ├── images └── logo.gif └── index.html /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | HowTomcatWorks 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 5 | org.eclipse.jdt.core.compiler.compliance=1.8 6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 11 | org.eclipse.jdt.core.compiler.source=1.8 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HowTomcatWorks 2 | 3 | # 目录 4 | 5 | [一、简单的Web服务器](#一简单的web服务器) 6 | 7 | - 构成 8 | - 类说明 9 | - 运行 10 | 11 | [二、简单的Servlet容器](#二简单的servlet容器) 12 | 13 | - 类图 14 | - 类说明 15 | - 运行 16 | 17 | [三、连接器](#三连接器) 18 | 19 | - 作用 20 | 21 | 22 | - 类说明 23 | 24 | - connector 25 | 26 | - 。。。 27 | 28 | 29 | - http 30 | - startup 31 | 32 | ## 一、简单的Web服务器 33 | 34 | ### 构成 35 | 我们的服务器程序在ex01包里,由下面的三个java文件组成: 36 | - HttpServer 37 | - Request 38 | - Response 39 | 40 | ### 类说明 41 | 1. HttpServer: 代表一个web服务器,能提供公共静态final变量`WEB_ROOT`所在目录和其中子目录的所有静态资源 42 | - await():使用while循环接收来自客户端的请求,当请求为`SHUTDOWN`时终止循环 43 | 2. Request:代表一个HTTP请求,从负责与客户端通信的Socket中传递过来的`InputStream`构造成这个类的一个实例 44 | - parse():读取HTTP里的请求 45 | - parseUri():从parse后的字符串中解析uri 46 | 3. Response: 代表一个HTTP响应,通过传递由套接字获得的`OutputStream`和`HttpServer`的`await()`来构造 47 | - sendStaticResource():用来发送一个静态资源,没有则打印错误码 48 | 49 | ### 运行 50 | 51 | 1. 启动HttpServer类 52 | 2. 打开你的浏览器并在地址栏或网址框中敲入下面的命令:http://localhost:8080/index.html 53 | 3. 结果: 54 | - 控制台 55 | 56 | ``` 57 | GET /index.html HTTP/1.1 58 | Host: localhost:8080 59 | Connection: keep-alive 60 | Upgrade-Insecure-Requests: 1 61 | User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 62 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 63 | DNT: 1 64 | Accept-Encoding: gzip, deflate, sdch, br 65 | Accept-Language: zh-CN,zh;q=0.8 66 | Cookie: oracle.uix=0^^GMT+8:00^p; _ga=GA1.1.1778589621.1515157486; Hm_lvt_512065947708a980c982b4401d14c2f5=1515157487,1516670331 67 | ``` 68 | 69 | - 浏览器 70 | 71 | 72 | ![](https://i.loli.net/2018/02/19/5a8ade1a3c57e.png) 73 | 74 | *** 75 | 76 | ## 二、简单的Servlet容器 77 | ### 类图 78 | 类图大致如下: 79 | ![类图](https://i.loli.net/2018/02/18/5a88bf4e12d38.png) 80 | 81 | ### 类说明 82 | #### ex01 83 | 1. HttpServer1:和上面的HttpServer差不多,不过同时支持静态资源和Servlet 84 | - await():可以将请求分发给`StaticResourceProcessor`和`ServletProcessor`,如果字符串包含`/servlet/`,请求将分发给后者 85 | 2. Request:继承自`ServletRequest`,代表一个Request对象并传给service方法 86 | - parse():读取HTTP里的请求 87 | - parseUri():从解析后的字符串中解析uri 88 | 3. Response:继承自`ServletResponse`,代表一个Response对象并传给service方法 89 | - sendStaticResource():用来发送一个静态资源 90 | - getWriter():重写方法,使用了`PrintWriter` 91 | 4. ServletProcessor1:加载Servlet 92 | - process():获取URI,创建一个类加载器并告诉类加载器加载的位置,即`Constants`指向的目录 93 | 5. StaticResourceProcessor:提供静态资源 94 | - process():调用Response的`sendStaticResource()` 95 | 6. Constants:指向静态资源保存的位置 96 | 97 | #### ex02 改进版 98 | 在`ex01`中,我们直接使用Request和Response直接向上转型为`ServletRequest `和 `ServletResponse`,这会危害到安全性。知道这点的程序员可以将`ServletRequest `和 `ServletResponse`向下转型为Request和Response,并调用他们的方法,如Request的`parseUri()`和Response的`sendStaticResource()`。一个解决方法是将它们的方法设为私有的,但这在内部就不可见了。当然可以设为默认修饰的,这样就内部可见了。但我们使用另一种更优雅的解决方法:**Facade模式**,又称门面模式、外观模式。 99 | 100 | ![Facade](https://i.loli.net/2018/02/18/5a88dea0856ae.jpg) 101 | 1. RequestFacade:构造了一个 `RequestFacade` 对象并把它传递给 `service() `方法,而不是向下转换Request 对象为` ServletRequest `对象并传递给 service 方法。Servlet 程序员仍然可以向下转换`ServletRequest `实例为 `RequestFacade`,不过它们只可以访问 `ServletRequest` 接口里边的公共方法。现在 `parseUri()` 方法就是安全的了 。 102 | 103 | - RequestFacade(Request request) :将传入的Request赋值给私有的`ServletRequest`对象 104 | 105 | 2. ResponseFacade:同上 106 | 107 | ### 运行 108 | 109 | 1. 启动HttpServer类 110 | 2. 打开你的浏览器并在地址栏或网址框中敲入下面的命令:http://localhost:8080/servlet/PrimitiveServlet 111 | 3. 结果: 112 | - 控制台 113 | 114 | ``` 115 | GET /servlet/PrimitiveServlet HTTP/1.1 116 | Host: localhost:8080 117 | Connection: keep-alive 118 | Upgrade-Insecure-Requests: 1 119 | User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 120 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 121 | DNT: 1 122 | Accept-Encoding: gzip, deflate, sdch, br 123 | Accept-Language: zh-CN,zh;q=0.8 124 | Cookie: oracle.uix=0^^GMT+8:00^p; _ga=GA1.1.1778589621.1515157486; Hm_lvt_512065947708a980c982b4401d14c2f5=1515157487,1516670331 125 | 126 | 127 | from service 128 | ``` 129 | 130 | *** 131 | 132 | ## 三、连接器 133 | 134 | Catalina 中有两个主要的模块:`连接器`和`容器` 135 | 136 | ### 作用 137 | 138 | 解析HTTP请求头部并让Servlet可以获得头部,cookie,参数名/值等。 139 | 140 | ### 类说明 141 | 142 | StringManager:为每个包都分配一个属性文件,用来存储错误信息,一个StringManager实例被一个包下的文件共享,这是一个单例类 143 | 144 | - getManager():获取一个StringManager实例 145 | 146 | - getString():返回一个错误信息 147 | 148 | 149 | ServletProcessor :加载Servlet 150 | 151 | - process():获取URI,创建一个类加载器并告诉类加载器加载的位置,并调用Servlet的`service`方法 152 | 153 | StaticResourceProcessor :提供静态资源请求 154 | 155 | - process():调用`HttpResponse` 的`sendStaticResource()` 156 | 157 | RequestStream:继承自`ServletInputStream ` 158 | 159 | ResponseStream :继承自`ServletOutputStream ` 160 | 161 | ResponseWriter :一个自动刷新的`PrintWriter` 162 | 163 | #### http 164 | 165 | 1. HttpConnector :连接器,继承Runable,创建一个套接字等待HTTP请求 166 | - run():等待HTTP请求,为每个请求创建`HttpProcessor`实例,并调用`HttpProcessor`的`process`方法 167 | - start():启动一个新线程 168 | 2. HttpProcessor : 169 | - process(): 170 | - 创建一个`HttpRequest`对象 171 | - 创建一个`HttpResponse`对象 172 | - 解析的HTTP请求的第一行和头部,并放入`HttpRequest`对象 173 | - 解析`HttpRequest`和`HttpResponse`对象到`ServletProcessor `或`StaticResourceProcessor ` 174 | - parseHeaders():解析请求头部 175 | - parseRequest():解析请求行 176 | - normalize():规范化地址 177 | 3. HttpRequest:HTTP请求类 178 | - 读取套接字的输入流 179 | - 解析请求行 180 | - 解析头部 181 | - 解析cookies 182 | - 获取参数 183 | 4. HttpResponse:HTTP响应类 184 | 5. HttpHeader:HTTP头部类 185 | 6. RequestFacade:`HttpRequest`门面类 186 | 7. ResponseFacade:`HttpResponse`门面类 187 | 8. HttpRequestLine :HTTP请求行类 188 | 9. SocketInputStream :扩展InputStream以获得更高效的读取行 189 | 10. Constants:指向静态资源保存的位置 190 | 191 | #### startup 192 | 193 | - boostrap:启动连接器 194 | 195 | 196 | -------------------------------------------------------------------------------- /lib/commons-beanutils.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/commons-beanutils.jar -------------------------------------------------------------------------------- /lib/commons-collections.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/commons-collections.jar -------------------------------------------------------------------------------- /lib/commons-daemon.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/commons-daemon.jar -------------------------------------------------------------------------------- /lib/commons-digester.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/commons-digester.jar -------------------------------------------------------------------------------- /lib/commons-logging.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/commons-logging.jar -------------------------------------------------------------------------------- /lib/commons-modeler.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/commons-modeler.jar -------------------------------------------------------------------------------- /lib/jaas.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/jaas.jar -------------------------------------------------------------------------------- /lib/jakarta-regexp-1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/jakarta-regexp-1.2.jar -------------------------------------------------------------------------------- /lib/jcert.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/jcert.jar -------------------------------------------------------------------------------- /lib/jnet.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/jnet.jar -------------------------------------------------------------------------------- /lib/jsse.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/jsse.jar -------------------------------------------------------------------------------- /lib/mx4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/mx4j.jar -------------------------------------------------------------------------------- /lib/naming-common.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/naming-common.jar -------------------------------------------------------------------------------- /lib/naming-factory.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/naming-factory.jar -------------------------------------------------------------------------------- /lib/naming-resources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/naming-resources.jar -------------------------------------------------------------------------------- /lib/servlet.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/servlet.jar -------------------------------------------------------------------------------- /lib/tomcat-util.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/tomcat-util.jar -------------------------------------------------------------------------------- /lib/xercesImpl.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/xercesImpl.jar -------------------------------------------------------------------------------- /lib/xmlParserAPIs.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/lib/xmlParserAPIs.jar -------------------------------------------------------------------------------- /src/ex01/WebServer/HttpServer.java: -------------------------------------------------------------------------------- 1 | package ex01.WebServer; 2 | import java.io.File; 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.InetAddress; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | 10 | /** 11 | * @author lc 12 | * 一个简单的 Web 服务器 13 | */ 14 | public class HttpServer { 15 | 16 | public static final String WEB_ROOT = 17 | System.getProperty("user.dir") + File.separator + "webroot"; 18 | private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 19 | private boolean shutdown = false; 20 | 21 | public static void main(String[] args) { 22 | HttpServer server = new HttpServer(); 23 | server.await(); 24 | } 25 | 26 | public void await() { 27 | ServerSocket serverSocket = null; 28 | int port = 8080; 29 | try { 30 | serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | System.exit(1); 34 | } 35 | //循环等待请求 36 | while(!shutdown) { 37 | Socket socket = null; 38 | InputStream input = null; 39 | OutputStream output = null; 40 | try { 41 | socket = serverSocket.accept(); 42 | input = socket.getInputStream(); 43 | output = socket.getOutputStream(); 44 | 45 | //创建请求对象并解析请求数据 46 | Request request = new Request(input); 47 | request.parse(); 48 | 49 | //创建响应对象 50 | Response response = new Response(output); 51 | response.setRequest(request); 52 | response.sendStaticResource(); 53 | 54 | //关闭Socket 55 | socket.close(); 56 | //检测请求是不是SHUTDOWN命令 57 | shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | continue; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/ex01/WebServer/Request.java: -------------------------------------------------------------------------------- 1 | package ex01.WebServer; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | /** 7 | * @author lc 8 | * HTTP 请求 9 | */ 10 | public class Request { 11 | 12 | private InputStream input; 13 | private String uri; 14 | 15 | public Request(InputStream input) { 16 | this.input = input; 17 | } 18 | 19 | public void parse() { 20 | // 21 | StringBuffer request = new StringBuffer(20); 22 | int i; 23 | byte[] buffer = new byte[2048]; 24 | try { 25 | i = input.read(buffer); 26 | } catch (IOException e) { 27 | e.printStackTrace(); 28 | i = -1; 29 | } 30 | for(int j = 0; j < i; j++) { 31 | request.append((char)buffer[j]); 32 | } 33 | System.out.println(request.toString()); 34 | uri = parseUri(request.toString()); 35 | } 36 | 37 | private String parseUri(String requestString) { 38 | int index1, index2; 39 | index1 = requestString.indexOf(' '); 40 | if(index1 != -1) { 41 | index2 = requestString.indexOf(' ', index1 + 1); 42 | if(index2 > index1) 43 | return requestString.substring(index1 + 1, index2); 44 | } 45 | return null; 46 | } 47 | 48 | public String getUri() { 49 | return uri; 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/ex01/WebServer/Response.java: -------------------------------------------------------------------------------- 1 | package ex01.WebServer; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | 8 | /** 9 | * @author lc 10 | * HTTP 响应 11 | */ 12 | public class Response { 13 | 14 | private static final int BUFFER_SIZE = 1024; 15 | Request request; 16 | OutputStream output; 17 | 18 | public Response(OutputStream output) { 19 | this.output = output; 20 | } 21 | 22 | public void setRequest(Request request) { 23 | this.request = request; 24 | } 25 | 26 | /** 27 | * @throws IOException 28 | * 发送一个静态资源,例如一个 HTML 文件 29 | */ 30 | public void sendStaticResource() throws IOException { 31 | byte[] bytes = new byte[BUFFER_SIZE]; 32 | FileInputStream fis = null; 33 | try { 34 | File file = new File(HttpServer.WEB_ROOT, request.getUri()); 35 | //将原始数据发送给浏览器 36 | if(file.exists()) { 37 | fis = new FileInputStream(file); 38 | int ch = fis.read(bytes, 0, BUFFER_SIZE); 39 | while(ch != -1) { 40 | output.write(bytes, 0, ch); 41 | ch = fis.read(bytes, 0, BUFFER_SIZE); 42 | } 43 | } else { 44 | //文件不存在 45 | String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 46 | "Content-Type: text/html\r\n" + 47 | "Content-Length: 23\r\n" + 48 | "\r\n" + 49 | "

File Not Found

"; 50 | output.write(errorMessage.getBytes()); 51 | } 52 | 53 | } catch (Exception e) { 54 | System.out.println(e.toString()); 55 | } finally { 56 | if(fis != null) 57 | fis.close(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/Constants.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer1; 2 | 3 | import java.io.File; 4 | 5 | public class Constants { 6 | 7 | public static final String WEB_ROOT = 8 | System.getProperty("user.dir") + File.separator + "webroot"; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/HttpServer.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer1; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.InetAddress; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | 10 | /** 11 | * 类似于 ex01 里边的简单服务器应用程序的HttpServer 类。不过,在这个应用程序里边 12 | * HttpServer 类可以同时提供静态资源和 servlet 13 | * @author lc 14 | */ 15 | public class HttpServer { 16 | 17 | private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 18 | private boolean shutdown = false; 19 | 20 | public static void main(String[] args) { 21 | HttpServer server = new HttpServer(); 22 | server.await(); 23 | } 24 | 25 | /** 26 | * 请求可以分发给一个 StaticResourceProcessor 或者一个 ServletProcessor。 27 | * 假如 URI 包括字符串/servlet/的话,请求将会转发到后面去。 28 | * 不然的话,请求将会传递给 StaticResourceProcessor 实例 instance 29 | */ 30 | public void await() { 31 | ServerSocket serverSocket = null; 32 | int port = 8080; 33 | try { 34 | serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | System.exit(1); 38 | } 39 | //循环等待请求 40 | while(!shutdown) { 41 | Socket socket = null; 42 | InputStream input = null; 43 | OutputStream output = null; 44 | try { 45 | socket = serverSocket.accept(); 46 | input = socket.getInputStream(); 47 | output = socket.getOutputStream(); 48 | 49 | //创建请求对象并解析 50 | Request request = new Request(input); 51 | request.parse(); 52 | 53 | //创建一个响应对象 54 | Response response = new Response(output); 55 | response.setRequest(request); 56 | 57 | //检查是否是 servlet 请求或者是 58 | //静态资源 59 | //一个以"/servlet/" 开头的 servlet 请求 60 | if(request.getUri().startsWith("/servlet/")) { 61 | ServletProcessor processor = new ServletProcessor(); 62 | processor.process(request, response); 63 | } else { 64 | StaticResourceProcessor processor = new StaticResourceProcessor(); 65 | processor.process(request, response); 66 | } 67 | 68 | //关闭 socket 69 | socket.close(); 70 | //检查 URI 是否是 shutdown 命令 71 | shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | System.exit(1); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/Request.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer1; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.UnsupportedEncodingException; 7 | import java.util.Enumeration; 8 | import java.util.Locale; 9 | import java.util.Map; 10 | 11 | import javax.servlet.AsyncContext; 12 | import javax.servlet.DispatcherType; 13 | import javax.servlet.RequestDispatcher; 14 | import javax.servlet.ServletContext; 15 | import javax.servlet.ServletInputStream; 16 | import javax.servlet.ServletRequest; 17 | import javax.servlet.ServletResponse; 18 | 19 | /** 20 | * 代表一个 request 对象并被传递给 servlet 的 service 方法 21 | * 实现 javax.servlet.ServletRequest 接口,仅实现部分方法 22 | * @author lc 23 | */ 24 | public class Request implements ServletRequest { 25 | 26 | private InputStream input; 27 | private String uri; 28 | 29 | public Request(InputStream input) { 30 | this.input = input; 31 | } 32 | 33 | public String getUri() { 34 | return uri; 35 | } 36 | 37 | public void parse() { 38 | //从 socket 读取字符 39 | StringBuffer request = new StringBuffer(2048); 40 | int i; 41 | byte[] buffer = new byte[2048]; 42 | try { 43 | i = input.read(buffer); 44 | } catch (IOException e) { 45 | e.printStackTrace(); 46 | i = -1; 47 | } 48 | for(int j = 0; j < i; j++) { 49 | request.append((char)buffer[j]); 50 | } 51 | System.out.println(request.toString()); 52 | uri = parseUri(request.toString()); 53 | } 54 | 55 | private String parseUri(String requestString) { 56 | int index1, index2; 57 | index1 = requestString.indexOf(" "); 58 | if(index1 != -1) { 59 | index2 = requestString.indexOf(" ", index1 + 1); 60 | if(index2 > index1) { 61 | return requestString.substring(index1 + 1, index2); 62 | } 63 | } 64 | return null; 65 | } 66 | 67 | @Override 68 | public AsyncContext getAsyncContext() { 69 | return null; 70 | } 71 | 72 | @Override 73 | public Object getAttribute(String arg0) { 74 | return null; 75 | } 76 | 77 | @Override 78 | public Enumeration getAttributeNames() { 79 | return null; 80 | } 81 | 82 | @Override 83 | public String getCharacterEncoding() { 84 | return null; 85 | } 86 | 87 | @Override 88 | public int getContentLength() { 89 | return 0; 90 | } 91 | 92 | @Override 93 | public long getContentLengthLong() { 94 | return 0; 95 | } 96 | 97 | @Override 98 | public String getContentType() { 99 | return null; 100 | } 101 | 102 | @Override 103 | public DispatcherType getDispatcherType() { 104 | return null; 105 | } 106 | 107 | @Override 108 | public ServletInputStream getInputStream() throws IOException { 109 | return null; 110 | } 111 | 112 | @Override 113 | public String getLocalAddr() { 114 | return null; 115 | } 116 | 117 | @Override 118 | public String getLocalName() { 119 | return null; 120 | } 121 | 122 | @Override 123 | public int getLocalPort() { 124 | return 0; 125 | } 126 | 127 | @Override 128 | public Locale getLocale() { 129 | return null; 130 | } 131 | 132 | @Override 133 | public Enumeration getLocales() { 134 | return null; 135 | } 136 | 137 | @Override 138 | public String getParameter(String arg0) { 139 | return null; 140 | } 141 | 142 | @Override 143 | public Map getParameterMap() { 144 | return null; 145 | } 146 | 147 | @Override 148 | public Enumeration getParameterNames() { 149 | return null; 150 | } 151 | 152 | @Override 153 | public String[] getParameterValues(String arg0) { 154 | return null; 155 | } 156 | 157 | @Override 158 | public String getProtocol() { 159 | return null; 160 | } 161 | 162 | @Override 163 | public BufferedReader getReader() throws IOException { 164 | return null; 165 | } 166 | 167 | @Override 168 | public String getRealPath(String arg0) { 169 | return null; 170 | } 171 | 172 | @Override 173 | public String getRemoteAddr() { 174 | return null; 175 | } 176 | 177 | @Override 178 | public String getRemoteHost() { 179 | return null; 180 | } 181 | 182 | @Override 183 | public int getRemotePort() { 184 | return 0; 185 | } 186 | 187 | @Override 188 | public RequestDispatcher getRequestDispatcher(String arg0) { 189 | return null; 190 | } 191 | 192 | @Override 193 | public String getScheme() { 194 | return null; 195 | } 196 | 197 | @Override 198 | public String getServerName() { 199 | return null; 200 | } 201 | 202 | @Override 203 | public int getServerPort() { 204 | return 0; 205 | } 206 | 207 | @Override 208 | public ServletContext getServletContext() { 209 | return null; 210 | } 211 | 212 | @Override 213 | public boolean isAsyncStarted() { 214 | return false; 215 | } 216 | 217 | @Override 218 | public boolean isAsyncSupported() { 219 | return false; 220 | } 221 | 222 | @Override 223 | public boolean isSecure() { 224 | return false; 225 | } 226 | 227 | @Override 228 | public void removeAttribute(String arg0) { 229 | 230 | } 231 | 232 | @Override 233 | public void setAttribute(String arg0, Object arg1) { 234 | 235 | } 236 | 237 | @Override 238 | public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException { 239 | 240 | } 241 | 242 | @Override 243 | public AsyncContext startAsync() throws IllegalStateException { 244 | return null; 245 | } 246 | 247 | @Override 248 | public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) throws IllegalStateException { 249 | return null; 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/Response.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer1; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | import java.io.PrintWriter; 9 | import java.util.Locale; 10 | 11 | import javax.servlet.ServletOutputStream; 12 | import javax.servlet.ServletResponse; 13 | 14 | /** 15 | * 实现了 javax.servlet.ServletResponse 16 | * @author lc 17 | */ 18 | public class Response implements ServletResponse { 19 | 20 | private static final int BUFFER_SIZE = 1024; 21 | Request request; 22 | OutputStream output; 23 | PrintWriter writer; 24 | 25 | public Response(OutputStream output) { 26 | this.output = output; 27 | } 28 | 29 | public void setRequest(Request request) { 30 | this.request = request; 31 | } 32 | 33 | //这个方法用于服务静态页面 34 | public void sendStaticResource() throws IOException { 35 | byte[] bytes = new byte[BUFFER_SIZE]; 36 | FileInputStream fis = null; 37 | try { 38 | File file = new File(Constants.WEB_ROOT, request.getUri()); 39 | fis = new FileInputStream(file); 40 | 41 | /*Http Response 数据结构 42 | * 协议 状态码 描述 例如:(HTTP 200 OK) 43 | * *(( general-header | response-header | entity-header ) CRLF) 44 | * CRLF 45 | * [ message-body ] 46 | * Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF 47 | */ 48 | int ch = fis.read(bytes, 0, BUFFER_SIZE); 49 | while(ch != -1) { 50 | output.write(bytes, 0, ch); 51 | ch = fis.read(bytes, 0, BUFFER_SIZE); 52 | } 53 | } catch (FileNotFoundException e) { 54 | String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 55 | "Content-Type: text/html\r\n" + 56 | "Content-Length: 23\r\n" + 57 | "\r\n" + 58 | "

File Not Found

"; 59 | output.write(errorMessage.getBytes()); 60 | } finally { 61 | if(fis != null) { 62 | fis.close(); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | public PrintWriter getWriter() throws IOException { 69 | //autoflush 为 true, println() 会刷新,但 print() 不会 70 | //因此,任何 print 方法的调用都会发生在 servlet 的 service 方法的最后一行,输出将不会 71 | //被发送到浏览器。这个缺点将会在下一个应用程序中修复 72 | writer = new PrintWriter(output, true); 73 | return writer; 74 | } 75 | 76 | @Override 77 | public void flushBuffer() throws IOException { 78 | 79 | } 80 | 81 | @Override 82 | public int getBufferSize() { 83 | return 0; 84 | } 85 | 86 | @Override 87 | public String getCharacterEncoding() { 88 | return null; 89 | } 90 | 91 | @Override 92 | public String getContentType() { 93 | return null; 94 | } 95 | 96 | @Override 97 | public Locale getLocale() { 98 | return null; 99 | } 100 | 101 | @Override 102 | public ServletOutputStream getOutputStream() throws IOException { 103 | return null; 104 | } 105 | 106 | @Override 107 | public boolean isCommitted() { 108 | return false; 109 | } 110 | 111 | @Override 112 | public void reset() { 113 | 114 | } 115 | 116 | @Override 117 | public void resetBuffer() { 118 | 119 | } 120 | 121 | @Override 122 | public void setBufferSize(int arg0) { 123 | 124 | } 125 | 126 | @Override 127 | public void setCharacterEncoding(String arg0) { 128 | 129 | } 130 | 131 | @Override 132 | public void setContentLength(int arg0) { 133 | 134 | } 135 | 136 | @Override 137 | public void setContentLengthLong(long arg0) { 138 | 139 | } 140 | 141 | @Override 142 | public void setContentType(String arg0) { 143 | 144 | } 145 | 146 | @Override 147 | public void setLocale(Locale arg0) { 148 | 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/ServletProcessor.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer1; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.net.URLStreamHandler; 8 | 9 | import javax.servlet.Servlet; 10 | 11 | public class ServletProcessor { 12 | 13 | /** 14 | * process 方法加载 servlet 15 | * @param request 16 | * @param response 17 | */ 18 | public void process(Request request, Response response) { 19 | 20 | String uri = request.getUri(); 21 | // URI 是以下形式的:/servlet/servletName 其中,servletName 是 servlet 类的名字 22 | String servletName = uri.substring(uri.lastIndexOf("/") + 1); 23 | URLClassLoader loader = null; 24 | 25 | try { 26 | //创建 URLClassLoader 并告诉这个类加载器要加载的类的位置 27 | //对于这个 servlet 容器,类加载器直接在 Constants 指向的目录里边查找 28 | URL[] urls = new URL[1]; 29 | URLStreamHandler streamHandler = null; 30 | File classPath = new File(Constants.WEB_ROOT); 31 | 32 | String repository = (new URL("file", null, classPath.getCanonicalPath() + 33 | File.separator)).toString(); 34 | urls[0] = new URL(null, repository, streamHandler); 35 | 36 | loader = new URLClassLoader(urls); 37 | } catch (IOException e) { 38 | System.out.println(e.toString()); 39 | } 40 | 41 | Class myClass = null; 42 | try { 43 | myClass = loader.loadClass(servletName); 44 | } catch(ClassNotFoundException e) { 45 | e.printStackTrace(); 46 | } 47 | 48 | //创建一个 servlet 类加载器的实例, 把它向下转型为 javax.servlet.Servlet, 49 | //并调用 servlet 的 service 方法 50 | Servlet servlet = null; 51 | try { 52 | servlet = (Servlet) myClass.newInstance(); 53 | servlet.service(request, response); 54 | } catch (Exception e) { 55 | System.out.println(e.toString()); 56 | } catch (Throwable e) { 57 | System.out.println(e.toString()); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/StaticResourceProcessor.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer1; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * 提供静态资源请求 7 | * @author lc 8 | */ 9 | public class StaticResourceProcessor { 10 | 11 | public void process(Request request, Response response) { 12 | try { 13 | response.sendStaticResource(); 14 | } catch (IOException e) { 15 | e.printStackTrace(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer1/servletContainer.md: -------------------------------------------------------------------------------- 1 | 一个全功能的 servlet 容器会为 servlet 的每个 HTTP 请求做下面一些工作: 2 | - 当第一次调用 servlet 的时候,加载该 servlet 类并调用 servlet 的 init 方法(仅仅一次)。 3 | - 对每次请求,构造一个` javax.servlet.ServletRequest `实例和一个 4 | `javax.servlet.ServletResponse `实例。 5 | 调用 servlet 的 service 方法,同时传递 `ServletRequest `和 `ServletResponse `对象。 6 | - 当 servlet 类被关闭的时候,调用 servlet 的 destroy 方法并卸载 servlet 类。 7 | 本章的第一个 servlet 容器不是全功能的。因此,她不能运行什么除了非常简单的 servlet, 8 | 而且也不调用 servlet 的 init 方法和 destroy 方法。相反它做了下面的事情: 9 | - 等待 HTTP 请求。 构造一个` ServletRequest `对象和一个 `ServletResponse `对象。 10 | - 假如该请求需要一个静态资源的话,调用 `StaticResourceProcessor `实例的 process 方 11 | 法,同时传递` ServletRequest `和 `ServletResponse `对象。 12 | - 假如该请求需要一个 servlet 的话,加载 servlet 类并调用 servlet 的 service 方法, 13 | 同时传递` ServletRequest `和 `ServletResponse` 对象。 14 | 15 | 第一个 servlet 容器不是全功能的。因此,她不能运行什么除了非常简单的 servlet, 16 | 而且也不调用 servlet 的` init `方法和 destroy 方法。相反它做了下面的事情: 17 | - 等待 HTTP 请求。 构造一个` ServletRequest `对象和一个 `ServletResponse `对象。 18 | - 假如该请求需要一个静态资源的话,调用 `StaticResourceProcessor` 实例的 process 方 19 | 法,同时传递` ServletRequest `和 `ServletResponse `对象。 20 | - 假如该请求需要一个 servlet 的话,加载 servlet 类并调用 servlet 的 service 方法, 21 | 同时传递` ServletRequest` 和 `ServletResponse `对象。 22 | 注意:在这个 servlet 容器中,每一次 servlet 被请求的时候, servlet 类都会被加载。 23 | 24 | http://machineName:port/staticResource 可以请求一个静态资源。 25 | 为了请求一个 servlet,你可以使用下面的 URL: 26 | http://machineName:port/servlet/servletClass 27 | 因此,假如你在本地请求一个名为 `PrimitiveServlet `的 servlet,你在浏览器的地址栏或 28 | 者网址框中敲入: 29 | http://localhost:8080/servlet/PrimitiveServlet 30 | servlet 容器可以就提供 PrimitiveServlet 了。不过,假如你调用其他 servlet,如 31 | ModernServlet, servlet 容器将会抛出一个异常。 32 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/Constants.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.File; 4 | 5 | public class Constants { 6 | 7 | public static final String WEB_ROOT = 8 | System.getProperty("user.dir") + File.separator + "webroot"; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/HttpServer2.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.InetAddress; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | 10 | /** 11 | * 类似于 ex01 里边的简单服务器应用程序的HttpServer 类。不过,在这个应用程序里边 12 | * HttpServer 类可以同时提供静态资源和 servlet 13 | * @author lc 14 | */ 15 | public class HttpServer2 { 16 | 17 | private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; 18 | private boolean shutdown = false; 19 | 20 | public static void main(String[] args) { 21 | HttpServer2 server = new HttpServer2(); 22 | server.await(); 23 | } 24 | 25 | /** 26 | * 请求可以分发给一个 StaticResourceProcessor 或者一个 ServletProcessor。 27 | * 假如 URI 包括字符串/servlet/的话,请求将会转发到后面去。 28 | * 不然的话,请求将会传递给 StaticResourceProcessor 实例 instance 29 | */ 30 | public void await() { 31 | ServerSocket serverSocket = null; 32 | int port = 8080; 33 | try { 34 | serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | System.exit(1); 38 | } 39 | //循环等待请求 40 | while(!shutdown) { 41 | Socket socket = null; 42 | InputStream input = null; 43 | OutputStream output = null; 44 | try { 45 | socket = serverSocket.accept(); 46 | input = socket.getInputStream(); 47 | output = socket.getOutputStream(); 48 | 49 | //创建请求对象并解析 50 | Request request = new Request(input); 51 | request.parse(); 52 | 53 | //创建一个响应对象 54 | Response response = new Response(output); 55 | response.setRequest(request); 56 | 57 | //检查是否是 servlet 请求或者是 58 | //静态资源 59 | //一个以"/servlet/" 开头的 servlet 请求 60 | if(request.getUri().startsWith("/servlet/")) { 61 | ServletProcessor2 processor = new ServletProcessor2(); 62 | processor.process(request, response); 63 | } else { 64 | StaticResourceProcessor processor = new StaticResourceProcessor(); 65 | processor.process(request, response); 66 | } 67 | 68 | //关闭 socket 69 | socket.close(); 70 | //检查 URI 是否是 shutdown 命令 71 | shutdown = request.getUri().equals(SHUTDOWN_COMMAND); 72 | } catch (Exception e) { 73 | e.printStackTrace(); 74 | System.exit(1); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/Request.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.UnsupportedEncodingException; 7 | import java.util.Enumeration; 8 | import java.util.Locale; 9 | import java.util.Map; 10 | 11 | import javax.servlet.AsyncContext; 12 | import javax.servlet.DispatcherType; 13 | import javax.servlet.RequestDispatcher; 14 | import javax.servlet.ServletContext; 15 | import javax.servlet.ServletInputStream; 16 | import javax.servlet.ServletRequest; 17 | import javax.servlet.ServletResponse; 18 | 19 | /** 20 | * 代表一个 request 对象并被传递给 servlet 的 service 方法 21 | * 实现 javax.servlet.ServletRequest 接口,仅实现部分方法 22 | * @author lc 23 | */ 24 | public class Request implements ServletRequest { 25 | 26 | private InputStream input; 27 | private String uri; 28 | 29 | public Request(InputStream input) { 30 | this.input = input; 31 | } 32 | 33 | public String getUri() { 34 | return uri; 35 | } 36 | 37 | public void parse() { 38 | //从 socket 读取字符 39 | StringBuffer request = new StringBuffer(2048); 40 | int i; 41 | byte[] buffer = new byte[2048]; 42 | try { 43 | i = input.read(buffer); 44 | } catch (IOException e) { 45 | e.printStackTrace(); 46 | i = -1; 47 | } 48 | for(int j = 0; j < i; j++) { 49 | request.append((char)buffer[j]); 50 | } 51 | System.out.println(request.toString()); 52 | uri = parseUri(request.toString()); 53 | } 54 | 55 | private String parseUri(String requestString) { 56 | int index1, index2; 57 | index1 = requestString.indexOf(" "); 58 | if(index1 != -1) { 59 | index2 = requestString.indexOf(" ", index1 + 1); 60 | if(index2 > index1) { 61 | return requestString.substring(index1 + 1, index2); 62 | } 63 | } 64 | return null; 65 | } 66 | 67 | @Override 68 | public AsyncContext getAsyncContext() { 69 | return null; 70 | } 71 | 72 | @Override 73 | public Object getAttribute(String arg0) { 74 | return null; 75 | } 76 | 77 | @Override 78 | public Enumeration getAttributeNames() { 79 | return null; 80 | } 81 | 82 | @Override 83 | public String getCharacterEncoding() { 84 | return null; 85 | } 86 | 87 | @Override 88 | public int getContentLength() { 89 | return 0; 90 | } 91 | 92 | @Override 93 | public long getContentLengthLong() { 94 | return 0; 95 | } 96 | 97 | @Override 98 | public String getContentType() { 99 | return null; 100 | } 101 | 102 | @Override 103 | public DispatcherType getDispatcherType() { 104 | return null; 105 | } 106 | 107 | @Override 108 | public ServletInputStream getInputStream() throws IOException { 109 | return null; 110 | } 111 | 112 | @Override 113 | public String getLocalAddr() { 114 | return null; 115 | } 116 | 117 | @Override 118 | public String getLocalName() { 119 | return null; 120 | } 121 | 122 | @Override 123 | public int getLocalPort() { 124 | return 0; 125 | } 126 | 127 | @Override 128 | public Locale getLocale() { 129 | return null; 130 | } 131 | 132 | @Override 133 | public Enumeration getLocales() { 134 | return null; 135 | } 136 | 137 | @Override 138 | public String getParameter(String arg0) { 139 | return null; 140 | } 141 | 142 | @Override 143 | public Map getParameterMap() { 144 | return null; 145 | } 146 | 147 | @Override 148 | public Enumeration getParameterNames() { 149 | return null; 150 | } 151 | 152 | @Override 153 | public String[] getParameterValues(String arg0) { 154 | return null; 155 | } 156 | 157 | @Override 158 | public String getProtocol() { 159 | return null; 160 | } 161 | 162 | @Override 163 | public BufferedReader getReader() throws IOException { 164 | return null; 165 | } 166 | 167 | @Override 168 | public String getRealPath(String arg0) { 169 | return null; 170 | } 171 | 172 | @Override 173 | public String getRemoteAddr() { 174 | return null; 175 | } 176 | 177 | @Override 178 | public String getRemoteHost() { 179 | return null; 180 | } 181 | 182 | @Override 183 | public int getRemotePort() { 184 | return 0; 185 | } 186 | 187 | @Override 188 | public RequestDispatcher getRequestDispatcher(String arg0) { 189 | return null; 190 | } 191 | 192 | @Override 193 | public String getScheme() { 194 | return null; 195 | } 196 | 197 | @Override 198 | public String getServerName() { 199 | return null; 200 | } 201 | 202 | @Override 203 | public int getServerPort() { 204 | return 0; 205 | } 206 | 207 | @Override 208 | public ServletContext getServletContext() { 209 | return null; 210 | } 211 | 212 | @Override 213 | public boolean isAsyncStarted() { 214 | return false; 215 | } 216 | 217 | @Override 218 | public boolean isAsyncSupported() { 219 | return false; 220 | } 221 | 222 | @Override 223 | public boolean isSecure() { 224 | return false; 225 | } 226 | 227 | @Override 228 | public void removeAttribute(String arg0) { 229 | 230 | } 231 | 232 | @Override 233 | public void setAttribute(String arg0, Object arg1) { 234 | 235 | } 236 | 237 | @Override 238 | public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException { 239 | 240 | } 241 | 242 | @Override 243 | public AsyncContext startAsync() throws IllegalStateException { 244 | return null; 245 | } 246 | 247 | @Override 248 | public AsyncContext startAsync(ServletRequest arg0, ServletResponse arg1) throws IllegalStateException { 249 | return null; 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/RequestFacade.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.UnsupportedEncodingException; 6 | import java.util.Enumeration; 7 | import java.util.Locale; 8 | import java.util.Map; 9 | 10 | import javax.servlet.RequestDispatcher; 11 | import javax.servlet.ServletContext; 12 | import javax.servlet.ServletInputStream; 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | 16 | 17 | public class RequestFacade implements ServletRequest { 18 | 19 | private ServletRequest request = null; 20 | 21 | public RequestFacade(Request request) { 22 | this.request = request; 23 | } 24 | 25 | @Override 26 | public Object getAttribute(String attribute) { 27 | return request.getAttribute(attribute); 28 | } 29 | 30 | @Override 31 | public Enumeration getAttributeNames() { 32 | return request.getAttributeNames(); 33 | } 34 | 35 | @Override 36 | public String getCharacterEncoding() { 37 | return null; 38 | } 39 | 40 | @Override 41 | public int getContentLength() { 42 | return 0; 43 | } 44 | 45 | public long getContentLengthLong() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public String getContentType() { 51 | return null; 52 | } 53 | 54 | 55 | @Override 56 | public ServletInputStream getInputStream() throws IOException { 57 | // TODO Auto-generated method stub 58 | return null; 59 | } 60 | 61 | @Override 62 | public Locale getLocale() { 63 | // TODO Auto-generated method stub 64 | return null; 65 | } 66 | 67 | @Override 68 | public Enumeration getLocales() { 69 | // TODO Auto-generated method stub 70 | return null; 71 | } 72 | 73 | @Override 74 | public String getParameter(String arg0) { 75 | // TODO Auto-generated method stub 76 | return null; 77 | } 78 | 79 | @Override 80 | public Map getParameterMap() { 81 | // TODO Auto-generated method stub 82 | return null; 83 | } 84 | 85 | @Override 86 | public Enumeration getParameterNames() { 87 | // TODO Auto-generated method stub 88 | return null; 89 | } 90 | 91 | @Override 92 | public String[] getParameterValues(String arg0) { 93 | // TODO Auto-generated method stub 94 | return null; 95 | } 96 | 97 | @Override 98 | public String getProtocol() { 99 | // TODO Auto-generated method stub 100 | return null; 101 | } 102 | 103 | @Override 104 | public BufferedReader getReader() throws IOException { 105 | // TODO Auto-generated method stub 106 | return null; 107 | } 108 | 109 | @Override 110 | public String getRealPath(String arg0) { 111 | // TODO Auto-generated method stub 112 | return null; 113 | } 114 | 115 | @Override 116 | public String getRemoteAddr() { 117 | // TODO Auto-generated method stub 118 | return null; 119 | } 120 | 121 | @Override 122 | public String getRemoteHost() { 123 | // TODO Auto-generated method stub 124 | return null; 125 | } 126 | 127 | @Override 128 | public RequestDispatcher getRequestDispatcher(String arg0) { 129 | // TODO Auto-generated method stub 130 | return null; 131 | } 132 | 133 | @Override 134 | public String getScheme() { 135 | // TODO Auto-generated method stub 136 | return null; 137 | } 138 | 139 | @Override 140 | public String getServerName() { 141 | // TODO Auto-generated method stub 142 | return null; 143 | } 144 | 145 | @Override 146 | public int getServerPort() { 147 | // TODO Auto-generated method stub 148 | return 0; 149 | } 150 | 151 | @Override 152 | public boolean isSecure() { 153 | // TODO Auto-generated method stub 154 | return false; 155 | } 156 | 157 | @Override 158 | public void removeAttribute(String arg0) { 159 | // TODO Auto-generated method stub 160 | 161 | } 162 | 163 | @Override 164 | public void setAttribute(String arg0, Object arg1) { 165 | // TODO Auto-generated method stub 166 | 167 | } 168 | 169 | @Override 170 | public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException { 171 | // TODO Auto-generated method stub 172 | 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/Response.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | import java.io.PrintWriter; 9 | import java.util.Locale; 10 | 11 | import javax.servlet.ServletOutputStream; 12 | import javax.servlet.ServletResponse; 13 | 14 | /** 15 | * 实现了 javax.servlet.ServletResponse 16 | * @author lc 17 | */ 18 | public class Response implements ServletResponse { 19 | 20 | private static final int BUFFER_SIZE = 1024; 21 | Request request; 22 | OutputStream output; 23 | PrintWriter writer; 24 | 25 | public Response(OutputStream output) { 26 | this.output = output; 27 | } 28 | 29 | public void setRequest(Request request) { 30 | this.request = request; 31 | } 32 | 33 | //这个方法用于服务静态页面 34 | public void sendStaticResource() throws IOException { 35 | byte[] bytes = new byte[BUFFER_SIZE]; 36 | FileInputStream fis = null; 37 | try { 38 | File file = new File(Constants.WEB_ROOT, request.getUri()); 39 | fis = new FileInputStream(file); 40 | 41 | /*Http Response 数据结构 42 | * 协议 状态码 描述 例如:(HTTP 200 OK) 43 | * *(( general-header | response-header | entity-header ) CRLF) 44 | * CRLF 45 | * [ message-body ] 46 | * Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF 47 | */ 48 | int ch = fis.read(bytes, 0, BUFFER_SIZE); 49 | while(ch != -1) { 50 | output.write(bytes, 0, ch); 51 | ch = fis.read(bytes, 0, BUFFER_SIZE); 52 | } 53 | } catch (FileNotFoundException e) { 54 | String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 55 | "Content-Type: text/html\r\n" + 56 | "Content-Length: 23\r\n" + 57 | "\r\n" + 58 | "

File Not Found

"; 59 | output.write(errorMessage.getBytes()); 60 | } finally { 61 | if(fis != null) { 62 | fis.close(); 63 | } 64 | } 65 | } 66 | 67 | @Override 68 | public PrintWriter getWriter() throws IOException { 69 | //autoflush 为 true, println() 会刷新,但 print() 不会 70 | //因此,任何 print 方法的调用都会发生在 servlet 的 service 方法的最后一行,输出将不会 71 | writer = new PrintWriter(output, true); 72 | return writer; 73 | } 74 | 75 | @Override 76 | public void flushBuffer() throws IOException { 77 | 78 | } 79 | 80 | @Override 81 | public int getBufferSize() { 82 | return 0; 83 | } 84 | 85 | @Override 86 | public String getCharacterEncoding() { 87 | return null; 88 | } 89 | 90 | @Override 91 | public String getContentType() { 92 | return null; 93 | } 94 | 95 | @Override 96 | public Locale getLocale() { 97 | return null; 98 | } 99 | 100 | @Override 101 | public ServletOutputStream getOutputStream() throws IOException { 102 | return null; 103 | } 104 | 105 | @Override 106 | public boolean isCommitted() { 107 | return false; 108 | } 109 | 110 | @Override 111 | public void reset() { 112 | 113 | } 114 | 115 | @Override 116 | public void resetBuffer() { 117 | 118 | } 119 | 120 | @Override 121 | public void setBufferSize(int arg0) { 122 | 123 | } 124 | 125 | @Override 126 | public void setCharacterEncoding(String arg0) { 127 | 128 | } 129 | 130 | @Override 131 | public void setContentLength(int arg0) { 132 | 133 | } 134 | 135 | @Override 136 | public void setContentLengthLong(long arg0) { 137 | 138 | } 139 | 140 | @Override 141 | public void setContentType(String arg0) { 142 | 143 | } 144 | 145 | @Override 146 | public void setLocale(Locale arg0) { 147 | 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/ResponseFacade.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.util.Locale; 6 | 7 | import javax.servlet.ServletOutputStream; 8 | import javax.servlet.ServletResponse; 9 | 10 | public class ResponseFacade implements ServletResponse { 11 | 12 | private Response response; 13 | 14 | public ResponseFacade(Response response) { 15 | this.response = response; 16 | } 17 | 18 | @Override 19 | public void flushBuffer() throws IOException { 20 | response.flushBuffer(); 21 | } 22 | 23 | @Override 24 | public int getBufferSize() { 25 | return response.getBufferSize(); 26 | } 27 | 28 | @Override 29 | public String getCharacterEncoding() { 30 | return response.getCharacterEncoding(); 31 | } 32 | 33 | @Override 34 | public String getContentType() { 35 | return response.getContentType(); 36 | } 37 | 38 | @Override 39 | public Locale getLocale() { 40 | return response.getLocale(); 41 | } 42 | 43 | @Override 44 | public ServletOutputStream getOutputStream() throws IOException { 45 | return response.getOutputStream(); 46 | } 47 | 48 | @Override 49 | public PrintWriter getWriter() throws IOException { 50 | return response.getWriter(); 51 | } 52 | 53 | @Override 54 | public boolean isCommitted() { 55 | return response.isCommitted(); 56 | } 57 | 58 | @Override 59 | public void reset() { 60 | response.reset(); 61 | } 62 | 63 | @Override 64 | public void resetBuffer() { 65 | // TODO Auto-generated method stub 66 | 67 | } 68 | 69 | @Override 70 | public void setBufferSize(int arg0) { 71 | // TODO Auto-generated method stub 72 | 73 | } 74 | 75 | @Override 76 | public void setCharacterEncoding(String arg0) { 77 | // TODO Auto-generated method stub 78 | 79 | } 80 | 81 | @Override 82 | public void setContentLength(int arg0) { 83 | // TODO Auto-generated method stub 84 | 85 | } 86 | 87 | @Override 88 | public void setContentLengthLong(long arg0) { 89 | // TODO Auto-generated method stub 90 | 91 | } 92 | 93 | @Override 94 | public void setContentType(String arg0) { 95 | // TODO Auto-generated method stub 96 | 97 | } 98 | 99 | @Override 100 | public void setLocale(Locale arg0) { 101 | // TODO Auto-generated method stub 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/ServletProcessor2.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.net.URLStreamHandler; 8 | import javax.servlet.Servlet; 9 | 10 | public class ServletProcessor2 { 11 | 12 | /** 13 | * process 方法加载 servlet 14 | * @param request 15 | * @param response 16 | */ 17 | public void process(Request request, Response response) { 18 | 19 | String uri = request.getUri(); 20 | // URI 是以下形式的:/servlet/servletName 其中,servletName 是 servlet 类的名字 21 | String servletName = uri.substring(uri.lastIndexOf("/") + 1); 22 | URLClassLoader loader = null; 23 | 24 | try { 25 | //创建 URLClassLoader 并告诉这个类加载器要加载的类的位置 26 | //对于这个 servlet 容器,类加载器直接在 Constants 指向的目录里边查找 27 | URL[] urls = new URL[1]; 28 | URLStreamHandler streamHandler = null; 29 | File classPath = new File(Constants.WEB_ROOT); 30 | 31 | String repository = (new URL("file", null, classPath.getCanonicalPath() + 32 | File.separator)).toString(); 33 | urls[0] = new URL(null, repository, streamHandler); 34 | 35 | loader = new URLClassLoader(urls); 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | 40 | Class myClass = null; 41 | try { 42 | myClass = loader.loadClass(servletName); 43 | } catch(ClassNotFoundException e) { 44 | e.printStackTrace(); 45 | } 46 | 47 | //创建一个 servlet 类加载器的实例, 把它向下转型为 javax.servlet.Servlet, 48 | //并调用 servlet 的 service 方法 49 | Servlet servlet = null; 50 | RequestFacade requestFacade = new RequestFacade(request); 51 | ResponseFacade responseFacade = new ResponseFacade(response); 52 | 53 | try { 54 | servlet = (Servlet) myClass.newInstance(); 55 | servlet.service(requestFacade, responseFacade); 56 | } catch (Exception e) { 57 | e.printStackTrace(); 58 | } catch (Throwable e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ex02/ServletContainer2/StaticResourceProcessor.java: -------------------------------------------------------------------------------- 1 | package ex02.ServletContainer2; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * 提供静态资源请求 7 | * @author lc 8 | */ 9 | public class StaticResourceProcessor { 10 | 11 | public void process(Request request, Response response) { 12 | try { 13 | response.sendStaticResource(); 14 | } catch (IOException e) { 15 | e.printStackTrace(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ex03/connector/RequestStream.java: -------------------------------------------------------------------------------- 1 | package ex03.connector; 2 | 3 | import ex03.connector.http.Constants; 4 | import ex03.connector.http.HttpRequest; 5 | import java.io.InputStream; 6 | import java.io.IOException; 7 | 8 | import javax.servlet.ReadListener; 9 | import javax.servlet.ServletInputStream; 10 | 11 | /** 12 | * Convenience implementation of ServletInputStream that works with 13 | * the standard implementations of Request. If the content length has 14 | * been set on our associated Request, this implementation will enforce 15 | * not reading more than that many bytes on the underlying stream. 16 | * 17 | * @author Craig R. McClanahan 18 | * @version $Revision: 1.6 $ $Date: 2002/03/18 07:15:39 $ 19 | * @deprecated 20 | */ 21 | 22 | public class RequestStream 23 | extends ServletInputStream { 24 | 25 | 26 | // ----------------------------------------------------------- Constructors 27 | 28 | 29 | /** 30 | * Construct a servlet input stream associated with the specified Request. 31 | * 32 | * @param request The associated request 33 | * @throws IOException 34 | */ 35 | public RequestStream(HttpRequest request) throws IOException { 36 | 37 | super(); 38 | closed = false; 39 | count = 0; 40 | length = request.getContentLength(); 41 | stream = request.getInputStream(); 42 | 43 | } 44 | 45 | 46 | // ----------------------------------------------------- Instance Variables 47 | 48 | 49 | /** 50 | * Has this stream been closed? 51 | */ 52 | protected boolean closed = false; 53 | 54 | 55 | /** 56 | * The number of bytes which have already been returned by this stream. 57 | */ 58 | protected int count = 0; 59 | 60 | 61 | /** 62 | * The content length past which we will not read, or -1 if there is 63 | * no defined content length. 64 | */ 65 | protected int length = -1; 66 | 67 | 68 | /** 69 | * The localized strings for this package. 70 | */ 71 | protected static StringManager sm = 72 | StringManager.getManager(Constants.Package); 73 | 74 | 75 | /** 76 | * The underlying input stream from which we should read data. 77 | */ 78 | protected InputStream stream = null; 79 | 80 | 81 | // --------------------------------------------------------- Public Methods 82 | 83 | 84 | /** 85 | * Close this input stream. No physical level I-O is performed, but 86 | * any further attempt to read from this stream will throw an IOException. 87 | * If a content length has been set but not all of the bytes have yet been 88 | * consumed, the remaining bytes will be swallowed. 89 | */ 90 | public void close() throws IOException { 91 | 92 | if (closed) 93 | throw new IOException(sm.getString("requestStream.close.closed")); 94 | 95 | if (length > 0) { 96 | while (count < length) { 97 | int b = read(); 98 | if (b < 0) 99 | break; 100 | } 101 | } 102 | 103 | closed = true; 104 | 105 | } 106 | 107 | 108 | 109 | /** 110 | * Read and return a single byte from this input stream, or -1 if end of 111 | * file has been encountered. 112 | * 113 | * @exception IOException if an input/output error occurs 114 | */ 115 | public int read() throws IOException { 116 | 117 | // Has this stream been closed? 118 | if (closed) 119 | throw new IOException(sm.getString("requestStream.read.closed")); 120 | 121 | // Have we read the specified content length already? 122 | if ((length >= 0) && (count >= length)) 123 | return (-1); // End of file indicator 124 | 125 | // Read and count the next byte, then return it 126 | int b = stream.read(); 127 | if (b >= 0) 128 | count++; 129 | return (b); 130 | 131 | } 132 | 133 | 134 | /** 135 | * Read some number of bytes from the input stream, and store them 136 | * into the buffer array b. The number of bytes actually read is 137 | * returned as an integer. This method blocks until input data is 138 | * available, end of file is detected, or an exception is thrown. 139 | * 140 | * @param b The buffer into which the data is read 141 | * 142 | * @exception IOException if an input/output error occurs 143 | */ 144 | public int read(byte b[]) throws IOException { 145 | 146 | return (read(b, 0, b.length)); 147 | 148 | } 149 | 150 | 151 | /** 152 | * Read up to len bytes of data from the input stream 153 | * into an array of bytes. An attempt is made to read as many as 154 | * len bytes, but a smaller number may be read, 155 | * possibly zero. The number of bytes actually read is returned as 156 | * an integer. This method blocks until input data is available, 157 | * end of file is detected, or an exception is thrown. 158 | * 159 | * @param b The buffer into which the data is read 160 | * @param off The start offset into array b at which 161 | * the data is written 162 | * @param len The maximum number of bytes to read 163 | * 164 | * @exception IOException if an input/output error occurs 165 | */ 166 | public int read(byte b[], int off, int len) throws IOException { 167 | 168 | int toRead = len; 169 | if (length > 0) { 170 | if (count >= length) 171 | return (-1); 172 | if ((count + len) > length) 173 | toRead = length - count; 174 | } 175 | int actuallyRead = super.read(b, off, toRead); 176 | return (actuallyRead); 177 | 178 | } 179 | 180 | 181 | 182 | @Override 183 | public boolean isFinished() { 184 | // TODO Auto-generated method stub 185 | return false; 186 | } 187 | 188 | 189 | 190 | @Override 191 | public boolean isReady() { 192 | // TODO Auto-generated method stub 193 | return false; 194 | } 195 | 196 | 197 | 198 | @Override 199 | public void setReadListener(ReadListener arg0) { 200 | // TODO Auto-generated method stub 201 | 202 | } 203 | 204 | 205 | } 206 | -------------------------------------------------------------------------------- /src/ex03/connector/ResponseStream.java: -------------------------------------------------------------------------------- 1 | package ex03.connector; 2 | 3 | import ex03.connector.http.HttpResponse; 4 | import java.io.IOException; 5 | import java.io.OutputStream; 6 | import javax.servlet.ServletOutputStream; 7 | import javax.servlet.WriteListener; 8 | 9 | /** 10 | * Convenience implementation of ServletOutputStream that works with 11 | * the standard ResponseBase implementation of Response. If the content 12 | * length has been set on our associated Response, this implementation will 13 | * enforce not writing more than that many bytes on the underlying stream. 14 | * 15 | * @author Craig R. McClanahan 16 | * @version $Revision: 1.6 $ $Date: 2002/03/18 07:15:39 $ 17 | * @deprecated 18 | */ 19 | 20 | public class ResponseStream extends ServletOutputStream { 21 | 22 | 23 | // ----------------------------------------------------------- Constructors 24 | 25 | 26 | /** 27 | * Construct a servlet output stream associated with the specified Request. 28 | * 29 | * @param response The associated response 30 | */ 31 | public ResponseStream(HttpResponse response) { 32 | 33 | super(); 34 | closed = false; 35 | commit = false; 36 | count = 0; 37 | this.response = response; 38 | // this.stream = response.getStream(); 39 | 40 | } 41 | 42 | 43 | // ----------------------------------------------------- Instance Variables 44 | 45 | 46 | /** 47 | * Has this stream been closed? 48 | */ 49 | protected boolean closed = false; 50 | 51 | 52 | /** 53 | * Should we commit the response when we are flushed? 54 | */ 55 | protected boolean commit = false; 56 | 57 | 58 | /** 59 | * The number of bytes which have already been written to this stream. 60 | */ 61 | protected int count = 0; 62 | 63 | 64 | /** 65 | * The content length past which we will not write, or -1 if there is 66 | * no defined content length. 67 | */ 68 | protected int length = -1; 69 | 70 | 71 | /** 72 | * The Response with which this input stream is associated. 73 | */ 74 | protected HttpResponse response = null; 75 | 76 | 77 | /** 78 | * The underlying output stream to which we should write data. 79 | */ 80 | protected OutputStream stream = null; 81 | 82 | 83 | // ------------------------------------------------------------- Properties 84 | 85 | 86 | /** 87 | * [Package Private] Return the "commit response on flush" flag. 88 | */ 89 | public boolean getCommit() { 90 | 91 | return (this.commit); 92 | 93 | } 94 | 95 | 96 | /** 97 | * [Package Private] Set the "commit response on flush" flag. 98 | * 99 | * @param commit The new commit flag 100 | */ 101 | public void setCommit(boolean commit) { 102 | 103 | this.commit = commit; 104 | 105 | } 106 | 107 | 108 | // --------------------------------------------------------- Public Methods 109 | 110 | 111 | /** 112 | * Close this output stream, causing any buffered data to be flushed and 113 | * any further output data to throw an IOException. 114 | */ 115 | public void close() throws IOException { 116 | if (closed) 117 | throw new IOException("responseStream.close.closed"); 118 | response.flushBuffer(); 119 | closed = true; 120 | } 121 | 122 | 123 | /** 124 | * Flush any buffered data for this output stream, which also causes the 125 | * response to be committed. 126 | */ 127 | public void flush() throws IOException { 128 | if (closed) 129 | throw new IOException("responseStream.flush.closed"); 130 | if (commit) 131 | response.flushBuffer(); 132 | 133 | } 134 | 135 | 136 | /** 137 | * Write the specified byte to our output stream. 138 | * 139 | * @param b The byte to be written 140 | * 141 | * @exception IOException if an input/output error occurs 142 | */ 143 | public void write(int b) throws IOException { 144 | 145 | if (closed) 146 | throw new IOException("responseStream.write.closed"); 147 | 148 | if ((length > 0) && (count >= length)) 149 | throw new IOException("responseStream.write.count"); 150 | 151 | response.write(b); 152 | count++; 153 | 154 | } 155 | 156 | 157 | public void write(byte b[]) throws IOException { 158 | write(b, 0, b.length); 159 | 160 | } 161 | 162 | 163 | public void write(byte b[], int off, int len) throws IOException { 164 | if (closed) 165 | throw new IOException("responseStream.write.closed"); 166 | 167 | int actual = len; 168 | if ((length > 0) && ((count + len) >= length)) 169 | actual = length - count; 170 | response.write(b, off, actual); 171 | count += actual; 172 | if (actual < len) 173 | throw new IOException("responseStream.write.count"); 174 | 175 | } 176 | 177 | 178 | // -------------------------------------------------------- Package Methods 179 | 180 | 181 | /** 182 | * Has this response stream been closed? 183 | */ 184 | boolean closed() { 185 | return (this.closed); 186 | 187 | } 188 | 189 | 190 | /** 191 | * Reset the count of bytes written to this stream to zero. 192 | */ 193 | void reset() { 194 | 195 | count = 0; 196 | 197 | } 198 | 199 | 200 | @Override 201 | public boolean isReady() { 202 | // TODO Auto-generated method stub 203 | return false; 204 | } 205 | 206 | 207 | @Override 208 | public void setWriteListener(WriteListener arg0) { 209 | // TODO Auto-generated method stub 210 | 211 | } 212 | 213 | 214 | } 215 | 216 | -------------------------------------------------------------------------------- /src/ex03/connector/ResponseWriter.java: -------------------------------------------------------------------------------- 1 | package ex03.connector; 2 | 3 | import java.io.OutputStreamWriter; 4 | import java.io.PrintWriter; 5 | 6 | /** 7 | * A subclass of PrintWriter that automatically flushes each time 8 | * a print() or println() method is called. 9 | */ 10 | 11 | public class ResponseWriter extends PrintWriter { 12 | 13 | public ResponseWriter(OutputStreamWriter writer) { 14 | super(writer); 15 | } 16 | 17 | public void print(boolean b) { 18 | super.print(b); 19 | super.flush(); 20 | } 21 | 22 | public void print(char c) { 23 | super.print(c); 24 | super.flush(); 25 | } 26 | 27 | public void print(char ca[]) { 28 | super.print(ca); 29 | super.flush(); 30 | } 31 | 32 | public void print(double d) { 33 | super.print(d); 34 | super.flush(); 35 | } 36 | 37 | public void print(float f) { 38 | super.print(f); 39 | super.flush(); 40 | } 41 | 42 | public void print(int i) { 43 | super.print(i); 44 | super.flush(); 45 | } 46 | 47 | public void print(long l) { 48 | super.print(l); 49 | super.flush(); 50 | } 51 | 52 | public void print(Object o) { 53 | super.print(o); 54 | super.flush(); 55 | } 56 | 57 | public void print(String s) { 58 | super.print(s); 59 | super.flush(); 60 | } 61 | 62 | public void println() { 63 | super.println(); 64 | super.flush(); 65 | } 66 | 67 | public void println(boolean b) { 68 | super.println(b); 69 | super.flush(); 70 | } 71 | 72 | public void println(char c) { 73 | super.println(c); 74 | super.flush(); 75 | } 76 | 77 | public void println(char ca[]) { 78 | super.println(ca); 79 | super.flush(); 80 | } 81 | 82 | public void println(double d) { 83 | super.println(d); 84 | super.flush(); 85 | } 86 | 87 | public void println(float f) { 88 | super.println(f); 89 | super.flush(); 90 | } 91 | 92 | public void println(int i) { 93 | super.println(i); 94 | super.flush(); 95 | } 96 | 97 | public void println(long l) { 98 | super.println(l); 99 | super.flush(); 100 | } 101 | 102 | public void println(Object o) { 103 | super.println(o); 104 | super.flush(); 105 | } 106 | 107 | public void println(String s) { 108 | super.println(s); 109 | super.flush(); 110 | } 111 | 112 | public void write(char c) { 113 | super.write(c); 114 | super.flush(); 115 | } 116 | 117 | public void write(char ca[]) { 118 | super.write(ca); 119 | super.flush(); 120 | } 121 | 122 | public void write(char ca[], int off, int len) { 123 | super.write(ca, off, len); 124 | super.flush(); 125 | } 126 | 127 | public void write(String s) { 128 | super.write(s); 129 | super.flush(); 130 | } 131 | 132 | public void write(String s, int off, int len) { 133 | super.write(s, off, len); 134 | super.flush(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/ex03/connector/ServletProcessor.java: -------------------------------------------------------------------------------- 1 | package ex03.connector; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.net.URLStreamHandler; 8 | 9 | import javax.servlet.Servlet; 10 | 11 | import ex03.connector.http.Constants; 12 | import ex03.connector.http.HttpRequest; 13 | import ex03.connector.http.HttpResponse; 14 | 15 | public class ServletProcessor { 16 | 17 | /** 18 | * process 方法加载 servlet 19 | * @param request 20 | * @param response 21 | */ 22 | public void process(HttpRequest request, HttpResponse response) { 23 | 24 | String uri = request.getRequestURI(); 25 | // URI 是以下形式的:/servlet/servletName 其中,servletName 是 servlet 类的名字 26 | String servletName = uri.substring(uri.lastIndexOf("/") + 1); 27 | URLClassLoader loader = null; 28 | 29 | try { 30 | //创建 URLClassLoader 并告诉这个类加载器要加载的类的位置 31 | //对于这个 servlet 容器,类加载器直接在 Constants 指向的目录里边查找 32 | URL[] urls = new URL[1]; 33 | URLStreamHandler streamHandler = null; 34 | File classPath = new File(Constants.WEB_ROOT); 35 | 36 | String repository = (new URL("file", null, classPath.getCanonicalPath() + 37 | File.separator)).toString(); 38 | urls[0] = new URL(null, repository, streamHandler); 39 | 40 | loader = new URLClassLoader(urls); 41 | } catch (IOException e) { 42 | System.out.println(e.toString()); 43 | } 44 | 45 | Class myClass = null; 46 | try { 47 | myClass = loader.loadClass(servletName); 48 | } catch(ClassNotFoundException e) { 49 | e.printStackTrace(); 50 | } 51 | 52 | //创建一个 servlet 类加载器的实例, 把它向下转型为 javax.servlet.Servlet, 53 | //并调用 servlet 的 service 方法 54 | Servlet servlet = null; 55 | try { 56 | servlet = (Servlet) myClass.newInstance(); 57 | servlet.service(request, response); 58 | ((HttpResponse)response).finishResponse(); 59 | } catch (Exception e) { 60 | System.out.println(e.toString()); 61 | } catch (Throwable e) { 62 | System.out.println(e.toString()); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/ex03/connector/StaticResourceProcessor.java: -------------------------------------------------------------------------------- 1 | package ex03.connector; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.URL; 6 | import java.net.URLClassLoader; 7 | import java.net.URLStreamHandler; 8 | 9 | import javax.servlet.Servlet; 10 | 11 | import ex03.connector.http.Constants; 12 | import ex03.connector.http.HttpRequest; 13 | import ex03.connector.http.HttpResponse; 14 | 15 | /** 16 | * 提供静态资源请求 17 | * @author lc 18 | */ 19 | public class StaticResourceProcessor { 20 | 21 | public void process(HttpRequest request, HttpResponse response) { 22 | try { 23 | response.sendStaticResource(); 24 | } 25 | catch (IOException e) { 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/ex03/connector/StringManager.java: -------------------------------------------------------------------------------- 1 | package ex03.connector; 2 | 3 | import java.util.Hashtable; 4 | 5 | /** 6 | * 单例 7 | * 处理错误信息 8 | * @author lc 9 | */ 10 | public class StringManager { 11 | 12 | private static Hashtable managers = new Hashtable(); 13 | private String packageName; 14 | 15 | private StringManager(String packageName) { 16 | this.packageName = packageName; 17 | } 18 | 19 | public synchronized static StringManager getManager(String packageName) { 20 | StringManager mgr = (StringManager) managers.get(packageName); 21 | if(mgr == null) { 22 | mgr = new StringManager(packageName); 23 | managers.put(packageName, mgr); 24 | } 25 | return mgr; 26 | } 27 | 28 | public String getString(String string) { 29 | return "HTTP connector has already been initialized"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ex03/connector/http/Constants.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.File; 4 | 5 | public class Constants { 6 | 7 | public static String Package = "ex03.connector.http"; 8 | public static final String WEB_ROOT = 9 | System.getProperty("user.dir") + File.separator + "webroot"; 10 | } 11 | -------------------------------------------------------------------------------- /src/ex03/connector/http/HttpConnector.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | 8 | /** 9 | * 连接器,创建一个套接字等待前来的 HTTP 请求 10 | * @author lc 11 | */ 12 | public class HttpConnector implements Runnable { 13 | 14 | boolean stopped; 15 | private String scheme = "http"; 16 | 17 | /** 18 | * @return HTTP 19 | */ 20 | public String getScheme() { 21 | return scheme; 22 | } 23 | 24 | /* 25 | * 等待 HTTP 请求 26 | * 为每个请求创建个 HttpProcessor 实例 27 | * 调用 HttpProcessor 的 process 方法 28 | */ 29 | @Override 30 | public void run() { 31 | ServerSocket serverSocket = null; 32 | int port = 8080; 33 | try { 34 | serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); 35 | } catch (IOException e) { 36 | e.printStackTrace(); 37 | System.exit(1); 38 | } 39 | while(!stopped) { 40 | //允许下一个来自server socket 的连接 41 | Socket socket = null; 42 | try { 43 | socket = serverSocket.accept(); 44 | } catch (Exception e) { 45 | continue; 46 | } 47 | //把这个套接字交给一个HttpProcessor 48 | HttpProcessor processor = new HttpProcessor(this); 49 | processor.process(socket); 50 | } 51 | } 52 | 53 | public void start() { 54 | Thread thread = new Thread(this); 55 | thread.start(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/ex03/connector/http/HttpHeader.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | /** 4 | * HTTP 头部类 5 | * @author lc 6 | */ 7 | public class HttpHeader { 8 | 9 | public static final int INTIAL_NAME_SIZE = 32; 10 | public static final int INTIAL_VALUE_SIZE = 64; 11 | public static final int MAX_VALUE_SIZE = 128; 12 | public static final int MAX_NAME_SIZE = 4096; 13 | 14 | public HttpHeader() { 15 | this(new char[INTIAL_NAME_SIZE], 0, new char[INTIAL_VALUE_SIZE], 0); 16 | } 17 | 18 | public HttpHeader(char[] name, int nameEnd, char[] value, int valueEnd) { 19 | super(); 20 | this.name = name; 21 | this.nameEnd = nameEnd; 22 | this.value = value; 23 | this.valueEnd = valueEnd; 24 | } 25 | 26 | public char[] name; 27 | public int nameEnd; 28 | public char[] value; 29 | public int valueEnd; 30 | protected int hashCode = 0; 31 | 32 | public void recycle() { 33 | nameEnd = 0; 34 | valueEnd = 0; 35 | hashCode = 0; 36 | } 37 | 38 | /** 39 | * 测试头部的名称是否与给定的字符数组相同 40 | * 所有的字符都必须是小写的 41 | */ 42 | public boolean equals(char[] buf, int end) { 43 | if(end != nameEnd) 44 | return false; 45 | for(int i = 0; i < end; i++) { 46 | if(buf[i] != name[i]) 47 | return false; 48 | } 49 | return true; 50 | } 51 | 52 | /** 53 | * 测试标题的名称是否等于给定的字符串 54 |      * 给定的字符串必须由小写字母组成 55 | */ 56 | public boolean equals(String str) { 57 | return equals(str.toCharArray(), str.length()); 58 | } 59 | 60 | public boolean valueEquals(char[] buf) { 61 | return valueEquals(buf, buf.length); 62 | } 63 | 64 | /** 65 | * 测试头部值是否等于给定字符数组 66 | */ 67 | public boolean valueEquals(char[] buf, int end) { 68 | if(end != valueEnd) 69 | return false; 70 | for(int i = 0; i< end; i++) { 71 | if(buf[i] != value[i]) 72 | return false; 73 | } 74 | return true; 75 | } 76 | 77 | /** 78 | * 测试头部值是否等于给定字符串 79 | */ 80 | public boolean valueEquals(String str) { 81 | return valueEquals(str.toCharArray(), str.length()); 82 | } 83 | 84 | 85 | /** 86 | * 测试头部值是否包含给定数组 87 | */ 88 | public boolean valueIncludes(char[] buf) { 89 | return valueIncludes(buf, buf.length); 90 | } 91 | 92 | 93 | /** 94 | * 测试头部值是否等于给定字符数组 95 | */ 96 | public boolean valueIncludes(char[] buf, int end) { 97 | char firstChar = buf[0]; 98 | int pos = 0; 99 | while (pos < valueEnd) { 100 | pos = valueIndexOf(firstChar, pos); 101 | if (pos == -1) 102 | return false; 103 | if ((valueEnd - pos) < end) 104 | return false; 105 | for (int i = 0; i < end; i++) { 106 | if (value[i + pos] != buf[i]) 107 | break; 108 | if (i == (end-1)) 109 | return true; 110 | } 111 | pos++; 112 | } 113 | return false; 114 | } 115 | 116 | 117 | /** 118 | * 测试头部是否包含给定字符串 119 | */ 120 | public boolean valueIncludes(String str) { 121 | return valueIncludes(str.toCharArray(), str.length()); 122 | } 123 | 124 | 125 | /** 126 | * 返回值中字符的索引 127 | */ 128 | public int valueIndexOf(char c, int start) { 129 | for (int i=start; i= 0) { 139 | request.setQueryString(new String(requestLine.uri, question + 1, 140 | requestLine.uriEnd - question - 1)); 141 | uri = new String(requestLine.uri, 0, question); 142 | } else { 143 | request.setQueryString(null); 144 | uri = new String(requestLine.uri, 0, requestLine.uriEnd); 145 | } 146 | 147 | //(使用HTTP协议)检查独立 URI 148 | if(!uri.startsWith("/")) { 149 | int pos = uri.indexOf("://"); 150 | //解析出协议和主机名 151 | if(pos != -1) { 152 | pos = uri.indexOf('/', pos + 3); 153 | if(pos == -1) { 154 | pos = uri.indexOf('/', pos + 3); 155 | uri = ""; 156 | } else { 157 | uri = uri.substring(pos); 158 | } 159 | } 160 | } 161 | 162 | //从请求 URI 中解析任何请求的会话 ID 163 | String match = ";jsessionid="; 164 | int semicolon = uri.indexOf(';'); 165 | if(semicolon >= 0) { 166 | String rest = uri.substring(semicolon + match.length()); 167 | int semicolon2 = rest.indexOf(';'); 168 | if(semicolon2 >= 0) { 169 | request.setRequestedSessionId(rest.substring(0, semicolon2)); 170 | rest = rest.substring(semicolon2); 171 | } else { 172 | request.setRequestedSessionId(rest); 173 | rest = ""; 174 | } 175 | request.setRequestedSessionURL(true); 176 | uri = uri.substring(0, semicolon) + rest; 177 | } else { 178 | request.setRequestedSessionId(null); 179 | request.setRequestedSessionURL(false); 180 | } 181 | 182 | //规范化URI(目前使用字符串操作) 183 | String normalizedUri = normalize(uri); 184 | 185 | ((HttpRequest) request).setMethod(method); 186 | request.setProtocol(protocol); 187 | if (normalizedUri != null) { 188 | ((HttpRequest) request).setRequestURI(normalizedUri); 189 | } else { 190 | ((HttpRequest) request).setRequestURI(uri); 191 | } 192 | 193 | if (normalizedUri == null) { 194 | throw new ServletException("Invalid URI: " + uri + "'"); 195 | } 196 | } 197 | 198 | /** 199 | * 返回一个上下文相关的路径,以“/”开头,代表 “..”和“.”之后指定路径的规范版本。 200 |     * 如果指定的路径试图超出当前上下文的边界(即太多的“..”路径元素存在) 201 |     * 返回 null 202 | * @param path 203 | * @return 204 | */ 205 | private String normalize(String path) { 206 | if(path == null) 207 | return null; 208 | //创建一个规范化的地址 209 | String normalized = path; 210 | 211 | //在开始时将“/%7E”和“/%7e”标准化为“/〜” 212 | if(normalized.startsWith("/%7E") || normalized.startsWith("/%7e")) 213 | normalized = "/~" + normalized.substring(4); 214 | 215 | //避免编码'%','/','。' 和“\”,这是特别保留字符 216 | if ((normalized.indexOf("%25") >= 0) 217 | || (normalized.indexOf("%2F") >= 0) 218 | || (normalized.indexOf("%2E") >= 0) 219 | || (normalized.indexOf("%5C") >= 0) 220 | || (normalized.indexOf("%2f") >= 0) 221 | || (normalized.indexOf("%2e") >= 0) 222 | || (normalized.indexOf("%5c") >= 0)) { 223 | return null; 224 | } 225 | 226 | if (normalized.equals("/.")) 227 | return "/"; 228 | 229 | //标准化斜杠并在必要时添加主斜杠 230 | if(normalized.indexOf("\\") >= 0) 231 | normalized = normalized.replace("\\", "/"); 232 | if(!normalized.startsWith("/")) 233 | normalized = "/" + normalized; 234 | 235 | // 在标准化路径中解决“//”的出现 236 | while (true) { 237 | int index = normalized.indexOf("//"); 238 | if (index < 0) 239 | break; 240 | normalized = normalized.substring(0, index) + 241 | normalized.substring(index + 1); 242 | } 243 | 244 | //解决标准化路径中“/./”的出现 245 | while (true) { 246 | int index = normalized.indexOf("/./"); 247 | if (index < 0) 248 | break; 249 | normalized = normalized.substring(0, index) + 250 | normalized.substring(index + 2); 251 | } 252 | 253 | // 解决标准化路径中“/../”的出现 254 | while (true) { 255 | int index = normalized.indexOf("/../"); 256 | if (index < 0) 257 | break; 258 | if (index == 0) 259 | return (null); // 尝试超出我们的范围 260 | int index2 = normalized.lastIndexOf('/', index - 1); 261 | normalized = normalized.substring(0, index2) + 262 | normalized.substring(index + 3); 263 | } 264 | 265 | //声明“/ ...”(三个或更多个点)的出现是无效的 266 | if (normalized.indexOf("/...") >= 0) 267 | return (null); 268 | 269 | // 返回规范化后的路径 270 | return (normalized); 271 | } 272 | 273 | } 274 | -------------------------------------------------------------------------------- /src/ex03/connector/http/HttpRequest.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.InetAddress; 9 | import java.net.Socket; 10 | import java.security.Principal; 11 | import java.text.ParseException; 12 | import java.text.SimpleDateFormat; 13 | import java.util.ArrayList; 14 | import java.util.Date; 15 | import java.util.Enumeration; 16 | import java.util.HashMap; 17 | import java.util.Locale; 18 | import java.util.Map; 19 | 20 | import javax.servlet.RequestDispatcher; 21 | import javax.servlet.ServletContext; 22 | import javax.servlet.ServletInputStream; 23 | import javax.servlet.ServletRequest; 24 | import javax.servlet.ServletResponse; 25 | import javax.servlet.http.Cookie; 26 | import javax.servlet.http.HttpSession; 27 | import org.apache.catalina.util.Enumerator; 28 | import org.apache.catalina.util.ParameterMap; 29 | import org.apache.catalina.util.RequestUtil; 30 | 31 | import ex03.connector.RequestStream; 32 | 33 | 34 | /** 35 | * Http 请求 36 | * 使用负责与客户端通信的 socket 传递的 InputStream 对象来构造 HttpRequest 类的实例 37 | * @author lc 38 | */ 39 | /** 40 | * @author lc 41 | * 42 | */ 43 | public class HttpRequest implements ServletRequest { 44 | 45 | private String contentType; 46 | private int contentLength; 47 | private InetAddress inetAddress; 48 | private InputStream stream; 49 | private String method; 50 | private String protocol; 51 | private String queryString; 52 | private String requestURI; 53 | private String serverName; 54 | private int serverPort; 55 | private Socket socket; 56 | private boolean requestedSessionCookie; 57 | private String requestedSessionId; 58 | private boolean requestedSessionURL; 59 | 60 | public HttpRequest(InputStream input) { 61 | this.stream = input; 62 | } 63 | 64 | /** 65 | * 请求的请求属性 66 | */ 67 | protected HashMap attributes = new HashMap(); 68 | 69 | /** 70 | * 请求权限 71 | */ 72 | protected String authorization = null; 73 | 74 | /** 75 | * 请求的上下文路径 76 | */ 77 | protected String contextPath = ""; 78 | 79 | /** 80 | * 与此请求相关的一组 cookies 81 | */ 82 | protected ArrayList cookies = new ArrayList(); 83 | 84 | /** 85 | * 在getDateHeader() 中使用的一组SimpleDateFormat格式。 86 | */ 87 | protected SimpleDateFormat formats[] = { 88 | new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.CHINA), 89 | new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.CHINA), 90 | new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.CHINA) 91 | }; 92 | 93 | /** 94 | * 与此请求关联的HTTP 头部,按名称输入。 这些值是相应头部值的ArrayLists 95 | */ 96 | protected HashMap> headers = new HashMap>(); 97 | 98 | /** 99 | * 请求的解析参数。 只有通过方法调用的 getParameter() 100 | * 系列之一请求参数信息时,才会填充此参数。 101 | * 关键是参数名称,而值是该参数值的字符串数组。 102 | * 一旦特定请求的参数被解析并存储在这里,它们就不会被修改。 103 | * 因此,应用程序对这些参数的访问不需要同步。 104 | */ 105 | protected ParameterMap parameters = null; 106 | /** 107 | * 请求被解析了吗 108 | */ 109 | protected boolean parsed = false; 110 | protected String pathInfo = null; 111 | 112 | /** 113 | * 已被 getReader返回的 reader 114 | */ 115 | protected BufferedReader reader = null; 116 | 117 | /** 118 | * 添加头部 119 | * @param name 120 | * @param value 121 | */ 122 | public void addHeader(String name, String value) { 123 | name = name.toLowerCase(); 124 | //同步 headers 125 | synchronized (headers) { 126 | ArrayList values = (ArrayList) headers.get(name); 127 | if(values == null) { 128 | values = new ArrayList(); 129 | headers.put(name, values); 130 | } 131 | values.add(value); 132 | } 133 | } 134 | 135 | /** 136 | * 解析此请求的参数(未解析过的) 137 | * 如果查询字符串和请求内容中都存在参数,则会合并它们 138 | * @throws IOException 139 | */ 140 | protected void parseParameters() throws IOException { 141 | if(parsed) 142 | return; 143 | ParameterMap results = parameters; 144 | if(results == null) 145 | results = new ParameterMap(); 146 | results.setLocked(false); 147 | String encoding = getCharacterEncoding(); 148 | if(encoding == null) 149 | encoding = "utf-8"; 150 | 151 | //解析查询字符串中指定的任何参数 152 | String queryString = getQueryString(); 153 | try { 154 | RequestUtil.parseParameters(results, queryString, encoding); 155 | } catch (Exception e) { 156 | } 157 | 158 | //解析输入流中指定的任何参数 159 | String contentType = getContentType(); 160 | if(contentType == null) { 161 | contentType = ""; 162 | int semicolon = contentType.indexOf(','); 163 | if(semicolon >= 0) { 164 | contentType = contentType.substring(0, semicolon).trim(); 165 | } 166 | } else { 167 | contentType = contentType.trim(); 168 | } 169 | if("POST".equals(getMethod()) && (getContentLength() > 0) && 170 | "application/x-www-form-urlencoded".equals(contentType)) { 171 | try { 172 | int max = getContentLength(); 173 | int len = 0; 174 | byte[] buf = new byte[getContentLength()]; 175 | ServletInputStream is = getInputStream(); 176 | while(len < max) { 177 | int next = is.readLine(buf, len, max - len); 178 | if(next < 0) { 179 | break; 180 | } 181 | len += next; 182 | } 183 | is.close(); 184 | if(len < max) { 185 | throw new RuntimeException("上下文长度不匹配"); 186 | } 187 | RequestUtil.parseParameters(results, buf, encoding); 188 | } catch (UnsupportedEncodingException e) { 189 | } 190 | } 191 | 192 | //存储最终结果 193 | results.setLocked(true); 194 | parsed = true; 195 | parameters = results; 196 | 197 | } 198 | 199 | public void addCookie(Cookie cookie) { 200 | synchronized (cookies) { 201 | cookies.add(cookie); 202 | } 203 | } 204 | 205 | public void setRequestedSessionCookie(boolean flag) { 206 | this.requestedSessionCookie = flag; 207 | } 208 | 209 | public void setRequestedSessionId(String requestedSessionId) { 210 | this.requestedSessionId = requestedSessionId; 211 | } 212 | 213 | public void setRequestedSessionURL(boolean flag) { 214 | requestedSessionURL = flag; 215 | } 216 | 217 | /* implementation of the HttpServletRequest */ 218 | public Object getAttribute(String name) { 219 | synchronized (attributes) { 220 | return (attributes.get(name)); 221 | } 222 | } 223 | 224 | public Enumeration getAttributeNames() { 225 | synchronized (attributes) { 226 | return (new Enumerator(attributes.keySet())); 227 | } 228 | } 229 | 230 | public String getAuthType() { 231 | return null; 232 | } 233 | 234 | public String getCharacterEncoding() { 235 | return null; 236 | } 237 | 238 | public int getContentLength() { 239 | return contentLength; 240 | } 241 | 242 | public String getContentType() { 243 | return contentType; 244 | } 245 | 246 | public String getContextPath() { 247 | return contextPath; 248 | } 249 | 250 | public Cookie[] getCookies() { 251 | synchronized (cookies) { 252 | if (cookies.size() < 1) 253 | return (null); 254 | Cookie results[] = new Cookie[cookies.size()]; 255 | return ((Cookie[]) cookies.toArray(results)); 256 | } 257 | } 258 | 259 | public long getDateHeader(String name) { 260 | String value = getHeader(name); 261 | if (value == null) 262 | return (-1L); 263 | 264 | value += " "; 265 | 266 | // 尝试以各种格式转换日期标题 267 | for (int i = 0; i < formats.length; i++) { 268 | try { 269 | Date date = formats[i].parse(value); 270 | return (date.getTime()); 271 | } catch (ParseException e) { 272 | ; 273 | } 274 | } 275 | throw new IllegalArgumentException(value); 276 | } 277 | 278 | public String getHeader(String name) { 279 | name = name.toLowerCase(); 280 | synchronized (headers) { 281 | ArrayList values = (ArrayList) headers.get(name); 282 | if (values != null) 283 | return ((String) values.get(0)); 284 | else 285 | return null; 286 | } 287 | } 288 | 289 | public Enumeration getHeaderNames() { 290 | synchronized (headers) { 291 | return (new Enumerator(headers.keySet())); 292 | } 293 | } 294 | 295 | public Enumeration getHeaders(String name) { 296 | name = name.toLowerCase(); 297 | synchronized (headers) { 298 | ArrayList values = (ArrayList) headers.get(name); 299 | if (values != null) 300 | return (new Enumerator(values)); 301 | else 302 | return (new Enumerator(new ArrayList<>())); 303 | } 304 | } 305 | 306 | public ServletInputStream getInputStream() throws IOException { 307 | if (reader != null) 308 | throw new IllegalStateException("getInputStream has been called"); 309 | 310 | if (stream == null) 311 | stream = getInputStream(); 312 | return (ServletInputStream) (stream); 313 | } 314 | 315 | public int getIntHeader(String name) { 316 | String value = getHeader(name); 317 | if (value == null) 318 | return (-1); 319 | else 320 | return (Integer.parseInt(value)); 321 | } 322 | 323 | public Locale getLocale() { 324 | return null; 325 | } 326 | 327 | public Enumeration getLocales() { 328 | return null; 329 | } 330 | 331 | public String getMethod() { 332 | return method; 333 | } 334 | 335 | public String getParameter(String name) { 336 | try { 337 | parseParameters(); 338 | } catch (IOException e) { 339 | e.printStackTrace(); 340 | } 341 | String values[] = (String[]) parameters.get(name); 342 | if (values != null) 343 | return (values[0]); 344 | else 345 | return (null); 346 | } 347 | 348 | public Map getParameterMap() { 349 | try { 350 | parseParameters(); 351 | } catch (IOException e) { 352 | e.printStackTrace(); 353 | } 354 | return (this.parameters); 355 | } 356 | 357 | public Enumeration getParameterNames() { 358 | try { 359 | parseParameters(); 360 | } catch (IOException e) { 361 | e.printStackTrace(); 362 | } 363 | return (new Enumerator(parameters.keySet())); 364 | } 365 | 366 | public String[] getParameterValues(String name) { 367 | try { 368 | parseParameters(); 369 | } catch (IOException e) { 370 | e.printStackTrace(); 371 | } 372 | String values[] = (String[]) parameters.get(name); 373 | if (values != null) 374 | return (values); 375 | else 376 | return null; 377 | } 378 | 379 | public String getPathInfo() { 380 | return pathInfo; 381 | } 382 | 383 | public String getPathTranslated() { 384 | return null; 385 | } 386 | 387 | public String getProtocol() { 388 | return protocol; 389 | } 390 | 391 | public String getQueryString() { 392 | return queryString; 393 | } 394 | 395 | public BufferedReader getReader() throws IOException { 396 | if (stream != null) 397 | throw new IllegalStateException("getInputStream has been called."); 398 | if (reader == null) { 399 | String encoding = getCharacterEncoding(); 400 | if (encoding == null) 401 | encoding = "ISO-8859-1"; 402 | InputStreamReader isr = new InputStreamReader(getInputStream(), encoding); 403 | reader = new BufferedReader(isr); 404 | } 405 | return (reader); 406 | } 407 | 408 | public String getRealPath(String path) { 409 | return null; 410 | } 411 | 412 | public String getRemoteAddr() { 413 | return null; 414 | } 415 | 416 | public String getRemoteHost() { 417 | return null; 418 | } 419 | 420 | public String getRemoteUser() { 421 | return null; 422 | } 423 | 424 | public RequestDispatcher getRequestDispatcher(String path) { 425 | return null; 426 | } 427 | 428 | public String getScheme() { 429 | return null; 430 | } 431 | 432 | public String getServerName() { 433 | return null; 434 | } 435 | 436 | public int getServerPort() { 437 | return 0; 438 | } 439 | 440 | public String getRequestedSessionId() { 441 | return null; 442 | } 443 | 444 | public String getRequestURI() { 445 | return requestURI; 446 | } 447 | 448 | public StringBuffer getRequestURL() { 449 | return null; 450 | } 451 | 452 | public HttpSession getSession() { 453 | return null; 454 | } 455 | 456 | public HttpSession getSession(boolean create) { 457 | return null; 458 | } 459 | 460 | public String getServletPath() { 461 | return null; 462 | } 463 | 464 | public Principal getUserPrincipal() { 465 | return null; 466 | } 467 | 468 | public boolean isRequestedSessionIdFromCookie() { 469 | return false; 470 | } 471 | 472 | public boolean isRequestedSessionIdFromUrl() { 473 | return isRequestedSessionIdFromURL(); 474 | } 475 | 476 | public boolean isRequestedSessionIdFromURL() { 477 | return false; 478 | } 479 | 480 | public boolean isRequestedSessionIdValid() { 481 | return false; 482 | } 483 | 484 | public boolean isSecure() { 485 | return false; 486 | } 487 | 488 | public boolean isUserInRole(String role) { 489 | return false; 490 | } 491 | 492 | public void removeAttribute(String attribute) { 493 | } 494 | 495 | public void setAttribute(String key, Object value) { 496 | } 497 | 498 | /** 499 | * 设置随此请求一起发送的权限 500 | * 501 | * @param 授权新的权限 502 | */ 503 | public void setAuthorization(String authorization) { 504 | this.authorization = authorization; 505 | } 506 | 507 | public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException { 508 | } 509 | 510 | public void setQueryString(String queryString) { 511 | this.queryString = queryString; 512 | } 513 | 514 | public void setRequestURI(String requestURI) { 515 | this.requestURI = requestURI; 516 | } 517 | 518 | public void setMethod(String method) { 519 | this.method = method; 520 | } 521 | 522 | public void setProtocol(String protocol) { 523 | this.protocol = protocol; 524 | } 525 | 526 | public String getLocalName() { 527 | // TODO Auto-generated method stub 528 | return null; 529 | } 530 | 531 | public int getLocalPort() { 532 | // TODO Auto-generated method stub 533 | return 0; 534 | } 535 | 536 | public ServletInputStream createInputStream() throws IOException { 537 | return (new RequestStream(this)); 538 | } 539 | 540 | public void setContentLength(int n) { 541 | this.contentLength = n; 542 | } 543 | 544 | public void setContentType(String value) { 545 | this.contentType = value; 546 | } 547 | } 548 | -------------------------------------------------------------------------------- /src/ex03/connector/http/HttpRequestLine.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | /** 4 | * HTTP 请求行类 5 | * @author lc 6 | * 7 | */ 8 | final class HttpRequestLine { 9 | 10 | public static final int INITIAL_METHOD_SIZE = 8; 11 | public static final int INITIAL_URL_SIZE = 64; 12 | public static final int INITIAL_PROTOCOL_SIZE = 8; 13 | public static final int MAX_METHOD_SIZE = 1024; 14 | public static final int MAX_URI_SIZE = 32768; 15 | public static final int MAX_PROTOCOL_SIZE = 1024; 16 | 17 | public HttpRequestLine() { 18 | this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URL_SIZE], 0, 19 | new char[INITIAL_PROTOCOL_SIZE], 0); 20 | } 21 | 22 | 23 | public HttpRequestLine(char[] method, int methodEnd, char[] uri, int uriEnd, char[] protocol, int protocolEnd) { 24 | super(); 25 | this.method = method; 26 | this.methodEnd = methodEnd; 27 | this.uri = uri; 28 | this.uriEnd = uriEnd; 29 | this.protocol = protocol; 30 | this.protocolEnd = protocolEnd; 31 | } 32 | 33 | 34 | public char[] method; 35 | public int methodEnd; 36 | public char[] uri; 37 | public int uriEnd; 38 | public char[] protocol; 39 | public int protocolEnd; 40 | 41 | /** 42 | * 释放所有对象引用,并初始化实例变量,以准备重用此对象 43 | */ 44 | public void recycle() { 45 | methodEnd = 0; 46 | uriEnd = 0; 47 | protocolEnd = 0; 48 | } 49 | 50 | /** 51 | * 测试 uri 是否包含给定数组 52 | */ 53 | public int indexOf(char[] buf) { 54 | return indexOf(buf, buf.length); 55 | } 56 | 57 | 58 | /** 59 | * 测试传入值是否包含给定的字符数组 60 | */ 61 | public int indexOf(char[] buf, int end) { 62 | char firstChar = buf[0]; 63 | int pos = 0; 64 | while(pos < uriEnd) { 65 | pos = indexOf(firstChar, pos); 66 | if(pos == -1) 67 | return -1; 68 | if((uriEnd - pos) < end) 69 | return -1; 70 | for(int i = 0; i < end; i++) { 71 | if(uri[i + pos] != buf[i]) 72 | break; 73 | if(i == (end - 1)) 74 | return pos; 75 | } 76 | pos++; 77 | } 78 | return -1; 79 | } 80 | 81 | 82 | /** 83 | * 返回给定值中字符的索引 84 | */ 85 | public int indexOf(char c, int start) { 86 | for(int i = start; i < uriEnd; i++) { 87 | if(uri[i] == c) 88 | return i; 89 | } 90 | return -1; 91 | } 92 | 93 | /** 94 | * 测试头部值是否包含给定字符串 95 | */ 96 | public int indexOf(String str) { 97 | return indexOf(str.toCharArray(), str.length()); 98 | } 99 | 100 | @Override 101 | public int hashCode() { 102 | return 0; 103 | } 104 | 105 | 106 | @Override 107 | public boolean equals(Object obj) { 108 | return false; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/ex03/connector/http/HttpResponse.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.File; 4 | import java.io.FileInputStream; 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.io.OutputStreamWriter; 8 | import java.io.PrintWriter; 9 | import java.text.SimpleDateFormat; 10 | import java.util.ArrayList; 11 | import java.util.Date; 12 | import java.util.HashMap; 13 | import java.util.Iterator; 14 | import java.util.Locale; 15 | import java.util.concurrent.ConcurrentHashMap; 16 | 17 | import javax.servlet.ServletOutputStream; 18 | import javax.servlet.ServletResponse; 19 | import javax.servlet.http.Cookie; 20 | import javax.servlet.http.HttpServletResponse; 21 | 22 | import ex03.connector.ResponseStream; 23 | import ex03.connector.ResponseWriter; 24 | 25 | public class HttpResponse implements ServletResponse { 26 | 27 | private static final int BUFFER_SIZE = 1024; 28 | HttpRequest request; 29 | OutputStream output; 30 | PrintWriter writer; 31 | protected byte[] buffer = new byte[BUFFER_SIZE]; 32 | protected int bufferCount = 0; 33 | protected boolean committed = false; //response 是否已经提交过了 34 | protected int contentCount = 0; //写入 response 的字节数 35 | protected int contentLength = -1; //与 Response 相关的内容长度 36 | protected String contentType = null; //与 Response 相关的内容类型 37 | protected String encoding = null; //与 Response 相关的编码格式 38 | 39 | /** 40 | * 与 Response 相关的Cookies 41 | */ 42 | protected ArrayList cookies = new ArrayList(); 43 | ConcurrentHashMap ch = new ConcurrentHashMap<>(); 44 | /** 45 | * 通过addHeader()明确添加的HTTP头,但不包括要添加 46 | * setContentLength(),setContentType()等。 47 |     * 头部名是 key ,value 是被设置过的 ArrayList 48 | */ 49 | protected HashMap headers = new HashMap(); 50 | 51 | protected final SimpleDateFormat format = 52 | new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",Locale.CHINA); 53 | 54 | /** 55 | * 错误信息设置为sendError() 56 | */ 57 | protected String message = getStatusMessage(HttpServletResponse.SC_OK); 58 | 59 | /** 60 | * 与 Response 有关的 Http状态码 61 | */ 62 | protected int status = HttpServletResponse.SC_OK; 63 | private HttpRequest requst; 64 | 65 | public HttpResponse(OutputStream output) { 66 | this.output = output; 67 | } 68 | 69 | private String getStatusMessage(int scOk) { 70 | return null; 71 | } 72 | 73 | @Override 74 | public void flushBuffer() throws IOException { 75 | if(bufferCount > 0) { 76 | try { 77 | output.write(buffer, 0, bufferCount); 78 | } finally { 79 | bufferCount = 0; 80 | } 81 | } 82 | } 83 | 84 | @Override 85 | public int getBufferSize() { 86 | return BUFFER_SIZE; 87 | } 88 | 89 | @Override 90 | public String getCharacterEncoding() { 91 | return encoding; 92 | } 93 | 94 | public String getContentType() { 95 | return contentType; 96 | } 97 | 98 | @Override 99 | public Locale getLocale() { 100 | return null; 101 | } 102 | 103 | @Override 104 | public ServletOutputStream getOutputStream() throws IOException { 105 | return null; 106 | } 107 | 108 | @Override 109 | public PrintWriter getWriter() throws IOException { 110 | ResponseStream newStream = new ResponseStream(this); 111 | newStream.setCommit(false); 112 | OutputStreamWriter osr = new OutputStreamWriter(newStream, getCharacterEncoding()); 113 | writer = new ResponseWriter(osr); 114 | return writer; 115 | } 116 | 117 | @Override 118 | public boolean isCommitted() { 119 | return committed; 120 | } 121 | 122 | @Override 123 | public void reset() { 124 | 125 | } 126 | 127 | @Override 128 | public void resetBuffer() { 129 | 130 | } 131 | 132 | @Override 133 | public void setBufferSize(int arg0) { 134 | 135 | } 136 | 137 | public void setCharacterEncoding(String arg0) { 138 | 139 | } 140 | 141 | @Override 142 | public void setContentLength(int arg0) { 143 | 144 | } 145 | 146 | public void setContentLengthLong(long arg0) { 147 | 148 | } 149 | 150 | @Override 151 | public void setContentType(String arg0) { 152 | // TODO Auto-generated method stub 153 | 154 | } 155 | 156 | @Override 157 | public void setLocale(Locale arg0) { 158 | // TODO Auto-generated method stub 159 | 160 | } 161 | 162 | public void setRequest(HttpRequest request) { 163 | this.requst = request; 164 | } 165 | 166 | public void finishResponse() { 167 | 168 | } 169 | 170 | public void addDateHeader(String name, long value) { 171 | if (isCommitted()) 172 | return; 173 | addHeader(name, format.format(new Date(value))); 174 | } 175 | 176 | public void addHeader(String name, String value) { 177 | if(isCommitted()) 178 | return; 179 | synchronized (headers) { 180 | ArrayList values = (ArrayList) headers.get(name); 181 | if(values == null) { 182 | values = new ArrayList<>(); 183 | headers.put(name, values); 184 | } 185 | 186 | values.add(value); 187 | } 188 | } 189 | 190 | public void addIntHeader(String name, int value) { 191 | if (isCommitted()) 192 | return; 193 | 194 | addHeader(name, "" + value); 195 | } 196 | 197 | public boolean containsHeader(String name) { 198 | synchronized (headers) { 199 | return (headers.get(name) != null); 200 | } 201 | } 202 | 203 | public void addCookie(Cookie cookie) { 204 | if(isCommitted()) 205 | return; 206 | 207 | synchronized (cookies) { 208 | cookies.add(cookie); 209 | } 210 | } 211 | 212 | /** 213 | * 发送 HTTP 响应头部 214 | */ 215 | public void sendHeaders() { 216 | if(isCommitted()) 217 | return; 218 | 219 | OutputStreamWriter osr = null; 220 | try { 221 | osr = new OutputStreamWriter(getStream(), getCharacterEncoding()); 222 | } catch (Exception e) { 223 | osr = new OutputStreamWriter(getStream()); 224 | } 225 | 226 | final PrintWriter outputWriter = new PrintWriter(osr); 227 | //发送 "status:" 头部 228 | outputWriter.print(status); 229 | if(message != null) { 230 | outputWriter.print(" "); 231 | outputWriter.print(message); 232 | } 233 | outputWriter.print("\r\n"); 234 | //发送 内容长度 及 内容类型 235 | if(getContentType() != null) { 236 | outputWriter.write("Content-Type: " + getContentType() + "\r\n"); 237 | } 238 | if(getContentLength() >= 0) { 239 | outputWriter.print("Content-Length: " + getContentLength() + "\r\n"); 240 | } 241 | 242 | //发送所有指定头部 243 | synchronized (headers) { 244 | Iterator names = headers.keySet().iterator(); 245 | while(names.hasNext()) { 246 | String name = (String) names.next(); 247 | ArrayList values = (ArrayList) headers.get(name); 248 | Iterator items = values.iterator(); 249 | while(items.hasNext()) { 250 | String value = (String) items.next(); 251 | outputWriter.print(name); 252 | outputWriter.print(": "); 253 | outputWriter.print(value); 254 | outputWriter.print("\r\n"); 255 | } 256 | } 257 | } 258 | 259 | synchronized (cookies) { 260 | Iterator items = cookies.iterator(); 261 | while(items.hasNext()) { 262 | Cookie cookie = (Cookie) items.next(); 263 | //outputWriter.print(CookieTools.getCookieHeaderName(cookie)); 264 | outputWriter.print(": "); 265 | //outputWriter.print(CookieTools.getCookieHeaderValue(cookie)); 266 | outputWriter.print("\r\n"); 267 | } 268 | } 269 | 270 | //发送一个终止空行来标记标题的结尾 271 | outputWriter.print("\r\n"); 272 | outputWriter.flush(); 273 | 274 | committed = true; 275 | } 276 | 277 | private int getContentLength() { 278 | return contentLength; 279 | } 280 | 281 | public OutputStream getStream() { 282 | return this.output; 283 | } 284 | 285 | /** 286 | * 处理静态页面 287 | * @throws IOException 288 | */ 289 | public void sendStaticResource() throws IOException { 290 | byte[] bytes = new byte[BUFFER_SIZE]; 291 | FileInputStream fis = null; 292 | try { 293 | //request.getUri 被替换为 request.getRequestURI 294 | File file = new File(Constants.WEB_ROOT, request.getRequestURI()); 295 | fis = new FileInputStream(file); 296 | int ch = fis.read(bytes, 0, BUFFER_SIZE); 297 | /* 298 | * HTTP/1.1 200 OK 299 | Date: Fri, 22 May 2009 06:07:21 GMT 300 | Content-Type: text/html; charset=UTF-8 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | */ 309 | while(ch != -1) { 310 | output.write(bytes, 0, ch); 311 | ch = fis.read(bytes, 0, BUFFER_SIZE); 312 | } 313 | } catch (Exception e) { 314 | String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + 315 | "Content-Type: text/html\r\n" + 316 | "Content-Length: 23\r\n" + "\r\n" + 317 | "

File Not Found

"; 318 | output.write(errorMessage.getBytes()); 319 | } finally { 320 | if (fis != null) 321 | fis.close(); 322 | } 323 | } 324 | 325 | public void write(int b) throws IOException { 326 | if(bufferCount >= buffer.length) 327 | flushBuffer(); 328 | buffer[bufferCount++] = (byte) b; 329 | contentCount++; 330 | } 331 | 332 | public void write(byte b[]) throws IOException { 333 | write(b, 0, b.length); 334 | } 335 | 336 | public void write(byte[] b, int off, int len) throws IOException { 337 | //如果整个东西都适合缓冲区,就把它放在那里 338 | if(len == 0) 339 | return; 340 | if(len <= (buffer.length - bufferCount)) { 341 | System.arraycopy(b, off, buffer, bufferCount, len); 342 | bufferCount += len; 343 | contentCount += len; 344 | return; 345 | } 346 | 347 | flushBuffer(); 348 | int iterations = len / buffer.length; 349 | int leftoverStart = iterations * buffer.length; 350 | int leftoverLen = len - leftoverStart; 351 | for(int i = 0; i < iterations; i++) { 352 | write(b, off + (i * buffer.length), buffer.length); 353 | } 354 | 355 | if(leftoverLen > 0) 356 | write(b, off + leftoverStart, leftoverLen); 357 | } 358 | 359 | public void setDateHeader(String name, long value) { 360 | if(isCommitted()) 361 | return; 362 | setHeader(name, format.format(new Date(value))); 363 | } 364 | public void setHeader(String name, String value) { 365 | if(isCommitted()) 366 | return ; 367 | ArrayList values = new ArrayList(); 368 | values.add(value); 369 | synchronized(headers) { 370 | headers.put(name, values); 371 | } 372 | String match = name.toLowerCase(); 373 | if(match.equals("content-length")) { 374 | int contentLength = -1; 375 | try { 376 | contentLength = Integer.parseInt(value); 377 | } catch (NumberFormatException e) { 378 | ; 379 | } 380 | if(contentLength >= 0) 381 | setContentLength(contentLength); 382 | } else if(match.equals("content-type")) { 383 | setContentType(value); 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/ex03/connector/http/RequestFacade.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.UnsupportedEncodingException; 6 | import java.util.Enumeration; 7 | import java.util.Locale; 8 | import java.util.Map; 9 | 10 | import javax.servlet.RequestDispatcher; 11 | import javax.servlet.ServletContext; 12 | import javax.servlet.ServletInputStream; 13 | import javax.servlet.ServletRequest; 14 | import javax.servlet.ServletResponse; 15 | import javax.servlet.http.HttpServletRequest; 16 | 17 | 18 | public class RequestFacade implements ServletRequest { 19 | 20 | private HttpServletRequest request = null; 21 | private String encoding; 22 | 23 | public RequestFacade(HttpRequest request) { 24 | this.request = (HttpServletRequest) request; 25 | } 26 | 27 | @Override 28 | public Object getAttribute(String attribute) { 29 | return request.getAttribute(attribute); 30 | } 31 | 32 | @Override 33 | public Enumeration getAttributeNames() { 34 | return request.getAttributeNames(); 35 | } 36 | 37 | @Override 38 | public String getCharacterEncoding() { 39 | return null; 40 | } 41 | 42 | @Override 43 | public int getContentLength() { 44 | return 0; 45 | } 46 | 47 | @Override 48 | public String getContentType() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public ServletInputStream getInputStream() throws IOException { 54 | // TODO Auto-generated method stub 55 | return null; 56 | } 57 | 58 | @Override 59 | public Locale getLocale() { 60 | // TODO Auto-generated method stub 61 | return null; 62 | } 63 | 64 | @Override 65 | public Enumeration getLocales() { 66 | // TODO Auto-generated method stub 67 | return null; 68 | } 69 | 70 | @Override 71 | public String getParameter(String arg0) { 72 | // TODO Auto-generated method stub 73 | return null; 74 | } 75 | 76 | @Override 77 | public Map getParameterMap() { 78 | // TODO Auto-generated method stub 79 | return null; 80 | } 81 | 82 | @Override 83 | public Enumeration getParameterNames() { 84 | // TODO Auto-generated method stub 85 | return null; 86 | } 87 | 88 | @Override 89 | public String[] getParameterValues(String arg0) { 90 | // TODO Auto-generated method stub 91 | return null; 92 | } 93 | 94 | @Override 95 | public String getProtocol() { 96 | // TODO Auto-generated method stub 97 | return null; 98 | } 99 | 100 | @Override 101 | public BufferedReader getReader() throws IOException { 102 | // TODO Auto-generated method stub 103 | return null; 104 | } 105 | 106 | @Override 107 | public String getRealPath(String arg0) { 108 | // TODO Auto-generated method stub 109 | return null; 110 | } 111 | 112 | @Override 113 | public String getRemoteAddr() { 114 | // TODO Auto-generated method stub 115 | return null; 116 | } 117 | 118 | @Override 119 | public String getRemoteHost() { 120 | // TODO Auto-generated method stub 121 | return null; 122 | } 123 | 124 | @Override 125 | public RequestDispatcher getRequestDispatcher(String arg0) { 126 | // TODO Auto-generated method stub 127 | return null; 128 | } 129 | 130 | @Override 131 | public String getScheme() { 132 | // TODO Auto-generated method stub 133 | return null; 134 | } 135 | 136 | @Override 137 | public String getServerName() { 138 | // TODO Auto-generated method stub 139 | return null; 140 | } 141 | 142 | @Override 143 | public int getServerPort() { 144 | // TODO Auto-generated method stub 145 | return 0; 146 | } 147 | 148 | @Override 149 | public boolean isSecure() { 150 | // TODO Auto-generated method stub 151 | return false; 152 | } 153 | 154 | @Override 155 | public void removeAttribute(String arg0) { 156 | // TODO Auto-generated method stub 157 | 158 | } 159 | 160 | @Override 161 | public void setAttribute(String arg0, Object arg1) { 162 | // TODO Auto-generated method stub 163 | 164 | } 165 | 166 | @Override 167 | public void setCharacterEncoding(String arg0) throws UnsupportedEncodingException { 168 | request.setCharacterEncoding(encoding); 169 | 170 | } 171 | 172 | } -------------------------------------------------------------------------------- /src/ex03/connector/http/ResponseFacade.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.IOException; 4 | import java.io.PrintWriter; 5 | import java.util.Locale; 6 | 7 | import javax.servlet.ServletOutputStream; 8 | import javax.servlet.ServletResponse; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | public class ResponseFacade implements ServletResponse { 12 | 13 | private HttpServletResponse response; 14 | 15 | public ResponseFacade(HttpResponse response) { 16 | this.response = (HttpServletResponse) response; 17 | } 18 | 19 | @Override 20 | public void flushBuffer() throws IOException { 21 | response.flushBuffer(); 22 | } 23 | 24 | @Override 25 | public int getBufferSize() { 26 | return response.getBufferSize(); 27 | } 28 | 29 | @Override 30 | public String getCharacterEncoding() { 31 | return response.getCharacterEncoding(); 32 | } 33 | 34 | public String getContentType() { 35 | return ((ResponseFacade) response).getContentType(); 36 | } 37 | 38 | @Override 39 | public Locale getLocale() { 40 | return response.getLocale(); 41 | } 42 | 43 | @Override 44 | public ServletOutputStream getOutputStream() throws IOException { 45 | return response.getOutputStream(); 46 | } 47 | 48 | @Override 49 | public PrintWriter getWriter() throws IOException { 50 | return response.getWriter(); 51 | } 52 | 53 | @Override 54 | public boolean isCommitted() { 55 | return response.isCommitted(); 56 | } 57 | 58 | @Override 59 | public void reset() { 60 | response.reset(); 61 | } 62 | 63 | @Override 64 | public void resetBuffer() { 65 | // TODO Auto-generated method stub 66 | 67 | } 68 | 69 | @Override 70 | public void setBufferSize(int arg0) { 71 | // TODO Auto-generated method stub 72 | 73 | } 74 | 75 | public void setCharacterEncoding(String arg0) { 76 | // TODO Auto-generated method stub 77 | 78 | } 79 | 80 | @Override 81 | public void setContentLength(int arg0) { 82 | // TODO Auto-generated method stub 83 | 84 | } 85 | 86 | public void setContentLengthLong(long arg0) { 87 | // TODO Auto-generated method stub 88 | 89 | } 90 | 91 | @Override 92 | public void setContentType(String arg0) { 93 | // TODO Auto-generated method stub 94 | 95 | } 96 | 97 | @Override 98 | public void setLocale(Locale arg0) { 99 | // TODO Auto-generated method stub 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/ex03/connector/http/SocketInputStream.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.http; 2 | 3 | import java.io.InputStream; 4 | 5 | import org.apache.tomcat.util.res.StringManager; 6 | 7 | import java.io.EOFException; 8 | import java.io.IOException; 9 | 10 | /** 11 | *在HTTP头处理过程中,扩展InputStream以获得更高效的读取行 12 | * @deprecated 13 | */ 14 | public class SocketInputStream extends InputStream { 15 | 16 | 17 | // -------------------------------------------------------------- Constants 18 | 19 | 20 | /** 21 | * CR. 22 | */ 23 | private static final byte CR = (byte) '\r'; 24 | 25 | 26 | /** 27 | * LF. 28 | */ 29 | private static final byte LF = (byte) '\n'; 30 | 31 | 32 | /** 33 | * SP. 34 | */ 35 | private static final byte SP = (byte) ' '; 36 | 37 | 38 | /** 39 | * HT. 40 | */ 41 | private static final byte HT = (byte) '\t'; 42 | 43 | 44 | /** 45 | * COLON. 46 | */ 47 | private static final byte COLON = (byte) ':'; 48 | 49 | 50 | /** 51 | * Lower case offset. 52 | */ 53 | private static final int LC_OFFSET = 'A' - 'a'; 54 | 55 | 56 | /** 57 | * Internal buffer. 58 | */ 59 | protected byte buf[]; 60 | 61 | 62 | /** 63 | * Last valid byte. 64 | */ 65 | protected int count; 66 | 67 | 68 | /** 69 | * Position in the buffer. 70 | */ 71 | protected int pos; 72 | 73 | 74 | /** 75 | * Underlying input stream. 76 | */ 77 | protected InputStream is; 78 | 79 | 80 | // ----------------------------------------------------------- Constructors 81 | 82 | 83 | /** 84 | * Construct a servlet input stream associated with the specified socket 85 | * input. 86 | * 87 | * @param is socket input stream 88 | * @param bufferSize size of the internal buffer 89 | */ 90 | public SocketInputStream(InputStream is, int bufferSize) { 91 | 92 | this.is = is; 93 | buf = new byte[bufferSize]; 94 | 95 | } 96 | 97 | 98 | // -------------------------------------------------------------- Variables 99 | 100 | 101 | /** 102 | * The string manager for this package. 103 | */ 104 | protected static StringManager sm = 105 | StringManager.getManager(Constants.Package); 106 | 107 | 108 | // ----------------------------------------------------- Instance Variables 109 | 110 | 111 | // --------------------------------------------------------- Public Methods 112 | 113 | 114 | /** 115 | * Read the request line, and copies it to the given buffer. This 116 | * function is meant to be used during the HTTP request header parsing. 117 | * Do NOT attempt to read the request body using it. 118 | * 119 | * @param requestLine Request line object 120 | * @throws IOException If an exception occurs during the underlying socket 121 | * read operations, or if the given buffer is not big enough to accomodate 122 | * the whole line. 123 | */ 124 | public void readRequestLine(HttpRequestLine requestLine) 125 | throws IOException { 126 | 127 | // Recycling check 128 | if (requestLine.methodEnd != 0) 129 | requestLine.recycle(); 130 | 131 | // Checking for a blank line 132 | int chr = 0; 133 | do { // Skipping CR or LF 134 | try { 135 | chr = read(); 136 | } catch (IOException e) { 137 | chr = -1; 138 | } 139 | } while ((chr == CR) || (chr == LF)); 140 | if (chr == -1) 141 | throw new EOFException 142 | (sm.getString("requestStream.readline.error")); 143 | pos--; 144 | 145 | // Reading the method name 146 | 147 | int maxRead = requestLine.method.length; 148 | int readStart = pos; 149 | int readCount = 0; 150 | 151 | boolean space = false; 152 | 153 | while (!space) { 154 | // if the buffer is full, extend it 155 | if (readCount >= maxRead) { 156 | if ((2 * maxRead) <= HttpRequestLine.MAX_METHOD_SIZE) { 157 | char[] newBuffer = new char[2 * maxRead]; 158 | System.arraycopy(requestLine.method, 0, newBuffer, 0, 159 | maxRead); 160 | requestLine.method = newBuffer; 161 | maxRead = requestLine.method.length; 162 | } else { 163 | throw new IOException 164 | (sm.getString("requestStream.readline.toolong")); 165 | } 166 | } 167 | // We're at the end of the internal buffer 168 | if (pos >= count) { 169 | int val = read(); 170 | if (val == -1) { 171 | throw new IOException 172 | (sm.getString("requestStream.readline.error")); 173 | } 174 | pos = 0; 175 | readStart = 0; 176 | } 177 | if (buf[pos] == SP) { 178 | space = true; 179 | } 180 | requestLine.method[readCount] = (char) buf[pos]; 181 | readCount++; 182 | pos++; 183 | } 184 | 185 | requestLine.methodEnd = readCount - 1; 186 | 187 | // Reading URI 188 | 189 | maxRead = requestLine.uri.length; 190 | readStart = pos; 191 | readCount = 0; 192 | 193 | space = false; 194 | 195 | boolean eol = false; 196 | 197 | while (!space) { 198 | // if the buffer is full, extend it 199 | if (readCount >= maxRead) { 200 | if ((2 * maxRead) <= HttpRequestLine.MAX_URI_SIZE) { 201 | char[] newBuffer = new char[2 * maxRead]; 202 | System.arraycopy(requestLine.uri, 0, newBuffer, 0, 203 | maxRead); 204 | requestLine.uri = newBuffer; 205 | maxRead = requestLine.uri.length; 206 | } else { 207 | throw new IOException 208 | (sm.getString("requestStream.readline.toolong")); 209 | } 210 | } 211 | // We're at the end of the internal buffer 212 | if (pos >= count) { 213 | int val = read(); 214 | if (val == -1) 215 | throw new IOException 216 | (sm.getString("requestStream.readline.error")); 217 | pos = 0; 218 | readStart = 0; 219 | } 220 | if (buf[pos] == SP) { 221 | space = true; 222 | } else if ((buf[pos] == CR) || (buf[pos] == LF)) { 223 | // HTTP/0.9 style request 224 | eol = true; 225 | space = true; 226 | } 227 | requestLine.uri[readCount] = (char) buf[pos]; 228 | readCount++; 229 | pos++; 230 | } 231 | 232 | requestLine.uriEnd = readCount - 1; 233 | 234 | // Reading protocol 235 | 236 | maxRead = requestLine.protocol.length; 237 | readStart = pos; 238 | readCount = 0; 239 | 240 | while (!eol) { 241 | // if the buffer is full, extend it 242 | if (readCount >= maxRead) { 243 | if ((2 * maxRead) <= HttpRequestLine.MAX_PROTOCOL_SIZE) { 244 | char[] newBuffer = new char[2 * maxRead]; 245 | System.arraycopy(requestLine.protocol, 0, newBuffer, 0, 246 | maxRead); 247 | requestLine.protocol = newBuffer; 248 | maxRead = requestLine.protocol.length; 249 | } else { 250 | throw new IOException 251 | (sm.getString("requestStream.readline.toolong")); 252 | } 253 | } 254 | // We're at the end of the internal buffer 255 | if (pos >= count) { 256 | // Copying part (or all) of the internal buffer to the line 257 | // buffer 258 | int val = read(); 259 | if (val == -1) 260 | throw new IOException 261 | (sm.getString("requestStream.readline.error")); 262 | pos = 0; 263 | readStart = 0; 264 | } 265 | if (buf[pos] == CR) { 266 | // Skip CR. 267 | } else if (buf[pos] == LF) { 268 | eol = true; 269 | } else { 270 | requestLine.protocol[readCount] = (char) buf[pos]; 271 | readCount++; 272 | } 273 | pos++; 274 | } 275 | 276 | requestLine.protocolEnd = readCount; 277 | 278 | } 279 | 280 | 281 | /** 282 | * Read a header, and copies it to the given buffer. This 283 | * function is meant to be used during the HTTP request header parsing. 284 | * Do NOT attempt to read the request body using it. 285 | * 286 | * @param requestLine Request line object 287 | * @throws IOException If an exception occurs during the underlying socket 288 | * read operations, or if the given buffer is not big enough to accomodate 289 | * the whole line. 290 | */ 291 | public void readHeader(HttpHeader header) 292 | throws IOException { 293 | 294 | // Recycling check 295 | if (header.nameEnd != 0) 296 | header.recycle(); 297 | 298 | // Checking for a blank line 299 | int chr = read(); 300 | if ((chr == CR) || (chr == LF)) { // Skipping CR 301 | if (chr == CR) 302 | read(); // Skipping LF 303 | header.nameEnd = 0; 304 | header.valueEnd = 0; 305 | return; 306 | } else { 307 | pos--; 308 | } 309 | 310 | // Reading the header name 311 | 312 | int maxRead = header.name.length; 313 | int readStart = pos; 314 | int readCount = 0; 315 | 316 | boolean colon = false; 317 | 318 | while (!colon) { 319 | // if the buffer is full, extend it 320 | if (readCount >= maxRead) { 321 | if ((2 * maxRead) <= HttpHeader.MAX_NAME_SIZE) { 322 | char[] newBuffer = new char[2 * maxRead]; 323 | System.arraycopy(header.name, 0, newBuffer, 0, maxRead); 324 | header.name = newBuffer; 325 | maxRead = header.name.length; 326 | } else { 327 | throw new IOException 328 | (sm.getString("requestStream.readline.toolong")); 329 | } 330 | } 331 | // We're at the end of the internal buffer 332 | if (pos >= count) { 333 | int val = read(); 334 | if (val == -1) { 335 | throw new IOException 336 | (sm.getString("requestStream.readline.error")); 337 | } 338 | pos = 0; 339 | readStart = 0; 340 | } 341 | if (buf[pos] == COLON) { 342 | colon = true; 343 | } 344 | char val = (char) buf[pos]; 345 | if ((val >= 'A') && (val <= 'Z')) { 346 | val = (char) (val - LC_OFFSET); 347 | } 348 | header.name[readCount] = val; 349 | readCount++; 350 | pos++; 351 | } 352 | 353 | header.nameEnd = readCount - 1; 354 | 355 | // Reading the header value (which can be spanned over multiple lines) 356 | 357 | maxRead = header.value.length; 358 | readStart = pos; 359 | readCount = 0; 360 | 361 | int crPos = -2; 362 | 363 | boolean eol = false; 364 | boolean validLine = true; 365 | 366 | while (validLine) { 367 | 368 | boolean space = true; 369 | 370 | // Skipping spaces 371 | // Note : Only leading white spaces are removed. Trailing white 372 | // spaces are not. 373 | while (space) { 374 | // We're at the end of the internal buffer 375 | if (pos >= count) { 376 | // Copying part (or all) of the internal buffer to the line 377 | // buffer 378 | int val = read(); 379 | if (val == -1) 380 | throw new IOException 381 | (sm.getString("requestStream.readline.error")); 382 | pos = 0; 383 | readStart = 0; 384 | } 385 | if ((buf[pos] == SP) || (buf[pos] == HT)) { 386 | pos++; 387 | } else { 388 | space = false; 389 | } 390 | } 391 | 392 | while (!eol) { 393 | // if the buffer is full, extend it 394 | if (readCount >= maxRead) { 395 | if ((2 * maxRead) <= HttpHeader.MAX_VALUE_SIZE) { 396 | char[] newBuffer = new char[2 * maxRead]; 397 | System.arraycopy(header.value, 0, newBuffer, 0, 398 | maxRead); 399 | header.value = newBuffer; 400 | maxRead = header.value.length; 401 | } else { 402 | throw new IOException 403 | (sm.getString("requestStream.readline.toolong")); 404 | } 405 | } 406 | // We're at the end of the internal buffer 407 | if (pos >= count) { 408 | // Copying part (or all) of the internal buffer to the line 409 | // buffer 410 | int val = read(); 411 | if (val == -1) 412 | throw new IOException 413 | (sm.getString("requestStream.readline.error")); 414 | pos = 0; 415 | readStart = 0; 416 | } 417 | if (buf[pos] == CR) { 418 | } else if (buf[pos] == LF) { 419 | eol = true; 420 | } else { 421 | // FIXME : Check if binary conversion is working fine 422 | int ch = buf[pos] & 0xff; 423 | header.value[readCount] = (char) ch; 424 | readCount++; 425 | } 426 | pos++; 427 | } 428 | 429 | int nextChr = read(); 430 | 431 | if ((nextChr != SP) && (nextChr != HT)) { 432 | pos--; 433 | validLine = false; 434 | } else { 435 | eol = false; 436 | // if the buffer is full, extend it 437 | if (readCount >= maxRead) { 438 | if ((2 * maxRead) <= HttpHeader.MAX_VALUE_SIZE) { 439 | char[] newBuffer = new char[2 * maxRead]; 440 | System.arraycopy(header.value, 0, newBuffer, 0, 441 | maxRead); 442 | header.value = newBuffer; 443 | maxRead = header.value.length; 444 | } else { 445 | throw new IOException 446 | (sm.getString("requestStream.readline.toolong")); 447 | } 448 | } 449 | header.value[readCount] = ' '; 450 | readCount++; 451 | } 452 | 453 | } 454 | 455 | header.valueEnd = readCount; 456 | 457 | } 458 | 459 | 460 | /** 461 | * Read byte. 462 | */ 463 | public int read() 464 | throws IOException { 465 | if (pos >= count) { 466 | fill(); 467 | if (pos >= count) 468 | return -1; 469 | } 470 | return buf[pos++] & 0xff; 471 | } 472 | 473 | 474 | /** 475 | * 476 | */ 477 | /* 478 | public int read(byte b[], int off, int len) 479 | throws IOException { 480 | 481 | } 482 | */ 483 | 484 | 485 | /** 486 | * 487 | */ 488 | /* 489 | public long skip(long n) 490 | throws IOException { 491 | 492 | } 493 | */ 494 | 495 | 496 | /** 497 | * Returns the number of bytes that can be read from this input 498 | * stream without blocking. 499 | */ 500 | public int available() 501 | throws IOException { 502 | return (count - pos) + is.available(); 503 | } 504 | 505 | 506 | /** 507 | * Close the input stream. 508 | */ 509 | public void close() 510 | throws IOException { 511 | if (is == null) 512 | return; 513 | is.close(); 514 | is = null; 515 | buf = null; 516 | } 517 | 518 | 519 | // ------------------------------------------------------ Protected Methods 520 | 521 | 522 | /** 523 | * Fill the internal buffer using data from the undelying input stream. 524 | */ 525 | protected void fill() 526 | throws IOException { 527 | pos = 0; 528 | count = 0; 529 | int nRead = is.read(buf, 0, buf.length); 530 | if (nRead > 0) { 531 | count = nRead; 532 | } 533 | } 534 | 535 | 536 | } 537 | -------------------------------------------------------------------------------- /src/ex03/connector/startup/Boostrap.java: -------------------------------------------------------------------------------- 1 | package ex03.connector.startup; 2 | 3 | import ex03.connector.http.HttpConnector; 4 | 5 | /** 6 | * 启动应用程序 7 | * @author lc 8 | */ 9 | public class Boostrap { 10 | 11 | public static void main(String[] args) { 12 | HttpConnector connector = new HttpConnector(); 13 | connector.start(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /webroot/PrimitiveServlet.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/webroot/PrimitiveServlet.class -------------------------------------------------------------------------------- /webroot/PrimitiveServlet.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/webroot/PrimitiveServlet.java -------------------------------------------------------------------------------- /webroot/images/logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoldenLiang/HowTomcatWorks/50c76d90d098377af0799a7306595cb37432a3a6/webroot/images/logo.gif -------------------------------------------------------------------------------- /webroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Welcome to BrainySoftware 4 | 5 | 6 | 7 |
8 | Welcome to BrainySoftware. 9 | 10 | --------------------------------------------------------------------------------