前言:对于微服务来讲,若是咱们要实现一个web服务, 大部分人可能直接用springboot的spring-boot-starter-web了。 咱们知道spring-boot-starter-web默认实现是tomcat,固然你也能够选择其余服务器类型,好比Jetty、Undertow等。 可是若是是一个非springboot项目,该如何实现呢?html
这里介绍了下四种实现方式,基于Tomcat、Jetty、JdkHttp、Netty实现内嵌web容器。java
Tomcat依赖的maven坐标:web
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <exclusions> <!--防止注解冲突,排除tomcat自带注解,springboot也是这样设置的--> <exclusion> <artifactId>tomcat-annotations-api</artifactId> <groupId>org.apache.tomcat</groupId> </exclusion> </exclusions> </dependency>
首先看下初始化启动的代码:spring
Tomcat tomcatServer = new Tomcat(); //静默方式启动 tomcatServer.setSilent(true); tomcatServer.setPort(8080); //是否设置自动部署 tomcatServer.getHost().setAutoDeploy(false); //建立上下文,拿到上下文后就能够设置整个访问地址了 StandardContext standardContext = new StandardContext(); standardContext.setPath(CONTEX_PATH); //监听上下文 standardContext.addLifecycleListener(new Tomcat.FixContextListener()); // tomcat容器添加standardContext 添加整个context tomcatServer.getHost().addChild(standardContext); // 建立servlet servlet的名字叫IndexServlet tomcatServer.addServlet(CONTEX_PATH, SERVLET_NAME, new JdkSimpleDispatchServlet()); // 添加servleturl映射 standardContext.addServletMappingDecoded("/*", SERVLET_NAME); try { tomcatServer.start(); server = tomcatServer; } catch (Exception e) { }
上面tomcat在注册servlet的时候,自定义了一个Servlet,而后映射了/*的请求。能够查看下JdkSimpleDispatchServlet这个类,代码以下:apache
@Slf4j public class JdkSimpleDispatchServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { log.info("path:{}, clientIp:{}", req.getRequestURL(), req.getRemoteHost()); PrintWriter writer = resp.getWriter(); writer.print("this is index"); writer.close(); } }Jetty
Jetty和tomcat很是相似,也是调用start方法启动。bootstrap
maven依赖以下:api
<dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-server</artifactId> </dependency> <!--添加servlet模块--> <dependency> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-servlet</artifactId> </dependency>
jetty服务初始化代码更简单,以下:tomcat
Server server = new Server(NetworkUtil.getHealthServerPort()); // ServletHandler是一种简单的建立上下文处理程序的简单方法,该上下文处理程序由Servlet实例支持。而后,须要将该处理程序注册到Server对象。 ServletHandler handler = new ServletHandler(); server.setHandler(handler); // 这是原始Servlet,而不是已配置的Servlet,JdkSimpleDispatchServlet和tomcat的相似 handler.addServletWithMapping(JdkSimpleDispatchServlet.class, "/*"); try { // Start things up! server.start(); jetty = server; } catch (Exception e) { }Netty
说了Tomcat和Jetty,咱们再看下Netty,以前所在的一家公司就是基于Netty封装了Web服务,Netty对Web支持也比较完善,默认基于NIO的多路复用IO模型支持单机上万的吞肚量。springboot
看下pom依赖:服务器
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency>
启动方式:
public class NettyHttpServerHealthCheckServer implements IHealthCheckServer { private static ExecutorService Pool = Executors.newSingleThreadExecutor( new NamedThreadFactory("Netty-HealthCheck-Pool", true)); ServerBootstrap bootstrap = new ServerBootstrap(); // boss线程,只需一个 EventLoopGroup boss = new NioEventLoopGroup(); // work线程 EventLoopGroup work = new NioEventLoopGroup(); @Override public void start() { try { // 由于监听服务是阻塞的,须要线程池异步监听 Pool.execute(() -> { try { // 配置channel、handle bootstrap.group(boss, work) // .handler(new LoggingHandler(LogLevel.INFO)) .channel(NioServerSocketChannel.class) // HttpServerInitializer即http编码解码和处理配置器 .childHandler(new HttpServerInitializer()); ChannelFuture f = bootstrap.bind(new InetSocketAddress(NetworkUtil.getHealthServerPort())).sync(); // 阻塞监听 f.channel().closeFuture().sync(); } catch (Exception e) { } }); } catch (Exception e) { return; } } @Override public void stop() { boss.shutdownGracefully(); work.shutdownGracefully(); } class HttpServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new HttpServerCodec());// http 编解码 pipeline.addLast("httpAggregator", new HttpObjectAggregator(512 * 1024)); // http 消息聚合器 512*1024为接收的最大contentlength pipeline.addLast(new HttpRequestHandler());// 请求处理器 } } class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception { /** * 100 Continue * 是这样的一种状况:HTTP客户端程序有一个实体的主体部分要发送给服务器,但但愿在发送以前查看下服务器是否会 * 接受这个实体,因此在发送实体以前先发送了一个携带100 * Continue的Expect请求首部的请求。服务器在收到这样的请求后,应该用 100 Continue或一条错误码来进行响应。 */ if (is100ContinueExpected(req)) { ctx.write(new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE)); } // 获取请求的uri String path = req.uri(); // 响应请求 fireResponse(ctx, HttpResponseStatus.OK, "hello", "text/html; charset=utf-8"); } private void fireResponse(ChannelHandlerContext ctx, HttpResponseStatus httpResponseStatus, String resp, String contentType) { FullHttpResponse responseDown = new DefaultFullHttpResponse( HttpVersion.HTTP_1_1, httpResponseStatus, Unpooled.copiedBuffer(resp, Charset.defaultCharset())); responseDown.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType); ctx.writeAndFlush(responseDown).addListener(ChannelFutureListener.CLOSE); } } }
从上面代码能够得知,Netty默认就提供了http编解码和协议的实现,很是方便。
JdkHttp最好介绍下不依赖第三方实现,使用JDK8内置的Http Server实现。
核心类HttpServer,HttpServer是属于rt包的类,须要下载rt包的源码,配置到IDEA。或者直接使用openjdk,也能够查看到源码。
rt包能够下载OpenJDK的源码,https://download.java.net/openjdk/jdk8/promoted/b132/openjdk-8-src-b132-03_mar_2014.zip
HttpServer源码:
package com.sun.net.httpserver; @Exported public abstract class HttpServer { protected HttpServer() { } public static HttpServer create() throws IOException { return create((InetSocketAddress)null, 0); } public static HttpServer create(InetSocketAddress var0, int var1) throws IOException { HttpServerProvider var2 = HttpServerProvider.provider(); return var2.createHttpServer(var0, var1); } public abstract void bind(InetSocketAddress var1, int var2) throws IOException; public abstract void start(); public abstract void setExecutor(Executor var1); public abstract Executor getExecutor(); public abstract void stop(int var1); public abstract HttpContext createContext(String var1, HttpHandler var2); public abstract HttpContext createContext(String var1); public abstract void removeContext(String var1) throws IllegalArgumentException; public abstract void removeContext(HttpContext var1); public abstract InetSocketAddress getAddress(); }
初始化Http服务:
public class JDKHttpServerHealthCheckServer implements IHealthCheckServer { HttpServer server; @Override public void start() { try { // 初始化监听 server = HttpServer.create(new InetSocketAddress(8080), 100); // 注册http请求处理类 server.createContext("/", new JdkHttpHandler()); // 启动服务 server.start(); } catch (Exception e) { return; } } @Override public void stop() { if (server != null) { server.stop(0); } } static class JdkHttpHandler implements HttpHandler { @Override public void handle(HttpExchange httpExchange) throws IOException { String path = httpExchange.getRequestURI() == null ? "/" : httpExchange.getRequestURI().getPath(); try { CharArrayWriter charArrayWriter = new CharArrayWriter(); charArrayWriter.write("hello"); httpExchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8"); // 这里必须指定字节大小,由于默认是固定大小的编码解码实现 httpExchange.sendResponseHeaders(200, charArrayWriter.size()); outputStream.write(charArrayWriter.toString().getBytes()); } catch (Exception e) { httpExchange.sendResponseHeaders(500, 0); } } } }
从上面四个实现来看,对http servlet规范支持比较完善的有Jetty、Tomcat。性能高的是Netty,实现最简单的是JDK默认HttpServer。