前言:對於微服務來講,若是咱們要實現一個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。