跳转到内容

Https 服务 🌐

出于安全服务需要,生产环境通常使用 Https 协议,Feat 也提供了相应的能力。

下文演示所使用的证书是通过 mkcert 生成的自签名证书。

生成 PEM 证书

执行以下命令生成证书:

Terminal window
mkcert example.com "*.example.com" example.test localhost 127.0.0.1 ::1

如果控制台出现以下提示信息,则表示证书生成成功。

Terminal window
Created a new certificate valid for the following names 📜
- "example.com"
- "*.example.com"
- "example.test"
- "localhost"
- "127.0.0.1"
- "::1"
Reminder: X.509 wildcards only go one level deep, so this won't match a.b.example.com ℹ️
The certificate is at "./example.com+5.pem" and the key at "./example.com+5-key.pem" ✅
It will expire on 30 April 2027

启动Https服务

将证书文件 example.com+5.pemexample.com+5-key.pem 拷贝到项目的 src/main/resources 目录下。

使用 smart-socket 提供的 SslPlugin 插件启动 Https 服务。

HttpsPemDemo.java
public class HttpsPemDemo {
public static void main(String[] args) throws Exception {
InputStream certPem = HttpsPemDemo.class.getClassLoader().getResourceAsStream("example.org.pem");
InputStream keyPem = HttpsPemDemo.class.getClassLoader().getResourceAsStream("example.org-key.pem");
SslPlugin sslPlugin = new SslPlugin(new PemServerSSLContextFactory(certPem, keyPem));
Feat.httpServer(opt -> opt.addPlugin(sslPlugin)).httpHandler(req -> {
req.getResponse().write("Hello Feat Https");
}).listen();
}
}

打开浏览器,访问:https://localhost:8080 ,若页面展示如下,说明 Https 服务启动成功。

hello world

SSLEngine 传递

HttpRequest 中提供了 getSslEngine() 方法,用于获取 SSLEngine。

但是,SSLEngine 是在底层的网络通信层创建的,应用层无法感知底层是否使用了 SSL 协议。 所以,默认情况下调用 getSslEngine() 方法获取到的 SSLEngine 为 null。

若需要获取 SSLEngine,必须在 SslPlugin 中配置: Consumer<SSLEngine>, 将 SSLEngine 注入到 ThreadLocal 中以供应用层获取。

HttpsSSLEngineDemo.java
public class HttpsSSLEngineDemo {
public static void main(String[] args) throws Exception {
InputStream certPem = HttpsSSLEngineDemo.class.getClassLoader().getResourceAsStream("example.com+5.pem");
InputStream keyPem = HttpsSSLEngineDemo.class.getClassLoader().getResourceAsStream("example.com+5-key.pem");
SslPlugin sslPlugin = new SslPlugin(new PemServerSSLContextFactory(certPem, keyPem), (Consumer<SSLEngine>) sslEngine -> {
sslEngine.setUseClientMode(false);
HttpRequest.SSL_ENGINE_THREAD_LOCAL.set(sslEngine);
});
Feat.httpServer(opt -> opt.addPlugin(sslPlugin)).httpHandler(req -> {
SSLEngine engine = req.getSslEngine();
if (engine == null) {
req.getResponse().write("engine is null");
} else {
req.getResponse().write("engine=" + engine);
}
}).listen();
}
}

TCP 连接建立成功后,应用层Endpoint.java会在第一时间获取 SSLEngine

Endpoint.java
protected Endpoint(AioSession aioSession, ServerOptions options) {
this.aioSession = aioSession;
this.options = options;
this.sslEngine = HttpRequest.SSL_ENGINE_THREAD_LOCAL.get();
if (sslEngine != null) {
HttpRequest.SSL_ENGINE_THREAD_LOCAL.remove();
}
}