Server configuration - Genesis Router
Genesis Router is responsible for all communication between the front end and back end. On the Genesis Platform, the front end connects to the back end through HTTPS or secure Websockets via a reverse proxy. This must run on the same instance as the back end.
The GENESIS_ROUTER service on the server acts as the endpoint for all API calls and listens (by default) to port 9064.
This is configured in the file genesis-router.kts.
Here is an example:
router {
webPort = 9064
socketPort = 9065
httpServerCodec {
maxInitialLineLength = 4096
maxHeaderSize = 8192
maxChunkSize = 8192
validateHeaders = true
initialBufferSize = 128
}
httpObjectAggregator {
maxContentLength = 262144
closeOnExpectationFailed = false
}
routes {
route(messageType = "ALL_ORDERS", process = "OEMS_DATASERVER")
route(messageType = "ALL_TRADES", process = "OEMS_DATASERVER")
route(messageType = "ALL_ORDER_AUDITS", process = "OEMS_DATASERVER")
}
allowList {
entry("ALL_ORDERS")
entry("ALL_TRADES")
entry("ALL_ORDER_AUDITS")
}
cookieAuthentication {
enabled = false
httpOnly = false
secure = false
wrap = false
path = "/"
domain = null
sameSite = SameSite.Lax
}
clientHandler {
channelCapacity = 20
onChannelFull = CLOSE
}
}
Router configuration
Let's have a look at the different options for configuring this file. You have seen some, but not all of these in the example above.
webPort
This port is used for tcp/ip socket. You must declare a port, and it cannot be below 1024.
socketPort
This port is used for http/websockets. You must declare a port, and it cannot be below 1024.
dataserverPollingTimeout
This setting contains the timeout (in seconds) to expire inactive dataserver subscriptions. Subscription inactivity is determined by the lack of HTTP requests from the client targeting a specific Data Server subscription. Default value is 60 seconds.
authDisabled
This is a dangerous setting! If set to true, it disables all authentication on the router. Typically, it is used for development mode. If you need to use this for another reason, see our section on non-authenticated Genesis Routers. Default value is false.
nettyLoggingEnabled
If set to true, this setting enables internal netty logging. Default value false.
Netty configuration
`httpServerCodecDefinition: A combination of HttpRequestDecoder and HttpResponseEncoder, which enables easier server-side HTTP implementation. You can find more information in the netty documentation.
Different decoder options
maxInitialLineLength
default value: 4096maxHeaderSize
default value: 8192maxChunkSize
default value: 8192validateHeaders
default value: trueinitialBufferSize
default value: 128
httpObjectAggregatorDefinition
A ChannelHandler that aggregates an HttpMessage and its following HttpContents into a single FullHttpRequest or FullHttpResponse (depending on if it used to handle requests or responses) with no following HttpContents.
There is more information in the netty documentation.
maxContentLength
The maximum length of the aggregated content in bytes. Default value: 262144closeOnExpectationFailed
If a 100-continue response is detected but the content length is too large, then true means close the connection. Otherwise, the connection will remain open and data will be consumed and discarded until the next request is received. Default value: false
Message routes
routes
You can redirect some microservice messages to particular processes by declaring new route
blocks within this one.
route
Is the defined route taking both a messageType
and a specific process
.
Blocked and allowed resources
You can control which resources are exposed to the front end by the Genesis Router using either allowList
or blockList
.
- If you specify one or more resources as
allowList
, then only these resources (and the Genesis defaults) are accessible. - If you specify one or more resources as
blockList
, then these resources are not exposed. All other resources (including the Genesis defaults) are accessible. - If you don't specify any resources as either
allowList
orblockList
, then all resources (including the Genesis defaults) are accessible.
The allowList
and blockList
tags are mutually exclusive. If you specify both, it will generate an error.
The default resources that are always exposed are:
- EVENT_LOGIN_AUTH
- EVENT_LOGOUT
- MORE_ROWS
- MORE_COLUMNS
- DATA_LOGOFF
- DATA_GET
entry
Is the additional accepted messageType
.
Cookie authentication
From GSF version 6.7 onwards, you can configure the HTTP authentication flow to use a cookie-based approach. When the cookie authentication mechanism is enabled:
- The Genesis Platform expects all login and logout events to be called via HTTP.
- Information related to the session itself (e.g. session id, session token, refresh token) is handled transparently using cookies.
Given these expectations, login and logout operations via Websocket is not allowed. A Websocket connection to GENESIS_ROUTER is only possible after a successful HTTP login.
The configuration options are:
enabled
defines whether the cookie authentication mechanism will be enabled or disabled at the Router level. Default: falsehttpOnly
sets the generated cookies to be 'HttpOnly' (more information here). Default: falsesecure
sets the generated cookies to be 'Secure' (more information here). Default: falsewrap
wraps the value of the cookie itself in double quotes (i.e. ") Default: falsepath
sets the specific path attributed to the cookie values. Default: "/"domain
sets the expected domain for the generated cookie. Default: nullsameSite
configures the behaviour of the cookie when used as part of cross-site requests. Cookies with undefined (i.e. null)sameSite
configuration will default to SameSite.Lax . Available values: SameSite.None, SameSite.Lax, SameSite.Strict. Default: null (i.e. therefore SameSite.Lax)
For more information about all the different cookie configuration options and the impact they have in terms of security, refer to the OWASP cookie testing guide.
Configuring client handling
For each client connection, the Router maintains its own message buffer (channel) for sending messages to a client. You can configure:
- the size of the channel buffer
- the behaviour if the buffer becomes full.
To configure client handling, open a clientHandler
block. There is an example of this in the main example at the top of the page. The configuration options are:
-
channelSize
sets the size of the channel buffer. Defaults to 32. -
onChannelFull
sets the behavior when the buffer is full:SUSPEND
is the default behaviour. This utilises Kotlin coroutines and they will wait (suspend) for the buffer to have space before adding any further messages to the buffer.CLOSE
causes the client connection to be closed if the channel buffer becomes full. This could happen with slow consumers, and can help protect the ROUTER process rather than causing a large backlog of messages, which consumes memory. In the worst case, this could cause an Out Of Memory error for the ROUTER process.
Configuring runtime
There are two important files in your application that contain configuration information; make sure that your Genesis Router is configured correctly in both of them:
- application-name-processes.xml
- application-name-service-definitions.xml
Configuring in processes.xml
Here is an example of the Genesis Router's configuration in an application's processes.xml file:
<process name="GENESIS_ROUTER">
<start>true</start>
<scheduleRestart>true</scheduleRestart>
<groupId>GENESIS</groupId>
<options>-Xmx512m -DXSD_VALIDATE=false</options>
<module>router</module>
<package>global.genesis.router,global.genesis.console</package>
<config>router-process-config.kts</config>
<script>genesis-router.kts</script>
<language>pal</language>
<classpath>genesis-console-*.jar</classpath>
<description>Socket, Websocket and HTTP proxy which routes incoming messages to GENESIS microservices</description>
</process>
For more information on the tags that can be set within the configuration for your application in this file, go to our page on processes.xml.
Configuring in service-definitions.xml
The service definition is designed to make sure that each module (service) has a unique port number for inter-process messaging. Here is an example:
<service host="localhost" name="GENESIS_ROUTER" port="9017"/>
For more information on the attributes that can be set here, go to our page on service definitions.
Custom endpoints
To create a custom endpoint using the Genesis Router, simply implement the WebEndpoint
interface provided by Genesis Router. Call upon the registerEndpoint
method of an injected WebEndpointRegistry
object.
In the following examples, a FileEndpointCommon
class has also been created to hold utility methods that may be needed across multiple endpoints:
FileEndpointCommon
- Kotlin
- Java
public class FileEndpointCommon {
companion object {
const val ENDPOINT_NAME = "file-handler"
}
}
public class FileEndpointCommon {
static final String ENDPOINT_NAME = "file-handler";
}
FileProcessor
- Kotlin
- Java
@Module
class FileProcessorKotlin @Inject constructor(
private val registry: WebEndpointRegistry
) : WebEndpoint {
@PostConstruct
fun init() {
registry.registerEndpoint(FileEndpointCommon.ENDPOINT_NAME, this)
}
override fun allowedMethods(): Set<RequestType> {
return ALLOWED_HTTP_METHODS
}
override fun name(): String {
return "upload"
}
override fun process(s: String, fullHttpRequest: FullHttpRequest, channel: Channel): Any {
LOG.debug("Hit {}/{} endpoint", FileEndpointCommon.ENDPOINT_NAME, name())
//This is where you would make calls to other services and libraries with the newly uploaded file.
val responseJson = "{ \"Result\": \"Successful upload\"}".toByteArray(StandardCharsets.UTF_8)
val responseBuffer = Unpooled.wrappedBuffer(responseJson)
val response = DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
responseBuffer
)
response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
HttpUtil.setContentLength(response, responseJson.size.toLong())
return response
}
override fun requiresAuth(): Boolean {
return if (System.getProperty("TEST_MODE") != null) {
false
} else {
super.requiresAuth()
}
}
companion object {
private val LOG = LoggerFactory.getLogger(FileProcessorKotlin::class.java)
private val ALLOWED_HTTP_METHODS: Set<RequestType> = ImmutableSet.of(RequestType.POST)
}
}
@Module
public class FileProcessor implements WebEndpoint {
private static final Logger LOG = LoggerFactory.getLogger(FileProcessor.class);
private static final Set<RequestType> ALLOWED_HTTP_METHODS = ImmutableSet.of(RequestType.POST);
private final WebEndpointRegistry registry;
@Inject
public FileProcessor(WebEndpointRegistry registry) {
this.registry = registry;
}
@PostConstruct
public void init() {
this.registry.registerEndpoint(FileEndpointCommon.ENDPOINT_NAME, this);
}
@NotNull
@Override
public Set<RequestType> allowedMethods() {
return ALLOWED_HTTP_METHODS;
}
@NotNull
@Override
public String name() {
return "upload";
}
@NotNull
@Override
public Object process(@NotNull String s, @NotNull FullHttpRequest fullHttpRequest, @NotNull Channel channel) {
final byte[] responseJson = "{ \"Result\": \"Successful upload\"}".getBytes(StandardCharsets.UTF_8);
//This is where you would make calls to other services and libraries with the newly uploaded file.
final ByteBuf responseBuffer = Unpooled.wrappedBuffer(responseJson);
final DefaultFullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
responseBuffer
);
response.headers().add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
HttpUtil.setContentLength(response, responseJson.length);
return response;
}
@Override
public boolean requiresAuth() {
if(System.getProperty("TEST_MODE") != null){
return false;
} else {
return WebEndpoint.super.requiresAuth();
}
}
}
Non-authenticated routers
As we have noted, the authDisabled
setting is dangerous. One way or another, it is essential that you make your Genesis Router secure. If you want to disable authentication for any other reason than local testing (for example, heavy interaction with legacy systems that can be secured at a legacy level), you still need to take the greatest care to ensure security:
- Use unique ports, and make sure there is no clash with other modules. By default, Genesis Router uses 9064/9065. Make sure this is correctly entered in your application-service-definitions.xml file.
- Make sure that the firewall settings for these ports are limited, so that unwanted external traffic cannot reach it.
- It is useful to rename your Genesis Router's genesis-router.kts file to genesis-router-no-auth.kts. In this file, you must list the event/dataserver/reqrep resource names in an
allowList
block (one entry per item) to specify the resources that can be hit. These are the only resources that can be hit. This is critical to ensuring security.
Once you have defined a non-authenticated Genesis Router and arranged its security, you need to make sure it has a correct entry in your application-processes file; this must point at your .kts file. For best practice, clearly name the process as non-authenticated. For example:
<process name="GENESIS_ROUTER_NO_AUTH">
<start>true</start>
<groupId>GENESIS</groupId>
<opKons>-Xmx512m -DXSD_VALIDATE=false</opKons>
<module>router</module>
<package>global.genesis.router </package>
<config>genesis-router-no-auth.kts</config>
<classpath>genesis-console-4*.jar,ppt-pdjandler-*.jar</classpath>
<descripKon>Socket, Websocket and HTTP proxy which routes incoming messages to GENESIS
microservices</descripKon>
</process>
Testing the Genesis Router
To create unit tests for Genesis Router, you can extend the AbstractGenesisTestSupport
class and specify the genesis-router.kts
as the Script file name. Examples of how you would initialise a test extending this class are provided below.
More information about how testing works is in our section on Integration testing.
- Kotlin
- Java
class TestEndpoint : AbstractGenesisTestSupport<GenesisSet>(
GenesisTestConfig {
packageNames = mutableListOf("global.genesis.router", "org.file.processor")
genesisHome = "/genesisHome"
scriptFileName = "genesis-router.kts"
parser = { it }
}
) {
override fun createDictionary(): GenesisDictionary = testDictionary()}
@Test
fun testRouterEndPoint() {
val client = HttpClient.newHttpClient()
val request = HttpRequest
.newBuilder(URI("http://localhost:9064/file-handler/upload"))
.version(HttpClient.Version.HTTP_1_1)
.POST(HttpRequest.BodyPublishers.ofString("TEXT"))
.build()
val response = client.send(request, HttpResponse.BodyHandlers.ofString())
Assert.assertEquals("{ \"Result\": \"Successful upload\"}", response.body())
}
public class TestEndpoint extends AbstractGenesisTestSupport<GenesisSet> {
public TestEndpoint () {
super(GenesisTestConfig.builder()
.setPackageNames(List.of("global.genesis.router", "org.file.processor"))
.setGenesisHome("/genesisHome")
.setScriptFileName("genesis-router.kts")
.setParser(e -> e)
.build());
}
@Test
public void testRouterEndpoint() throws URISyntaxException, IOException, InterruptedException {
var client = HttpClient.newHttpClient();
var request = HttpRequest
.newBuilder(new URI("http://localhost:9064/file-handler/upload"))
.version(HttpClient.Version.HTTP_1_1)
.POST(HttpRequest.BodyPublishers.ofString("TEXT"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals("{ \"Result\": \"Successful upload\"}", response.body());
}
}