This project is still under initial development!
Socius Web provides an embeddable asynchronous non-blocking message-oriented Netty-based HTTP proxy server that facilitates the processing of HTTP requests as a multiplexed stream of generic Protobuf-encoded messages flowing through one-or-more Cascade-based actors.
Let's break that down a little:
- Socius Web is an HTTP server.
- Socius Web is embeddable, which means that the server is simply an object within your Java program, rather than a standalone process of its own.
- Socius Web is asynchronous, which means the server can handle many requests concurrently.
- Socius Web is non-blocking, which means the server uses NIO-based sockets. Consequently, the server can handle thousands of simultaneous connections using a single thread. Other servers, which use blocking sockets, spawn a separate thread for each connection, which does not scale well.
- Socius Web is message-oriented, which means that requests/responses are converted to/from message objects, where each message has a finite size. Thus, Socius Web is not appropriate, if you need streaming capablities.
Of Note:
- Socius Web is intended for use as an upstream server hidden behind a load balancer or other edge server.
- Socius Web is not intended to be used directly as an edge server itself.
- Socius Web is not a general purpose web server.
package com.mackenziehigh.socius.web.examples;
import com.google.protobuf.ByteString;
import com.mackenziehigh.cascade.Cascade;
import com.mackenziehigh.cascade.Cascade.Stage;
import com.mackenziehigh.cascade.Cascade.Stage.Actor;
import com.mackenziehigh.socius.web.messages.web_m.ServerSideHttpRequest;
import com.mackenziehigh.socius.web.messages.web_m.ServerSideHttpResponse;
import com.mackenziehigh.socius.web.server.WebServer;
import java.io.IOException;
import java.time.Instant;
/**
* Trivial Server Example.
*/
public final class Example
{
/**
* Create and start the server.
*/
private void start ()
{
final WebServer server = WebServer.newWebServer()
.withDefaultSettings()
.withPort(8080)
.withAcceptFilter()
.withUnsecureSockets()
.build()
.start();
/**
* Create an actor that will asynchronously handle the HTTP requests.
*/
final Stage stage = Cascade.newStage();
final Actor<ServerSideHttpRequest, ServerSideHttpResponse> webapp = stage
.newActor()
.withScript(this::onHttpRequest)
.create();
/**
* Connect the actor to the server.
* The server will send requests to the actor.
* The actor will then send responses to the server.
*/
server.requestsOut().connect(webapp.input());
server.responsesIn().connect(webapp.output());
}
/**
* This method defines how the actor will handle each HTTP request.
*
* @param request was just received by the actor from the server.
* @return the generate HTTP response.
*/
private ServerSideHttpResponse onHttpRequest (final ServerSideHttpRequest request)
{
final String html = String.format("<p>Hello World at %s</p>\r\n", Instant.now());
final ServerSideHttpResponse response = ServerSideHttpResponse
.newBuilder()
.setCorrelationId(request.getCorrelationId())
.setStatus(200)
.setBody(ByteString.copyFromUtf8(html))
.setContentType("text/html")
.build();
return response;
}
public static void main (String[] args)
throws IOException
{
final Example webapp = new Example();
webapp.start();
System.in.read();
}
}
cd /tmp/ ; rm -f /tmp/keystore.jks ; keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -keypass letmein -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999 -dname "CN=AA, OU=BB, O=CC, L=Washington, ST=DC, C=US"
package com.mackenziehigh.socius.web.examples;
import com.google.protobuf.ByteString;
import com.mackenziehigh.cascade.Cascade;
import com.mackenziehigh.cascade.Cascade.Stage;
import com.mackenziehigh.cascade.Cascade.Stage.Actor;
import com.mackenziehigh.socius.web.messages.web_m.ServerSideHttpRequest;
import com.mackenziehigh.socius.web.messages.web_m.ServerSideHttpResponse;
import com.mackenziehigh.socius.web.server.WebServer;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.time.Instant;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
/**
* Trivial Server Example.
*/
public final class Example
{
/**
* Create and start the server.
*
* @throws Exception for brevity in the example.
*/
private void start ()
throws Exception
{
/**
* Create the SSL Context that provides the SSL certificate, etc.
*/
final SSLContext context = createSSLContext();
/**
* Create the secured server.
*/
final WebServer server = WebServer.newWebServer()
.withDefaultSettings()
.withPort(8080)
.withAcceptFilter()
.withSecureSockets(() -> secure(context))
.build()
.start();
/**
* Create an actor that will asynchronously handle the HTTP requests.
*/
final Stage stage = Cascade.newStage();
final Actor<ServerSideHttpRequest, ServerSideHttpResponse> webapp = stage
.newActor()
.withScript(this::onHttpRequest)
.create();
/**
* Connect the actor to the server.
* The server will send requests to the actor.
* The actor will then send responses to the server.
*/
server.requestsOut().connect(webapp.input());
server.responsesIn().connect(webapp.output());
}
private SSLContext createSSLContext ()
throws Exception
{
try (FileInputStream fin = new FileInputStream("/tmp/keystore.jks"))
{
// Create the KeyStore.
final KeyStore keystore = KeyStore.getInstance("JKS");
keystore.load(fin, "password".toCharArray());
// Create the KeyManager.
final KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
factory.init(keystore, "letmein".toCharArray());
final KeyManager[] manager = factory.getKeyManagers();
// Create the SSLContext.
final SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(manager, null, null);
return context;
}
}
private SSLEngine secure (final SSLContext context)
{
final SSLEngine engine = context.createSSLEngine();
engine.setUseClientMode(false);
return engine;
}
/**
* This method defines how the actor will handle each HTTP request.
*
* @param request was just received by the actor from the server.
* @return the generate HTTP response.
*/
private ServerSideHttpResponse onHttpRequest (final ServerSideHttpRequest request)
{
final String html = String.format("<p>Hello World at %s</p>\r\n", Instant.now());
final ServerSideHttpResponse response = ServerSideHttpResponse
.newBuilder()
.setCorrelationId(request.getCorrelationId())
.setStatus(200)
.setBody(ByteString.copyFromUtf8(html))
.setContentType("text/html")
.build();
return response;
}
public static void main (String[] args)
throws Exception
{
final Example webapp = new Example();
webapp.start();
System.in.read();
}
}
curl -k "https://127.0.0.1:8080"
Socius Web uses Protobuf-encoded messages between itself and the connected web-application.
Message Definitions: web_m.proto
Advantages:
- Since the messages are Protobuf-based, they can be trivially serialized/deserialized. Moreover, the encoding is not Java-centric. Rather, the messages can be deserialized easily in any language that Protobuf supports.
- The messages are inherently immutable.
Disadvantages:
- GC Pressure
Attribute | Type | About |
---|---|---|
Server Name | String | Human readable name of the server receiving the request. |
Server ID | String | UUID of the server receiving the request. |
Reply To | String | Optional, ignored by server, for application use. |
Socius Web is configurable to meet your security and performance needs.
General Server Settings:
Name | Description | Default |
---|---|---|
Bind Address | The network-interface that the server will listen on. | 127.0.0.1 |
Port | The port number that the server will listen on. | 8080 |
Soft Connection Limit | The limit at which new connections will be sent a default error-response. | 128 |
Hard Connection Limit | The limit at which new connections will simply not be accepted. | 512 |
General Per Connection Settings:
Name | Description | Default |
---|---|---|
Uplink Timeout | The max duration to wait for a request to be read of the socket | 8 seconds |
Response Timeout | The max duration to wait for a response to be formulated. | 32 seconds |
Downlink Timeout | The max duration to wait for a response to be written to the socket. | 8 seconds |
Request Filter | The predicate(s) that filter out unwanted requests | DENY |
General HTTP Settings:
Name | Description | Default |
---|---|---|
Max Request Size | The max number of bytes in a single HTTP request | 65536 |
Max Initial Line Size | The max number of bytes in the first line of the request | 1024 |
Max Headers Size | The max number of bytes in the initial request headers. | 8192 |
Settings for Compression of HTTP Responses:
Name | Description | Default |
---|---|---|
Compression Level | A value between zero (no compression) and nine (max compression) | 6 |
Compression Window Bits | A value between nine (worse compression, less memory) and fifteen (better compression, more memory). | 15 |
Compression Memory Level | A value between one (worse compression, less memory) and nine (better compression, more memory). | 8 |
Compression Threshold | The min size a response must be in order for compression to be applied. | 0 (Always Compress) |
Settings for Advanced Performance Tuning:
Name | Description | Default |
---|---|---|
Max Messages Per Read | The max number of times the socket will be polled per read. | 1 |
Recv Allocator Min | The min size of the memory buffers used for receiving data. | 64 |
Recv Allocator Max | The max size of the memory buffers used for receiving data. | 131072 |
Recv Allocator Initial | The initial size of the memory buffers used for receiving data. | 1024 |
TODO