Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/en/changes/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
* Fix gRPC alarm cannot update settings from dynamic configuration source.
* Add Python Websocket module component ID(7018).
* [Optional] Optimize single trace query performance by customizing routing in ElasticSearch. SkyWalking trace segments and Zipkin spans are using trace ID for routing. This is OFF by default, controlled by `storage/elasticsearch/enableCustomRouting`.
* Enhance OAP HTTP server to support HTTPS
* Enhance OAP HTTP server to support HTTPS (Support reload certs).

#### UI

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.skywalking.oap.server.library.server.http;

import com.google.common.collect.Sets;

import com.linecorp.armeria.common.HttpMethod;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
Expand All @@ -34,13 +35,16 @@
import java.net.InetSocketAddress;

import java.time.Duration;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.skywalking.oap.server.library.server.Server;
import org.apache.skywalking.oap.server.library.server.ssl.PrivateKeyUtil;
import org.apache.skywalking.oap.server.library.util.MultipleFilesChangeMonitor;

import static java.util.Objects.requireNonNull;

Expand All @@ -51,6 +55,12 @@ public class HTTPServer implements Server {
// Health check service, supports HEAD, GET method.
private final Set<HttpMethod> allowedMethods = Sets.newHashSet(HttpMethod.HEAD);

private Set<Object> handlers;

private MultipleFilesChangeMonitor monitor;

private com.linecorp.armeria.server.Server httpServer;

public HTTPServer(HTTPServerConfig config) {
this.config = config;
}
Expand All @@ -60,24 +70,14 @@ public void initialize() {
// TODO replace prefix with real context path when Armeria supports it
final String contextPath = StringUtils.stripEnd(config.getContextPath(), "/");
sb = com.linecorp.armeria.server.Server
.builder()
.serviceUnder(contextPath + "/docs", DocService.builder().build())
.service("/internal/l7check", HealthCheckService.of())
.workerGroup(config.getMaxThreads())
.http1MaxHeaderSize(config.getMaxRequestHeaderSize())
.idleTimeout(Duration.ofMillis(config.getIdleTimeOut()))
.decorator(Route.ofCatchAll(), (delegate, ctx, req) -> {
if (!allowedMethods.contains(ctx.method())) {
return HttpResponse.of(HttpStatus.METHOD_NOT_ALLOWED);
}
return delegate.serve(ctx, req);
})
.decorator(LoggingService.newDecorator());

.builder();
normalInitialize(sb);
if (config.isEnableTLS()) {
handlers = new HashSet<>();
sb.https(new InetSocketAddress(
config.getHost(),
config.getPort()));

try (InputStream cert = new FileInputStream(config.getTlsCertChainPath());
InputStream key = PrivateKeyUtil.loadDecryptionKey(config.getTlsKeyPath())) {
sb.tls(cert, key);
Expand All @@ -90,11 +90,33 @@ public void initialize() {
config.getPort()
));
}

monitor = new MultipleFilesChangeMonitor(
10,
readableContents -> updateCert(),
config.getTlsCertChainPath(),
config.getTlsKeyPath());

log.info("Server root context path: {}", contextPath);
}

private void normalInitialize(ServerBuilder sb) {
final String contextPath = StringUtils.stripEnd(config.getContextPath(), "/");
sb.serviceUnder(contextPath + "/docs", DocService.builder().build())
.service("/internal/l7check", HealthCheckService.of())
.workerGroup(config.getMaxThreads())
.http1MaxHeaderSize(config.getMaxRequestHeaderSize())
.idleTimeout(Duration.ofMillis(config.getIdleTimeOut()))
.decorator(Route.ofCatchAll(), (delegate, ctx, req) -> {
if (!allowedMethods.contains(ctx.method())) {
return HttpResponse.of(HttpStatus.METHOD_NOT_ALLOWED);
}
return delegate.serve(ctx, req);
})
.decorator(LoggingService.newDecorator());
if (config.getAcceptQueueSize() > 0) {
sb.maxNumConnections(config.getAcceptQueueSize());
}

log.info("Server root context path: {}", contextPath);
}

/**
Expand All @@ -107,15 +129,37 @@ public void addHandler(Object handler, List<HttpMethod> httpMethods) {
"Bind handler {} into http server {}:{}",
handler.getClass().getSimpleName(), config.getHost(), config.getPort()
);

if (config.isEnableTLS()) {
handlers.add(handler);
}
sb.annotatedService()
.pathPrefix(config.getContextPath())
.build(handler);
this.allowedMethods.addAll(httpMethods);
}

public void updateCert() {
log.info("TLS cert chain file or TLS key file has been updated, reloading...");
httpServer.reconfigure(sb -> {
try (InputStream cert = new FileInputStream(config.getTlsCertChainPath());
InputStream key = PrivateKeyUtil.loadDecryptionKey(config.getTlsKeyPath())) {
sb.tls(cert, key);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
normalInitialize(sb);
sb.annotatedService()
.pathPrefix(config.getContextPath())
.build(handlers.toArray());
Comment on lines +150 to +153
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you confirm this is needed when we only want to reconfigure the tls cert? I think Armeria will maintain the existing service endpoint if we didn't add new or remove existing service.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reconfigure method will creat a new ServerBuild. So I think it is needed.
image
image

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sb.buildServerConfig(config()) only set the host and port.

unit test of Armeria can also prove it.

});
}

@Override
public void start() {
sb.build().start().join();
httpServer = sb.build();
httpServer.start().join();
if (config.isEnableTLS()) {
monitor.start();
}
}
}