blob: b0728953b6763ee4937a6c799bc3cb57048040b4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.server;
import java.io.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import jakarta.ws.rs.core.UriBuilder;
import org.glassfish.grizzly.CompletionHandler;
import org.glassfish.grizzly.GrizzlyFuture;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.StaticHttpHandler;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import org.slf4j.Logger;
import org.apache.hugegraph.config.HugeConfig;
import org.apache.hugegraph.config.ServerOptions;
import org.apache.hugegraph.event.EventHub;
import org.apache.hugegraph.util.E;
import org.apache.hugegraph.util.Log;
import org.apache.hugegraph.version.ApiVersion;
public class RestServer {
private static final Logger LOG = Log.logger(RestServer.class);
private final HugeConfig conf;
private final EventHub eventHub;
private HttpServer httpServer = null;
public RestServer(HugeConfig conf, EventHub hub) {
this.conf = conf;
this.eventHub = hub;
}
public void start() throws IOException {
String url = this.conf.get(ServerOptions.REST_SERVER_URL);
URI uri = UriBuilder.fromUri(url).build();
ResourceConfig rc = new ApplicationConfig(this.conf, this.eventHub);
this.httpServer = this.configHttpServer(uri, rc);
try {
// Register HttpHandler for swagger-ui
this.httpServer.getServerConfiguration()
.addHttpHandler(new StaticHttpHandler("swagger-ui"),
"/swagger-ui");
this.httpServer.start();
} catch (Throwable e) {
this.httpServer.shutdownNow();
throw e;
}
this.calcMaxWriteThreads();
}
private HttpServer configHttpServer(URI uri, ResourceConfig rc) {
String protocol = uri.getScheme();
final HttpServer server;
if (protocol != null && protocol.equals("https")) {
SSLContextConfigurator sslContext = new SSLContextConfigurator();
String keystoreFile = this.conf.get(
ServerOptions.SSL_KEYSTORE_FILE);
String keystorePass = this.conf.get(
ServerOptions.SSL_KEYSTORE_PASSWORD);
sslContext.setKeyStoreFile(keystoreFile);
sslContext.setKeyStorePass(keystorePass);
SSLEngineConfigurator sslConfig = new SSLEngineConfigurator(
sslContext);
sslConfig.setClientMode(false);
sslConfig.setWantClientAuth(true);
server = GrizzlyHttpServerFactory.createHttpServer(uri, rc, true,
sslConfig);
} else {
server = GrizzlyHttpServerFactory.createHttpServer(uri, rc, false);
}
Collection<NetworkListener> listeners = server.getListeners();
E.checkState(listeners.size() > 0,
"Http Server should have some listeners, but now is none");
NetworkListener listener = listeners.iterator().next();
// Option max_worker_threads
int maxWorkerThreads = this.conf.get(ServerOptions.MAX_WORKER_THREADS);
listener.getTransport()
.getWorkerThreadPoolConfig()
.setCorePoolSize(maxWorkerThreads)
.setMaxPoolSize(maxWorkerThreads);
// Option keep_alive
int idleTimeout = this.conf.get(ServerOptions.CONN_IDLE_TIMEOUT);
int maxRequests = this.conf.get(ServerOptions.CONN_MAX_REQUESTS);
listener.getKeepAlive().setIdleTimeoutInSeconds(idleTimeout);
listener.getKeepAlive().setMaxRequestsCount(maxRequests);
// Option transaction timeout
int transactionTimeout = this.conf.get(ServerOptions.REQUEST_TIMEOUT);
listener.setTransactionTimeout(transactionTimeout);
return server;
}
public Future<HttpServer> shutdown() {
E.checkNotNull(this.httpServer, "http server");
/*
* Since 2.3.x shutdown() won't call shutdownNow(), so the event
* ApplicationEvent.Type.DESTROY_FINISHED also won't be triggered,
* which is listened by ApplicationConfig.GraphManagerFactory, we
* manually call shutdownNow() here when the future is completed.
* See shutdown() change:
* https://github.com/javaee/grizzly/commit/182d8bcb4e45de5609ab92f6f1d5980f95d79b04
* #diff-f6c130f38a1ec11bdf9d3cb7e0a81084c8788c79a00befe65e40a13bc989b098R388
*/
CompletableFuture<HttpServer> future = new CompletableFuture<>();
future.whenComplete((server, exception) -> {
this.httpServer.shutdownNow();
});
GrizzlyFuture<HttpServer> grizzlyFuture = this.httpServer.shutdown();
grizzlyFuture.addCompletionHandler(new CompletionHandler<HttpServer>() {
@Override
public void cancelled() {
future.cancel(true);
}
@Override
public void failed(Throwable throwable) {
future.completeExceptionally(throwable);
}
@Override
public void completed(HttpServer result) {
future.complete(result);
}
@Override
public void updated(HttpServer result) {
// pass
}
});
return future;
}
public void shutdownNow() {
E.checkNotNull(this.httpServer, "http server");
this.httpServer.shutdownNow();
}
public static RestServer start(String conf, EventHub hub) throws Exception {
LOG.info("RestServer starting...");
ApiVersion.check();
HugeConfig config = new HugeConfig(conf);
RestServer server = new RestServer(config, hub);
server.start();
LOG.info("RestServer started");
return server;
}
private void calcMaxWriteThreads() {
int maxWriteThreads = this.conf.get(ServerOptions.MAX_WRITE_THREADS);
if (maxWriteThreads > 0) {
// Use the value of MAX_WRITE_THREADS option if it's not 0
return;
}
assert maxWriteThreads == 0;
int maxWriteRatio = this.conf.get(ServerOptions.MAX_WRITE_RATIO);
assert maxWriteRatio >= 0 && maxWriteRatio <= 100;
int maxWorkerThreads = this.conf.get(ServerOptions.MAX_WORKER_THREADS);
maxWriteThreads = maxWorkerThreads * maxWriteRatio / 100;
E.checkState(maxWriteThreads >= 0,
"Invalid value of maximum batch writing threads '%s'",
maxWriteThreads);
if (maxWriteThreads == 0) {
E.checkState(maxWriteRatio == 0,
"The value of maximum batch writing threads is 0 " +
"due to the max_write_ratio '%s' is too small, " +
"set to '%s' at least to ensure one thread." +
"If you want to disable batch write, " +
"please let max_write_ratio be 0", maxWriteRatio,
(int) Math.ceil(100.0 / maxWorkerThreads));
}
LOG.info("The maximum batch writing threads is {} (total threads {})",
maxWriteThreads, maxWorkerThreads);
// NOTE: addProperty will make exist option's value become List
this.conf.setProperty(ServerOptions.MAX_WRITE_THREADS.name(),
String.valueOf(maxWriteThreads));
}
}