NIO开发Http服务器(4):Response封装和响应
最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室。我们是做WEB开发的,整天围着tomcat、nginx转,所以选择了一个新的方向,就是自己开发一个简单的Http服务器,在总结Java NIO的同时,也加深一下对http协议的理解。
项目实现了静态资源(html、css、js和图片)和简单动态资源的处理,可以实现监听端口、部署目录、资源过期的配置。涉及到了NIO缓冲区、通道和网络编程的核心知识点,还是比较基础的。
本文主要讲解Http响应的封装和输出
文章目录:
NIO开发Http服务器(3):核心配置和Request封装
NIO开发Http服务器(5-完结):HttpServer服务器类
Github地址:
https://github.com/xuguofeng/http-server
一、Response响应
1、Cookie类
public class Cookie {
private String name;
private String value;
private long age;
private String path = "/";
private String domain;
public Cookie() {
super();
}
public Cookie(String name, String value, long age) {
super();
this.name = name;
this.value = value;
this.age = age;
}
// getter and setter
}
2、Response接口
该接口定义了Response对象需要有的核心方法
// 设置http响应状态码
void setResponseCode(int status); // 设置http响应的Content-Type
void setContentType(String contentType); // 设置header
void setHeader(String headerName, String headerValue); // 添加一个cookie到响应中
void addCookie(Cookie cookie); // 设置响应编码字符集
void setCharsetEncoding(String charsetName); // 响应
void response(); // 获取当前请求所对应的客户端socket通道
@Deprecated
SocketChannel getOut(); // 把指定的字符串写入响应缓冲区
void print(String line); // 把指定的字符串写入响应缓冲区,末尾有换行符
void println(String line);
二、HttpResponse实现类
1、核心字段
// 时间格式化工具
private static SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); // 编码字符集
private CharsetEncoder encoder; // 响应的Content-Type
private String contentType = "text/html;charset=utf-8"; // 响应状态码
private int status = 0; // 响应头
private Map<String, String> headers = new HashMap<String, String>(); // 响应cookie
private List<Cookie> cookies = new ArrayList<Cookie>(); // 本地资源输入通道
private FileChannel in; // 客户端输出通道
private SocketChannel out; // 动态资源生成的数据
private StringBuilder content = new StringBuilder(); // 获取服务器配置
HttpServerConfig config = HttpServerConfig.getInstance();
2、构造方法
提供两个构造方法
public HttpResponse(SocketChannel sChannel) {
// 获取GBK字符集
Charset c1 = Charset.forName(config.getResponseCharset());
// 获取编码器
this.encoder = c1.newEncoder();
// 获取Content-Type
this.setContentType(ContentTypeUtil.getContentType(ContentTypeUtil.HTML));
this.headers.put("Date", sdf.format(new Date()));
this.headers.put("Server", "nginx");
this.headers.put("Connection", "keep-alive");
// 客户端输出通道
this.out = sChannel;
}
此方法初始化编码字符集、设置基础的响应头
下面的构造方法比前一个多了一些内容:根据资源uri获取本地资源输入通道、设置资源的Expires头,所以在请求静态资源时使用这个方法创建Response对象
public HttpResponse(Request req, SocketChannel sChannel) {
this(sChannel);
// 获取请求资源URI
String uri = req.getRequestURI();
// 获取本地输入通道
this.getLocalFileChannel(uri);
// 设置Content-Type
this.setContentType(req.getContentType());
// 设置静态资源过期响应头
int expires = config.getExpiresMillis(this.contentType);
if (expires > 0) {
long expiresTimeStamp = System.currentTimeMillis() + expires;
this.headers.put("Expires", sdf.format(new Date(expiresTimeStamp)));
}
}
3、从请求uri获取本地输入通道
这是一个私有方法,会尝试根据参数uri到站点root下面寻找资源文件,并且打开输入通道。
如果打开通道正常,则设置200响应码,设置Content-Length响应头。
如果抛出NoSuchFileException异常设置404响应码。
如果是其他的异常设置500响应码
private void getLocalFileChannel(String uri) {
// 打开本地文件
try {
this.in = FileChannel.open(Paths.get(config.getRoot(), uri),
StandardOpenOption.READ);
// 设置Content-Length响应头
this.setHeader("Content-Length", String.valueOf(in.size()));
// 设置响应状态码200
this.setResponseCode(ResponseUtil.RESPONSE_CODE_200);
} catch (NoSuchFileException e) {
// 没有本地资源被找到
// 设置响应状态码404
this.setResponseCode(ResponseUtil.RESPONSE_CODE_404);
// 关闭本地文件通道
this.closeLocalFileChannel();
} catch (IOException e) {
// 打开资源时出现异常
// 设置响应状态码500
this.setResponseCode(ResponseUtil.RESPONSE_CODE_500);
// 关闭本地文件通道
this.closeLocalFileChannel();
}
}
4、setCharsetEncoding方法
public void setCharsetEncoding(String charsetName) {
// 获取GBK字符集
Charset c1 = Charset.forName(charsetName);
// 获取编码器
this.encoder = c1.newEncoder();
}
5、response方法
- 输出响应首行
- 输出响应头
- 输出cookie
- 打印一个空白行后,输出响应主体
- 最后关闭输入通道
public void response() {
try {
// 输出响应首行
this.writeResponseLine();
// 输出Header
this.writeHeaders();
// 输出全部cookie
this.writeCookies();
// 再输出一个换行,目的是输出一个空白行,下面就是响应主体了
this.newLine();
//
if (this.status == ResponseUtil.RESPONSE_CODE_304) {
return;
}
// 输出响应主体
if (in != null && in.size() > 0) {
// 输出本地资源
long size = in.size();
long pos = 0;
long count = 0;
while (pos < size) {
count = size - pos > 31457280 ? 31457280 : size - pos;
pos += in.transferTo(pos, count, out);
}
} else {
// 输出动态程序解析后的字符串
this.write(content.toString());
}
} catch (IOException e) {
} finally {
// 关闭本地文件通道
this.closeLocalFileChannel();
}
}
6、writeResponseLine、writeHeaders、writeCookies方法
这几个私有方法分别用于输出响应首行、输出响应头和响应cookie
private void writeResponseLine() throws IOException {
this.write(ResponseUtil.getResponseLine(this.status));
this.newLine();
}
private void writeHeaders() throws IOException {
Set<Entry<String, String>> entrys = this.headers.entrySet();
for (Iterator<Entry<String, String>> i = entrys.iterator(); i.hasNext();) {
Entry<String, String> entry = i.next();
String headerContent = entry.getKey() + ": " + entry.getValue();
this.write(headerContent);
this.newLine();
}
}
private void writeCookies() throws IOException {
for (Cookie cookie : this.cookies) {
String name = cookie.getName();
String value = cookie.getValue();
if (StringUtil.isNullOrEmpty(name)
|| StringUtil.isNullOrEmpty(value)) {
continue;
}
// 构造cookie响应头
StringBuilder s = new StringBuilder("Set-Cookie: ");
// cookie名字和值
s.append(name);
s.append("=");
s.append(value);
s.append("; ");
// 设置过期时间
long age = cookie.getAge();
if (age > -1) {
long expiresTimeStamp = System.currentTimeMillis() + age;
s.append("Expires=");
s.append(sdf.format(new Date(expiresTimeStamp)));
s.append("; ");
}
// 设置path
String path = cookie.getPath();
if (!StringUtil.isNullOrEmpty(path)) {
s.append("Path=");
s.append(path);
s.append("; ");
}
// 设置domain
String domain = cookie.getDomain();
if (!StringUtil.isNullOrEmpty(domain)) {
s.append("Domain=");
s.append(domain);
s.append("; ");
}
// http only
s.append("HttpOnly");
// 写到响应通道
this.write(s.toString());
this.newLine();
}
}
7、write和newLine方法
private void newLine() throws IOException {
this.write("\n");
}
private void write(String content) throws IOException {
CharBuffer cBuf = CharBuffer.allocate(content.length());
cBuf.put(content);
cBuf.flip();
ByteBuffer bBuf = this.encoder.encode(cBuf);
this.out.write(bBuf);
}
newLine方法会输出一个换行符
write方法会把指定的参数字符串输出到响应输出通道
NIO开发Http服务器(4):Response封装和响应的更多相关文章
- NIO开发Http服务器(3):核心配置和Request封装
最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室.我们是做WEB开发的,整天围着tomcat.n ...
- NIO开发Http服务器(5-完结):HttpServer服务器类
最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室.我们是做WEB开发的,整天围着tomcat.n ...
- NIO开发Http服务器(2):项目结构
最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室.我们是做WEB开发的,整天围着tomcat.n ...
- NIO开发Http服务器(1):项目下载、打包和部署
最近学习了Java NIO技术,觉得不能再去写一些Hello World的学习demo了,而且也不想再像学习IO时那样编写一个控制台(或者带界面)聊天室.我们是做WEB开发的,整天围着tomcat.n ...
- Netty精粹之JAVA NIO开发需要知道的
学习Netty框架以及相关源码也有一小段时间了,恰逢今天除夕,写篇文章总结一下.Netty是个高效的JAVA NIO框架,总体框架基于异步非阻塞的设计,基于网络IO事件驱动,主要贡献在于可以让用户基于 ...
- 使用C#开发HTTP服务器系列之访问主页
各位朋友大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com.在这个系列文章的第一篇中,我们着重认识和了解了HTTP协议,并在此基础上实现了一个可交互的W ...
- .Net Core 跨平台开发实战-服务器缓存:本地缓存、分布式缓存、自定义缓存
.Net Core 跨平台开发实战-服务器缓存:本地缓存.分布式缓存.自定义缓存 1.概述 系统性能优化的第一步就是使用缓存!什么是缓存?缓存是一种效果,就是把数据结果存在某个介质中,下次直接重用.根 ...
- 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版
上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新 可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文 ...
- 使用Go开发web服务器
原文链接 Go(Golang.org)是在标准库中提供HTTP协议支持的系统语言,通过他可以快速简单的开发一个web服务器.同时,Go语言为开发者提供了很多便利.这本篇博客中我们将列出使用Go开发HT ...
随机推荐
- hive 整合ranger
一.安装hive插件 1.解压安装 # tar zxvf ranger-2.0.0-SNAPSHOT-hive-plugin.tar.gz -C /data1/hadoop/ 2.修改install ...
- clion下批量删除断点
- Alpha3
队名:福大帮 组长博客链接:https://www.cnblogs.com/mhq-mhq/p/11899921.html 作业博客 :https://edu.cnblogs.com/campus/f ...
- Spring Cloud Ribbon---微服务调用和客户端负载均衡
前面分析了Eureka的使用,作为服务注册中心,Eureka 分为 Server 端和 Client 端,Client 端作为服务的提供者,将自己注册到 Server 端,Client端高可用的方式是 ...
- Python常用模块大全
Python常用模块大全 os模块: os.remove() 删除文件 os.unlink() 删除文件 os.rename() 重命名文件 os.listdir() 列出指定目录下所有文件 os.c ...
- SymPy解方程的实现
https://www.cnblogs.com/zgyc/p/6277562.html SymPy完全是用Python写的,并不需要外部的库 原理: 单纯用语言内置的运算与变量解决的是,由值求结果.如 ...
- LinQ中List,取某个字段,然后用逗号拼接
string htDetails = string.Join(",", DemoList.Select(t => t.id).Distinct().ToArray());
- linux升级python到2.7版本
linux的python安装包默认版本是2.6.6,yum程序默认也是依赖这个版本的python包的,但是其他一些程序如nodejs,却要的是2.7版本,因此必须要考虑升级后与yum的兼容问题.两步走 ...
- [LeetCode] 246. Strobogrammatic Number 对称数
A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside ...
- SpringBoot系列教程web篇之如何自定义参数解析器
title: 190831-SpringBoot系列教程web篇之如何自定义参数解析器 banner: /spring-blog/imgs/190831/logo.jpg tags: 请求参数 cat ...