基于netty简单的http服务器搭建

  • 简介
  • 一、http协议简介
  • 1.1 HttpRequest
  • 1.2 HttpResponse
  • 二、服务端开发
  • 三、测试


简介

 HTTP是一个超文本传输协议,是一种建立在TCP传输协议之上的应用层协议。http是目前web的主流协议,本章讲述的是基于netty NIO开发的http服务端。本章项目源码地址:https://github.com/itwwj/netty-learn.git中的netty-day05-httpServer项目。

一、http协议简介

 HTTP是一个应用层的面向对象的协议,http协议的主要特点如下:

  • 支持Client/Server模式。
  • 简单, 客户端向服务端请求服务时,只需要指定URL和携带必要的参数或者请求体。
  • 灵活,HTTP允许传输任意类型的数据对象,传输的内容类型由HTTP消息头中的Content-Type加以标记。
  • 无状态,服务器不需要保存请求的先前信息所以应答较快,负载较轻。

1.1 HttpRequest

HttpRequest由三部分组成:

  • 请求行
  • 请求头
  • 请求体
     请求行以一个方法符开头,以空格分开,后面跟着请求的URI和协议的版本,格式为:Method Request-URI HTTP-Version CRLF。其中 Method表示请求方法,Request-URI是一个统一资源标识符,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车和换行。请求方法:

请求方法

介绍

GET

请求获取Request-URI所标识的资源

POST

在Request-URI所标识的资源后附加新的提交数据

HEAD

请求获取由Request-URI所标识的资源的响应消息报头

PUT

请求服务器储存一个资源,并用Request-URI作为其标识符

DELETE

请求服务器删除Request-URI所标识的资源

TRACE

请求服务器回送收到的请求消息,主要用于测试或诊断

CONNECT

保留将来使用

OPTIONS

请求查询服务器的性能,或者查询与资源相关的选项和需求

http部分请求头列表:

名称

作用

Accept

用于指定客户端接收哪些类型的消息。例如:Accept:image/gif

Accept-Charset

用于指定客户端接收的字符集

Accept-Encoding

用于指定客户端接受的内容编码

Accept-Language

指定一种自然语言

Authorization

主要用于证明客户端有权查看某个资源,当浏览器访问一个页面时,如果收到服务器的响代码为401,可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行认证

Host

发送请求时,该报头域是必需的,用于指定被请求资源的Internet主机和端口号,它通常是从HTTP URL中提取出来的

User-Agent

允许客户端将它的操作系统、浏览器和其他属性告诉服务器

Content-length

请求消息体的长度

Content-Type

表示后面的文档属于什么MIME类型。servlet默认为text/plain,由于经常要设置Content-Type,因此HttpServletResPonse提供了一个专用的方法setContentType

Connection

连接类型

netty中的HttpRequest类:

Netty服务端 ios端连接会被拒绝_java

1.2 HttpResponse

HttpResponse也是由三个部分组成:

  • 状态行
  • 消息报头
  • 响应正文
     状态行的格式为:HTTP-Version Status-Code Reason-Phrase CRLF,其中HTTP-Version表示HTTP协议的版本,Status-Code 表示服务器返回的响应状态码。常见响应状态码:

状态码

状态描述

200

OK,客户端请求成功

400

客户端请求语法错误,不能被服务器理解

401

请求未经授权,这个状态码必须和WWW-Authenticate报头域一起使用

403

服务器收到请求,但是拒绝提供服务

404

请求资源不存在

500

服务期发生不可预期的错误

503

服务器当前不能处理客户端的请求,一段时间后可能恢复正常

netty 中的HttpResponse类:

Netty服务端 ios端连接会被拒绝_HTTP_02

二、服务端开发

启动类:

/**
 * netty服务端启动类
 *
 * @author jie
 */
public class NettyServer {
    /**
     * 设置端口号
     */
    private static int port = 1100;

    public static void main(String[] args) {
        //用于处理服务端接受客户端连接
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        //用于SocketChannel的网络读写操作
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //用于启动NIO服务端的辅助启动类,目的是降低服务端的开发复杂度
            ServerBootstrap b = new ServerBootstrap();
            b.group(boosGroup, workerGroup)
                    //对应JDK NIO类库中的ServerSocketChannel
                    .channel(NioServerSocketChannel.class)
                    //配置NioServerSocketChannel的TCP参数 指定处理客户端连接队列大小
                    //ChannelOption.SO_KEEPALIVE 一直保持连接活动状态
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    //绑定I/O的事件处理类
                    .childHandler(new MyServerChannelInitializer());
            //调用它的bind操作监听端口号,sync 等待异步操作执行完毕
            ChannelFuture f = b.bind(port).sync();
            //异步操作的通知回调
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //优雅的退出,释放线程池资源
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

事件处理类:

/**
 *
 * MyChannelInitializer的主要目的是为程序员提供了一个简单的工具,用于在某个Channel注册到EventLoop后,对这个Channel执行一些初始
 * 化操作。ChannelInitializer虽然会在一开始会被注册到Channel相关的pipeline里,但是在初始化完成之后,ChannelInitializer会将自己
 * 从pipeline中移除,不会影响后续的操作。
 * @author jie
 */
public class MyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    /**
     * 这个方法在Channel被注册到EventLoop的时候会被调用
     * @param socketChannel
     * @throws Exception
     */
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 数据解码操作
        socketChannel.pipeline().addLast(new HttpResponseEncoder());
        // 数据编码操作
        socketChannel.pipeline().addLast(new HttpRequestDecoder());

        socketChannel.pipeline().addLast(new MyServerHandler());
    }
}

事件操作类:

/**
 * 操作类
 *实现简单的http服务
 * @author jie
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {


    /**
     * 通道有消息触发
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //请求参数
        //HttpRequest Http请求的第一个部分包含了HTTP的头部信息
        if (msg instanceof HttpRequest) {
            DefaultHttpRequest request = (DefaultHttpRequest) msg;
            System.out.println("URI:" + request.getUri());
            System.out.println(msg);
        }

        //HttpContent 包含了数据
        if (msg instanceof HttpContent) {
            LastHttpContent httpContent = (LastHttpContent) msg;
            ByteBuf byteData = httpContent.content();
            if (!(byteData instanceof EmptyByteBuf)) {
                //接收msg消息
                byte[] msgByte = new byte[byteData.readableBytes()];
                byteData.readBytes(msgByte);
                System.out.println(new String(msgByte, Charset.forName("UTF-8")));
            }
        }
        String sendMsg = "来自netty服务端的回复:";
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                Unpooled.wrappedBuffer(sendMsg.getBytes(Charset.forName("UTF-8"))));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        ctx.write(response);
        ctx.flush();
    }

    /**
     * 当Channel上的一个读操作完成时被调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 当连接发生异常时触发
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }
}

三、测试

Netty服务端 ios端连接会被拒绝_java_03


至此使用netty开发一个简单的http接受和响应消息完成