手把手教你撸一个Web服务器(二)
书接上回
进阶实现的重构:
一、封装请求的代码
·1请求封装到HttpRequest中,观察Http的请求,进行封装
其中有3个请求参数(请求方式,资源路径,请求协议),构造方法传入InputStream读入请求并赋值(3个参数)
·2在ClientHandler类中调用HttpRequest;
二、封装响应的代码
·1响应封装到HttpResponse中,观察Http的响应,进行封装
其中有4个响应参数(协议名,状态码,响应数据格式,响应数据长度),构造方法传入OutputStream添加get,set方法;
改造getOutputStream,拼接Http响应的标准格式;
因为响应只能发送一次所以做一个boolean类型的变量(标记)记录有没有响应请求,判断该变量决定是否响应;
·2在ClientHandler类中调用HttpResponse
设置HttpResponse中的四个变量的值,输出页面只需要改为HttpResponse中的getOutputStream方法调用write方法
以上,我们就完成了请求与响应代码的封装,以后就可以直接用了,如果不增加新功能就不用再写了
1 /** 2 * 封装Http请求 3 * @author shaking 4 * 5 */ 6 public class HttpRequest { 7 8 private String method; 9 private String uri; 10 private String protocol; 11 12 public HttpRequest(InputStream inputStream) { 13 try { 14 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 15 String line = reader.readLine(); 16 String[] s = line.split(" "); 17 method = s[0]; 18 uri = s[1]; 19 protocol = s[2]; 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 public String getMethod() { 26 return method; 27 } 28 29 public void setMethod(String method) { 30 this.method = method; 31 } 32 33 public String getUri() { 34 return uri; 35 } 36 37 public void setUri(String uri) { 38 this.uri = uri; 39 } 40 41 public String getProtocol() { 42 return protocol; 43 } 44 45 public void setProtocol(String protocol) { 46 this.protocol = protocol; 47 } 48 49 }
1 /** 2 * 封装Http响应 3 * @author shaking 4 * 5 */ 6 public class HttpResponse { 7 8 private String protocol; 9 private int status; 10 private String contentType; 11 private int contentLength; 12 private OutputStream outputStream; 13 private boolean isSend; 14 15 public HttpResponse(OutputStream outputStream) { 16 this.outputStream = outputStream; 17 } 18 19 public OutputStream getOutputStream() { 20 if(!isSend) { 21 PrintStream ps = new PrintStream(outputStream); 22 ps.println(protocol + " " + status + " " + "OK"); 23 ps.println("Content-Type:" + contentType); 24 ps.println("Content-Length:" + contentLength); 25 26 ps.println(); 27 28 isSend = true; 29 } 30 return outputStream; 31 } 32 33 public void setOutputStream(OutputStream outputStream) { 34 this.outputStream = outputStream; 35 } 36 37 public String getProtocol() { 38 return protocol; 39 } 40 41 public void setProtocol(String protocol) { 42 this.protocol = protocol; 43 } 44 45 public int getStatus() { 46 return status; 47 } 48 49 public void setStatus(int status) { 50 this.status = status; 51 } 52 53 public String getContentType() { 54 return contentType; 55 } 56 57 public void setContentType(String contentType) { 58 this.contentType = contentType; 59 } 60 61 public int getContentLength() { 62 return contentLength; 63 } 64 65 public void setContentLength(int contentLength) { 66 this.contentLength = contentLength; 67 } 68 69 }
1 /** 2 * 封装好Http的ClientHandler写法 3 * @author shaking 4 * 5 */ 6 public class ClientHandler implements Runnable{ 7 8 private Socket socket; 9 10 public ClientHandler(Socket socket) { 11 this.socket = socket; 12 } 13 14 public void run() { 15 try { 16 HttpRequest httpRequest = new HttpRequest(socket.getInputStream()); 17 HttpResponse httpResponse = new HttpResponse(socket.getOutputStream()); 18 19 httpResponse.setProtocol("HTTP/1.1"); 20 httpResponse.setStatus(200); 21 httpResponse.setContentType("text/html"); 22 23 String pathName = "WebContent/" + httpRequest.getUri(); 24 File file = new File(pathName); 25 httpResponse.setContentLength((int)file.length()); 26 httpResponse.getOutputStream(); 27 28 BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); 29 30 byte[] bs = new byte[(int) file.length()]; 31 32 bis.read(bs); 33 httpResponse.getOutputStream().write(bs); 34 bis.close(); 35 socket.close(); 36 } catch (IOException e) { 37 e.printStackTrace(); 38 } 39 40 } 41 42 }
进阶实现的二次重构:
一、将代码中的固定的变量(例如端口号,线程池设置的数量,服务器路径等)提取出去,放入common包中,ServletContext,HttpContext
·1ServletContext封装服务器相关参数,这个时候需要用到xml文件配置(建立config文件夹于项目目录下,创建web.xml)
·2在该类中声明4个配置文件中相关变量,利用静态代码块初始化变量值
而利用XML配置就需要导入dom4j的jar包
用SAXReader reader对象解析XML文件;
用reader调用read方法传入文件,new File("config/web.xml");返回的是Document document对象
用document调用getRootElement()方法获取根元素<server></server>
获取根元素后,便可以一层一层的获取它的子元素信息,如果知道子元素的标签名称便可以直接调用element("name")方法获取该子元素,也可以通过elements()方法获得所有子元素,返回的是一个List;
输出元素信息的方式:调用getName方法获取当前元素的元素名,调用attributeValue方法获取属性名;如果当前元素没有子元素调用getText方法后取元素值;这样便可以给相关变量赋值了
·3把之前固定的变量改为修改好的变量
注意:输出的元素信息都是字符串类型需要类型转换
1 /** 2 * 配置参数类 3 * @author shaking 4 * 5 */ 6 public class ServletContext { 7 8 public static int port; 9 public static int maxThread; 10 public static String protocol; 11 public static String webRoot; 12 public static String notFoundPage; 13 14 static { 15 init(); 16 } 17 18 private static void init() { 19 try { 20 SAXReader reader = new SAXReader(); 21 Document document = reader.read("config/web.xml"); 22 Element rootElement = document.getRootElement(); 23 Element connElement = rootElement.element("service"); 24 25 port = Integer.valueOf(connElement.element("connector").attributeValue("port")); 26 maxThread = Integer.valueOf(connElement.element("connector").attributeValue("maxThread")); 27 protocol = connElement.element("connector").attributeValue("protocol"); 28 webRoot = rootElement.element("service").elementText("webroot"); 29 notFoundPage = rootElement.element("service").elementText("not-found-page"); 30 31 } catch (DocumentException e) { 32 e.printStackTrace(); 33 } 34 } 35 36 }
1 <?xml version="1.0" encoding="UTF-8"?> 2 <server> 3 <service> 4 <connector port="8080" maxThread="100" protocol="HTTP/1.1"></connector> 5 <webroot>WebContent</webroot> 6 <not-found-page>404.html</not-found-page> 7 </service> 8 </server>
将ServletContext中的静态变量放在适当的地方使程序更加灵活,此处不再贴代码
二、根据输入地址的后缀,动态的改变Content-Type的类型
·1配置web.xml <type-mappings ext="html" type="text/html"></type-mappings>
·2根据配置去选取需要的类型,因为是对应关系(键值对)所以选择HashMap去接收;
获取type-mappings的所有元素用到elements(),返回一个List
再根据List的每个值取出ext和type即key,value存入HashMap中
·3把之前的setContentType里固定的属性改为新的值
这里修改还有说几句,不能像之前直接写,需要定义一个方法获取地址的后缀,根据split()方法用点分割,这里注意点是保留字符,需要转义;然后再根据后缀名为key取得Map对应的value返回;
1 <?xml version="1.0" encoding="UTF-8"?> 2 <server> 3 <service> 4 <connector port="8080" maxThread="100" protocol="HTTP/1.1"></connector> 5 <webroot>WebContent</webroot> 6 <not-found-page>404.html</not-found-page> 7 </service> 8 <type-mappings ext="html" type="text/html" ></type-mappings> 9 <type-mappings ext="jpg" type="image/jpg" ></type-mappings> 10 <type-mappings ext="png" type="image/png" ></type-mappings> 11 </server>
1 public class ServletContext{ 2 public static Map<String, String> typeMap = new HashMap<String, String>(); 3 4 private static void init() { 5 List<Element> list = rootElement.elements("type-mappings"); 6 for(Element type : list) { 7 String key = type.attributeValue("ext"); 8 String value = type.attributeValue("type"); 9 typeMap.put(key, value); 10 } 11 } 12 }
//如何获得后缀名 String[] s1 = httpRequest.getUri().split("\\."); String ext = s1[1]; if(ServletContext.typeMap.containsKey(ext)) { httpResponse.setContentType(ServletContext.typeMap.get(ext)); }
三、重构HttpContext封装http相关参数
·1声明常量(常见的状态吗与对应的值)
·2将改变应用到HttpResponse中;同二中的2,3;不同的是此处的Map需要在构造方法中初始化直接put就好
1 /** 2 * 封装Http相关参数 3 * @author shaking 4 * 5 */ 6 public class HttpContext { 7 8 public static final int CODE_OK = 200; 9 public static final int CODE_NOT_FOUND = 404; 10 public static final String DESC_OK = "OK"; 11 public static final String DESC_NOT_FOUND = "Not Found"; 12 public static Map<Integer, String> httpMap = new HashMap<Integer, String>(); 13 14 }
1 public class HttpResponse { 2 public HttpResponse(OutputStream outputStream) { 3 this.outputStream = outputStream; 4 HttpContext.httpMap.put(HttpContext.CODE_OK, HttpContext.DESC_OK); 5 HttpContext.httpMap.put(HttpContext.CODE_NOT_FOUND, HttpContext.DESC_NOT_FOUND); 6 } 7 } 8 public OutputStream getOutputStream() { 9 if(!isSend) { 10 PrintStream ps = new PrintStream(outputStream); 11 ps.println(protocol + " " + status + " " + HttpContext.httpMap.get(status)); 12 ps.println("Content-Type:" + contentType); 13 ps.println("Content-Length:" + contentLength); 14 15 ps.println(); 16 17 isSend = true; 18 } 19 return outputStream; 20 }
至此简单的Web服务器就完成了!其他功能我会自己慢慢添加,包括对这些代码的重构等等,我会慢慢更新~
转载请注明出处:http://www.cnblogs.com/shak1ng/