package com.vtence.molecule.servers; import com.vtence.molecule.Application; import com.vtence.molecule.Body; import com.vtence.molecule.BodyPart; import com.vtence.molecule.FailureReporter; import com.vtence.molecule.Request; import com.vtence.molecule.Response; import com.vtence.molecule.Server; import org.simpleframework.http.Part; import org.simpleframework.http.core.Container; import org.simpleframework.http.core.ContainerSocketProcessor; import org.simpleframework.transport.connect.Connection; import org.simpleframework.transport.connect.SocketConnection; import javax.net.ssl.SSLContext; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.CompletionException; import java.util.function.Consumer; public class SimpleServer implements Server { private static final int DEFAULT_NUMBER_OF_THREADS = 8; private final String host; private final int port; private final int numberOfThreads; private FailureReporter failureReporter = FailureReporter.IGNORE; private Connection connection; public SimpleServer(String host, int port) { this(host, port, DEFAULT_NUMBER_OF_THREADS); } public SimpleServer(String host, int port, int numberOfThreads) { this.host = host; this.port = port; this.numberOfThreads = numberOfThreads; } public void reportErrorsTo(FailureReporter reporter) { this.failureReporter = reporter; } public int port() { return port; } public String host() { return host; } public void run(final Application app) throws IOException { run(app, null); } public void run(final Application app, SSLContext context) throws IOException { connection = new SocketConnection(new ContainerSocketProcessor(new ApplicationContainer(app), numberOfThreads)); connection.connect(new InetSocketAddress(host, port), context); } public void shutdown() throws IOException { if (connection != null) connection.close(); } public class ApplicationContainer implements Container { private final Application app; public ApplicationContainer(Application app) { this.app = app; } public void handle(org.simpleframework.http.Request httpRequest, org.simpleframework.http.Response httpResponse) { final List<Closeable> resources = new ArrayList<>(); final Request request = new Request(); final Response response = new Response(); try { read(request, httpRequest, resources); app.handle(request, response); response.whenSuccessful(commitTo(httpResponse)) .whenFailed((result, error) -> failureReporter.errorOccurred(error)) .whenComplete((result, error) -> closeAll(resources, httpResponse)); } catch (Throwable failure) { failureReporter.errorOccurred(failure); closeAll(resources, httpResponse); } } private void read(Request request, org.simpleframework.http.Request httpRequest, Collection<Closeable> resources) throws IOException { readInfo(request, httpRequest); readHeaders(request, httpRequest); readParameters(request, httpRequest); readMultiPartData(request, httpRequest, resources); readBody(request, httpRequest, resources); } private void readInfo(Request request, org.simpleframework.http.Request httpRequest) { request.serverHost(host); request.serverPort(port); request.uri(httpRequest.getTarget()); request.path(httpRequest.getPath().getPath()); request.query(httpRequest.getQuery().toString()); request.remoteIp(httpRequest.getClientAddress().getAddress().getHostAddress()); request.remotePort(httpRequest.getClientAddress().getPort()); request.remoteHost(httpRequest.getClientAddress().getHostName()); request.timestamp(httpRequest.getRequestTime()); request.scheme(schemeOf(httpRequest)); request.protocol(String.format("HTTP/%s.%s", httpRequest.getMajor(), httpRequest.getMinor())); request.secure(httpRequest.isSecure()); request.method(httpRequest.getMethod()); } private String schemeOf(org.simpleframework.http.Request httpRequest) { // Prefer the scheme specified in the request line if any String scheme = httpRequest.getAddress().getScheme(); if (scheme != null) return scheme; return httpRequest.isSecure() ? "https" : "http"; } private void readHeaders(Request request, org.simpleframework.http.Request httpRequest) { final List<String> names = httpRequest.getNames(); for (String header : names) { // Apparently there's no way to know the number of values for a given name, // so we have to iterate until we reach a null value int index = 0; while (httpRequest.getValue(header, index) != null) { request.addHeader(header, httpRequest.getValue(header, index)); index++; } } } private void readParameters(Request request, org.simpleframework.http.Request httpRequest) { for (String name : httpRequest.getQuery().keySet()) { final List<String> values = httpRequest.getQuery().getAll(name); for (String value : values) { request.addParameter(name, value); } } } private void readMultiPartData(Request request, org.simpleframework.http.Request httpResponse, Collection<Closeable> resources) throws IOException { for (Part part : httpResponse.getParts()) { final InputStream input = part.getInputStream(); resources.add(input); request.addPart(new BodyPart().content(input) .contentType(contentTypeOf(part)) .name(part.getName()) .filename(part.getFileName())); } } private String contentTypeOf(Part part) { return part.getContentType() != null ? part.getContentType().toString() : null; } private void readBody(Request request, org.simpleframework.http.Request httpResponse, Collection<Closeable> resources) throws IOException { final InputStream input = httpResponse.getInputStream(); resources.add(input); request.body(input); } private Consumer<Response> commitTo(org.simpleframework.http.Response httpResponse) { return response -> { try { commit(httpResponse, response); } catch (IOException e) { throw new CompletionException(e); } }; } private void commit(org.simpleframework.http.Response httpResponse, Response response) throws IOException { writeStatusLine(httpResponse, response); writeHeaders(httpResponse, response); writeBody(httpResponse, response); } private void writeStatusLine(org.simpleframework.http.Response httpResponse, Response response) { httpResponse.setCode(response.statusCode()); httpResponse.setDescription(response.statusText()); } private void writeHeaders(org.simpleframework.http.Response httpResponse, Response response) { for (String name : response.headerNames()) { for (String value: response.headers(name)) { httpResponse.addValue(name, value); } } } private void writeBody(org.simpleframework.http.Response httpResponse, Response response) throws IOException { final Body body = response.body(); body.writeTo(httpResponse.getOutputStream(), response.charset()); body.close(); } // too bad Response does not implement Closeable private void closeAll(Iterable<Closeable> resources, org.simpleframework.http.Response httpResponse) { for (Closeable resource : resources) { close(resource); } close(httpResponse); } private void close(org.simpleframework.http.Response httpResponse) { try { httpResponse.close(); } catch (IOException e) { failureReporter.errorOccurred(e); } } private void close(Closeable resource) { try { resource.close(); } catch (IOException e) { failureReporter.errorOccurred(e); } } } }