HTTP协议介绍
HTTP是一个属于应用层的面向对象的协议,其主要特点如下。
- 支持Client、Server模式
- 简单—客户向服务器请求服务时,只需要指定服务URL,携带必要的请求参数或者消息体
- 灵活—HTTP允许传输任意类型的数据对象,传输的内容类型由HTTP消息头中的Content-Type去标记
- 无状态—HTTP协议是无状态协议,无状态是指协议对于事务处理没有记忆能力,缺少状态意味着如果后续处理需要之前的信息,则它必须重传,这样可能导致每次连接传送的数据量增大,另一方面,在服务器不需要先前信息时它的应答就较快,负载较轻
HTTP请求消息
HTTP请求由三部分组成,分别为:HTTP请求行、HTTP请求头、HTTP请求正文。
请求行
以一个方法符开头,以空格分开,后面跟着请求的URI和协议的版本,格式为:
Method Request-URI HTTP-Version CRLF
其中Method表示请求方法(GET/POST/DELETE等),Request-URI是一个统一资源标识符,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车和换号
请求头
包含表示请求的各种条件和属性的各类首部信息
请求正文
这部分内容是可选的,请求正文通常都是应该被发送的数据
完整示例:
HTTP响应消息
HTTP响应也是由三个部分组成,分别是状态行、消息报头、响应正文。
状态行
包含表明响应结果的状态码、原因短语和HTTP版本
消息报头
包含表示请求的各种条件和属性的各类首部

响应正文
即服务端发送给客户端的数据,可以是文本,xml,json等。
完整示例:
HTTP请求过程
1、首先进行DNS域名解析(本地浏览器缓存、操作系统缓存或者DNS服务器)
- 首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)
- 如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的DNS缓存
- 如果还没找到,那么尝试从hosts文件里面去找
- 在前面三个过程都没获取到的情况下,就去域名服务器查找
2、三次握手建立TCP连接
在HTTP工作开始之前,客户端首先要通过网络与服务器建立连接,HTTP连接是通过TCP来完成的,HTTP是比TCP更高层次的应用层协议,根据规则,只有底层协议建立之后,才能进行高层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80
3、客户端发起HTTP请求
4、服务器响应HTTP请求
#5、客户端解析html代码,并请求html代码中的资源
浏览器拿到html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载
6、客户端渲染展示内容
7、关闭TCP连接
一般情况下,一旦服务器向客户单返回了请求数据,它就要关闭TCP连接,但是如果客户端或者服务器在其头信息中加入了这行代码 Conneciton:keep-alive,TCP连接在发送后将仍然保持打开状态,于是,客户端可以继续通过相同的连接发送请求,也就是说前面的3到6步,可以反复进行。保持连接节省了为每个请求建立新连接所需要的时间,还节约了网络带宽。
Netty提供的HTTP编解码器
HttpRequestEncoder
将 HttpRequest、HttpContent 和 LastHttpContent 消息编码为字节
HttpResponseEncoder
将 HttpResponse、HttpContent 和 LastHttpContent 消息编码为字节
HttpRequestDecoder
将字节解码为 HttpRequest、HttpContent 和 LastHttpContent 消息
HttpResponseDecoder
将字节解码为 HttpResponse、HttpContent 和 LastHttpContent 消息
如图所示:一个HTTP 请求/响应可能由多个数据部分组成,FullHttpRequest 和FullHttpResponse 消息是特殊的子类型,分别代表了完整的请求和响应。所有类型的 HTTP 消息(FullHttpRequest、 LastHttpContent 等等)都实现了 HttpObject 接口。
构建基于Netty的HTTP服务端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.HttpContentCompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
public class HttpServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//对Response 响应报文进行编码
pipeline.addLast("encoder",new HttpResponseEncoder());
//对Request 请求报文进行解码
pipeline.addLast("decoder",new HttpRequestDecoder());
/*
聚合http为一个完整的报文,
它可以将多个消息部分合并为FullHttpRequest或者FullHttpResponse消息,
1*1024*1024规定了聚合内容的最大长度为1M,如果超过这个长度将会返回:413 Request Entity Too Large
*/
pipeline.addLast("aggregator",new HttpObjectAggregator(1*1024*1024));
/*
当使用HTTP时,建议开启压缩功能以尽可能多的减少传输数据的大小,特别是对文本数据来说,虽然压缩会带来一些CPU上的开销。
*/
pipeline.addLast("compressor",new HttpContentCompressor());
//业务handler
pipeline.addLast(new HttpServerHandler());
}
});
ChannelFuture future = b.bind(8088).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端连接信息:" + ctx.channel().remoteAddress());
}
private void response(ChannelHandlerContext ctx, String context, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
status, Unpooled.copiedBuffer(context, CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
FullHttpRequest httpRequest = (FullHttpRequest) msg;
try {
String uri = httpRequest.uri();
String content = httpRequest.content().toString(CharsetUtil.UTF_8);
HttpMethod method = httpRequest.method();
System.out.println("httpRequest uri:" + uri);
System.out.println("httpRequest method: " + method);
System.out.println("httpRequest content: " + content);
if (!"/test".equals(uri)) {
response(ctx, "非法请求!" + uri, HttpResponseStatus.BAD_REQUEST);
return;
}
response(ctx, "hello client", HttpResponseStatus.OK);
return;
} finally {
httpRequest.release();
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
}
}
请求结果
除了浏览器之外,当然也可以自己构建客户端
HTTP客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
public class HttpClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//和服务端正好相反,对Response 响应报文进行解码
pipeline.addLast(new HttpResponseDecoder());
//对Request 请求报文进行编码
pipeline.addLast(new HttpRequestEncoder());
//聚合http
pipeline.addLast(new HttpObjectAggregator(1*1024*1024));
//解压缩
pipeline.addLast(new HttpContentDecompressor());
//业务handler
pipeline.addLast(new HttpClientHandler());
}
});
ChannelFuture future = b.connect("127.0.0.1", 8088).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
import java.net.URI;
public class HttpClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
URI uri = new URI("/test");
//构建http请求
FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpMethod.GET,
uri.toString(),
Unpooled.copiedBuffer("你好,服务端!", CharsetUtil.UTF_8));
httpRequest.headers().set(HttpHeaderNames.HOST, "127.0.0.1");
httpRequest.headers().set(HttpHeaderNames.CONNECTION,
HttpHeaderValues.KEEP_ALIVE);
httpRequest.headers().set(HttpHeaderNames.CONTENT_LENGTH,
httpRequest.content().readableBytes());
// 发送http请求
ctx.writeAndFlush(httpRequest);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
FullHttpResponse httpResponse = (FullHttpResponse) msg;
System.out.println("httpResponse status: " + httpResponse.status());
System.out.println("httpResponse header: " + httpResponse.headers());
System.out.println("httpResponse content: " + httpResponse.content().toString(CharsetUtil.UTF_8));
httpResponse.release();
}
}
请求结果
客户端
服务端