测试demo和启动
import io.netty.bootstrap.ServerBootstrap;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
public class HttpServer {
private final int port;
public HttpServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
new HttpServer(9004).start();
}
public void start() throws Exception {
ServerBootstrap b = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
b.group(group).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
System.out.println("initChannel ch:" + ch);
ch.pipeline()
.addLast("decoder", new HttpRequestDecoder()) // 1
.addLast("encoder", new HttpResponseEncoder()) // 2
.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3
.addLast("handler", new HttpHandler()); // 4
}
})
.option(ChannelOption.SO_BACKLOG, 128) // determining the number of connections queued
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.bind(port).sync();
}
}
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import static io.netty.handler.codec.http.HttpHeaders.Names.*;
public class HttpHandler extends SimpleChannelInboundHandler { // 1
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
System.out.println("class:" + msg.getClass().getName());
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer("test".getBytes())); // 2
HttpHeaders heads = response.headers();
heads.add(CONTENT_TYPE, "text/plain; charset=UTF-8");
heads.add(CONTENT_LENGTH, response.content().readableBytes()); // 3
heads.add(CONNECTION, "keep-alive");
ctx.write(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
super.channelReadComplete(ctx);
ctx.flush(); // 4
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught");
if (null != cause) cause.printStackTrace();
if (null != ctx) ctx.close();
}
}
package main
import (
"fmt"
"net"
"time"
)
func main() {
c, err := net.Dial("tcp", "localhost:9004")
if err != nil {
fmt.Println(err)
return
}
go func() {
for {
buf := make([]byte, 100)
n, err := c.Read(buf)
if err != nil {
fmt.Print(err)
return
}
fmt.Println(string(buf[:n]))
}
}()
c.Write([]byte("GET /"))
time.Sleep(200 * time.Millisecond)
c.Write([]byte(" HTTP/1.1\r\n"))
c.Write([]byte("Host: localhost:9004\r\n"))
time.Sleep(5 * time.Second)
c.Write([]byte("Accept: */*\r\n"))
time.Sleep(200 * time.Millisecond)
c.Write([]byte("Content-Length: 0\r\n"))
c.Write([]byte("\r\n"))
time.Sleep(500 * time.Millisecond)
time.Sleep(5 * time.Second)
}
启动服务端。
启动客户端。或telnet手动进行http协议文本拼接。
执行结果
由执行结果可看出:将http协议报文随意拆分上传,中间有不同程度的sleep,服务端仍然可以正确处理。
分析过程
函数调用栈信息:
代码注释:
io.netty.handler.codec.ByteToMessageDecoder
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) { //1. channel接收到io事件,传入byteBuf。可能是完整的http协议请求,也可能是不完整;header可能不足行或跨行,即协议的语义不够完整。
RecyclableArrayList out = RecyclableArrayList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
//2. cumulation为null,创建可进行累计的ByteBuf。
if (first) {
cumulation = data;
} else {
if (cumulation.writerIndex() > cumulation.maxCapacity() - data.readableBytes()
|| cumulation.refCnt() > 1) {
// Expand cumulation (by replace it) when either there is not more room in the buffer
// or if the refCnt is greater then 1 which may happen when the user use slice().retain() or
// duplicate().retain().
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
expandCumulation(ctx, data.readableBytes());
}
//3. cumulation不为null,即表明当前有不完整的http语义的报文,需要追加当前IO事件读取到的byteBuf。
//比如:上一个byteBuf中是"GET /", http codec 发现请求行不完整,需要继续等待新IO事件,接收新的byteBuf,进行累加,再次触发http codec的解析。
cumulation.writeBytes(data);
data.release();
}
//4. 进行http解码操作。
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
//5. 如果cumulation不为null且没有可读数据,就释放cumulation。即当cumulation中没有不完整的http语义就可以释放cumulation,有不完整http语义就不能释放。
//这里需要注意,不完整的http语义不是完整的请求,比如:Host: localhost:9004是完整的一个请求头,是完整的http语义,但不是完整请求。
//因此http codec 解析后会将key value存到对应的反序列化的message对象中,该cumulation可以释放了,不需要等待完整请求接收后才释放数据。
if (cumulation != null && !cumulation.isReadable()) {
cumulation.release();
cumulation = null;
}
int size = out.size();
decodeWasNull = size == 0;
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.get(i));
}
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
io.netty.handler.codec.ReplayingDecoder
@Override
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List
io.netty.handler.codec.http.HttpObjectDecoder
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List
readHeaders
private State readHeaders(ByteBuf buffer) {
final HttpMessage message = this.message;
final HttpHeaders headers = message.headers();
//1. 不断循环解析header。
//如果行数据长度大于0,完成key value拆分,则解析下一行数据;
//如果行数据长度等于0,退出循环。认为header已经全部接收处理。
//如果行数据不是完整的语义,在headerParser.parse(buffer)中会抛出REPLAY异常。
AppendableCharSequence line = headerParser.parse(buffer);
if (line.length() > 0) {
do {
char firstChar = line.charAt(0);
if (name != null && (firstChar == ' ' || firstChar == '\t')) {
StringBuilder buf = new StringBuilder(value.length() + line.length() + 1);
buf.append(value);
buf.append(' ');
buf.append(line.toString().trim());
value = buf.toString();
} else {
if (name != null) {
headers.add(name, value);
}
splitHeader(line);
}
line = headerParser.parse(buffer);
} while (line.length() > 0);
}
// Add the last header.
if (name != null) {
headers.add(name, value);
}
// reset name and value fields
name = null;
value = null;
//2. 解析完所有header,判断body状态。
State nextState;
if (isContentAlwaysEmpty(message)) {
HttpHeaders.removeTransferEncodingChunked(message);
nextState = State.SKIP_CONTROL_CHARS;
} else if (HttpHeaders.isTransferEncodingChunked(message)) {
nextState = State.READ_CHUNK_SIZE;
} else if (contentLength() >= 0) {
nextState = State.READ_FIXED_LENGTH_CONTENT;
} else {
nextState = State.READ_VARIABLE_LENGTH_CONTENT;
}
return nextState;
}
parser的全部逻辑
public AppendableCharSequence parse(ByteBuf buffer) {
seq.reset();
//1. 遍历buf每个字节,直到遇到\r\n,并返回索引值。如果没有抛出异常,则更新readerIndex为下一个索引。即保证从下一个header开始读取。
int i = buffer.forEachByte(this);
buffer.readerIndex(i + 1);
// Call checkpoint to make sure the readerIndex is updated correctly
checkpoint();
return seq;
}
@Override
public int forEachByte(ByteBufProcessor processor) {
//2. 索引值如果<0,即没有查找到\r\n,抛出REPLAY异常。
int ret = buffer.forEachByte(processor);
if (ret < 0) {
throw REPLAY;
} else {
return ret;
}
}
@Override
public int forEachByte(ByteBufProcessor processor) {
int index = readerIndex;
int length = writerIndex - index;
ensureAccessible();
//3. length是当前待读取的长度。
return forEachByteAsc0(index, length, processor);
}
private int forEachByteAsc0(int index, int length, ByteBufProcessor processor) {
if (processor == null) {
throw new NullPointerException("processor");
}
//4. 待读取长度为0,无可读取数据,返回-1。如果是http协议的空白行,\r\n,长度是2。不会满足当前条件。
if (length == 0) {
return -1;
}
final int endIndex = index + length;
int i = index;
try {
do {
//5. 循环遍历每个字节。不是目标结束符,index++,判断下一个。
if (processor.process(_getByte(i))) {
i ++;
} else {
return i;
}
} while (i < endIndex);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
return -1;
}
@Override
public boolean process(byte value) throws Exception {
char nextByte = (char) value;
if (nextByte == HttpConstants.CR) {
return true;
}
//6. 读到\n行终止符,返回false,终止上层遍历。
if (nextByte == HttpConstants.LF) {
return false;
}
if (size >= maxLength) {
// TODO: Respond with Bad Request and discard the traffic
// or close the connection.
// No need to notify the upstream handlers - just log.
// If decoding a response, just throw an exception.
throw newException(maxLength);
}
size ++;
seq.append(nextByte);
return true;
}