最近在IDEA插件开发中遇到了一个场景,就是插件需要接受某个网页的HTTP请求,然后根据该请求做出某些响应。

比如说集成单点登录系统,需要插件在本地浏览器中打开某个页面,然后在网页中进行登录,登录完成后由网页调用插件对外暴露的接口,通知插件已经完成登录。 这个过程中就需要插件具有一个webserver。

一说起webserver,咱们可能首先想到的是tomcat,apache等等,不是说这些不可以完成上述需求,只不过为了这么一个接口嵌入这么多的依赖,有点得不偿失,更何况是插件开发,追求的就是最小依赖产生最大的价值。

经调研有两种实现方法,一种是基于jdk自带的HttpServer类实现,另一种是使用apache.http包的Boostrap实现(推荐)。

JDK的HttpServer实现

这种实现方式最大的优势在于不用引入第三方依赖,实现较为简单。源码如下:

import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;

public class HttpServerTest {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
        server.createContext("/hello", exchange ->
                exchange.getResponseBody().write("hello world".getBytes(StandardCharsets.UTF_8)));

        server.start();
//        server.stop(0);
//		server.start(); 此行会报错
    }
}

但是这种实现也有其缺点,目前遇到的最大的问题就是重启问题。查看了源代码中的stop方法注释:

/**
     * stops this server by closing the listening socket and disallowing
     * any new exchanges from being processed. The method will then block
     * until all current exchange handlers have completed or else when
     * approximately <i>delay</i> seconds have elapsed (whichever happens
     * sooner). Then, all open TCP connections are closed, the background
     * thread created by start() exits, and the method returns.
     * Once stopped, a HttpServer cannot be re-used. <p>
     *
     * @param delay the maximum time in seconds to wait until exchanges have finished.
     * @throws IllegalArgumentException if delay is less than zero.
     */
    public abstract void stop (int delay);

重点就是:Once stop, a HttpServer cannot be re-used.

也就是说用这种方式开启HTTP服务是不能随用随启的,只能一直开启,因此这种在插件开发中有安全隐患。

使用apache.http的Bootstrap实现

这种实现方法需要使用到第三方依赖,不过在IDEA插件开发过程中不需要在意这个,因为官方的SDK中就集成了apache的工具包,包含了http模块。源代码如下:

import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.impl.bootstrap.HttpServer;
import org.apache.http.impl.bootstrap.ServerBootstrap;

import java.io.ByteArrayInputStream;
import java.io.IOException;

public class ApacheHttpServerTest {
    public static void main(String[] args) throws IOException {
        ServerBootstrap bootstrap = ServerBootstrap.bootstrap();
        bootstrap.setListenerPort(8080);
        bootstrap.registerHandler("/hello", (httpRequest, httpResponse, httpContext) -> {
            BasicHttpEntity entity = new BasicHttpEntity();
            entity.setContent(new ByteArrayInputStream("hello world".getBytes()));
            httpResponse.setEntity(entity);
        });
        HttpServer httpServer = bootstrap.create();
        httpServer.start();
        httpServer.stop();
        httpServer.start();
    }
}

这种实现也比较简单,而且与servlet的写法像。最重要的是支持“重启”,也就意味着可以随用随启,不用的时候关闭,也不会有什么安全隐患了。