netty实现http服务器

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

Netty是一款基于NIO的网络编程框架,提供了高效、稳定、灵活的网络编程能力。使用Netty实现代理服务器可以简化开发过程,提高性能和可维护性。 以下是使用Netty实现代理服务器的示例代码: ``` import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; import io.netty.channel.*; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.*; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; public class ProxyServer { public static void main(String[] args) throws Exception { EventLoopGroup workerGroup = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(workerGroup) .channel(NioSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .option(ChannelOption.AUTO_READ, false) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpClientCodec()); ch.pipeline().addLast(new HttpObjectAggregator(65536)); ch.pipeline().addLast(new ChunkedWriteHandler()); ch.pipeline().addLast(new ProxyServerHandler()); } }); ChannelFuture future = bootstrap.connect("www.example.com", 80).sync(); future.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); } } private static class ProxyServerHandler extends ChannelInboundHandlerAdapter { private Channel remoteChannel; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { remoteChannel = ctx.channel(); ctx.read(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpRequest) { HttpRequest request = (HttpRequest) msg; String host = request.headers().get("Host"); ChannelFuture future = new Bootstrap() .group(ctx.channel().eventLoop()) .channel(ctx.channel().getClass()) .handler(new LoggingHandler(LogLevel.INFO)) .option(ChannelOption.AUTO_READ, false) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpResponseDecoder()); ch.pipeline().addLast(new HttpObjectAggregator(65536)); ch.pipeline().addLast(new ChunkedWriteHandler()); ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(request); ctx.read(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; response.headers().remove("Transfer-Encoding"); response.headers().remove("Content-Length"); remoteChannel.writeAndFlush(response); remoteChannel.writeAndFlush(new ChunkedNioStream((ByteBuf) msg)); } else if (msg instanceof HttpContent) { remoteChannel.writeAndFlush(new ChunkedNioStream((ByteBuf) msg)); if (msg instanceof LastHttpContent) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } }); } }) .connect(host, 80); remoteChannel.config().setAutoRead(false); future.addListener((ChannelFutureListener) future1 -> { if (future1.isSuccess()) { remoteChannel.config().setAutoRead(true); ctx.channel().config().setAutoRead(true); } else { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } }); } else if (msg instanceof HttpContent) { remoteChannel.writeAndFlush(new ChunkedNioStream((ByteBuf) msg)); if (msg instanceof LastHttpContent) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); } } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (remoteChannel != null) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); if (remoteChannel != null) { remoteChannel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT) .addListener(ChannelFutureListener.CLOSE); } ctx.close(); } } } ``` 以上代码中,代理服务器连接到目标服务器的IP地址和端口号是硬编码的,你需要根据实际情况进行修改。在启动代理服务器之后,当客户端发送HTTP请求时,会在一个新的线程中处理请求,解析请求并连接到目标服务器,将请求转发给目标服务器。接收到目标服务器响应后,将响应转发给客户端
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值