最近学习了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

 

一、Response响应

1、Cookie类

 1 public class Cookie {
 2 
 3     private String name;
 4     private String value;
 5     private long age;
 6     private String path = "/";
 7     private String domain;
 8 
 9     public Cookie() {
10         super();
11     }
12 
13     public Cookie(String name, String value, long age) {
14         super();
15         this.name = name;
16         this.value = value;
17         this.age = age;
18     }
19 
20     // getter and setter
21 }

 

2、Response接口

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

 

 1 // 设置http响应状态码
 2 void setResponseCode(int status);
 3 
 4 // 设置http响应的Content-Type
 5 void setContentType(String contentType);
 6 
 7 // 设置header
 8 void setHeader(String headerName, String headerValue);
 9 
10 // 添加一个cookie到响应中
11 void addCookie(Cookie cookie);
12 
13 // 设置响应编码字符集
14 void setCharsetEncoding(String charsetName);
15 
16 // 响应
17 void response();
18 
19 // 获取当前请求所对应的客户端socket通道
20 @Deprecated
21 SocketChannel getOut();
22 
23 // 把指定的字符串写入响应缓冲区
24 void print(String line);
25 
26 // 把指定的字符串写入响应缓冲区,末尾有换行符
27 void println(String line);

 

二、HttpResponse实现类

1、核心字段

 1 // 时间格式化工具
 2 private static SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
 4 
 5 // 编码字符集
 6 private CharsetEncoder encoder;
 7 
 8 // 响应的Content-Type
 9 private String contentType = "text/html;charset=utf-8";
10 
11 // 响应状态码
12 private int status = 0;
13 
14 // 响应头
15 private Map<String, String> headers = new HashMap<String, String>();
16 
17 // 响应cookie
18 private List<Cookie> cookies = new ArrayList<Cookie>();
19 
20 // 本地资源输入通道
21 private FileChannel in;
22 
23 // 客户端输出通道
24 private SocketChannel out;
25 
26 // 动态资源生成的数据
27 private StringBuilder content = new StringBuilder();
28 
29 // 获取服务器配置
30 HttpServerConfig config = HttpServerConfig.getInstance();

 

2、构造方法

提供两个构造方法

 1 public HttpResponse(SocketChannel sChannel) {
 2     // 获取GBK字符集
 3     Charset c1 = Charset.forName(config.getResponseCharset());
 4     // 获取编码器
 5     this.encoder = c1.newEncoder();
 6     // 获取Content-Type
 7     this.setContentType(ContentTypeUtil.getContentType(ContentTypeUtil.HTML));
 8     this.headers.put("Date", sdf.format(new Date()));
 9     this.headers.put("Server", "nginx");
10     this.headers.put("Connection", "keep-alive");
11     // 客户端输出通道
12     this.out = sChannel;
13 }

 

此方法初始化编码字符集、设置基础的响应头

 

下面的构造方法比前一个多了一些内容:根据资源uri获取本地资源输入通道、设置资源的Expires头,所以在请求静态资源时使用这个方法创建Response对象

 1 public HttpResponse(Request req, SocketChannel sChannel) {
 2 
 3     this(sChannel);
 4 
 5     // 获取请求资源URI
 6     String uri = req.getRequestURI();
 7 
 8     // 获取本地输入通道
 9     this.getLocalFileChannel(uri);
10 
11     // 设置Content-Type
12     this.setContentType(req.getContentType());
13 
14     // 设置静态资源过期响应头
15     int expires = config.getExpiresMillis(this.contentType);
16     if (expires > 0) {
17         long expiresTimeStamp = System.currentTimeMillis() + expires;
18         this.headers.put("Expires", sdf.format(new Date(expiresTimeStamp)));
19     }
20 }

 

3、从请求uri获取本地输入通道

这是一个私有方法,会尝试根据参数uri到站点root下面寻找资源文件,并且打开输入通道。

如果打开通道正常,则设置200响应码,设置Content-Length响应头。

如果抛出NoSuchFileException异常设置404响应码。

如果是其他的异常设置500响应码

 1 private void getLocalFileChannel(String uri) {
 2     // 打开本地文件
 3     try {
 4         this.in = FileChannel.open(Paths.get(config.getRoot(), uri),
 5                 StandardOpenOption.READ);
 6         // 设置Content-Length响应头
 7         this.setHeader("Content-Length", String.valueOf(in.size()));
 8         // 设置响应状态码200
 9         this.setResponseCode(ResponseUtil.RESPONSE_CODE_200);
10     } catch (NoSuchFileException e) {
11         // 没有本地资源被找到
12         // 设置响应状态码404
13         this.setResponseCode(ResponseUtil.RESPONSE_CODE_404);
14         // 关闭本地文件通道
15         this.closeLocalFileChannel();
16     } catch (IOException e) {
17         // 打开资源时出现异常
18         // 设置响应状态码500
19         this.setResponseCode(ResponseUtil.RESPONSE_CODE_500);
20         // 关闭本地文件通道
21         this.closeLocalFileChannel();
22     }
23 }

 

4、setCharsetEncoding方法

1 public void setCharsetEncoding(String charsetName) {
2     // 获取GBK字符集
3     Charset c1 = Charset.forName(charsetName);
4     // 获取编码器
5     this.encoder = c1.newEncoder();
6 }

 

5、response方法

  • 输出响应首行
  • 输出响应头
  • 输出cookie
  • 打印一个空白行后,输出响应主体
  • 最后关闭输入通道
 1 public void response() {
 2     try {
 3         // 输出响应首行
 4         this.writeResponseLine();
 5         // 输出Header
 6         this.writeHeaders();
 7         // 输出全部cookie
 8         this.writeCookies();
 9 
10         // 再输出一个换行,目的是输出一个空白行,下面就是响应主体了
11         this.newLine();
12 
13         // 304
14         if (this.status == ResponseUtil.RESPONSE_CODE_304) {
15             return;
16         }
17 
18         // 输出响应主体
19         if (in != null && in.size() > 0) {
20             // 输出本地资源
21             long size = in.size();
22             long pos = 0;
23             long count = 0;
24 
25             while (pos < size) {
26                 count = size - pos > 31457280 ? 31457280 : size - pos;
27                 pos += in.transferTo(pos, count, out);
28             }
29         } else {
30             // 输出动态程序解析后的字符串
31             this.write(content.toString());
32         }
33     } catch (IOException e) {
34     } finally {
35         // 关闭本地文件通道
36         this.closeLocalFileChannel();
37     }
38 }
View Code

相关文章:

  • 2021-06-23
  • 2021-07-04
  • 2022-12-23
  • 2022-12-23
  • 2021-08-07
  • 2021-08-17
  • 2021-10-08
猜你喜欢
  • 2022-02-10
  • 2021-09-16
  • 2021-09-02
  • 2021-12-18
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案