Routing Multiple REST Endpoints on One HTTP Listener¶
The HTTP Listener source binds a port, not a path. It accepts every request that arrives on that port regardless of URL, and there is no per-path filter on the receiver itself. So a REST API with several endpoints on the same base URL, for example:
POST /api/external/claim
POST /api/external/{id}/heartbeat
POST /api/external/{id}/report
POST /api/external/{id}/complete
POST /api/runs
GET /api/runs/{id}
POST /api/queries/{id}/run
is served by a single channel that inspects the request and routes internally. The pattern is:
- The receiver accepts every request on the port.
- A first mapping node parses the method and path once into a short route key (and captures any
{id}path parameter). - A condition node per endpoint checks that key and sends matching requests down that endpoint's branch.
- Requests that match nothing fall through to a
404response.
Doing the path matching once, up front, keeps the messy regex in one place and turns every condition node into a trivial equality check.
Source-side configuration¶
On the HTTP Listener source node:
- Leave Extract Content As Message unchecked, so the whole request arrives as the
/RequestXML envelope (method, request URI, query string, headers, and body). See HTTP Listener for the envelope layout. - Set Response to From Mapping or Destination node, so each endpoint branch returns its own reply by calling
qie.postMessageResponse(). - Raise the Timeout past your slowest handler's database or downstream call, so a still-processing request is not cut off with a
504.
First mapping node: parse the route once¶
Add a single mapping node immediately after the receiver. It reads the method and path from the /Request envelope, matches them against a route table, and stores the result in the per-message cache for the condition nodes downstream. Each {id} template is matched with a regex capture group, so the path parameter is extracted at the same time.
// One row per endpoint: the HTTP method, a regex matching the path
// (capturing any {id} segment), and the key this channel routes on.
var ROUTES = [
{ key: 'claim', method: 'POST', pattern: /^\/api\/external\/claim$/ },
{ key: 'heartbeat', method: 'POST', pattern: /^\/api\/external\/([^\/]+)\/heartbeat$/ },
{ key: 'report', method: 'POST', pattern: /^\/api\/external\/([^\/]+)\/report$/ },
{ key: 'complete', method: 'POST', pattern: /^\/api\/external\/([^\/]+)\/complete$/ },
{ key: 'createRun', method: 'POST', pattern: /^\/api\/runs$/ },
{ key: 'getRun', method: 'GET', pattern: /^\/api\/runs\/([^\/]+)$/ },
{ key: 'runQuery', method: 'POST', pattern: /^\/api\/queries\/([^\/]+)\/run$/ }
];
// Coerce the Java strings returned by getNode() to JavaScript strings.
var method = source.getNode('/Request/Method') + '';
var uri = source.getNode('/Request/RequestURI') + '';
var route = 'notFound';
var pathId = '';
for (var i = 0; i < ROUTES.length; i++) {
if (ROUTES[i].method != method) {
continue;
}
var match = ROUTES[i].pattern.exec(uri);
if (match) {
route = ROUTES[i].key;
pathId = match[1] || ''; // the {id} segment, when the route defines one
break;
}
}
messageCache.put('route', route);
messageCache.put('pathId', pathId);
Adding a new endpoint later is a single row in ROUTES plus one condition node, with no change to the matching logic.
Condition nodes: one per endpoint¶
After the mapping node, add one Script condition node per endpoint. Each is a one-liner that matches the route key:
Wire the condition's pass link to that endpoint's handler branch (its mapping and destination nodes) and its fail link to the next condition node. Chaining the fail links this way makes the first matching condition win, and no request touches more than the conditions ahead of its match. The handler branch reads the captured path parameter with messageCache.get('pathId').
The fail link of the last condition is the notFound path. Wire it to a node that returns a 404 (below).
Codeless conditions
If you would rather not use Script conditions, have the mapping node write the route key into the message model instead of the cache, then use Standard condition nodes that match on that node path. The Script-condition form above avoids modifying the message and is usually simpler for this pattern.
Responding to the client¶
Each handler branch posts its own reply with qie.postMessageResponse(), using the optional header lines to set the status and content type (see HTTP Listener). A handler that created a resource:
qie.postMessageResponse(
'HTTP Status: 201\r\n' +
'Content-Type: application/json\r\n' +
'\r\n' +
'{"id":"' + messageCache.get('pathId') + '","status":"accepted"}'
);
The notFound branch returns a 404:
qie.postMessageResponse(
'HTTP Status: 404\r\n' +
'Content-Type: application/json\r\n' +
'\r\n' +
'{"error":"No endpoint for the requested path"}'
);
Synchronous vs. fire-and-forget endpoints¶
How much work happens before the response splits the endpoints into two kinds:
- Synchronous: the reply depends on processing (
GET /api/runs/{id}returning data,POST /api/runsreturning the new run). Do the work and callqie.postMessageResponse()inside the router channel; the request stays open until the handler branch replies. - Fire-and-forget: the client only needs to know the request was accepted (
heartbeat,report). If you want each of these endpoints isolated in its own channel (with its own error queue, persistence level, and start/stop) route the branch to a Channel Queue sender feeding a dedicated per-endpoint channel. Because the Channel Queue is asynchronous, post the202 Acceptedreply in the router before the hand-off; you cannot wait on the downstream channel's result. See Acknowledging Receipt Before Processing for the fast-acknowledge pattern.
This keeps the router channel thin (authenticate, parse, route, respond) while heavier asynchronous endpoints get their own channels, all sharing the one HTTP Listener port.
See also¶
- HTTP Listener, the source node's fields, the
/Requestenvelope, and the response options. - Validating an Inbound Bearer Token. Reject unauthorized requests in the preprocessor before they enter the routing tree.
- Acknowledging Receipt Before Processing. Return a fast reply and finish the work in the background.