前面了解了netty的启动流程,实现http协议服务在pipeline里添加对应的http协议报文处理器即可。
server端:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workGroup = new NioEventLoopGroup(4);
ServerBootstrap bootstrap = new ServerBootstrap().group(bossGroup,workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new HttpServerCodec());
p.addLast(new HttpObjectAggregator(65536));
p.addLast(new CustomHttpServerHandler());
}
});
bootstrap.bind(8080).sync();
这里添加了三个编解码处理handler。HttpServerCodec这个是http协议格式处理器,其相当于HttpRequestDecoder和HttpResponseEncoder一对编解码处理器。HttpObjectAggregator是用来报文数据聚合,合并请求数据成一个报文。CustomHttpServerHandler这个是我们自定义的报文处理器,
public class CustomHttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
String content = "Hello";
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(content.getBytes()));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
ctx.writeAndFlush(response);
}
}
这里收到消息经过前面两个handler处理后类型是FullHttpRequest,在channelRead0方法里构造了一个简单的FullHttpResponse对象,返回数据给客户端。
这样访问http://localhost:8080就会看到服务端返回的hello。
上面例子只是做了一个简单的请求响应。没有对请求数据进行解析,也没有根据资源uri进行转发不同处理。
请求地址可以从入参FullHttpRequest msg中获取
HttpMethod method = msg.method();
System.out.println("接收请求:"+msg.uri()+"请求方法:"+msg.method().name());
这里uri的dispatch就不处理了,主要是了解报文数据处理。下面来看不同报文格式数据如何处理。同样的这里也只考虑POST请求。
我们平时web开发知道,POST传送数据有好几种方式,其报文数据格式是不一样的。不同数据格式可以从请求报文头的Content-type获取。
1、form表单形式(application/x-www-form-urlencoded)
这种方式参数还是key=value&key=value形式放到请求body中传过来的,可以使用HttpPostRequestDecoder来进行解析.
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), msg);
List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
for (InterfaceHttpData data : postData) {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
// 处理表单字段
Attribute attribute = (Attribute) data;
String fieldName = attribute.getName();
String fieldValue = null;
try {
fieldValue = attribute.getValue();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(fieldName + ": " + fieldValue);
}
}
2、包含文件(multipart/form-data)
这种方式有一个boundary来区分不同的表单项。可以用HttpPostMultipartRequestDecoder来解析
HttpPostMultipartRequestDecoder decoder = new HttpPostMultipartRequestDecoder(new DefaultHttpDataFactory(false), msg);
List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
for (InterfaceHttpData data : postData) {
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
// 处理表单字段
Attribute attribute = (Attribute) data;
String fieldName = attribute.getName();
String fieldValue = null;
try {
fieldValue = attribute.getValue();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(fieldName + ": " + fieldValue);
} else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
// 处理文件上传字段
FileUpload fileUpload = (FileUpload) data;
String fieldName = fileUpload.getName();
String fileName = fileUpload.getFilename();
String fileType = fileUpload.getContentType();
long fileSize = fileUpload.length();
System.out.println("文件上传属性: " + fieldName + ", 文件名: " + fileName + ", 文件类型: " + fileType + ", 文件大小: " + fileSize + " bytes");
}
}
这里要注意文件的大小,还记得最开始添加的报文聚合handler:HttpObjectAggregator,如果超过了这个指定的大小会直接返回414错误。
另外像其它如json、xml这种格式,直接获取报文体进行处理即可,不需要特殊的处理
ByteBuf content = msg.content();
String requestBody = content.toString(StandardCharsets.UTF_8);
System.out.println("请求内容:"+requestBody);
请求报文处理完了之后还是最后要构建一个FullHttpResponse对象将响应数据返回给客户端。这里整个处理下来搭建一个完整的http服务还是有许多东西要考虑的,不如直接使用现有的web容器,这里仅仅是为了演示,也为下一篇说websocket做准备。