手把手教你撸一个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/

猜你喜欢

转载自www.cnblogs.com/shak1ng/p/8963609.html