All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.codestory.http.AbstractWebServer Maven / Gradle / Ivy

/**
 * Copyright (C) 2013-2015 [email protected]
 *
 * Licensed 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 net.codestory.http;

import static java.util.Collections.singletonList;
import static net.codestory.http.Configuration.*;
import static net.codestory.http.misc.MemoizingSupplier.memoize;

import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.function.Supplier;

import net.codestory.http.internal.*;
import net.codestory.http.logs.*;
import net.codestory.http.misc.*;
import net.codestory.http.payload.*;
import net.codestory.http.reload.*;
import net.codestory.http.routes.*;
import net.codestory.http.ssl.*;
import net.codestory.http.websockets.*;

import javax.net.ssl.*;

public abstract class AbstractWebServer> {
  protected static final int PORT_8080 = 8080;
  protected static final int RANDOM_PORT_START_RETRY = 30;

  protected final Supplier server;
  protected final Env env;

  protected RoutesProvider routesProvider;
  protected int port = -1;

  protected AbstractWebServer() {
    this.server = memoize(() -> createHttpServer(this::handleHttp, this::handleWebSocket));
    this.env = createEnv();
  }

  protected abstract HttpServerWrapper createHttpServer(Handler httpHandler, WebSocketHandler webSocketHandler);

  protected Env createEnv() {
    return new Env();
  }

  @SuppressWarnings("unchecked")
  public T configure(Configuration configuration) {
    this.routesProvider = env.prodMode()
        ? RoutesProvider.fixed(env, configuration)
        : RoutesProvider.reloading(env, configuration);
    return (T) this;
  }

  public T startOnRandomPort() {
    for (int i = 0; i < RANDOM_PORT_START_RETRY; i++) {
      try {
        return start(0);
      } catch (IllegalStateException e) {
        if (!e.getMessage().contains("Port already in use")) {
          Logs.unableToBindServer(e);
        }
      } catch (Exception e) {
        Logs.unableToBindServer(e);
      }
    }

    throw new IllegalStateException("Unable to start server");
  }

  public T start() {
    return start(PORT_8080);
  }

  public T start(int port) {
    return startWithContext(port, null, false);
  }

  public T startSSL(int port, Path pathCertificate, Path pathPrivateKey) {
    return startSSL(port, singletonList(pathCertificate), pathPrivateKey, null);
  }

  public T startSSL(int port, List pathChain, Path pathPrivateKey) {
    return startSSL(port, pathChain, pathPrivateKey, null);
  }

  public T startSSL(int port, List pathChain, Path pathPrivateKey, List pathTrustAnchors) {
    SSLContext context;
    try {
      context = new SSLContextFactory().create(pathChain, pathPrivateKey, pathTrustAnchors);
    } catch (Exception e) {
      throw new IllegalStateException("Unable to read certificate or key", e);
    }
    boolean authReq = pathTrustAnchors != null;
    return startWithContext(port, context, authReq);
  }

  @SuppressWarnings("unchecked")
  protected T startWithContext(int port, SSLContext context, boolean authReq) {
    if (routesProvider == null) {
      configure(NO_ROUTE);
    }

    int thePort = (port == 0) ? 0 : env.overriddenPort(port);

    try {
      Logs.mode(env.prodMode());

      this.port = server.get().start(thePort, context, authReq);

      Logs.started(this.port);
    } catch (RuntimeException e) {
      throw e;
    } catch (Exception e) {
      if ((e instanceof BindException) || (e.getCause() instanceof BindException)) {
        throw new IllegalStateException("Port already in use " + this.port);
      }
      throw new IllegalStateException("Unable to bind the web server on port " + this.port, e);
    }

    return (T) this;
  }

  public int port() {
    if (port == -1) {
      throw new IllegalStateException("The web server is not started");
    }

    return port;
  }

  protected void handleHttp(Request request, Response response) {
    // We need to make sure that these two lines cannot fail
    // Otherwise no response is made to the client
    //
    RouteCollection routes = routesProvider.get();

    PayloadWriter writer = routes.createPayloadWriter(request, response);
    try {
      Payload payload = routes.apply(request, response);

      writer.writeAndClose(payload);
    } catch (Exception e) {
      writer.writeErrorPage(e);
    }
  }

  protected void handleWebSocket(WebSocketSession session, Request request, Response response) {
    RouteCollection routes = routesProvider.get();

    try {
      WebSocketListener listener = routes.createWebSocketListener(session, request, response);
      session.register(listener);
    } catch (Exception e) {
      throw new IllegalStateException("WebSocket error", e);
    }
  }

  public void stop() {
    try {
      env.folderWatcher().stop();
      server.get().stop();
    } catch (Exception e) {
      throw new IllegalStateException("Unable to stop the web server", e);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy