Processors

A processor fires after the server processes a request, before the response is sent to the client. Processors can inspect and modify the response — status code, headers, and body — or leave it unchanged.

Processors are stored as HTML Microdata items and discovered automatically via selector queries, the same way triggers are.

Quick example

This processor intercepts 404 responses and replaces the body with a custom error page:

<div itemscope itemtype="https://pagelove.org/Processor">
  <meta itemprop="resource" content="*">
  <meta itemprop="method" content="GET">
  <meta itemprop="status" content="404">
  <div itemprop="action" itemscope itemtype="https://pagelove.org/Sessel">
    <script itemprop="source" type="text/sessel">
      @schema Context url("https://pagelove.org/Context");
      Context.response.body = "<h1>Page not found</h1><p>Sorry, that page doesn't exist.</p>"
    </script>
  </div>
</div>

Request lifecycle

Processors sit near the end of the request lifecycle:

  1. Request arrives
  2. Trigger phase — matching triggers execute
  3. Core processing (GET, PUT, POST, DELETE, etc.)
  4. Processor phase — matching processors execute in order
  5. Async dispatch — queued outbound HTTP requests fire
  6. Response sent

Properties

All filter properties are optional. Omitting a filter means "match all."

resource

One or more glob patterns. The processor fires only when the request path matches at least one pattern. Same syntax as trigger resource.

<meta itemprop="resource" content="/blog/*">

method

HTTP methods to match. Same rules as trigger method.

<meta itemprop="method" content="GET">

selector

A CSS selector with semantic matching. Same behaviour as trigger selector.

status

HTTP status codes or class prefixes. The processor fires only when the response status matches one of the listed values.

<!-- Exact match -->
<meta itemprop="status" content="404">

<!-- Class prefix — matches any status 400-499 -->
<meta itemprop="status" content="4xx">

<!-- Multiple patterns (OR'd) — use separate elements -->
<meta itemprop="status" content="404">
<meta itemprop="status" content="410">

<!-- Match all errors -->
<meta itemprop="status" content="4xx">
<meta itemprop="status" content="5xx">

An empty or omitted status filter matches any status code.

when

A Sessel or JavaScript expression that gates execution. Same behaviour as trigger when, but with access to the response for status-conditional gating.

<div itemprop="when" itemscope itemtype="https://pagelove.org/Sessel">
  <script itemprop="source" type="text/sessel">
    @schema Context url("https://pagelove.org/Context");
    Context.response.status == 404
  </script>
</div>

The JavaScript equivalent reads ctx.response instead of Context.response:

<div itemprop="when" itemscope itemtype="https://pagelove.org/JavaScript/Module">
  <script type="module" itemprop="source">
    export default function(ctx) {
      return ctx.response.status === 404;
    }
  </script>
</div>

action

One or more actions, same types as trigger actions: Sessel, JavaScript, and outbound HTTP request.

<div itemprop="action" itemscope itemtype="https://pagelove.org/JavaScript/Module">
  <script type="module" itemprop="source">
    export default function(ctx) {
      throw {
        schema_url: "https://pagelove.org/HTTPResponse",
        status: 200,
        body: "<p>Custom 404 page</p>"
      };
    }
  </script>
</div>

A JavaScript processor action cannot mutate the response directly (see Response-mutation asymmetry) — it must throw an HTTPResponse to replace it, the same shape used by trigger chain termination.

Context

Sessel expressions in processors have access to both Context.request and Context.response:

Property Type Description
Context.request.method String HTTP method
Context.request.path String Request path
Context.request.headers Map Request headers
Context.request.query Map Query string parameters
Context.response.status Number HTTP status code
Context.response.body String Response body (fully buffered)
Context.response.headers Map Response headers

JavaScript when/action modules receive the equivalent context as their first positional argument (conventionally named ctx):

Property Type Description
ctx.request Object Same shape as the trigger JavaScript context
ctx.response.status Number HTTP status code
ctx.response.body String Response body (fully buffered)
ctx.response.headers Object Response headers

Reading and writing the response

Both reading and writing use Context.response:

Context.response.status    // 404
Context.response.body      // "<p>Not Found</p>"

Context.response.status = 200
Context.response.body = "<p>Found it after all</p>"

Pass-through

If no Sessel action modifies any Context.response property, the original response is forwarded unchanged. Processors are pass-through by default — they only affect the response when an action explicitly writes to Context.response.status, Context.response.body, or Context.response.headers.

Response-mutation asymmetry

This direct-mutation style is Sessel-only. JavaScript processor actions run the same evaluation path used for when gates, which does not detect writes to ctx.response — assigning to it has no effect. A JavaScript processor action that needs to change the response must throw an HTTPResponse (see action above) rather than mutate ctx.response in place.

Writing from processors

Like triggers, processor Sessel actions can write to the database using Pagelove.PUT() and Pagelove.DELETE(). This, too, is Sessel-only — see Writing from triggers for the JavaScript alternative (an outbound HTTP request).

<div itemprop="action" itemscope itemtype="https://pagelove.org/Sessel">
  <script itemprop="source" type="text/sessel">
    @schema Pagelove url("https://pagelove.org/1.0");
    @schema AuditLog url("https://example.com/AuditLog");
    let log = new AuditLog { status: Context.response.status };
    Pagelove.PUT(log, "/audit/latest.html")
  </script>
</div>

Side-effect writes from processors follow the same semantics as trigger writes — see Writing from triggers for details.

Chain termination

A Sessel or JavaScript action can terminate the processor chain by throwing an HTTPResponse, the same mechanism as in triggers. The thrown response replaces whatever core produced.

Execution order

Same rules as triggers — lexicographic by document path, document order within each document.

Error handling

Same rules as triggers — malformed Microdata is skipped, Sessel or JavaScript runtime errors surface as error responses.

See also