Skip to content

Commit

Permalink
Select server to create dynamically to avoid classpath issue.
Browse files Browse the repository at this point in the history
Load server classes dynamically to avoid `NoClassDefFoundError`s. Select Simple or Undertow depending on which one is available on the classpath.

Closes #53
  • Loading branch information
testinfected committed Nov 9, 2016
1 parent e05e515 commit 3f93810
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 58 deletions.
17 changes: 6 additions & 11 deletions README.md
Expand Up @@ -32,7 +32,8 @@ Molecule is small, it weights less than 150k, and will stay as lean as possible.
of rack middlewares and offers some out-of-the box integrations. You're free to use the built-in options
or provide your own implementations.

Molecule requires Java 8. It runs an embedded web-server powered by [Simple](http://simpleframework.org) or [Undertow](http://undertow.io). Both are fully asynchronous and non-blocking, which means they can scale to very high loads.
Molecule requires Java 8. It runs an embedded web-server powered by [Simple](http://simpleframework.org) or [Undertow](http://undertow.io).
Both are fully asynchronous and non-blocking, which means they can scale to very high loads.

Have fun!

Expand All @@ -48,7 +49,7 @@ You can get the latest release version from Maven Central:
</dependency>
```

To use the default web server, you also need to add [Simple](http://www.simpleframework.org) as a dependency:
To use Simple as your web server, add [Simple](http://www.simpleframework.org) as a dependency:

```xml
<dependency>
Expand All @@ -59,7 +60,7 @@ To use the default web server, you also need to add [Simple](http://www.simplefr
</dependency>

```
To use the Undertow web server, add [Undertow](http://undertow.io) instead to your dependencies:
To choose Undertow as your web server, add [Undertow](http://undertow.io) instead to your dependencies:

```xml
<dependency>
Expand Down Expand Up @@ -102,8 +103,7 @@ First thing first, you need a server to run your app:
WebServer server = WebServer.create();
```

This will set the default web server, which is powered by [Simple](http://www.simpleframework.org),
to run locally on port 8080.
This will set the web server to run locally on port 8080.

To start the server, give it an app:

Expand All @@ -123,15 +123,10 @@ You can optionally specify the interface and port to bound to when creating the
WebServer server = WebServer.create("0.0.0.0", 8088);
```

To run [Undertow](http://undertow.io) instead of Simple:
```java
WebServer server = WebServer.undertow("127.0.0.1", 8080);
```


## Asynchronous Processing

Molecule uses [Simple](http://www.simpleframework.org) as a default webserver. You have the choice to run using [Undertow](http://undertow.io) instead. Both are fully asynchronous and non-blocking. This allows the server to scale to very high loads and handle as many concurrent connections as possible, even when depending on a high latency external resource.
Molecule uses [Simple](http://www.simpleframework.org) as a default web server. You have the choice to run using [Undertow](http://undertow.io) instead. Both are fully asynchronous and non-blocking. This allows the server to scale to very high loads and handle as many concurrent connections as possible, even when depending on a high latency external resource.

What this means is you can serve your response content from a thread separate to the original servicing thread. For instance your application
might need to wait for some remote process that takes some time to complete, such as an HTTP or SOAP request to an external server. You can simply
Expand Down
51 changes: 13 additions & 38 deletions src/main/java/com/vtence/molecule/WebServer.java
Expand Up @@ -4,8 +4,7 @@
import com.vtence.molecule.middlewares.FilterMap;
import com.vtence.molecule.middlewares.Router;
import com.vtence.molecule.routing.RouteBuilder;
import com.vtence.molecule.servers.SimpleServer;
import com.vtence.molecule.servers.UndertowServer;
import com.vtence.molecule.servers.Servers;

import javax.net.ssl.SSLContext;
import java.io.File;
Expand All @@ -28,14 +27,14 @@ public class WebServer {
private SSLContext ssl;

/**
* Creates a default WebServer listening on the default interface (0.0.0.0) and port (8080)
* Creates a WebServer listening on the default interface (0.0.0.0) and port (8080).
*/
public static WebServer create() {
return create(DEFAULT_PORT);
}

/**
* Creates a default WebServer listening on the default interface (0.0.0.0) and the specified port.
* Creates a WebServer listening on the default interface (0.0.0.0) and the specified port.
*
* @param port the port to listen on
*/
Expand All @@ -44,36 +43,13 @@ public static WebServer create(int port) {
}

/**
* Creates a default WebServer listening on the specified interface and port. The default web server
* is powered by Simple.
* Creates a WebServer listening on the specified interface and port.
*
* @param host the hostname to bind to
* @param port the port to listen on
* @see #simple(String, int)
* @see #undertow(String, int)
*/
public static WebServer create(String host, int port) {
return simple(host, port);
}

/**
* Creates a WebServer powered by Simple listening on the specified interface and port.
*
* @param host the hostname to bind to
* @param port the port to listen on
*/
public static WebServer simple(String host, int port) {
return new WebServer(new SimpleServer(host, port));
}

/**
* Creates a WebServer powered by Undertow listening on the specified interface and port.
*
* @param host the hostname to bind to
* @param port the port to listen on
*/
public static WebServer undertow(String host, int port) {
return new WebServer(new UndertowServer(host, port));
return new WebServer(Servers.create(host, port));
}

/**
Expand All @@ -90,10 +66,9 @@ public WebServer(Server server) {
* Secures connections made to this WebServer with TLS using the specified key store and credentials.
* The key store must be of the default platform type and use the default key manager algorithm.
*
* @param keyStore the location of the key store containing the certificate and keys
* @param keyStore the location of the key store containing the certificate and keys
* @param storePassword the password that opens the key store
* @param keyPassword the password for using the keys
*
* @param keyPassword the password for using the keys
* @see com.vtence.molecule.ssl.SecureProtocol
* @see com.vtence.molecule.ssl.KeyStoreType
*/
Expand All @@ -106,9 +81,9 @@ public WebServer enableSSL(File keyStore, String storePassword, String keyPasswo
* Secures connections made to this WebServer using the provided security context. This allows to use
* security settings that differ from platform defaults (such as using PKCS12 instead of JKS).
*
* @param context the security context for securing connections
* @see com.vtence.molecule.ssl.SecureProtocol
* @see com.vtence.molecule.ssl.KeyStoreType
* @param context the security context for securing connections
*/
public WebServer enableSSL(SSLContext context) {
this.ssl = context;
Expand Down Expand Up @@ -139,7 +114,7 @@ public WebServer add(Middleware middleware) {
* Adds a middleware filter to this WebServer's stack of configured middlewares. The server will apply the
* given filter to all requests with a path starting with the specified prefix.
*
* @param path the path to trigger filtering
* @param path the path to trigger filtering
* @param filter the filter to apply to incoming requests that match the path prefix
*/
public WebServer filter(String path, Middleware filter) {
Expand All @@ -152,7 +127,7 @@ public WebServer filter(String path, Middleware filter) {
* filter to all requests matched by the specified matcher.
*
* @param requestMatcher the matcher to trigger filtering
* @param filter the filter to apply to incoming requests that are matched
* @param filter the filter to apply to incoming requests that are matched
*/
public WebServer filter(Matcher<? super Request> requestMatcher, Middleware filter) {
stack.use(new FilterMap().map(requestMatcher, filter));
Expand All @@ -164,7 +139,7 @@ public WebServer filter(Matcher<? super Request> requestMatcher, Middleware filt
* which target that path or any sub-path to the specified application.
*
* @param path the mount point
* @param app the application to attach to the mount point
* @param app the application to attach to the mount point
*/
public WebServer mount(String path, Application app) {
stack.mount(path, app);
Expand All @@ -184,7 +159,7 @@ public WebServer warmup(Consumer<Application> warmup) {
/**
* Boots and starts this WebServer using the previously configured middlewares and create a router
* with the specified routes. The router will run at the root mount point.
*
* <p>
* The server will start accepting and processing incoming requests.
*
* @param routes the routes to run at the root mount point (/)
Expand All @@ -196,7 +171,7 @@ public Server start(RouteBuilder routes) throws IOException {
/**
* Boots and starts this WebServer using the previously configured middlewares
* and the specified application at the root the mount point.
*
* <p>
* The server will start accepting and processing incoming requests.
*
* @param application the application to run at the root mount point (/)
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/com/vtence/molecule/servers/Servers.java
@@ -0,0 +1,69 @@
package com.vtence.molecule.servers;

import com.vtence.molecule.Server;

import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Objects;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;

public final class Servers {

private static final List<String> supported = asList("Simple", "Undertow");

private static final List<? extends Class<? extends Server>> available = supported.stream()
.map(Servers::loadClass)
.filter(Objects::nonNull)
.collect(toList());

private static class NoneAvailableException extends RuntimeException {
public String getMessage() {
return "No server implementation is available. Add Simple or Undertow jars to your classpath.";
}
}

private static class CreationFailed extends RuntimeException {
private final Class<? extends Server> clazz;

public CreationFailed(Class<? extends Server> clazz, Throwable cause) {
super(cause);
this.clazz = clazz;
}

public String getMessage() {
return "Failed to create " + clazz.getSimpleName();
}
}

private Servers() {}

public static Server create(String host, int port) {
return available.stream()
.map(server -> instantiate(server, host, port))
.findFirst()
.orElseThrow(NoneAvailableException::new);
}

private static Server instantiate(Class<? extends Server> clazz, String host, int port) {
try {
Constructor<? extends Server> constructor = clazz.getDeclaredConstructor(String.class, Integer.TYPE);
return constructor.newInstance(host, port);
} catch (Exception e) {
throw new CreationFailed(clazz, e);
}
}

private static Class<? extends Server> loadClass(String type) {
try {
return Class.forName(className(type)).asSubclass(Server.class);
} catch (ClassNotFoundException | NoClassDefFoundError notAvailable) {
return null;
}
}

private static String className(String type) {
return Servers.class.getPackage().getName() + "." + type + "Server";
}
}
9 changes: 0 additions & 9 deletions src/test/java/com/vtence/molecule/WebServerTest.java
Expand Up @@ -41,15 +41,6 @@ public void runsServerOnPort8080ByDefault() throws IOException, GeneralSecurityE
assertThat(response).hasBodyText("It works!");
}

@Test
public void canBePoweredByUndertow() throws Exception {
server = WebServer.undertow("localhost", 8080);
server.start((request, response) -> response.body("It works even faster!").done());

response = request.get("/");
assertThat(response).hasBodyText("It works even faster!");
}

@Test
public void knowsServerUri() throws IOException {
server = WebServer.create("0.0.0.0", 9000);
Expand Down

0 comments on commit 3f93810

Please sign in to comment.