四種方法實現http服務


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

相關文章
相關標籤/搜索