基于netty简单的http服务器搭建
- 简介
- 一、http协议简介
- 1.1 HttpRequest
- 1.2 HttpResponse
- 二、服务端开发
- 三、测试
简介
HTTP是一个超文本传输协议,是一种建立在TCP传输协议之上的应用层协议。http是目前web的主流协议,本章讲述的是基于netty NIO开发的http服务端。本章项目源码地址:https://github.com/itwwj/netty-learn.git中的netty-day05-httpServer项目。
一、http协议简介
HTTP是一个应用层的面向对象的协议,http协议的主要特点如下:
- 支持Client/Server模式。
- 简单, 客户端向服务端请求服务时,只需要指定URL和携带必要的参数或者请求体。
- 灵活,HTTP允许传输任意类型的数据对象,传输的内容类型由HTTP消息头中的Content-Type加以标记。
- 无状态,服务器不需要保存请求的先前信息所以应答较快,负载较轻。
1.1 HttpRequest
HttpRequest由三部分组成:
- 请求行
- 请求头
- 请求体
请求行以一个方法符开头,以空格分开,后面跟着请求的URI和协议的版本,格式为:Method Request-URI HTTP-Version CRLF。其中 Method表示请求方法,Request-URI是一个统一资源标识符,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车和换行。请求方法:
请求方法 | 介绍 |
GET | 请求获取Request-URI所标识的资源 |
POST | 在Request-URI所标识的资源后附加新的提交数据 |
HEAD | 请求获取由Request-URI所标识的资源的响应消息报头 |
PUT | 请求服务器储存一个资源,并用Request-URI作为其标识符 |
DELETE | 请求服务器删除Request-URI所标识的资源 |
TRACE | 请求服务器回送收到的请求消息,主要用于测试或诊断 |
CONNECT | 保留将来使用 |
OPTIONS | 请求查询服务器的性能,或者查询与资源相关的选项和需求 |
http部分请求头列表:
名称 | 作用 |
Accept | 用于指定客户端接收哪些类型的消息。例如:Accept:image/gif |
Accept-Charset | 用于指定客户端接收的字符集 |
Accept-Encoding | 用于指定客户端接受的内容编码 |
Accept-Language | 指定一种自然语言 |
Authorization | 主要用于证明客户端有权查看某个资源,当浏览器访问一个页面时,如果收到服务器的响代码为401,可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行认证 |
Host | 发送请求时,该报头域是必需的,用于指定被请求资源的Internet主机和端口号,它通常是从HTTP URL中提取出来的 |
User-Agent | 允许客户端将它的操作系统、浏览器和其他属性告诉服务器 |
Content-length | 请求消息体的长度 |
Content-Type | 表示后面的文档属于什么MIME类型。servlet默认为text/plain,由于经常要设置Content-Type,因此HttpServletResPonse提供了一个专用的方法setContentType |
Connection | 连接类型 |
netty中的HttpRequest类:
1.2 HttpResponse
HttpResponse也是由三个部分组成:
- 状态行
- 消息报头
- 响应正文
状态行的格式为:HTTP-Version Status-Code Reason-Phrase CRLF,其中HTTP-Version表示HTTP协议的版本,Status-Code 表示服务器返回的响应状态码。常见响应状态码:
状态码 | 状态描述 |
200 | OK,客户端请求成功 |
400 | 客户端请求语法错误,不能被服务器理解 |
401 | 请求未经授权,这个状态码必须和WWW-Authenticate报头域一起使用 |
403 | 服务器收到请求,但是拒绝提供服务 |
404 | 请求资源不存在 |
500 | 服务期发生不可预期的错误 |
503 | 服务器当前不能处理客户端的请求,一段时间后可能恢复正常 |
netty 中的HttpResponse类:
二、服务端开发
启动类:
/**
* netty服务端启动类
*
* @author jie
*/
public class NettyServer {
/**
* 设置端口号
*/
private static int port = 1100;
public static void main(String[] args) {
//用于处理服务端接受客户端连接
EventLoopGroup boosGroup = new NioEventLoopGroup();
//用于SocketChannel的网络读写操作
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度
ServerBootstrap b = new ServerBootstrap();
b.group(boosGroup, workerGroup)
//对应JDK NIO类库中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
//配置NioServerSocketChannel的TCP参数 指定处理客户端连接队列大小
//ChannelOption.SO_KEEPALIVE 一直保持连接活动状态
.option(ChannelOption.SO_BACKLOG, 1024)
//绑定I/O的事件处理类
.childHandler(new MyServerChannelInitializer());
//调用它的bind操作监听端口号,sync 等待异步操作执行完毕
ChannelFuture f = b.bind(port).sync();
//异步操作的通知回调
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//优雅的退出,释放线程池资源
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
事件处理类:
/**
*
* MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
* 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
* 从pipeline中移除,不会影响后续的操作。
* @author jie
*/
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 这个方法在Channel被注册到EventLoop的时候会被调用
* @param socketChannel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// 数据解码操作
socketChannel.pipeline().addLast(new HttpResponseEncoder());
// 数据编码操作
socketChannel.pipeline().addLast(new HttpRequestDecoder());
socketChannel.pipeline().addLast(new MyServerHandler());
}
}
事件操作类:
/**
* 操作类
*实现简单的http服务
* @author jie
*/
public class MyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 通道有消息触发
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//请求参数
//HttpRequest Http请求的第一个部分包含了HTTP的头部信息
if (msg instanceof HttpRequest) {
DefaultHttpRequest request = (DefaultHttpRequest) msg;
System.out.println("URI:" + request.getUri());
System.out.println(msg);
}
//HttpContent 包含了数据
if (msg instanceof HttpContent) {
LastHttpContent httpContent = (LastHttpContent) msg;
ByteBuf byteData = httpContent.content();
if (!(byteData instanceof EmptyByteBuf)) {
//接收msg消息
byte[] msgByte = new byte[byteData.readableBytes()];
byteData.readBytes(msgByte);
System.out.println(new String(msgByte, Charset.forName("UTF-8")));
}
}
String sendMsg = "来自netty服务端的回复:";
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(sendMsg.getBytes(Charset.forName("UTF-8"))));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
ctx.flush();
}
/**
* 当Channel上的一个读操作完成时被调用
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
/**
* 当连接发生异常时触发
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
cause.printStackTrace();
}
}
三、测试
至此使用netty开发一个简单的http接受和响应消息完成