由于Tomcat、Jetty等容器比较笨重,而在很多场景下,我们其实并不需要笨重的web容器,所以我们可以自行来开发HTTP协议的服务,由于Netty天生是异步事件驱动的框架,因此开发出的HTTP协议栈也是天生非阻塞的,性能极高。
本节代码参看资料:
https://github.com/cyfonly/netty-http
以及官网示例。
要想处理HTTP请求,就需要对HTTP请求信息尽心解析,主要包括:请求头、请求参数、请求的路径等等。
Netty提供了一系列对http的支持,可以查看官网:
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http2
这个例子比较简单,就是在浏览器请求我们使用Netty编写的HTTP服务器,能够返回一个字符串即可。
package com.firewolf.java.io.http.helloworld;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import com.firewolf.java.io.http.utils.NettyHttpUtils;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpUtil;
/**
* 作者:刘兴 时间:2019-05-30
**/
public final class NettyHttpHelloworldServer {
private int port;
public NettyHttpHelloworldServer(int port) {
this.port = port;
}
public void startUp() {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(
new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new HttpServerCodec()) //请求解码器和响应编码器,等价于下面两行
// .addLast(new HttpRequestDecoder()) //请求解码器
// .addLast(new HttpResponseEncoder()) //响应编码器
.addLast(new NettyHttpHelloWorldServerHandler());
}
}
);
Channel ch = b.bind(port).sync().channel();
System.out.println(String.format("服务已经启动,端口号为:%s", port));
ch.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭服务
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
class NettyHttpHelloWorldServerHandler extends SimpleChannelInboundHandler {
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
boolean keepAlive = HttpUtil.isKeepAlive(req);
//构造响应数据
FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK,
Unpooled.wrappedBuffer("Hello, Welcome Wto Netty Server !!! ".getBytes()));
response.headers()
.set(CONTENT_TYPE, "text/plain;charset=utf-8"); //设置响应类型
//给客户端响应信息
NettyHttpUtils.sendResponse(ctx, req, keepAlive, response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
public static void main(String[] args) throws Exception {
new NettyHttpHelloworldServer(9998).startUp();
}
}
其中用到的NettyHttpUtils用来返回信息,内容如下:
package com.firewolf.java.io.http.utils;
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE;
import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpRequest;
/**
* 作者:刘兴 时间:2019-05-30
**/
public class NettyHttpUtils {
public static void sendResponse(ChannelHandlerContext ctx, HttpRequest request, boolean keepAlive,
FullHttpResponse response) {
response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); //设置响应长度
if (keepAlive) {
if (!request.protocolVersion().isKeepAliveDefault()) {
response.headers().set(CONNECTION, KEEP_ALIVE);
}
} else {
response.headers().set(CONNECTION, CLOSE);
}
ChannelFuture f = ctx.writeAndFlush(response);
if (!keepAlive) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
}
启动后,浏览器输入:http://localhost:9998/
效果如下:
我这里仅仅对GET请求和部分类型的POST请求进行了处理,获取了相关的参数,请求类型、路径等,并简单的返回了一些数据。
其实主启动和上面的没什么变化,主要是多了一个HttpObjectAggregator,用于解析POST请求参数。
package com.firewolf.java.io.http.request;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.HttpServerExpectContinueHandler;
/**
* 作者:刘兴 时间:2019-05-30
**/
public class NettyHttpServer {
private int port;
public NettyHttpServer(int port) {
this.port = port;
}
public void startServer() {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker);
bootstrap.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1 << 10) //backlog
.childOption(ChannelOption.SO_KEEPALIVE, true) // 长链接
.childOption(ChannelOption.TCP_NODELAY, true) //无延迟
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast("req and res codec", new HttpServerCodec()) //等价于下面两个,设置请求解码器和相应编码器
.addLast(new HttpObjectAggregator(1 << 20)) //用于处理post请求,否则获取不到请求信息
.addLast(new NettyHttpHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(port).sync();
System.out.println(String.format("Netty HTTP 服务器已经启动了,监听端口为:%s", port));
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyHttpServer(9999).startServer();
}
}
package com.firewolf.java.io.http.request;
import static com.firewolf.java.io.http.request.ContentType.CONTENT_FILE;
import static com.firewolf.java.io.http.request.ContentType.CONTENT_FORM;
import static com.firewolf.java.io.http.request.ContentType.CONTENT_JSON;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.firewolf.java.io.http.utils.NettyHttpUtils;
import com.google.common.base.Charsets;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* 作者:刘兴 时间:2019-05-30
**/
public class NettyHttpHandler extends SimpleChannelInboundHandler {
//浏览器发起请求的时候,会先请求一次图标,这里先不做处理
private static final String FAVICON_ICO = "/favicon.ico";
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
System.out.println("接收到了Http请求了.....");
//解析请求参数
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
boolean keepAlive = HttpUtil.isKeepAlive(request);
String uri = request.uri();
if (uri.equals(FAVICON_ICO)) { //不处理图标
return;
}
QueryStringDecoder paramDecoder = new QueryStringDecoder(uri);
System.out.println("请求路径为:" + paramDecoder.path());
//判断请求类型
HttpMethod method = request.method();
System.out.println("请求类型:" + method.toString());
System.out.println("接受到请求中的参数如下:");
if (method == GET) { //get请求
//使用Netty提供的解析URL的工具来解析URL中的参数
Map> parameters = paramDecoder.parameters(); //解析出所有的参数
parameters.entrySet().forEach(x -> {
System.out
.println(String.format("%s : %s", x.getKey(), Arrays.toString(x.getValue().toArray(new String[0]))));
});
} else if (method == POST) { //处理post请求
FullHttpRequest fullRequest = (FullHttpRequest) msg;
String contentType = fullRequest.headers().get(CONTENT_TYPE).split(";")[0]; //参数类型
switch (contentType) {
case CONTENT_JSON: { //JSON数据
//解析JSON字符串
String jsonStr = fullRequest.content().toString(Charsets.UTF_8);
JSONObject obj = JSON.parseObject(jsonStr);
obj.keySet().forEach(x -> {
Object o = obj.get(x);
if (o instanceof JSONArray) {
System.out.print("数组: ");
}
System.out.println(x + "," + o);
});
break;
}
case CONTENT_FORM: { //表单提交数据
//解析表单参数
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(fullRequest);
List datas = decoder.getBodyHttpDatas(); //获取请求数据
datas.forEach(x -> {
try {
if (x.getHttpDataType() == HttpDataType.Attribute) {
Attribute attribute = (Attribute) x;
System.out.println(attribute.getName() + "=" + attribute.getValue());
}
} catch (IOException e) {
e.printStackTrace();
}
});
break;
}
case CONTENT_FILE: { //文件上传
break;
}
default:
return;
}
}
//回写信息
//构造响应数据
FullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(), OK,
Unpooled.wrappedBuffer("我受到了你的请求!!! ".getBytes()));
response.headers()
.set(CONTENT_TYPE, "text/plain;charset=utf-8"); //设置响应类型
NettyHttpUtils.sendResponse(ctx, request, keepAlive, response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
其中文件上传的暂时没做处理。
package com.firewolf.java.io.http.request;
/**
* 作者:刘兴 时间:2019-05-30
**/
public class ContentType {
//提交的json数据
public final static String CONTENT_JSON = "application/json";
//表单提交
public final static String CONTENT_FORM = "application/x-www-form-urlencoded";
//文件上传
public final static String CONTENT_FILE = "multipart/form-data";
}