一、HTTP协议
HTTP(超文本传输协议)是建立在TCP传输协议之上的应用层协议,它目前主流是针对于WEB开发,HTTP协议应用非常广泛,因此掌握HTTP协议的开发非常之重要。我们主要讲解Netty如何基于HTTP协议进行开发,那么使用Netty的Http协议也是异步非阻塞的。
二、HTTP协议特点
简单:客户端请求服务器时只需要指定URL和携带必要的参数即可
灵活:Http协议允许传输任意类型的数据对象,传输内容由HTTP消息头中的Content-Type加以标记。
无状态:Http协议是无状态的,无状态指的是协议对事务处理没有记忆能力。这意味着如果后续处理需要之前的信息,则它必须重新获取。也从侧面体现Http的设计是为了使网络传输更加的轻量级、敏捷、负载较轻。
HTTP协议组成部分:请求行、请求头、请求正文(实体内容)
三、HTTP协议请求方式
GET:获取Request-URI所标识的资源
POST:在Request-URI所标识的资源附加新的提交数据
HEAD:请求获取由Request-URI所标识的资源的响应消息头
PUT:请求服务器存储一个资源,并用Request-URI作为标识
DELETE:请求服务器删除Request-URI所标识的资源
TRACE:请求服务器回送收到的请求信息,主要是测试和诊断使用(@trace)CONNECT:保留将来使用
OPTIONS:请求查询服务器的性能忙活着查询相关资源的选项和需求。
四、HTTP协议的响应消息、状态种类、状态码
响应消息也是由三部分组成的:状态行、消息头、响应正文
响应状态种类:
1xx:提示信息。表示请求已经接收继续处理。
2xx:成功。表示请求已经接收成功。
3xx:重定向。要完成的请求必须进行更进一步的操作。4xx:客户端错误。可能是请求语法错误或者请求无法实现
5xx:服务器端错误。服务器未能处理请求(可能内部出现异常)。
常见相应状态码:
200 OK:成功。
400 Bad Reuqest:错误的请求语法,不能被服务器理解。401 Unauthorized:请求未经授权。
403 Forbidden:服务器收到请求,但请求被服务器拒绝。404 Not Found:请求资源不存在
405 Method Not Allowed:请求方式不被允许,如只支持get请求,但客户端使用了post请求)
500 Inernal Server Error:服务器发送不可预期的错误。
503 Server Unavailable:服务器当前不能处理客户端请求,一段时间后可能恢复正常
五、Netty HTTP文件服务端开发
Netty的Http协议栈无论从性能上还是可靠性上都表现优异,非常适合WEB容器的场景下引用,相比于传统的Tomcat、Jetty等Web容器,它更加的轻量和小巧,灵活性和制定性也更好。
1、Netty实现简单的HTTP服务器
我们来看一个简单的Netty实现HTTP服务器开发示例,服务端的示例代码:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* An HTTP server that sends back the content of the received HTTP request
* in a pretty plaintext form.
*/
public final class HttpHelloWorldServer {
static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080"));
public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
} else {
sslCtx = null;
}
// Configure the server.
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)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpHelloWorldServerInitializer(sslCtx));
Channel ch = b.bind(PORT).sync().channel();
System.err.println("Open your web browser and navigate to " +
(SSL ? "https" : "http") + "://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端自定义的ChannelInitializer:
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslContext;
public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslCtx;
public HttpHelloWorldServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new HttpServerCodec());
p.addLast(new HttpHelloWorldServerHandler());
}
}
服务端的业务处理类:
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpRequest;
import static io.netty.handler.codec.http.HttpHeaderNames.*;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.*;
public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
private static final byte[] CONTENT = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg;
if (HttpHeaderUtil.is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) {
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
启动服务端后,其控制台输出内容:
Open your web browser and navigate to http://127.0.0.1:8080/
浏览器中访问上述地址,页面的输出内容:
Netty实现HTTP服务器主要依赖于Netty包中的HttpServerCodec、HttpRequest、FullHttpResponse类。HttpServerCodec是netty针对http请求编解码的处理类,但是这些只能处理像get的请求
2、Netty实现HTTP文件下载服务器
接着我们再看一个HTTP文件服务端下载示例,Netty服务端的代码:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
public class HttpFileServer {
private static final String DEFAULT_URL = "/sources/";
public void run(final int port, final String url) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHa