概述:
这是一个基于netty的http服务器其中整合了spring的相关配置 ,但是他只是在大体上实现了http的功能对于映射他的实现方法是一个类一个映射而不是mvc的一个类多个映射。-这是本人在git上找到的netty工程我在上面进行了比较详细的注解方便与新学的或者相对spring框架的注解配置有些更深入的理解。
如果想看spring框架初始化中对注解做的一些匹配工作可以看链接:https://blog.csdn.net/horse_xiao/article/details/95020952
源码链接:https://github.com/flytotop/netty-http-server
启动类
对于spring boot来说该框架整合了tomcat所以如果你需要编写属于自己的http服务器那么首先你需要要停止tomcat的启动 并将netty启动类加载到配置项。
package com.farsunset.httpserver; import com.farsunset.httpserver.netty.annotation.NettyHttpHandler; import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication() // @ComponentScan(includeFilters = @ComponentScan.Filter(NettyHttpHandler.class)) public class HttpServerApplication { public static void main(String[] args) { //停止tomcat的相关启动 new SpringApplicationBuilder(HttpServerApplication.class).web(WebApplicationType.NONE).run(args); } }
接下来就是编写netty的相关信息 首先就是启动类
package com.farsunset.httpserver.configuration; import com.farsunset.httpserver.netty.iohandler.InterceptorHandler; import com.farsunset.httpserver.netty.iohandler.FilterLogginglHandler; import com.farsunset.httpserver.netty.iohandler.HttpServerHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioChannelOption; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Configuration; import org.springframework.lang.NonNull; import javax.annotation.Resource; @Configuration public class NettyHttpServer implements ApplicationListener<ApplicationStartedEvent> { private static final Logger LOGGER = LoggerFactory.getLogger(NettyHttpServer.class); @Value("${server.port}") private int port; @Resource private InterceptorHandler interceptorHandler; @Resource private HttpServerHandler httpServerHandler; @Override public void onApplicationEvent(@NonNull ApplicationStartedEvent event) { ServerBootstrap bootstrap = new ServerBootstrap(); EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); bootstrap.group(bossGroup, workerGroup); bootstrap.channel(NioServerSocketChannel.class); //channel的属性配置 bootstrap.childOption(NioChannelOption.TCP_NODELAY, true);//是否使用fullrequest fullresponse发送数据 bootstrap.childOption(NioChannelOption.SO_REUSEADDR,true); //是否允许端口占用 bootstrap.childOption(NioChannelOption.SO_KEEPALIVE,false);//是否设置长连接 bootstrap.childOption(NioChannelOption.SO_RCVBUF, 2048); //设置接收数据大小 bootstrap.childOption(NioChannelOption.SO_SNDBUF, 2048);//设置发送数据大小 bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast("codec", new HttpServerCodec());//http编解码器 //对httpmsg进行聚合 转化为fullHttpRequest 或者fullHttpResponse并设置最大数据长度 ch.pipeline().addLast("aggregator", new HttpObjectAggregator(512 * 1024)); ch.pipeline().addLast("logging", new FilterLogginglHandler());//日志 ch.pipeline().addLast("interceptor", interceptorHandler);//拦截器配置 ch.pipeline().addLast("bizHandler", httpServerHandler); //请求匹配处理 } }) ; ChannelFuture channelFuture = bootstrap.bind(port).syncUninterruptibly().addListener(future -> { String logBanner = "\n\n" + "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" + "* *\n" + "* *\n" + "* Netty Http Server started on port {}. *\n" + "* *\n" + "* *\n" + "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n"; LOGGER.info(logBanner, port); }); //通过引入监听器对象监听future状态,当future任务执行完成后会调用-》{}内的方法 channelFuture.channel().closeFuture().addListener(future -> { LOGGER.info("Netty Http Server Start Shutdown ............"); /** * 优雅关闭 */ bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }); } }
日志处理器配置
package com.farsunset.httpserver.netty.iohandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import java.net.SocketAddress; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; public class FilterLogginglHandler extends LoggingHandler { public FilterLogginglHandler() { super(LogLevel.INFO); } @Override public void channelRegistered(ChannelHandlerContext ctx) { ctx.fireChannelRegistered(); } @Override public void channelUnregistered(ChannelHandlerContext ctx) { ctx.fireChannelUnregistered(); } @Override public void channelActive(ChannelHandlerContext ctx) { ctx.fireChannelActive(); } @Override public void channelInactive(ChannelHandlerContext ctx) { ctx.fireChannelInactive(); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { ctx.fireUserEventTriggered(evt); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise){ if (this.logger.isEnabled(this.internalLevel)) { this.logger.log(this.internalLevel,ctx.channel().toString() + " WRITE \n" + msg.toString()); } ctx.write(msg, promise); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (this.logger.isEnabled(this.internalLevel)) { HttpRequest request = (HttpRequest) msg; String log = request.method() + " " + request.uri() + " " + request.protocolVersion() + "\n" + CONTENT_TYPE + ": " + request.headers().get(CONTENT_TYPE) + "\n" + CONTENT_LENGTH + ": " + request.headers().get(CONTENT_LENGTH) + "\n"; this.logger.log(this.internalLevel,ctx.channel().toString() + " READ \n" + log); } /** * 请求转发给下个处理器 */ ctx.fireChannelRead(msg); } @Override public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) { ctx.bind(localAddress, promise); } @Override public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) { ctx.connect(remoteAddress, localAddress, promise); } @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) { ctx.disconnect(promise); } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) { ctx.close(promise); } @Override public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) { ctx.deregister(promise); } @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.fireChannelReadComplete(); } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) { ctx.fireChannelWritabilityChanged(); } @Override public void flush(ChannelHandlerContext ctx) { ctx.flush(); } }
对于客户端的请求在这里封装了两个对象用来接收Fullhttprequest 的和用来发送response的fuhttpreponse
package com.farsunset.httpserver.netty.http; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.http.*; import java.nio.charset.Charset; import java.util.Objects; public class NettyHttpRequest implements FullHttpRequest { private FullHttpRequest realRequest; public NettyHttpRequest(FullHttpRequest request){ this.realRequest = request; } public String contentText(){ return content().toString(Charset.forName("UTF-8")); } public long getLongPathValue(int index){ String[] paths = uri().split("/"); return Long.parseLong(paths[index]); } public String getStringPathValue(int index){ String[] paths = uri().split("/"); return paths[index]; } public int getIntPathValue(int index){ String[] paths = uri().split("/"); return Integer.parseInt(paths[index]); } public boolean isAllowed(String method){ return getMethod().name().equalsIgnoreCase(method); } public boolean matched(String path,boolean equal){ String uri = uri().toLowerCase(); return equal ? Objects.equals(path,uri) : uri.startsWith(path); } @Override public ByteBuf content() { return realRequest.content(); } @Override public HttpHeaders trailingHeaders() { return realRequest.trailingHeaders(); } @Override public FullHttpRequest copy() { return realRequest.copy(); } @Override public FullHttpRequest duplicate() { return realRequest.duplicate(); } @Override public FullHttpRequest retainedDuplicate() { return realRequest.retainedDuplicate(); } @Override public FullHttpRequest replace(ByteBuf byteBuf) { return realRequest.replace(byteBuf); } @Override public FullHttpRequest retain(int i) { return realRequest.retain(i); } @Override public int refCnt() { return realRequest.refCnt(); } @Override public FullHttpRequest retain() { return realRequest.retain(); } @Override public FullHttpRequest touch() { return realRequest.touch(); } @Override public FullHttpRequest touch(Object o) { return realRequest.touch(o); } @Override public boolean release() { return realRequest.release(); } @Override public boolean release(int i) { return realRequest.release(i); } @Override public HttpVersion getProtocolVersion() { return realRequest.protocolVersion(); } @Override public HttpVersion protocolVersion() { return realRequest.protocolVersion(); } @Override public FullHttpRequest setProtocolVersion(HttpVersion httpVersion) { return realRequest.setProtocolVersion(httpVersion); } @Override public HttpHeaders headers() { return realRequest.headers(); } @Override public HttpMethod getMethod() { return realRequest.getMethod(); } @Override public HttpMethod method() { return realRequest.method(); } @Override public FullHttpRequest setMethod(HttpMethod httpMethod) { return realRequest.setMethod(httpMethod); } @Override public String getUri() { return realRequest.getUri(); } @Override public String uri() { return realRequest.uri(); } @Override public FullHttpRequest setUri(String s) { return realRequest.setUri(s); } @Override public DecoderResult getDecoderResult() { return realRequest.getDecoderResult(); } @Override public DecoderResult decoderResult() { return realRequest.decoderResult(); } @Override public void setDecoderResult(DecoderResult decoderResult) { realRequest.setDecoderResult(decoderResult); } }
package com.farsunset.httpserver.netty.http; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import static io.netty.handler.codec.http.HttpHeaderNames.*; public class NettyHttpResponse extends DefaultFullHttpResponse { private static final PooledByteBufAllocator BYTE_BUF_ALLOCATOR = new PooledByteBufAllocator(false); /** * 状态码设置 */ private static final String CONTENT_NORMAL_200 = "{\"code\":200,\"message\":\"OK\"}"; private static final String CONTENT_ERROR_401 = "{\"code\":401,\"message\":\"UNAUTHORIZED\"}"; private static final String CONTENT_ERROR_404 = "{\"code\":404,\"message\":\"REQUEST PATH NOT FOUND\"}"; private static final String CONTENT_ERROR_405 = "{\"code\":405,\"message\":\"METHOD NOT ALLOWED\"}"; private static final String CONTENT_ERROR_500 = "{\"code\":500,\"message\":\"%s\"}"; private String content; private NettyHttpResponse(HttpResponseStatus status, ByteBuf buffer ) { super(HttpVersion.HTTP_1_1, status,buffer); headers().set(CONTENT_TYPE, "application/json"); headers().setInt(CONTENT_LENGTH, content().readableBytes()); /** * 支持CORS 跨域访问 */ headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); headers().set(ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept, RCS-ACCESS-TOKEN"); headers().set(ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE"); } public static FullHttpResponse make(HttpResponseStatus status) { if (HttpResponseStatus.UNAUTHORIZED == status) { return NettyHttpResponse.make(HttpResponseStatus.UNAUTHORIZED, CONTENT_ERROR_401); } if (HttpResponseStatus.NOT_FOUND == status) { return NettyHttpResponse.make(HttpResponseStatus.NOT_FOUND, CONTENT_ERROR_404); } if (HttpResponseStatus.METHOD_NOT_ALLOWED == status) { return NettyHttpResponse.make(HttpResponseStatus.METHOD_NOT_ALLOWED, CONTENT_ERROR_405); } return NettyHttpResponse.make(HttpResponseStatus.OK,CONTENT_NORMAL_200); } public static FullHttpResponse makeError(Exception exception) { String message = exception.getClass().getName() + ":" + exception.getMessage(); return NettyHttpResponse.make(HttpResponseStatus.INTERNAL_SERVER_ERROR, String.format(CONTENT_ERROR_500,message)); } public static FullHttpResponse ok(String content) { return make(HttpResponseStatus.OK,content); } private static FullHttpResponse make(HttpResponseStatus status,String content) { byte[] body = content.getBytes(); ByteBuf buffer = BYTE_BUF_ALLOCATOR.buffer(body.length); buffer.writeBytes(body); NettyHttpResponse response = new NettyHttpResponse(status,buffer); response.content = content; return response; } @Override public String toString(){ StringBuilder builder = new StringBuilder(); builder.append(protocolVersion().toString()).append(" ").append(status().toString()).append("\n"); builder.append(CONTENT_TYPE).append(": ").append(headers().get(CONTENT_TYPE)).append("\n"); builder.append(CONTENT_LENGTH).append(": ").append(headers().get(CONTENT_LENGTH)).append("\n"); builder.append("content-body").append(": ").append(content).append("\n"); return builder.toString(); } }
拦截处理器
package com.farsunset.httpserver.netty.iohandler; import com.farsunset.httpserver.netty.http.NettyHttpResponse; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.ReferenceCountUtil; import org.springframework.stereotype.Component; @ChannelHandler.Sharable @Component /** * 在这里可以做拦截器,验证一些请求的合法性 */ public class InterceptorHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext context, Object msg) { if (isPassed((FullHttpRequest) msg)){ /** * 提交给下一个ChannelHandler去处理 * 并且不需要调用ReferenceCountUtil.release(msg);来释放引用计数 */ context.fireChannelRead(msg); return; } /** * 非异常引起的错误需要手动关闭channel通道 */ ReferenceCountUtil.release(msg); context.writeAndFlush(NettyHttpResponse.make(HttpResponseStatus.UNAUTHORIZED)).addListener(ChannelFutureListener.CLOSE); } /** * 修改实现来验证合法性 * @param request * @return */ private boolean isPassed(FullHttpRequest request){ return true; } }
请求处理器
package com.farsunset.httpserver.netty.iohandler; import com.farsunset.httpserver.dto.Response; import com.farsunset.httpserver.netty.annotation.NettyHttpHandler; import com.farsunset.httpserver.netty.exception.IllegalMethodNotAllowedException; import com.farsunset.httpserver.netty.exception.IllegalPathDuplicatedException; import com.farsunset.httpserver.netty.exception.IllegalPathNotFoundException; import com.farsunset.httpserver.netty.handler.IFunctionHandler; import com.farsunset.httpserver.netty.http.NettyHttpRequest; import com.farsunset.httpserver.netty.http.NettyHttpResponse; import com.farsunset.httpserver.netty.path.Path; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.ReferenceCountUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Stream; @ChannelHandler.Sharable @Component /** * 通过实现ApplicationContextAware将setApplicationContext()函数引入spring初始化内容中去 */ public class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> implements ApplicationContextAware { private static final Logger LOGGER = LoggerFactory.getLogger(HttpServerHandler.class); private HashMap<Path, IFunctionHandler> functionHandlerMap = new HashMap<>(); /** * 线程工厂 */ private ExecutorService executor = Executors.newCachedThreadPool(runnable -> { Thread thread = Executors.defaultThreadFactory().newThread(runnable); thread.setName("NettyHttpHandler-" + thread.getName()); return thread; }); @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { FullHttpRequest copyRequest = request.copy(); /** * 通过内部类的形式传入一个runnable的实现类并重写了run方法 线程池在执行的时候会调用这个方法 */ executor.execute(() -> onReceivedRequest(ctx,new NettyHttpRequest(copyRequest))); } /** * * @param context * @param request */ private void onReceivedRequest(ChannelHandlerContext context, NettyHttpRequest request){ /** * 处理request请求 */ FullHttpResponse response = handleHttpRequest(request); /** * 通过channel将结果输出 并通过添加监听器的方式关闭channel通道 */ context.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); /** * 释放bytebuf缓存 */ ReferenceCountUtil.release(request); } /** * * @param request NettyHttpRequest extends Fullhttpreuqest * @return 请求处理结果 */ private FullHttpResponse handleHttpRequest(NettyHttpRequest request) { IFunctionHandler functionHandler = null; /** * 请求处理并根据不同的结果或者捕获的异常进行状态码转换并返回 */ try { functionHandler = matchFunctionHandler(request); //这边通过注解与请求路径的匹配获得相应的处理器对象之后通过对接口方法的调用达到方法执行的效果 Response response = functionHandler.execute(request); return NettyHttpResponse.ok(response.toJSONString()); } catch (IllegalMethodNotAllowedException error){ return NettyHttpResponse.make(HttpResponseStatus.METHOD_NOT_ALLOWED); } catch (IllegalPathNotFoundException error){ return NettyHttpResponse.make(HttpResponseStatus.NOT_FOUND); } catch (Exception error){ LOGGER.error(functionHandler.getClass().getSimpleName() + " Error",error); return NettyHttpResponse.makeError(error); } } /** * spring初始化加载此函数 * @param applicationContext */ @Override public void setApplicationContext(ApplicationContext applicationContext) { /** * 获得所有NettyHttpHandler的注解类 */ Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(NettyHttpHandler.class); for (Map.Entry<String, Object> entry : handlers.entrySet()) { Object handler = entry.getValue(); Path path = Path.make(handler.getClass().getAnnotation(NettyHttpHandler.class)); /** * 查询是否当前处理器的注解是否已经存在(类似于SSM中controler的注解不能重复) * 1.存在则抛出异常 * 2. 不存在则存入Map集合中 在SSM中是通过对类方法注解的扫描 存入内部类mapperRegistry中 */ if (functionHandlerMap.containsKey(path)){ LOGGER.error("IFunctionHandler has duplicated :" + path.toString(),new IllegalPathDuplicatedException()); System.exit(0); } functionHandlerMap.put(path, (IFunctionHandler) handler); } } private IFunctionHandler matchFunctionHandler(NettyHttpRequest request) throws IllegalPathNotFoundException, IllegalMethodNotAllowedException { AtomicBoolean matched = new AtomicBoolean(false); Stream<Path> stream = functionHandlerMap.keySet().stream() .filter(((Predicate<Path>) path -> { /** *过滤 Path URI 不匹配的 */ if (request.matched(path.getUri(), path.isEqual())) { matched.set(true); return matched.get(); } return false; }).and(path -> { /** * 过滤 Method 匹配的 */ return request.isAllowed(path.getMethod()); })); Optional<Path> optional = stream.findFirst(); stream.close(); if (!optional.isPresent() && !matched.get()){ throw new IllegalPathNotFoundException(); } if (!optional.isPresent() && matched.get()){ throw new IllegalMethodNotAllowedException(); } return functionHandlerMap.get(optional.get()); } }
请求处理这边设置了一些对与访问异常的处理
package com.farsunset.httpserver.netty.exception; public class IllegalMethodNotAllowedException extends Exception { public IllegalMethodNotAllowedException() { super("METHOD NOT ALLOWED"); } }----------------------------------------------------------------------------------------------------------------------------------------------------------------
package com.farsunset.httpserver.netty.exception; public class IllegalPathNotFoundException extends Exception { public IllegalPathNotFoundException() { super("PATH NOT FOUND"); } }
控制层示例
-
package com.farsunset.httpserver.netty.handler; import com.farsunset.httpserver.dto.Response; import com.farsunset.httpserver.netty.annotation.NettyHttpHandler; import com.farsunset.httpserver.netty.http.NettyHttpRequest; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @NettyHttpHandler(path = "/moment/list/",equal = false) public class PathVariableHandler implements IFunctionHandler<List<HashMap<String,String>>> { @Override public Response<List<HashMap<String,String>>> execute(NettyHttpRequest request) { /** * 通过请求uri获取到path参数 */ String id = request.getStringPathValue(3); List<HashMap<String,String>> list = new LinkedList<>(); HashMap<String,String> data = new HashMap<>(); data.put("id","1"); data.put("name","Bluesky"); data.put("text","hello sea!"); data.put("time","2018-08-08 08:08:08"); list.add(data); return Response.ok(list); } }