15、Netty 基础 之 HTTP服务

一、使用netty开发一个简单的http服务

1、 netty服务器在6668端口监听,浏览器发出请求http://localhost:6668/;
在写netty的http server的例子过程中,发现浏览器使用端口号6668一直无法连接,报错ERR_UNSAFE_PORT。改成7000就可以了。
2、 服务器可以回复消息给客户端“hello,我是服务器”,并对特定请求资源进行过滤;
3、 目的:netty可以做http服务开发,并且理解handler实例和客户端及其请求的关系;

代码;

二、代码

1、 HttpServer.java;

package netty.http;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class HttpServer {
	public static void main(String[] args) {
		
		EventLoopGroup bossGroup = new NioEventLoopGroup(1);
		EventLoopGroup workerGroup = new NioEventLoopGroup(8);
		
		try {
			ServerBootstrap bootstrap = new ServerBootstrap();
			bootstrap.group(bossGroup, workerGroup)
			.channel(NioServerSocketChannel.class)
			.childHandler(new HttpServerInitializer());
			
			ChannelFuture channelFuture = bootstrap.bind(7000).sync();
			
			channelFuture.channel().closeFuture().sync();
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}
}

2、 HttpServerInitializer.java;

package netty.http;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;

public class HttpServerInitializer extends ChannelInitializer<SocketChannel>{

	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		
		//向管道加入处理器
		
		//得到管道
		ChannelPipeline pipeline = ch.pipeline();
		
		//加入一个netty提供的httpServerCodec(编解码器)
		//HttpServerCodec的说明
		//1. HttpServerCodec是netty提供的处理http的编解码器
		pipeline.addLast("MyHttpServerCodec", new HttpServerCodec());
		
		//增加一个自定义的Handler
		pipeline.addLast("MyHttpServerHandler", new HttpServerHandler());
		
	}

}

3、 HttpServerHandler.java;

package netty.http;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

//SimpleChannelInboundHandler继承了ChannelInboundHandlerAdapter
//HttpObject指定了客户端和服务器端,在处理的时候的数据类型
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

	//channelRead0:读取客户端数据
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
		
		//判断msg是不是HttpRequest请求
		if (msg instanceof HttpRequest) {
			System.out.println("msg 类型 = " + msg.getClass());
			System.out.println("客户端地址 = " + ctx.channel().remoteAddress());
			
			//回复信息给浏览器 [http协议]
			ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器!", CharsetUtil.UTF_8);
			//构造一个http的响应,即httpResponse
			FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
			
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
			
			//将构建好的response返回
			ctx.writeAndFlush(response);
			
		}
	}

}

4、 执行结果;

msg 类型 = class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址 = /127.0.0.1:58715
msg 类型 = class io.netty.handler.codec.http.DefaultHttpRequest
客户端地址 = /127.0.0.1:58715

为什么请求了2次???
 

第二次请求是favicon.ico文件

三、过滤请求

1、 改写HttpServerHandler.java;

package netty.http;

import java.net.URI;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

//SimpleChannelInboundHandler继承了ChannelInboundHandlerAdapter
//HttpObject指定了客户端和服务器端,在处理的时候的数据类型
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

	//channelRead0:读取客户端数据
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
		
		//判断msg是不是HttpRequest请求
		if (msg instanceof HttpRequest) {
			System.out.println("msg 类型 = " + msg.getClass());
			System.out.println("客户端地址 = " + ctx.channel().remoteAddress());
			
			//过滤信息
			HttpRequest httpRequest = (HttpRequest) msg;
			//获取uri
			URI uri = new URI(httpRequest.uri());
			if ("/favicon.ico".equals(uri.getPath())) {
				System.out.println("请求了favicon.ico,不做响应");
				return;
			}
			
			//回复信息给浏览器 [http协议]
			ByteBuf content = Unpooled.copiedBuffer("hello,我是服务器!", CharsetUtil.UTF_8);
			//构造一个http的响应,即httpResponse
			FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
			
			response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
			response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());
			
			//将构建好的response返回
			ctx.writeAndFlush(response);
			
		}
	}

}

2、 执行结果;
 

四、每一个客户端是独享一个handler

因为每个浏览器request到服务器的时候在服务器这边对应一个channel,每个channel有自己的pipeline即一连串的handler。