Interface AsyncJobsMixin

All Known Implementing Classes:
JobsRest

public interface AsyncJobsMixin
Mixin interface that exposes the async-job SSE progress stream and cancel endpoint on any Juneau REST resource.

An implementing resource supplies its AsyncJobRegistry via asyncJobRegistry() — typically a single per-process instance — and gets two endpoints for free:

  • GET /juneau-jobs/{jobId}/stream — a held-open text/event-stream of the job's progress, terminated by one result event carrying the terminal ActionResult. Each progress event carries an SSE id; a fresh attach is live-only, and a Last-Event-ID header or lastEventId query parameter resumes from a position (see streamJob(String, RestRequest, RestResponse)).
  • POST /juneau-jobs/{jobId}/cancel — cancels the job and returns its terminal ActionResult (a cancelled / cancelled-after-effect outcome).
Authorization: the stream URL is the capability

The stream is a held-open GET, so LoopbackBoundary applies only its Host check (DNS-rebinding stays covered on every request); the Origin/CSRF/JSON checks apply to non-safe methods only. A browser EventSource cannot set an X-Csrf-Token header and cookies are unsound on a loopback port, so CSRF-on-GET is not an available control. Access is therefore gated by the unguessability of the jobId path segment — an 256-bit SecureRandom capability — and nothing else. An unknown id answers 404 (a miss is indistinguishable, so the id space cannot be enumerated); a job already at its subscriber cap answers 429. A resume value grants nothing: it only picks a position inside a job the caller already holds the capability for, and it is checked only after the 404, so a bad value cannot reveal whether a job exists.

Server-side hard limits

Every limit is enforced in AsyncJob / AsyncJobRegistry, not here and not on the client: the hard timeout, the per-job output cap, the concurrent-job cap and the per-job subscriber cap. The stream loop simply drains the job until it is terminal, which the timeout guarantees will happen.

Example:

@Rest public class MyResource extends BasicRestServlet implements AsyncJobsMixin { private final AsyncJobRegistry jobs = new AsyncJobRegistry(); @Override public AsyncJobRegistry asyncJobRegistry() { return jobs; } }

See Also:
Since:
10.0.0
  • Field Details

  • Method Details

    • asyncJobRegistry

      Returns the AsyncJobRegistry backing this resource's async jobs.
      Returns:
      The registry. Never null.
    • streamJob

      default void streamJob(@Path("jobId") String jobId, RestRequest req, RestResponse res) throws IOException
      [GET /juneau-jobs/{jobId}/stream] — stream a job's progress as Server-Sent Events.

      Streams progress as progress events, then sends exactly one result event carrying the terminal ActionResult as JSON and closes. The progress event taken from the job's buffered event i is always sent with SSE id i+1; the result event has no id, so a browser's last-event-ID stays at the last progress event.

      The start position comes from a resume value, in this order:

      1. The Last-Event-ID header, if non-empty after trimming (a browser's own reconnect re-sends the same URL, query included, and adds this header).
      2. Otherwise the lastEventId query parameter, if non-empty after trimming (an explicit opt-in to the buffered history; lastEventId=0 replays everything).
      3. Otherwise none: a fresh attach is live-only and sends only progress produced after it attached.
      A value k resumes with buffered event k (id k+1). A malformed value (anything but 1 to 10 ASCII digits after trimming SP/HTAB) or one past the end of the buffer is a 400. An ended job still sends its result on every attach.

      Access is gated solely by the unguessable jobId (see the interface javadoc). The checks run in this order: an unknown id is a 404; a bad resume value is a 400; a job already at its subscriber cap is a 429. The cursor is fixed before the subscriber slot is taken, so a 400 never holds a slot.

      Parameters:
      jobId - The job's unguessable capability id.
      req - The REST request (for the resume value).
      res - The REST response.
      Throws:
      IOException - If the stream could not be written.
    • cancelJob

      default ActionResult cancelJob(@Path("jobId") String jobId, RestResponse res)
      [POST /juneau-jobs/{jobId}/cancel] — cancel a job and return its terminal result.

      A non-safe POST, so the boundary applies its full Origin/CSRF/JSON checks. Cancellation is enforced server-side (a client cannot be trusted to stop the work); the returned outcome distinguishes cancelled from cancelled-after-effect. An unknown id is a 404 carrying a named refusal.

      Parameters:
      jobId - The job's unguessable capability id.
      res - The REST response.
      Returns:
      The job's terminal ActionResult.
    • streamPath

      static String streamPath(String jobId)
      Builds the servlet-relative stream path for a job id.
      Parameters:
      jobId - The job's capability id.
      Returns:
      The stream path, e.g. /juneau-jobs/<id>/stream.
    • cancelPath

      static String cancelPath(String jobId)
      Builds the servlet-relative cancel path for a job id.
      Parameters:
      jobId - The job's capability id.
      Returns:
      The cancel path, e.g. /juneau-jobs/<id>/cancel.