前言:对于微服务来说,如果咱们要实现一个web服务,
大部分人可能间接用springboot的spring-boot-starter-web了。
咱们晓得spring-boot-starter-web默认实现是tomcat,当然你也能够抉择其余服务器类型,比方Jetty、Undertow等。
然而如果是一个非springboot我的项目,该如何实现呢?

这里介绍了下四种实现形式,基于Tomcat、Jetty、JdkHttp、Netty实现内嵌web容器。

Tomcat

依赖的maven坐标:

        <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>

首先看下初始化启动的代码:

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 增加整个contexttomcatServer.getHost().addChild(standardContext);// 创立servlet   servlet的名字叫IndexServlettomcatServer.addServlet(CONTEX_PATH, SERVLET_NAME, new JdkSimpleDispatchServlet());// 增加servleturl映射standardContext.addServletMappingDecoded("/*", SERVLET_NAME);try {    tomcatServer.start();    server = tomcatServer;} catch (Exception e) {}

下面tomcat在注册servlet的时候,自定义了一个Servlet,而后映射了/*的申请。能够查看下JdkSimpleDispatchServlet这个类,代码如下:

@Slf4jpublic 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办法启动。

maven依赖如下:

        <dependency>            <groupId>org.eclipse.jetty</groupId>            <artifactId>jetty-server</artifactId>        </dependency>        <!--增加servlet模块-->        <dependency>            <groupId>org.eclipse.jetty</groupId>            <artifactId>jetty-servlet</artifactId>        </dependency>

jetty服务初始化代码更简略,如下:

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模型反对单机上万的吞肚量。

看下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,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;@Exportedpublic 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。

本文由猿必过 YBG 公布