Skip to content

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:

  1. The receiver accepts every request on the port.
  2. A first mapping node parses the method and path once into a short route key (and captures any {id} path parameter).
  3. A condition node per endpoint checks that key and sends matching requests down that endpoint's branch.
  4. Requests that match nothing fall through to a 404 response.

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 /Request XML 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:

return (messageCache.get('route') + '') === 'heartbeat';

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/runs returning the new run). Do the work and call qie.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 the 202 Accepted reply 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