/** * 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.jooby.internal.undertow; import java.lang.reflect.Field; import java.util.Map; import java.util.Optional; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Provider; import javax.net.ssl.SSLContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnio.Option; import org.xnio.Options; import com.typesafe.config.Config; import com.typesafe.config.ConfigException; import com.typesafe.config.ConfigValue; import io.undertow.Undertow; import io.undertow.Undertow.Builder; import io.undertow.UndertowOptions; import io.undertow.server.HttpHandler; import io.undertow.server.handlers.GracefulShutdownHandler; import io.undertow.server.protocol.http2.Http2UpgradeHandler; public class UndertowServer implements org.jooby.spi.Server { private interface SetOption { void set(String name); } /** The logging system. */ private static final Logger log = LoggerFactory.getLogger(org.jooby.spi.Server.class); private Undertow server; private final GracefulShutdownHandler shutdown; private long awaitShutdown; @Inject public UndertowServer(final org.jooby.spi.HttpHandler dispatcher, final Config conf, final Provider<SSLContext> sslContext) throws Exception { awaitShutdown = conf.getDuration("undertow.awaitShutdown", TimeUnit.MILLISECONDS); boolean http2 = conf.getBoolean("server.http2.enabled"); HttpHandler handler = doHandler(dispatcher, conf); if (http2) { handler = new Http2UpgradeHandler(handler); } shutdown = new GracefulShutdownHandler(handler); Undertow.Builder ubuilder = configure(conf, io.undertow.Undertow.builder()) .addHttpListener(conf.getInt("application.port"), host(conf.getString("application.host"))); ubuilder.setServerOption(UndertowOptions.ENABLE_HTTP2, http2); boolean securePort = conf.hasPath("application.securePort"); if (securePort) { ubuilder.addHttpsListener(conf.getInt("application.securePort"), host(conf.getString("application.host")), sslContext.get()); } this.server = ubuilder.setHandler(shutdown) .build(); } private String host(final String host) { return "localhost".equals(host) ? "0.0.0.0" : host; } @SuppressWarnings("unchecked") static Builder configure(final Config config, final Builder builder) { Config $undertow = config.getConfig("undertow"); set($undertow, "bufferSize", name -> { int value = $undertow.getBytes(name).intValue(); log.debug("undertow.bufferSize({})", value); builder.setBufferSize(value); }); set($undertow, "directBuffers", name -> { boolean value = $undertow.getBoolean(name); log.debug("undertow.directBuffers({})", value); builder.setDirectBuffers(value); }); set($undertow, "ioThreads", name -> { int value = $undertow.getInt(name); log.debug("undertow.ioThreads({})", value); builder.setIoThreads(value); }); set($undertow, "workerThreads", name -> { int value = $undertow.getInt(name); log.debug("undertow.workerThreads({})", value); builder.setWorkerThreads(value); }); $undertow.getConfig("server").root().entrySet() .forEach(setOption($undertow, "server", (option, value) -> builder.setServerOption(option, value))); $undertow.getConfig("worker").root().entrySet() .forEach(setOption($undertow, "worker", (option, value) -> builder.setWorkerOption(option, value))); $undertow.getConfig("socket").root().entrySet() .forEach(setOption($undertow, "socket", (option, value) -> builder.setSocketOption(option, value))); return builder; } @SuppressWarnings("rawtypes") private static Consumer<Map.Entry<String, ConfigValue>> setOption(final Config config, final String level, final BiConsumer<Option, Object> setter) { return entry -> { String name = entry.getKey(); Object value = entry.getValue().unwrapped(); Option option = findOption(name, UndertowOptions.class, Options.class); if (option != null) { // parse option to adjust correct type Object parsedValue = value.toString(); try { parsedValue = option.parseValue(value.toString(), null); } catch (NumberFormatException ex) { // try bytes try { parsedValue = option.parseValue(config.getBytes(level + "." + name).toString(), null); } catch (ConfigException.BadValue badvalue) { // try duration parsedValue = option.parseValue( config.getDuration(level + "." + name, TimeUnit.MILLISECONDS) + "", null); } } log.debug("{}.{}({})", level, option.getName(), parsedValue); setter.accept(option, parsedValue); } else { log.error("Unknown option: 'undertow.{}.{} = {}'", level, name, value); } }; } @SuppressWarnings("rawtypes") private static Option findOption(final String name, final Class... where) { for (Class owner : where) { try { Field fld = owner.getDeclaredField(name); return (Option) fld.get(null); } catch (NoSuchFieldException | IllegalAccessException ex) { log.trace("Unknown option: '{}'", name); } } return null; } private static void set(final Config config, final String name, final SetOption setter) { if (config.hasPath(name)) { setter.set(name); } } private static HttpHandler doHandler(final org.jooby.spi.HttpHandler dispatcher, final Config config) { return new UndertowHandler(dispatcher, config); } @Override public void start() throws Exception { server.start(); } @Override public void join() throws InterruptedException { // NOOP } @Override public void stop() throws Exception { shutdown.shutdown(); shutdown.awaitShutdown(awaitShutdown); server.stop(); } @Override public Optional<Executor> executor() { return Optional.ofNullable(server.getWorker()); } }