Skip to content

Commit

Permalink
add route support at the RestServer level
Browse files Browse the repository at this point in the history
  • Loading branch information
ar committed Jan 28, 2019
1 parent 0bb4376 commit 405dae7
Show file tree
Hide file tree
Showing 8 changed files with 325 additions and 22 deletions.
37 changes: 35 additions & 2 deletions doc/src/asciidoc/module_qrest.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ we've created a little **QRest** module that can be configured like this:
</qrest>
----------------------------------------------------------------------------------------
<1> Listening port
<2> Transaction manager queue name
<2> Transaction manager queue name (if no specific routes are present)
<3> `true` to enable TLS
<4> Set to `false` in order to allow self-signed certificates
<5> `true` requires client-side certificates
Expand All @@ -61,6 +61,25 @@ Once the server receives an HTTP request, it creates a `org.jpos.transaction.Con
(under the Constant name `REQUEST` defined in the `org.jpos.qrest.Constants` enum), and to the session in the `SESSION`
constant (so that a `SendResponse` participant can reply) and send it off to the TransactionManager for processing.

If no specific `<route>` entries are present in the QRest configuration, incoming messages are sent to the `queue`
specified in the `queue` property, but you can override those with a route like this:

[source,xml]
----------------------------------------------------------------------------------------
<qrest class='org.jpos.qrest.RestServer' logger='Q2'>
<property name='port' value='8081' />
<property name='queue' value='TXNMGR' />
...
...
<route path="/v2/**" method="GET" queue="TXNMGR.2"/> <1>
<route path="/v2/**" method="POST" queue="TXNMGR.2"/> <2>
</qrest>
----------------------------------------------------------------------------------------
<1> All `GET` methods starting with `/v2` will get queued to `TXNMGR.2` instead of the
standard `TXNMGR` queue.
<2> Likewise, `POST` starting with `/v2` will get queued to the `TXNMGR.2` too.


The TransactionManager is configured like this:

[source,xml]
Expand All @@ -81,7 +100,6 @@ The TransactionManager is configured like this:
<group name="q2">
<participant class="org.jpos.qrest.participant.Q2Info" />
</group>
..
..
<group name="group1">
Expand All @@ -97,6 +115,21 @@ The TransactionManager is configured like this:
----------------------------------------------------------------------------------------
<1> This route is special, see below, route processing gets delegated to the Q2Info class

In situations where multiple routes are defined at the QRest server configuration,
classes like `Q2Info` that internally process routes may need to know about the
prefix in use. This can be configured using the `prefix` property, i.e.:


[source,xml]
----------------------------------------------------------------------------------------
<group name="q2">
<participant class="org.jpos.qrest.participant.Q2Info">
<property name="prefix" value="/v2" /> <1>
</participant>
</group>
----------------------------------------------------------------------------------------
<1> `prefix` property should match the route's prefix

[TIP]
=====
This old link:http://jpos.org/blog/2013/10/eating-our-own-dogfood/[Blog Post] explained how
Expand Down
2 changes: 2 additions & 0 deletions modules/qrest/src/dist/deploy/50_qrest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@
<property name="enabled-cipher" value="TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" />
<property name="enabled-cipher" value="TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" />
-->
<route path="/v2/**" method="GET" queue="TXNMGR.2"/>
<route path="/v2/**" method="POST" queue="TXNMGR.2"/>
</qrest>
54 changes: 50 additions & 4 deletions modules/qrest/src/main/java/org/jpos/qrest/RestServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,25 @@
import java.io.FileInputStream;
import java.io.IOException;
import java.net.BindException;
import java.net.URI;
import java.security.*;
import java.util.Arrays;
import java.util.*;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.jdom2.Element;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.core.XmlConfigurable;
import org.jpos.iso.ISOUtil;
import org.jpos.q2.QBeanSupport;
import org.jpos.space.Space;
Expand All @@ -47,7 +52,11 @@

import javax.net.ssl.*;

public class RestServer extends QBeanSupport implements Runnable {
import static org.jpos.qrest.Constants.PATHPARAMS;
import static org.jpos.qrest.Constants.QUERYPARAMS;
import static org.jpos.qrest.Constants.REQUEST;

public class RestServer extends QBeanSupport implements Runnable, XmlConfigurable {
private ServerBootstrap serverBootstrap;
private ChannelFuture cf;
private EventLoopGroup bossGroup;
Expand All @@ -64,6 +73,9 @@ public class RestServer extends QBeanSupport implements Runnable {
private boolean clientAuthNeeded=false;
private String[] enabledCipherSuites;
private String[] enabledProtocols;
private String queue;
private Map<String,List<Route<String>>> routes = new HashMap<>();


@Override
protected void initService() throws GeneralSecurityException, IOException {
Expand Down Expand Up @@ -144,8 +156,9 @@ public void run() {
}
}

public void queue (Context ctx) {
sp.out(cfg.get("queue"), ctx, 60000L);
@SuppressWarnings("unchecked")
public void queue (FullHttpRequest request, Context ctx) {
sp.out(getQueue(request), ctx, 60000L);
}

@Override
Expand All @@ -159,6 +172,25 @@ public void setConfiguration (Configuration cfg) throws ConfigurationException {
serverAuthNeeded = cfg.getBoolean("server-auth", false);
enabledCipherSuites = cfg.getAll("enabled-cipher");
enabledProtocols = cfg.getAll("enable-protocol");
queue = cfg.get("queue");
}

@Override
public void setConfiguration(Element e) throws ConfigurationException {
try {
for (Element r : e.getChildren("route")) {
routes.computeIfAbsent(
r.getAttributeValue("method"),
k -> new ArrayList<>()).add(
new Route<>(
r.getAttributeValue("path"),
r.getAttributeValue("method"),
(t, s) -> r.getAttributeValue("queue"))
);
}
} catch (Throwable t) {
throw new ConfigurationException(t);
}
}

private SSLEngine getSSLEngine(SSLContext sslContext) throws IOException {
Expand Down Expand Up @@ -234,4 +266,18 @@ protected String getPassword() {
protected String getKeyPassword() {
return System.getProperty("jpos.ssl.keypass", null);
}

private String getQueue(FullHttpRequest request) {
List<Route<String>> routesByMethod = routes.get(request.method().name());
QueryStringDecoder decoder = new QueryStringDecoder(request.uri());

if (routesByMethod != null) {
Optional<Route<String>> route = routesByMethod.stream().filter(r -> r.matches(decoder.uri())).findFirst();
String path = URI.create(decoder.uri()).getPath();
if (route.isPresent()) {
return route.get().apply(route.get(), path);
}
}
return cfg.get("queue");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void channelRead(ChannelHandlerContext ch, Object msg) throws Exception {
ctx.put(Constants.REQUEST, request);
if (contentKey != null)
ctx.put(contentKey, request.content().toString(CharsetUtil.UTF_8));
server.queue(ctx);
server.queue(request, ctx);
} else {
super.channelRead(ch, msg);
}
Expand Down
1 change: 0 additions & 1 deletion modules/qrest/src/main/java/org/jpos/qrest/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public Route(String path, String method, BiFunction<Route<T>, String,T> supplier
Objects.requireNonNull(path);
Objects.requireNonNull(method);
Objects.requireNonNull(supplier);

this.path = path;
this.pathPattern = buildPathPattern(path);
this.method = method;
Expand Down
39 changes: 25 additions & 14 deletions modules/qrest/src/main/java/org/jpos/qrest/participant/Q2Info.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,34 @@
import java.util.stream.Collectors;

import io.netty.handler.codec.http.*;
import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.q2.Q2;
import org.jpos.q2.iso.QMUX;
import org.jpos.qrest.Response;
import org.jpos.qrest.Route;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionManager;
import org.jpos.transaction.TransactionParticipant;
import org.jpos.util.Caller;
import org.jpos.util.NameRegistrar;

import static org.jpos.qrest.Constants.REQUEST;
import static org.jpos.qrest.Constants.RESPONSE;

public class Q2Info implements TransactionParticipant {
public class Q2Info implements TransactionParticipant, Configurable {
private TransactionManager txnmgr;
private Q2 q2;
private List<Route<Map<String,Object>>> routes = new ArrayList<>();
private String prefix;

public Q2Info() {
initInternalRoutes();
}
public Q2Info() { }

@Override
public int prepare(long id, Serializable context) {
Context ctx = (Context) context;
FullHttpRequest request = (FullHttpRequest) ctx.get(REQUEST);
FullHttpRequest request = ctx.get(REQUEST);
QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
Map<String,Object> response = null;
Optional<Route<Map<String,Object>>> route = routes.stream().filter(rr -> rr.matches(decoder.uri())).findFirst();
Expand Down Expand Up @@ -86,6 +89,12 @@ public void setTransactionManager (TransactionManager txnmgr) {
this.q2 = txnmgr.getServer();
}

@Override
public void setConfiguration(Configuration cfg) {
this.prefix = cfg.get("prefix");
initInternalRoutes();
}

private Map<String, Object> muxes () {
List<Object> muxes = NameRegistrar.getAsMap().entrySet()
.stream().filter(e -> e.getValue() instanceof QMUX && e.getKey().startsWith("mux."))
Expand Down Expand Up @@ -155,15 +164,16 @@ private Map<String,Object> txnmgrInfo(TransactionManager txnmgr) {


private void initInternalRoutes() {
routes.add(new Route<>("/q2/version**", "GET", (t,s) -> mapOf ("version", q2Version())));
routes.add(new Route<>("/q2/applicationVersion**", "GET", (t,s) -> mapOf("applicationVersion", Q2.getAppVersionString())));
routes.add(new Route<>("/q2/instanceId**", "GET", (t,s) -> mapOf("instanceId", q2.getInstanceId())));
routes.add(new Route<>("/q2/uptime**", "GET", (t,s) -> mapOf("uptime", q2.getUptime())));
routes.add(new Route<>("/q2/started**", "GET", (t,s) -> mapOf("started", new Date(System.currentTimeMillis() - q2.getUptime()))));
routes.add(new Route<>("/q2/diskspace**", "GET", (t,s) -> diskspace()));
routes.add(new Route<>("/q2/mux/{muxname}**", "GET", (t,s) -> muxInfo(t,s)));
routes.add(new Route<>("/q2/mux**", "GET", (t,s) -> muxes()));
routes.add(new Route<>("/q2/txnmgr**", "GET", (t,s) -> txnmgr()));
routes.add(new Route<>(prefix + "/q2/version**", "GET", (t,s) -> mapOf ("version", q2Version())));
routes.add(new Route<>(prefix + "/q2/applicationVersion**", "GET", (t,s) -> mapOf("applicationVersion", Q2.getAppVersionString())));
routes.add(new Route<>(prefix + "/q2/instanceId**", "GET", (t,s) -> mapOf("instanceId", q2.getInstanceId())));
routes.add(new Route<>(prefix + "/q2/uptime**", "GET", (t,s) -> mapOf("uptime", q2.getUptime())));
routes.add(new Route<>(prefix + "/q2/started**", "GET", (t,s) -> mapOf("started", new Date(System.currentTimeMillis() - q2.getUptime()))));
routes.add(new Route<>(prefix + "/q2/diskspace**", "GET", (t,s) -> diskspace()));
routes.add(new Route<>(prefix + "/q2/mux/{muxname}**", "GET", (t,s) -> muxInfo(t,s)));
routes.add(new Route<>(prefix + "/q2/mux**", "GET", (t,s) -> muxes()));
routes.add(new Route<>(prefix + "/q2/txnmgr/name", "GET", (t,s) -> mapOf("name", txnmgr.getName())));
routes.add(new Route<>(prefix + "/q2/txnmgr**", "GET", (t,s) -> txnmgr()));
}

private Map<String, Object> newMap () {
Expand All @@ -186,4 +196,5 @@ private Map<String,Object> diskspace() {
m.put("usable", f.getUsableSpace());
return mapOf("diskspace", m);
}

}
Loading

0 comments on commit 405dae7

Please sign in to comment.