Where things stand
WOMPRequestHandler is currently a placeholder. It substring-matches the request body for keywords (TERMINATE, STATISTICS, REFUSE) and reacts; the statistics response is a hardcoded XML stub from 2021 served from x-statistics-response.xml resource. Lifebeats are sent properly (NGLifebeatThread is current), but the receiving side of the protocol — the admin endpoint that wotaskd calls into — is stubbed.
For ng-objects apps to deploy alongside wonder-slim apps under the same wotaskd, this needs to do the actual work.
What the WO admin protocol actually looks like
The contract is defined by WOAdminAction.instanceRequestAction() in JavaWebObjects. wotaskd POSTs to /cgi-bin/WebObjects/<App>.woa/womp/instanceRequest with a single XML envelope:
<instanceRequest type="NSDictionary">
<commandInstance type="NSDictionary"> <!-- optional -->
<command type="NSString">TERMINATE</command>
<!-- or REFUSE / ACCEPT -->
<minimumActiveSessionsCount type="NSNumber">5</minimumActiveSessionsCount>
</commandInstance>
<queryInstance type="NSString">STATISTICS</queryInstance> <!-- optional -->
</instanceRequest>
A single request can carry both a command and a query. The response envelope mirrors the shape:
<instanceResponse type="NSDictionary">
<commandInstanceResponse type="NSDictionary">
<success type="NSString">YES</success>
</commandInstanceResponse>
<queryInstanceResponse type="NSString">{ ASCII plist }</queryInstanceResponse>
</instanceResponse>
Commands
TERMINATE — graceful shutdown. Send willStop, return success, exit. Current implementation does this with a 1-second sleep-then-exit hack on a separate thread; should move to a proper shutdown hook.
REFUSE — start refusing new sessions; existing sessions continue. Optionally reads minimumActiveSessionsCount to decide when to actually terminate ("refuse new sessions, then terminate when active sessions drop below N"). Currently throws IllegalArgumentException.
ACCEPT — resume accepting new sessions after a previous REFUSE. Currently unhandled.
Queries
STATISTICS — return the runtime statistics dictionary serialized as an ASCII plist. WO returns its full WOStatisticsStore.statistics() dict (Memory, Sessions, Transactions, DirectActions, Pages, WebService, Details). ng-objects has no equivalent today.
Access control
WO's WOAdminAction.instanceRequestAction() rejects requests where aRequest.isUsingWebServer() is true OR the originating address isn't a local interface. The admin endpoint is intended to be reachable only by wotaskd running on the same host. Worth replicating: ng-objects should reject admin requests that don't originate from a local address.
What to implement
1. Replace substring-matching with actual XML parsing
Currently WOMPRequestHandler does request.contentString().contains(\"TERMINATE\"). This breaks if the request body happens to contain that string for any other reason (an embedded XML comment, a query parameter passed through, future-protocol additions). Replace with a parser that decodes the instanceRequest envelope and reads the typed fields.
Probably reuse the same XML format wonder-slim uses — FoundationCoder / _JavaMonitorCoder — or implement a minimal parser specifically for this envelope shape if reusing wonder-slim's coder isn't desirable. The envelope is small enough that a hand-rolled parser is reasonable.
2. Local-address access control
Mirror WO's gate: reject the request unless the originating IP is a local interface and the request didn't come through a web-server adaptor. This is the same protection the wonder-slim fork relies on for /wlb (lifebeats) and /womp/instanceRequest. Without it, anyone who can reach the admin endpoint can terminate the application.
3. Implement REFUSE and ACCEPT
ng-objects needs a notion of "refusing new sessions." The semantic is:
- New session requests get rejected (with a redirect to a refusing-instance page, or a 503, or whatever ng-objects' equivalent is).
- Existing sessions continue normally.
ACCEPT reverses the state.
- Optionally: when
REFUSE is set with minimumActiveSessionsCount, the app self-terminates once active sessions drop below that threshold. This is wotaskd's primary graceful-rolling-restart mechanism — refuse new sessions, drain existing ones, then terminate.
The state itself is a single boolean (or enum) on NGApplication. The session-creation path consults it. Wotaskd then needs no special integration beyond sending the command — the app handles draining and self-termination.
4. Proper TERMINATE
The current sleep-then-exit hack works but is the wrong shape. The right pattern is a JVM shutdown hook that runs the willStop notification, gives in-flight requests a brief grace period to complete, then exits. Removes the magic 1-second sleep and the dedicated thread.
5. Statistics
The hardest piece, and worth thinking about carefully because the WO statistics surface is largely irrelevant to a modern app (we surveyed this in wonder-slim-deployment#22). Three options:
- Match WO's shape with stub data (current behavior). Keeps wotaskd happy but the data is meaningless. Acceptable if statistics aren't load-bearing for any consumer.
- Match WO's shape with real ng-objects data. Map ng concepts onto WO's keys: total request count →
Transactions.Transactions, memory → Memory.{Total,Free}, etc. Lossy but compatible.
- Send a richer ng-native shape under a different key, plus the WO-compatible shape for legacy consumers. Lets ng apps surface metrics that mean something in modern terms (request rate, latency percentiles, GC pressure, app-defined signals) without breaking JavaMonitor's display of WO apps.
The third is the right long-term shape and aligns with wonder-slim-deployment#22 (modulo measures real metrics) and wonder-slim-deployment#33 (bidirectional metrics with app-injected signals). But it requires JavaMonitor to learn the new keys; coordinated change.
The pragmatic near-term path: option 2 with a deliberately minimal mapping. Send what's easy and meaningful (memory snapshot, total request count, uptime); fill in zeros for the things WO measures that ng doesn't track (component action transactions, page render counts). Document the mapping so it's clear what's real vs. placeholder. Once wonder-slim-deployment#33's richer protocol is defined, replace this with the proper implementation.
Eventual: protocol customization per app type
The longer-term goal is that wotaskd recognizes different app types (WO / ng-objects / future kinds) and uses an appropriate protocol for each. ng apps don't need to pretend to speak WO's protocol forever — wotaskd can ask "what kind of app are you?" on first contact and route subsequent comms accordingly.
That requires a protocol-version handshake on the wonder-slim-deployment side and isn't in scope for this issue. For now, ng-objects speaks wotaskd's existing dialect well enough to integrate.
Related issues in undur/wonder-slim-deployment
These describe parts of the larger picture this work intersects with:
- wonder-slim-deployment#22: per-app request metrics in modulo. Replaces WO's misleading 'transactions' counter; informs what statistics ng-objects should actually surface.
- wonder-slim-deployment#33: bidirectional metrics exchange between modulo and instances; introduces the idea of app-injected signals (
WOApplication.application().registerStat(...)). The proper long-term target for ng's statistics surface.
- wonder-slim-deployment#34: define and enforce authority across the deployment stack. Affects who's allowed to send
TERMINATE / REFUSE to an instance.
- wonder-slim-deployment#35: deployment toolchain should be safe over the public internet. Affects whether the local-address gate is sufficient or whether bearer-token auth is needed on
/womp/instanceRequest.
- wonder-slim-deployment#38: bundled platform distribution. ng-objects apps deployed alongside wonder-slim apps share the same JDK and platform footprint.
Current implementation as reference
ng-appserver/src/main/java/ng/appserver/wointegration/WOMPRequestHandler.java is the substring-matching placeholder being replaced.
ng-appserver/src/main/java/ng/appserver/wointegration/NGLifebeatThread.java is the outgoing lifebeat side, which was recently modernized to use HttpClient and is current. The receive side (this issue) is what's still placeholder-shaped.
Effort
Probably 2-3 focused days for items 1-4 (parser, access control, REFUSE/ACCEPT, shutdown hook). Item 5 (statistics) is more like 1-2 days for the pragmatic stub mapping; the richer protocol direction is much larger and gated on wonder-slim-deployment#33.
Doable in isolation; doesn't depend on other ng-objects work.
Where things stand
WOMPRequestHandleris currently a placeholder. It substring-matches the request body for keywords (TERMINATE,STATISTICS,REFUSE) and reacts; the statistics response is a hardcoded XML stub from 2021 served fromx-statistics-response.xmlresource. Lifebeats are sent properly (NGLifebeatThreadis current), but the receiving side of the protocol — the admin endpoint that wotaskd calls into — is stubbed.For ng-objects apps to deploy alongside wonder-slim apps under the same wotaskd, this needs to do the actual work.
What the WO admin protocol actually looks like
The contract is defined by
WOAdminAction.instanceRequestAction()in JavaWebObjects. wotaskd POSTs to/cgi-bin/WebObjects/<App>.woa/womp/instanceRequestwith a single XML envelope:A single request can carry both a command and a query. The response envelope mirrors the shape:
Commands
TERMINATE— graceful shutdown. SendwillStop, return success, exit. Current implementation does this with a 1-second sleep-then-exit hack on a separate thread; should move to a proper shutdown hook.REFUSE— start refusing new sessions; existing sessions continue. Optionally readsminimumActiveSessionsCountto decide when to actually terminate ("refuse new sessions, then terminate when active sessions drop below N"). Currently throwsIllegalArgumentException.ACCEPT— resume accepting new sessions after a previousREFUSE. Currently unhandled.Queries
STATISTICS— return the runtime statistics dictionary serialized as an ASCII plist. WO returns its fullWOStatisticsStore.statistics()dict (Memory, Sessions, Transactions, DirectActions, Pages, WebService, Details). ng-objects has no equivalent today.Access control
WO's
WOAdminAction.instanceRequestAction()rejects requests whereaRequest.isUsingWebServer()is true OR the originating address isn't a local interface. The admin endpoint is intended to be reachable only by wotaskd running on the same host. Worth replicating: ng-objects should reject admin requests that don't originate from a local address.What to implement
1. Replace substring-matching with actual XML parsing
Currently
WOMPRequestHandlerdoesrequest.contentString().contains(\"TERMINATE\"). This breaks if the request body happens to contain that string for any other reason (an embedded XML comment, a query parameter passed through, future-protocol additions). Replace with a parser that decodes theinstanceRequestenvelope and reads the typed fields.Probably reuse the same XML format wonder-slim uses —
FoundationCoder/_JavaMonitorCoder— or implement a minimal parser specifically for this envelope shape if reusing wonder-slim's coder isn't desirable. The envelope is small enough that a hand-rolled parser is reasonable.2. Local-address access control
Mirror WO's gate: reject the request unless the originating IP is a local interface and the request didn't come through a web-server adaptor. This is the same protection the wonder-slim fork relies on for
/wlb(lifebeats) and/womp/instanceRequest. Without it, anyone who can reach the admin endpoint can terminate the application.3. Implement
REFUSEandACCEPTng-objects needs a notion of "refusing new sessions." The semantic is:
ACCEPTreverses the state.REFUSEis set withminimumActiveSessionsCount, the app self-terminates once active sessions drop below that threshold. This is wotaskd's primary graceful-rolling-restart mechanism — refuse new sessions, drain existing ones, then terminate.The state itself is a single boolean (or enum) on
NGApplication. The session-creation path consults it. Wotaskd then needs no special integration beyond sending the command — the app handles draining and self-termination.4. Proper
TERMINATEThe current sleep-then-exit hack works but is the wrong shape. The right pattern is a JVM shutdown hook that runs the willStop notification, gives in-flight requests a brief grace period to complete, then exits. Removes the magic 1-second sleep and the dedicated thread.
5. Statistics
The hardest piece, and worth thinking about carefully because the WO statistics surface is largely irrelevant to a modern app (we surveyed this in
wonder-slim-deployment#22). Three options:Transactions.Transactions, memory →Memory.{Total,Free}, etc. Lossy but compatible.The third is the right long-term shape and aligns with
wonder-slim-deployment#22(modulo measures real metrics) andwonder-slim-deployment#33(bidirectional metrics with app-injected signals). But it requires JavaMonitor to learn the new keys; coordinated change.The pragmatic near-term path: option 2 with a deliberately minimal mapping. Send what's easy and meaningful (memory snapshot, total request count, uptime); fill in zeros for the things WO measures that ng doesn't track (component action transactions, page render counts). Document the mapping so it's clear what's real vs. placeholder. Once
wonder-slim-deployment#33's richer protocol is defined, replace this with the proper implementation.Eventual: protocol customization per app type
The longer-term goal is that wotaskd recognizes different app types (WO / ng-objects / future kinds) and uses an appropriate protocol for each. ng apps don't need to pretend to speak WO's protocol forever — wotaskd can ask "what kind of app are you?" on first contact and route subsequent comms accordingly.
That requires a protocol-version handshake on the wonder-slim-deployment side and isn't in scope for this issue. For now, ng-objects speaks wotaskd's existing dialect well enough to integrate.
Related issues in
undur/wonder-slim-deploymentThese describe parts of the larger picture this work intersects with:
WOApplication.application().registerStat(...)). The proper long-term target for ng's statistics surface.TERMINATE/REFUSEto an instance./womp/instanceRequest.Current implementation as reference
ng-appserver/src/main/java/ng/appserver/wointegration/WOMPRequestHandler.javais the substring-matching placeholder being replaced.ng-appserver/src/main/java/ng/appserver/wointegration/NGLifebeatThread.javais the outgoing lifebeat side, which was recently modernized to useHttpClientand is current. The receive side (this issue) is what's still placeholder-shaped.Effort
Probably 2-3 focused days for items 1-4 (parser, access control, REFUSE/ACCEPT, shutdown hook). Item 5 (statistics) is more like 1-2 days for the pragmatic stub mapping; the richer protocol direction is much larger and gated on
wonder-slim-deployment#33.Doable in isolation; doesn't depend on other ng-objects work.