zoukankan      html  css  js  c++  java
  • NIO开发Http服务器(3):核心配置和Request封装

    最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室。我们是做WEB开发的,整天围着tomcatnginx转,所以选择了一个新的方向,就是自己开发一个简单的Http服务器,在总结Java NIO的同时,也加深一下对http协议的理解。

    项目实现了静态资源(htmlcssjs和图片)和简单动态资源的处理,可以实现监听端口、部署目录、资源过期的配置。涉及到了NIO缓冲区、通道和网络编程的核心知识点,还是比较基础的。

    之前的两篇文章介绍了项目的部署、运行和开发环境等,从本篇文章开始详细介绍下项目的代码,本文首先讲核心的配置类、配置文件和http请求的封装。

    文章目录:

    NIO开发Http服务器(1):项目下载、打包和部署

    NIO开发Http服务器(2):项目结构

    NIO开发Http服务器(3):核心配置和Request封装

    NIO开发Http服务器(4):Response封装和响应

    NIO开发Http服务器(5-完结):HttpServer服务器类

    Github地址:

    https://github.com/xuguofeng/http-server

    一、核心配置类

    1、ContentTypeUtil工具类

    这个类使用一个静态Map保存资源后缀和ContentType的对应关系,主要支持:htmlcssjsjpgjpegpnggificotxt

    提供一个静态方法通过资源后缀获取对应的ContentType

     1 public static String getContentType(String suffix) {
     2     // 如果资源uri后缀为空,直接返回text/html
     3     if (StringUtil.isNullOrEmpty(suffix)) {
     4         return contentTypes.get(HTML);
     5     }
     6     // 根据后缀从contentTypes获取
     7     String contentType = contentTypes.get(suffix);
     8     // 如果没有获取到,返回application/octet-stream
     9     if (contentType == null) {
    10         return APPLICATION_OCTET_STREAM;
    11     }
    12     return contentType;
    13 }

    2、ResponseUtil工具类

    这个类定义了一些常用的响应状态码和响应首行字符串,如下:

    1 public static final int RESPONSE_CODE_200 = 200;
    2 public static final int RESPONSE_CODE_304 = 304;
    3 public static final int RESPONSE_CODE_404 = 404;
    4 public static final int RESPONSE_CODE_500 = 500;
    5 
    6 public static final String RESPONSE_LINE_200 = "HTTP/1.1 200 OK";
    7 public static final String RESPONSE_LINE_304 = "HTTP/1.1 304 Not Modified";
    8 public static final String RESPONSE_LINE_404 = "HTTP/1.1 404 Not Found";
    9 public static final String RESPONSE_LINE_500 = "HTTP/1.1 500 Internal Server Error";

    其他的WEB_ROOTRESPONSE_PAGE_404CHARSET都不再使用,已经被配置文件替代

    提供一个静态方法根据指定状态码获取对应的响应首行字符串

     1 public static String getResponseLine(int status) {
     2     switch (status) {
     3     case RESPONSE_CODE_200:
     4         return RESPONSE_LINE_200;
     5     case RESPONSE_CODE_304:
     6         return RESPONSE_LINE_304;
     7     case RESPONSE_CODE_404:
     8         return RESPONSE_LINE_404;
     9     case RESPONSE_CODE_500:
    10         return RESPONSE_LINE_500;
    11     }
    12     return RESPONSE_LINE_200;
    13 }

    3、HttpServerConfig和server.properties配置文件

    先看一下server.properties配置文件

     1 # 服务监听端口
     2 server.port=8082
     3 # 服务部署根目录
     4 server.root=WebContent
     5 # 404页面
     6 server.404.page=WebContent/404.html
     7 
     8 # 编码
     9 request.charset=utf-8
    10 response.charset=utf-8
    11 
    12 # 静态资源过期设置
    13 expires.jpg=120000
    14 expires.jpeg=120000
    15 expires.png=120000
    16 expires.js=60000
    17 expires.css=60000
    18 expires.ico=7200000
    19 
    20 # 动态资源配置
    21 servlet.test.do=org.net5ijy.nio.servlet.TestServlet

    配置比较简单,其中

    server.root配置web站点的部署目录,默认使用当前工作目录下面的WebContent目录

    server.404.page配置资源404时的响应页面

    expires.xxx可以配置一些静态资源的过期时间,单位为毫秒。响应时会在header中添加Expires响应头

    servlet.xxx可以配置动态资源的处理类型,xxx部分配置资源的uri,值为处理这个请求的类的全限定名

    然后看一下HttpServerConfig类,这个会读取server.properties配置文件,使用单例模式保存配置,对外提供相关方法获取配置信息以便程序使用

    代码比较简单,就不再做过多的介绍了

    二、Request接口和HttpRequest类

    1、Request接口

    该接口定义了Request对象需要有的核心方法

     1 // 获取请求方法
     2 String getMethod();
     3 // 获取请求资源uri
     4 String getRequestURI();
     5 // 获取客户端请求的协议版本
     6 String getProtocol();
     7 // 获取请求的主机名
     8 String getHost();
     9 // 获取请求的端口
    10 int getPort();
    11 // 获取请求资源的Content-Type
    12 String getContentType();
    13 // 获取全部请求参数
    14 Map<String, String> getParameters();
    15 // 获取指定的请求参数的值
    16 String getParameter(String paramaterName);
    17 // 获取全部请求头
    18 Map<String, String> getHeaders();
    19 // 获取指定的请求头的值
    20 String getHeader(String headerName);
    21 // 获取请求里面携带的cookie
    22 List<Cookie> getCookies();

    2、HttpRequest类

    该类实现了Request接口

    内部保存请求的uri、请求方法、协议版本、hostport、参数、请求头和cookie

    构造方法需要传入客户端请求的字符串,内部进行解析

    3、解析请求uri、请求方法、协议版本

     1 // 所在平台的行分隔符
     2 String lineSeparator = System.getProperty("line.separator");
     3 
     4 // 获取请求行
     5 String requestLine = body.substring(0, body.indexOf(lineSeparator));
     6 // 获取请求方法和资源uri
     7 String[] requestLines = requestLine.split("\s+");
     8 this.method = requestLines[0];
     9 this.protocol = requestLines[2];
    10 
    11 // 解析请求uri
    12 String[] uri = requestLines[1].split("\?");
    13 this.requestURI = uri[0];

    4、获取请求头

     1 // 截取请求头和请求主体
     2 body = body.substring(body.indexOf(lineSeparator) + 2);
     3 
     4 // 获取请求头
     5 int num = 0;
     6 String[] headerAndParameter = body.split(lineSeparator);
     7 for (; num < headerAndParameter.length; num++) {
     8     String headerLine = headerAndParameter[num];
     9     // 遍历到请求主体上面的空白行就停止
    10     if (StringUtil.isNullOrEmpty(headerLine)) {
    11         break;
    12     }
    13     // 获取第一个“:”的下标
    14     int indexOfMaohao = headerLine.indexOf(":");
    15     if (indexOfMaohao == -1) {
    16         continue;
    17     }
    18     String headerName = headerLine.substring(0, indexOfMaohao).trim();
    19     String headerValue = headerLine.substring(indexOfMaohao + 1).trim();
    20     this.headers.put(headerName, headerValue);
    21 }
    • 把请求头和请求参数从请求字符串中截取出来
    • 按行分割
    • 遍历分割后的字符串数组
    • 解析出请求头的行,再使用:”进行分割
    • 最后把请求头放入Map

    5、获取请求cookie

     1 String cookieHeader = headers.get("Cookie");
     2 if (!StringUtil.isNullOrEmpty(cookieHeader)) {
     3     String[] cookiesArray = cookieHeader.split(";\s*");
     4     for (int i = 0; i < cookiesArray.length; i++) {
     5         String cookieStr = cookiesArray[i];
     6         if (!StringUtil.isNullOrEmpty(cookieStr)) {
     7             String[] cookieArray = cookieStr.split("=");
     8             if (cookieArray.length == 2) {
     9                 Cookie c = new Cookie(cookieArray[0], cookieArray[1], -1);
    10                 this.cookies.add(c);
    11                 if (log.isDebugEnabled()) {
    12                     log.debug("Recieve request cookie " + c);
    13                 }
    14             }
    15         }
    16     }
    17 }
    • headers中获取出Cookie的头
    • 使用空白分割Cookie
    • 遍历分割后的数组
    • 再使用=”分割每一个元素
    • 创建Cookie对象并放入List

    6、获取请求参数

    获取请求参数分为两部分

    第一部分是解析uri后面的如arg1=val1&arg2=val2形式的参数,代码如下:

    // 解析uri后面跟的请求参数
    if (uri.length > 1) {
        this.parameters.putAll(resolveRequestArgs(uri[1]));
    }

    此处使用了一个私有方法解析如arg1=val1&arg2=val2形式的参数,方法如下:

     1 private Map<String, String> resolveRequestArgs(String args) {
     2     Map<String, String> map = new HashMap<String, String>();
     3     if (!StringUtil.isNullOrEmpty(args)) {
     4         String[] argss = args.split("&+");
     5         for (int i = 0; i < argss.length; i++) {
     6             String arg = argss[i];
     7             String[] nameAndValue = arg.split("\s*=\s*");
     8             if (nameAndValue.length == 2) {
     9                 map.put(nameAndValue[0], nameAndValue[1]);
    10             } else if (nameAndValue.length == 1) {
    11                 map.put(nameAndValue[0], "");
    12             }
    13         }
    14     }
    15     return map;
    16 }
    • 使用&+”分割参数字符串
    • 遍历分割后的数组
    • 再使用=”分割元素
    • 把分割后的数据放入Map中并返回

    另外一部分是使用POST方式发送的请求参数,支持JSONarg1=val1&arg2=val2形式的参数,代码如下:

     1 // 获取请求参数
     2 num++;
     3 StringBuilder builder = new StringBuilder();
     4 for (; num < headerAndParameter.length; num++) {
     5     builder.append(headerAndParameter[num]);
     6 }
     7 String requestArgs = builder.toString();
     8 
     9 if (!StringUtil.isNullOrEmpty(requestArgs)) {
    10     if (requestArgs.indexOf("{") > -1) {
    11         ObjectMapper mapper = new ObjectMapper();
    12         try {
    13             @SuppressWarnings("unchecked")
    14             Map<String, String> map = mapper.readValue(requestArgs, Map.class);
    15             this.parameters.putAll(map);
    16         } catch (Exception e) {
    17         }
    18     } else if (requestArgs.indexOf("&") > -1) {
    19         this.parameters.putAll(resolveRequestArgs(requestArgs));
    20     }
    21 }

    此处使用了jackson

     1 <dependency>
     2     <groupId>com.fasterxml.jackson.core</groupId>
     3     <artifactId>jackson-core</artifactId>
     4     <version>2.9.4</version>
     5 </dependency>
     6 <dependency>
     7     <groupId>com.fasterxml.jackson.core</groupId>
     8     <artifactId>jackson-databind</artifactId>
     9     <version>2.9.4</version>
    10 </dependency>

    7、几个核心方法实现方式

    getHost()方法

    1 public String getHost() {
    2     String host = this.headers.get("Host");
    3     if (!StringUtil.isNullOrEmpty(host)) {
    4         this.host = host.split(":")[0];
    5     }
    6     return this.host;
    7 }

    getPort()方法

    1 public int getPort() {
    2     String host = this.headers.get("Host");
    3     if (!StringUtil.isNullOrEmpty(host) && host.indexOf(":") > -1) {
    4         this.port = Integer.parseInt(host.split(":")[1]);
    5     }
    6     return this.port;
    7 }

    getContentType()方法

    1 public String getContentType() {
    2     if (this.requestURI.indexOf(".") == -1) {
    3         return ContentTypeUtil.getContentType(ContentTypeUtil.HTML);
    4     }
    5     String suffix = this.requestURI.substring(this.requestURI.lastIndexOf(".") + 1);
    6     return ContentTypeUtil.getContentType(suffix);
    7 }
  • 相关阅读:
    CSS3 target伪类简介
    不用position,让div垂直居中
    css3 在线编辑工具 连兼容都写好了
    a标签伪类的顺序
    oncopy和onpaste
    【leetcode】1523. Count Odd Numbers in an Interval Range
    【leetcode】1518. Water Bottles
    【leetcode】1514. Path with Maximum Probability
    【leetcode】1513. Number of Substrings With Only 1s
    【leetcode】1512. Number of Good Pairs
  • 原文地址:https://www.cnblogs.com/xugf/p/9603434.html
Copyright © 2011-2022 走看看