0 概述
HTTP(超文本傳輸協議)是建立在TCP傳輸協議之上的應用層協議,由於其簡單、靈活,其應用也非常之廣泛。本文主要講解如何用Netty 實現一個簡單的http服務器。
1 http 請求消息
Http的請求由三部分組成:請求行、消息頭、請求正文(body)。
請求行以一個方法開頭,以空格分開,後面跟着請求URI和協議版本,格式爲:Method(方法) Request-URI (統一資源標識符) HTTP-version(請求版本) CRLF(換行符)。
GET / HTTP/1.1
POST / HTTP/1.1
請求方法有多種,各種方法如下:
- GET 請求獲取Request-URI 所標識的資源。
- POST 在Request-URI 所標識的資源後附加新的提交數據
- HEAD 請求獲取由Request-URI 所標識的資源相應消息報頭
- PUT 請求服務器存儲一個資源,並用Request-URI作爲其標識。
- DELETE 請求服務器刪除Request-URI 所標識的資源。
- TRACE:請求服務器回收到的請求信息,主要用於測試或者診斷
- CONNECT 保留將來使用
- OPTIONS 請求查詢服務器性能或者查詢與資源相關的選項需求。
下面是一個http 請求消息(沒有請求正文),請求行後面都是請求頭。
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.8
2 http 響應消息
HTPP響應消息也是由3部分組成,分別是:狀態行、消息報頭、響應正文。
狀態行的格式爲: HTTP-version Status-Code Reason-Phrase CRLF.
具體如下
HTTP/1.1 200 OK
HTTP/1.1 404 Not Found
狀態碼由三位數字組成,第一個數字表示響應類別,有五種可能取值。
- 1xx:指示信息。表示請求已經接受,繼續處理。
- 2xx :成功。表示請求已經被成功接收,理解、接受
- 3xx :重定向。要完成請求必須進行進一步的操作。
- 4xx:客戶端錯誤,請求有語法錯誤,或者請求無法完成。
- 5xx:服務器錯誤。服務器未能處理。
3 netty 實現http服務器
服務端實現
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpServerCodec;
/**
* Created by apple on 17/10/21.
*/
public class HttpServer {
public static void start(final int port) throws Exception {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup woker = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
try {
serverBootstrap.channel(NioServerSocketChannel.class)
.group(boss, woker)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-decoder",new HttpServerCodec());
ch.pipeline().addLast(new HttpServerHandler());
}
});
ChannelFuture future = serverBootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
woker.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
start(8080);
}
}
HttpServerHandler 類
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import java.util.HashMap;
import java.util.Map;
/**
* Created by apple on 17/10/21.
*/
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private String content = "hello world";
private final static String LOC = "302";
private final static String NOT_FOND = "404";
private final static String BAD_REQUEST = "400";
private final static String INTERNAL_SERVER_ERROR = "500";
private static Map<String, HttpResponseStatus> mapStatus = new HashMap<String, HttpResponseStatus>();
static {
mapStatus.put(LOC, HttpResponseStatus.FOUND);
mapStatus.put(NOT_FOND, HttpResponseStatus.NOT_FOUND);
mapStatus.put(BAD_REQUEST, HttpResponseStatus.BAD_REQUEST);
mapStatus.put(INTERNAL_SERVER_ERROR, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
boolean keepaLive = HttpUtil.isKeepAlive(request);
System.out.println("method" + request.method());
System.out.println("uri" + request.uri());
String uri = request.uri().replace("/", "").trim();
FullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
if (mapStatus.get(uri) != null) {
httpResponse.setStatus(mapStatus.get(uri));
httpResponse.content().writeBytes(mapStatus.get(uri).toString().getBytes());
} else {
httpResponse.content().writeBytes(content.getBytes());
}
//重定向處理
if (httpResponse.status().equals(HttpResponseStatus.FOUND)) {
httpResponse.headers().set(HttpHeaderNames.LOCATION, "https://www.baidu.com/");
}
httpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
httpResponse.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpResponse.content().readableBytes());
if (keepaLive) {
httpResponse.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.writeAndFlush(httpResponse);
} else {
ctx.writeAndFlush(httpResponse).addListener(ChannelFutureListener.CLOSE);
}
}
}
}
啓動服務後,可以通過瀏覽器測試如下
http://127.0.0.1:8080/302