Tomcat作为开源的轻量级WEB服务器,虽然不是很适合某些大型项目,但是它开源,读其源代码可以很好的提高我们的编程功底和设计思维。Tomcat中用到了很多比较好的设计模式,其中代码风格也很值得我们去效仿。前阵子看了Tomcat源码分析这本书,特此过来分享分享自己的学习过程记录。说得不好,大神不要喷我。
也不废话了,直入主题上代码。Tomcat是什么,Tomcat是一个web服务器,能够接收请求,作出响应。接收请求,作出响应让我们联想到Socket编程。我们可以起一个线程服务ServerSocket来监听本机的8080端口(可配置),然后就可以在浏览器上访问http://localhost:8080/index.html,这个时候就可以通过socket的inputstream获取到浏览器封装的HTTP请求了,然后就可以针对这个请求来大做文章。以下是服务端的代码
1 package cn.tim.server.core; 2 3 import java.io.File; 4 import java.io.IOException; 5 import java.io.InputStream; 6 import java.io.OutputStream; 7 import java.net.InetAddress; 8 import java.net.ServerSocket; 9 import java.net.Socket; 10 11 12 /** 13 * HTTP服务器,主类 14 * @author TIM 15 * 16 */ 17 public class HttpServer { 18 19 20 /** 21 * 端口 22 */ 23 public int PORT = 8080; 24 25 26 /** 27 * 关闭指令 28 */ 29 public final static String SHUTDOWN = "SHUTDOWN"; 30 31 32 /** 33 * webroot根目录 34 */ 35 public static final String WEB_ROOT = 36 System.getProperty("user.dir") + File.separator + "WebRoot"; 37 38 39 public static void main(String[] args) { 40 41 new HttpServer().await(); 42 43 } 44 45 46 /** 47 * 线程监听 48 */ 49 private void await() { 50 51 ServerSocket server = null; 52 try { 53 server = new ServerSocket(PORT,1, 54 InetAddress.getByName("127.0.0.1")); 55 } catch (Exception e) { 56 e.printStackTrace(); 57 } 58 59 boolean shutdown = false; 60 while(!shutdown) { 61 Socket client = null; 62 InputStream in = null; 63 OutputStream out = null; 64 try { 65 // 获取到请求socket 66 client = server.accept(); 67 in = client.getInputStream(); 68 out = client.getOutputStream(); 69 70 // 生成request同时解析请求 71 Request request = new Request(in); 72 request.parse(); 73 74 // 生成response 75 Response response = new Response(out); 76 response.setRequest(request); 77 // 根据资源定位符发送对应资源 78 response.sendStaticResource(); 79 client.close(); 80 81 shutdown = request.getUri().equals(SHUTDOWN); 82 } catch (IOException e) { 83 // TODO Auto-generated catch block 84 e.printStackTrace(); 85 continue; 86 } 87 88 } 89 90 } 91 92 }
既然服务端HttpServer都出来了,Request都干些什么,request顾名思义,请求肯定是封装请求的一个JAVA类,肯定要能够解析HTTP请求,例如访问静态资源,就得获取到静态资源的资源定位符uri。
HTTP请求Request类:
1 GET /index.html HTTP/1.1 2 Accept: text/html, application/xhtml+xml, */* 3 Accept-Language: zh-CN 4 User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0; MALCJS) 5 Accept-Encoding: gzip, deflate 6 Host: localhost:8080 7 DNT: 1 8 Connection: Keep-Alive 9 Cookie: principal=user:admin__password:admin
1 package cn.tim.server.core; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 6 /** 7 * 封装请求 8 * @author TIM 9 * 10 */ 11 public class Request { 12 13 14 /** 15 * 请求输入流 16 */ 17 private InputStream in; 18 19 20 /** 21 * 资源定位符 22 */ 23 private String uri; 24 25 26 /** 27 * 初始化request,传入socket输入流 28 * @param in 29 */ 30 public Request(InputStream in) { 31 this.in = in; 32 } 33 34 35 /** 36 * 根据请求字符串解析请求 37 */ 38 public void parse() { 39 40 try { 41 byte[] bytes = new byte[2048]; 42 int i = in.read(bytes); 43 44 StringBuffer buffer = new StringBuffer(2048); 45 for(int j=0; j<i; j++) { 46 buffer.append((char)bytes[j]); 47 } 48 System.out.println(buffer.toString()); 49 uri = parseUri(buffer.toString()); 50 System.out.println(uri); 51 } catch (IOException e) { 52 // TODO Auto-generated catch block 53 e.printStackTrace(); 54 } 55 56 } 57 58 59 /** 60 * 解析出资源定位符uri,实际上就是用字符串分拆获取到GET /index.html HTTP/1.1中的/index,html 61 * @return 62 */ 63 private String parseUri(String requestString) { 64 65 int index1 = requestString.indexOf(" "); 66 if(index1 != -1) { 67 int index2 = requestString.indexOf(" ", index1+1); 68 if(index2>index1) { 69 return requestString.substring(index1+1, index2); 70 } 71 } 72 return null; 73 } 74 75 76 public InputStream getIn() { 77 return in; 78 } 79 80 81 public String getUri() { 82 return uri; 83 } 84 85 }
获取到资源定位符,接下来就是根据资源定位符来作出相应,当然实际的Tomcat处理方式肯定是很复杂的,我们只模仿其中简单的方式,访问静态资源。
Response类:
1 package cn.tim.server.core; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.io.OutputStream; 8 9 public class Response { 10 11 12 /** 13 * 输出 14 */ 15 private OutputStream out; 16 17 18 /** 19 * 缓冲大小 20 */ 21 public final static int BUFFER_SIZE = 2048; 22 23 24 /** 25 * 请求,根据请求作出对应的响应 26 */ 27 private Request request; 28 29 30 public Response(OutputStream out) { 31 this.out = out; 32 } 33 34 35 /** 36 * 发送静态资源 37 */ 38 public void sendStaticResource() { 39 40 byte[] bytes = new byte[BUFFER_SIZE]; 41 InputStream in = null; 42 try { 43 File file = new File(HttpServer.WEB_ROOT, request.getUri()); 44 // 请求的资源存在 45 if(file.exists()) { 46 in = new FileInputStream(file); 47 int ch; 48 if((ch=in.read(bytes, 0, BUFFER_SIZE))!=-1) { 49 out.write(bytes, 0, ch); 50 } 51 } 52 // 请求资源不存在报404 53 else { 54 String errorMessage = "HTTP/1.1 404 File Not Found " + 55 "Content-Type: text/html " + 56 "Content-Length: 23 " + 57 " " + 58 "<h1>File Not Found</h1>"; 59 out.write(errorMessage.getBytes()); 60 } 61 } catch (Exception e) { 62 // TODO Auto-generated catch block 63 e.printStackTrace(); 64 } finally { 65 if(in!=null) 66 try { 67 in.close(); 68 } catch (IOException e) { 69 // TODO Auto-generated catch block 70 e.printStackTrace(); 71 } 72 } 73 74 } 75 76 77 public void setRequest(Request request) { 78 this.request = request; 79 } 80 81 }
这样,一个简单的Web服务器就实现了,可以访问静态资源,直接在浏览器上访问,没有找到对应的资源还可以报404错误。今天就写到这里,继续努力。。。