Espacio Psicoanalítico de Barcelona
15 June 2026
RexxHTTP is a servlet processor for ooRexx and Apache. It runs as a CGI program under the Apache HTTP server: Apache hands it an incoming request, and RexxHTTP turns the raw CGI environment into a small set of objects, runs your servlet against them, and writes the result back as an HTTP response. A servlet is an ordinary ooRexx program, so the whole of ooRexx is available to it, with the HTTP request and the response it is about to build handed in as objects rather than scraped out of environment variables by hand.
For each request RexxHTTP builds three objects. A
request object exposes the request line, the headers
and the GET/POST arguments through named methods, decoding percent- and
plus-escapes for you. A response object collects the
status line and the response headers and lets you attach cookies. And an
output stream receives the body: it is a buffering
subclass of the built-in Stream class, and that buffering
is what makes the response headers lazy — you may keep changing
status, headers and cookies after you have already started writing the
body, right up until the response is committed. The request and the
response reach the servlet as the .request and
.response objects, placed in the environment by RexxHTTP;1 the body is produced with ordinary
Say or CharOut instructions. The response is
committed on the first flush, which normally happens when the servlet
ends. This is the central idea of the design, and the chapter The request/response model returns
to it in detail.
RexxHTTP requires ooRexx 5.3.0 or later and Apache 2.4 or later. It is released under the Apache License 2.0 and published at https://rexx.epbcn.com/rexxhttp/ and on GitHub at https://github.com/JosepMariaBlasco/RexxHTTP.
RexxHTTP first appeared in 2006 as a more ambitious thing: alongside CGI it supported mod_rexx, ran on OS/2 and Windows as well as Unix, was meant to work under Microsoft IIS, and defined a Rexx Server Pages compiler interface for mixing HTML and Rexx in a single page. None of that survives in the 1.0 version. This release is CGI-on-Apache only — no mod_rexx, no IIS, no OS/2, no page compilers — and the manual describes only what the 1.0 version actually does, verified against the code and against a live Apache. The 2006 manual2 remains the record of the wider original; this one is deliberately narrower, and says so plainly rather than promising features the released code no longer carries.
RexxHTTP runs as a CGI program under Apache. This chapter assumes you already keep an Apache server and know how your platform applies its configuration; it covers only what is specific to RexxHTTP — which files make up the package, the one wrinkle in how the processor is invoked, and the handful of directives that turn a file into a servlet. The wrinkle is worth understanding rather than copying blindly, so the chapter explains the why alongside the what.
RexxHTTP needs ooRexx 5.3.0 or later and an Apache 2.4 server with
the cgid and actions modules enabled. It also
uses curl for the smoke test at the end.
Installing ooRexx itself is outside the scope of this manual; the
interpreter is obtained and installed separately, from the Open Object
Rexx project.3 RexxHTTP assumes a working
rexx on the server and nothing more.
On Debian or Ubuntu, Apache and the two modules are:
sudo apt-get install -y apache2
sudo a2enmod cgid actions
cgid is what runs the CGI program; actions
is what lets you route a request to a CGI program of your choosing
rather than executing the requested file directly. The next section
explains why that matters.
A RexxHTTP installation is five files: the processor,
RexxHTTP.rex, and the four classes it requires —
HTTP.Request.cls, HTTP.Response.cls,
HTTP.OutputStream.cls and HTTP.Cookie.cls.
They are distributed together in the download.4 The
processor's ::Requires directives name the four classes,
which ooRexx then locates by its ordinary search rules, so the classes
need only be reachable from the processor — they do not have to
sit in the same directory. Keeping all five together is recommended. The
examples below assume the five files in a directory RHDIR
and the wrapper, introduced next, in a CGI-enabled directory; substitute
your own paths.
To avoid problems with CRLF line endings and shebangs, we use a one-line wrapper:
#!/usr/bin/env rexx
Call "RHDIR/RexxHTTP.rex"
The wrapper is the one file that must be LF and executable; the five sources stay exactly as shipped.
The actions module lets you define a handler —
a named action backed by a CGI program — and then hand selected requests
to it. RexxHTTP uses this to route a request at the wrapper instead of
at the requested file. The handler is declared once, pointing at the
wrapper through a ScriptAlias that exposes it as an
executable CGI:
ScriptAlias /rexxhttp-bin/ "RHDIR/"
Action RexxHTTPCGI /rexxhttp-bin/wrapper
ScriptAlias makes the wrapper runnable under an internal
URL path; Action names the handler RexxHTTPCGI
and binds it to that wrapper. Nothing under /rexxhttp-bin/
is meant for the public — it is only the address the handler dispatches
through.
With the handler defined, you decide which files are servlets. The
recommended model — the reasoning is in the Rationale chapter — is a
whitelist: a file is a servlet only when you say so
explicitly, and everything else is served as ordinary static content.
You whitelist a file by handing it to the handler with
SetHandler. The RexxHTTP-specific part of a site's
configuration is therefore just these directives, scoped to wherever the
servlets live:
<Directory "/path/to/your/site">
Options +ExecCGI
<Files "index">
SetHandler RexxHTTPCGI
</Files>
</Directory>
A request for /index now runs the servlet; everything
beside it — templates, images, stylesheets — is served straight off
disk. Each extra servlet is one more <Files> block.
The same <Files> lines work verbatim in an
.htaccess file inside the directory, if you prefer
per-directory control to editing the server config, provided
AllowOverride permits it.
That block is all RexxHTTP adds. Where it goes, and how you
activate it, is your platform's business, not this manual's: on Debian
or Ubuntu the directives live in a site file under
sites-available and are enabled with a2ensite
and a reload; on a prepackaged Windows Apache they go in the bundled
httpd.conf or an included fragment. RexxHTTP does not care
which, as long as the handler, the ScriptAlias and the
<Files> whitelist end up in effect for the directory
that holds your servlets.
If a servlet needs environment values of its own, set them the usual
Apache way, with SetEnv, and read them back through the
request object as the request/response model chapter
describes.
A minimal servlet confirms the montage end to end. Put this in a file
named hola in a whitelisted directory:
Use Arg request, response
response~content_type = "text/plain"
Say "Hola, mundo"
Whitelist it (<Files "hola">), reload Apache, and
request it:
$ curl http://example.epbcn.com/hola
Hola, mundo
A 200 with that body means all the moving parts line up:
Apache routed the request through the handler, the wrapper called the
processor, the processor built the request and response objects, ran the
servlet, and flushed the body back. If instead you get a
500, the first place to look is the wrapper's line endings
and the path it Calls; if you get the file's source text
rather than its output, the file was served statically and is missing
from the whitelist.
A second, more thorough check is to run the bundled integration suite, which stands up its own throwaway Apache montage, exercises the whole pipeline and tears it down again. It is described in the test suite's own readme and is the same suite the project verifies every release against.
This chapter builds a few small servlets, each one introducing a
little more of the API, until the shape of a RexxHTTP servlet is
familiar. It assumes RexxHTTP is installed and reachable, as the Installation chapter describes; the examples
are written for a servlet directory mapped at /test, the
layout the test suite uses, so a servlet in the file hola
answers at /test/hola. Adjust the path to wherever your own
deployment maps its servlets.
A servlet is an ordinary ooRexx program. There is no framework class
to subclass and no entry point to declare: the file is the
servlet, and running it produces the response. What RexxHTTP adds is the
two objects it hands the servlet — the request that came in, and the
response being built — and the redirection that turns a plain
Say into body output. Everything below is a consequence of
those two facts.
The smallest useful servlet greets the world. Put this in a file
named hola in the servlet directory:
Use Arg request, response
response~content_type = "text/plain"
Say "Hola, mundo"
Request it:
$ curl http://127.0.0.1/test/hola
Hola, mundo
Three lines, and each earns its place.
Use Arg request, response picks up the two objects the
processor passes in. The assignment sets the response's
Content-Type; a fresh response already carries
text/plain; charset=utf-8, so for this particular servlet
the line is strictly redundant, but it is worth writing out once to see
where the content type comes from — and the moment you emit HTML you
will need it in earnest
(response~content_type = "text/html"). The Say
writes the body.
That last point is the one to dwell on. Say does not
print to a terminal here: while the servlet runs, the default output
stream is the response's buffered body, so Say appends
a line to the response. Nothing is sent yet — the body is collected
in a buffer — and when the servlet ends, the processor flushes it,
writing the headers and then the body to the client. A servlet that does
nothing but Say still produces a complete, well-formed HTTP
response; the processor handles the envelope.
The request and response arguments are one
of two equivalent ways to reach those objects. The processor also
publishes them in the global environment as .request and
.response, which work in any scope — inside a routine or a
method, without the variable having to be passed along — and that is the
form this manual prefers. The servlet above could just as well have
read:
.response~content_type = "text/plain"
Say "Hola, mundo"
and omitted the Use Arg line entirely. Both forms are
official; the environment symbols are the more robust of the two,
because they do not depend on a variable being in scope. The rest of
this chapter uses whichever reads more clearly at the point in
question.
In fact the content type can go too. A fresh response already carries
Content-Type: text/plain; charset=utf-8, so the smallest
servlet that works is a single line:
Say "Hello, world"
That is a complete servlet. The Say writes the body, the
processor flushes it, and the default content type RexxHTTP seeded the
response with does the rest.
The charset=utf-8 in that default is not decoration.
RexxHTTP labels the response as UTF-8, so if your editor speaks Unicode
you can write it straight into the servlet and it reaches the browser
intact:
Say "नमस्ते", "P ≝ 𝔐", "🦞🍐"
That single Say produces three lines: Devanagari,
mathematical notation, a couple of emoji — and the browser renders them
correctly, because the response already announces the encoding the bytes
are in. There is nothing to configure: the default content type carries
the charset, and the body is sent as written.
Most servlets do something with what the client sent. Request
arguments — the query string of a GET, the form body of a
POST — are reached through arg, which is
modelled on the built-in Rexx Arg function: called with no
argument it returns the count, and otherwise the first argument selects
one, by position when it is a whole number and by name otherwise. A
second argument says what to return — Value (the default),
Name, Exists or Omitted.
Here is a servlet that enumerates every argument it received, by position:
n = .request~arg
Say "count="n
Do i = 1 To n
Say i": ".request~arg(i, "Name")"=".request~arg(i, "Value")
End
Calling it shows the arguments back in the order they arrived, names in their original spelling:
$ curl "http://127.0.0.1/test/multi?q=hello&Lang=ca"
count=2
1: q=hello
2: Lang=ca
Lookup by name is the more common case, and it is caseless —
arg("lang") finds Lang — while
Exists lets a servlet branch on whether an argument was
sent at all:
Say "q=".request~arg("q", "Value")
Say "exists=".request~arg("q", "Exists")
Say "missing_exists=".request~arg("nope", "Exists")
$ curl "http://127.0.0.1/test/qs?q=hello"
q=hello
exists=1
missing_exists=0
Two things are happening here that the servlet never has to code
around. First, names and values arrive decoded: percent-escapes
and the +-for-space convention of form encoding are already
undone, on both sides of each pair, so an argument the form sent as
user+name is reached as arg("user name").
Second, the arguments are guaranteed well-formed before the servlet
runs. RexxHTTP applies a strict argument policy — every parameter must
have the shape name=value, names must be
non-empty, must not begin with a digit, and must be unique caselessly —
and a request that breaks a rule is answered with
400 Bad Request and never reaches the servlet. So
arg never has to cope with a malformed or duplicated
parameter; the servlet may assume every argument has a name and a value
and that a name names exactly one argument. The Rationale chapter explains why strict-by-default
is the right policy for this kind of deployment, rather than a hazard.
The full repertoire of arg — positional versus named
lookup, the four options, the treatment of duplicates — is in the
HTTP.Request reference in the Class Reference.
The same servlet code serves a GET and a form
POST without change: the processor parses the query string
of the one and the application/x-www-form-urlencoded body
of the other into the same arguments, so arg reads
identically either way.
A cookie the response sets is an HTTP.Cookie object:
create it, set its attributes, and hand it to
.response~addcookie. The cookie is scheduled then, and
emitted as a Set-Cookie header when the response is
committed.
c = .HTTP.Cookie~new("session")
c~value = "abc123"
c~path = "/"
.response~addcookie(c)
Say "cookie set"
$ curl -i "http://127.0.0.1/test/setck"
HTTP/1.1 200 OK
...
Set-Cookie: session=abc123; Path=/
Content-Type: text/plain; charset=utf-8
cookie set
A freshly created cookie is the quietest thing possible — a session
cookie with name=value and nothing else — and you add only
the attributes you want: c~max_age = 86400 for a lifetime,
c~httponly = 1 to keep it from JavaScript,
c~samesite = "Lax" and so on. The cookie validates eagerly:
an inconsistent combination, such as SameSite=None without
Secure, is refused at addcookie rather than
emitted as a header the browser would silently drop. The attribute set
and the consistency rules are in the HTTP.Cookie
reference.
Reading a cookie back is the request side, and it parallels
arg:
Say "count=".request~cookie()
Say "session=".request~cookie("session", "Value")
$ curl --cookie "session=abc123" "http://127.0.0.1/test/readck"
count=1
session=abc123
The two halves form a round trip: a value is taken verbatim on the
way out (encode any reserved characters yourself) and returned
percent-decoded on the way in. Note one asymmetry from arg:
cookie lookup by name is case-sensitive, because user agents
treat session and Session as different
cookies, and the option word is given as an abbreviation rather than by
its first letter alone. The details are in the cookie entry
of the HTTP.Request reference.
Because the body is buffered, the response headers stay changeable after output has begun — right up until the response is committed. This is unusual enough to be worth seeing directly. The following servlet writes a line of body, then sets a header, then writes more body:
Say "body first line"
.response~content_type = "text/plain"
.response~x_late_header = "yes"
Say "body second line"
The header set after the first Say still reaches the
client:
$ curl -i "http://127.0.0.1/test/late"
HTTP/1.1 200 OK
...
X-Late-Header: yes
Content-Type: text/plain; charset=utf-8
body first line
body second line
The body the servlet "wrote" went into the buffer, not onto the wire,
so when x_late_header was assigned the head had not yet
been sent and the new header simply joined the others. Nothing is
committed until the first flush — here, the processor's automatic one
after the servlet returns — at which point the whole head goes out in
front of the whole body. (The message form
.response~x_late_header = "yes" reaches the header
X-Late-Header; the response object turns underscores into
hyphens. A header whose name truly contains an underscore needs the
bracket form, response["X_Odd_Name"] = — see the
HTTP.Response reference.)
This freedom has one edge. Once the response is committed the head is on the wire and can no longer change, so a header assignment after that point is a programming error and raises, rather than failing silently. A servlet that just writes body and lets the processor flush never meets this edge; a servlet that flushes early does, deliberately — which is the next topic.
A servlet may flush the response itself, before it returns, by
calling .response~flush:
.response~content_type = "text/plain"
Say "before flush"
.response~flush
Say "after flush"
$ curl "http://127.0.0.1/test/partial"
before flush
after flush
The output is identical to a servlet that never flushed — and that is
the honest result to show. The first flush commits the response: it
writes the headers, then the body buffered so far, and from that point
the head is frozen. What an explicit flush buys is therefore not what
intuition (or the 2006 manual) suggests. It does not reliably
make the client see "before flush" appear before "after flush": in a
production deployment mod_deflate sits downstream and
buffers the CGI output to compress it, so progressive, client-visible
streaming is not something a servlet can count on. What an early flush
does buy is control over when the head is committed — fixing
the headers and status at a chosen point — which is occasionally what
you want and is the mechanism behind the lazy-header behaviour above.
The The request/response model
chapter tells this story properly, mod_deflate and all; for
everyday servlets, simply never calling flush and letting
the processor do it is exactly right.
Two common responses have their own one-line forms, written to be the last thing a servlet does. To send an error page:
If \recordExists(.request~arg("id")) Then
Exit .response~error(404, "no such record")
.response~content_type = "text/html; charset=utf-8"
Say "<p>found it</p>"
error(status [, detail]) sets the status, switches the
content type to text/html, and replaces any buffered body
with a short error page; the optional detail is HTML-escaped and shown
as a diagnostic line. .response~404(detail) is shorthand
for the commonest case. To redirect:
Exit .response~redirect("/test/hola")
redirect(location [, status]) defaults to
302; pass 301 for a permanent move or
303 after a form POST. Both error
and redirect follow the same commit discipline as a late
header: they discard an uncommitted body and do their work, but raise if
the response has already been committed, since by then the status can no
longer change. Their full behaviour is in the HTTP.Response
reference.
These servlets exercise most of the everyday API: reading arguments and cookies, writing the body, setting headers and cookies on the response, and the two shorthand exits. The one idea underneath all of them — the buffered body, the lazy headers, the single commit — is the subject of the next chapter, The request/response model, which is worth reading before building anything larger. The exhaustive, method-by-method account of the four classes is the Class Reference.
The Tutorial showed servlets that work without explaining why they work. This chapter is the why. It describes the three objects RexxHTTP builds for each request, the buffering that sits under the response, and the single moment — the commit — that turns a buffer full of headers and body into bytes on the wire. Almost everything that can surprise a servlet author, from a header that takes effect after it was "too late" to set, to one that mysteriously raises, follows from this one model.
For each request, before the servlet runs, RexxHTTP builds three objects and hands the servlet the first two.
A request (HTTP.Request) is the
incoming side, read-only. It holds the request line and headers —
reached as named accessors or, for anything CGI passed as an environment
variable, through the environment pool — and it holds the arguments and
cookies the client sent, parsed and decoded. By the time the servlet
runs the parsing is already done and the argument policy already
applied, so the request a servlet sees is always well-formed.
A response (HTTP.Response) is the
outgoing side, and it is the object this chapter is really about. It is
made of three parts: a set of headers (seeded with one,
Content-Type: text/plain; charset=utf-8), an optional set
of cookies, and a buffered output stream that collects the body. The
headers and cookies stay freely modifiable for as long as the body is
being written — that is the whole point of the design — and are
serialised only once, at commit.
An output stream (HTTP.OutputStream) is
that body buffer: a write-only subclass of Stream that the
response creates for itself. While the servlet runs, the default output
stream is redirected to it, which is why a plain Say writes
to the response body rather than to a console that is not there. The
servlet almost never names this object; it just uses
Say.
The request and response reach the servlet two ways at once — as the
two arguments to Use Arg, and as the environment symbols
.request and .response — and the two are the
same objects. This manual prefers the environment symbols, because they
reach the response from any scope (a routine, a method) without the
object having to be passed along, and because the body of a servlet
rarely wants to thread response through every call that
might need to set a header.
A CGI program speaks to the web server through its standard output: the bytes it writes become the HTTP response, headers first, then a blank line, then the body. The catch is order. Once a single header byte has been written, the headers are decided; anything the program "sets" after that is too late, because it has already gone out — or worse, the body has, and the header lands in the middle of it.
RexxHTTP removes that catch by not writing the body straight to the
server. The output stream collects it in a buffer instead. While the
buffer fills, the headers and cookies are still just entries in two
directories on the response object: setting one, changing one, adding a
cookie are all ordinary updates to in-memory state, and the order in
which the servlet does them relative to its Says does not
matter, because none of it has been serialised yet.
This is what the introduction and the Tutorial called lazy
headers. A servlet can write half its page, decide from what it
computed there that the content type should be text/html
rather than the default, set it, and finish the page — and the header is
correct on the wire, because at the moment it was set nothing had yet
been committed. The buffer buys the servlet the freedom to decide
late.
Commit is the moment the buffered head becomes bytes. It
happens once per response, and it does a fixed sequence: it writes the
Content-Type header, then one Set-Cookie line
per scheduled cookie, then every other header, then the blank line that
separates head from body — all straight to the server's output stream,
ahead of the buffered body. After commit the head is on the wire and
cannot be changed.
Commit is idempotent: the first call emits the head and marks the response committed; any later call returns at once, having done nothing. A servlet therefore never has to track whether the response is "already" committed before asking for a flush — asking twice is harmless.
Servlets rarely call commit directly. It is driven by
flush. The response's flush (and the output
stream's, which it delegates to) commits the response if it has not been
committed yet, and only then writes the buffered body out and empties
the buffer. So the first flush is what commits, and
that first flush is one of two things:
.response~flush — for instance to fix the headers at a
deliberate point; orA servlet that never flushes still gets exactly one flush, the
processor's final one, and so still produces a complete response with
its head committed once. The common case needs no flush at
all.
Everything a servlet can do to the head — set a header
(.response[...]= or the message form), schedule a cookie
(addcookie), or replace the response wholesale
(error, 404, redirect) — is legal
before commit and an error after it. The reason is
physical: past commit the head is already on the wire, so a late change
could not possibly take effect. RexxHTTP treats the attempt as a
programming error and raises (Syntax 93.900, the
response is already committed) rather than accept a call it would
have to silently ignore. The body may still be written and flushed after
commit; only the head is frozen.
This is why the order of guards matters in one place worth noting.
When a cookie is added to an already-committed response, the failure
reported is already committed, not not a cookie even
if the argument is also wrong: the commit check comes first, because the
commit state outranks argument validation. The same fail-fast governs
error and redirect, which discard an
uncommitted buffered body and do their work, but raise on a committed
response instead of pretending to change a status that has already
shipped.
There is also a brief internal window the servlet never sees but
which explains the care in the design: while commit is
partway through writing the head, the response is committing
but not yet committed. RexxHTTP's own error handling
distinguishes the two, so that a fault raised in the middle of emitting
headers does not try to re-assert headers over a response that is
already half-written. A servlet only ever observes the two stable
states, before and after; committed reports which. The
per-method detail — what error, addcookie,
commit and committed each do, argument by
argument — is in the Class Reference.
It is tempting to read "the servlet can flush early" as "the servlet can stream a page to the browser progressively, a piece at a time." The 2006 manual made roughly that promise. In a modern production deployment it no longer holds, and this manual will not repeat it.
The reason is mod_deflate. To compress the response,
Apache's mod_deflate buffers the CGI program's output on
its own account before sending it on, which means the boundaries at
which a servlet flushes are not the boundaries at which the client
receives anything: the compressor decides those. An early
.response~flush does not reliably make the first
half of a page appear in the browser before the servlet has finished
computing the second half.
What an early flush still does — and the reason the flush mechanism
is kept — is internal and entirely real. It commits the
response at a chosen point, fixing the headers and status exactly there
rather than at the end. That control over commit ordering is
occasionally what a servlet wants, and it is the mechanism behind the
lazy-header behaviour this chapter described. Think of
flush as "decide the head now," not as "push bytes to the
browser now." For the great majority of servlets neither concern arises:
they write their body, return, and let the processor's final flush
commit and send the whole thing at once.
HTTP.Cookie classAn HTTP.Cookie instance represents one cookie the
response will set: one instance, one Set-Cookie header. The
servlet creates the cookie, sets its attributes, and hands it to
.response~addcookie:
c = .HTTP.Cookie~new("session", "abc123")
c~max_age = 86400
c~samesite = "Lax"
c~httponly = 1
.response~addcookie(c)
This is a response-side class only. Cookies arriving with
the request are not HTTP.Cookie instances; they reach the
servlet as plain name/value pairs through .request~cookie
(see the HTTP.Request reference).
HTTP.Cookie is a modern, RFC 6265 cookie: lifetime is
expressed with Max-Age (the class does not emit
Expires, which survives only for user agents long gone —
when both are present, Max-Age wins anyway), and the
SameSite, Secure and HttpOnly
attributes and the __Host- and __Secure- name
prefixes are supported.
The class validates eagerly, so that an invalid cookie fails in the
servlet, never as a Set-Cookie line the browser silently
discards. An invalid name is rejected at creation. Rules that
involve several attributes are checked when the cookie is
serialised — in practice, at addcookie — so that attributes
may be set in any order.
A cookie must satisfy all of these to serialise:
SameSite=None requires Secure.__Secure- prefix requires
Secure.__Host- prefix requires
Secure, Path=/, and no
Domain.Prefix matching is case-insensitive, mirroring what user agents do:
__host- is __Host-.
newcookie = .HTTP.Cookie~new(name, value)
cookie = .HTTP.Cookie~new(name) -- value defaults to ""
Creates a cookie named name with the given value (default: the null string — a cookie with an empty value is legal).
The name must not be the null string and must not begin with
$; its characters must be ASCII, and neither control
characters nor any of the separator characters
( ) < > @ , ; : \ " / [ ] ? = { }, space, or tab. The
__Host- and __Secure- prefixes are recognised;
the requirements they carry are listed above and checked at
serialisation time.
The value is taken as given: no encoding is performed. If the value
carries reserved characters (;, ,, spaces...),
percent-encode it yourself; on the way back,
.request~cookie returns it already decoded, so the round
trip is: encode on output, read plain on input.
A freshly created cookie is a session cookie with everything
at its quietest: no Max-Age, no Path, no
Domain, no SameSite, not Secure,
not HttpOnly. Only name=value is emitted.
namestring = cookie~name
Returns the cookie's name. There is no name=: a cookie's
name is fixed at creation.
value, value=string = cookie~value
cookie~value = newvalue
The cookie's value. As at creation, the new value is taken as given — encoding reserved characters is the caller's business.
path, path=string = cookie~path
cookie~path = path
The Path attribute, emitted as given. The default, the
null string, omits the attribute. A __Host- cookie must
have Path=/.
domain, domain=string = cookie~domain
cookie~domain = domain
The Domain attribute, emitted as given. The default, the
null string, omits the attribute. A __Host- cookie must not
set one.
max_age,
max_age=seconds = cookie~max_age
cookie~max_age = seconds
The cookie's lifetime, a whole number of seconds. A negative value —
the default is −1 — makes it a session cookie: the attribute is omitted.
Zero instructs the user agent to delete the cookie, which is also how
you delete one — set Max-Age to zero on a cookie
with the same name (and the same Path and
Domain, if the original had them):
gone = .HTTP.Cookie~new("session", "")
gone~max_age = 0
.response~addcookie(gone)
secure, secure=flag = cookie~secure
cookie~secure = flag
The Secure attribute: 1 emits it,
0 — the default — does not. Required by
SameSite=None and by the __Secure- and
__Host- name prefixes.
httponly,
httponly=flag = cookie~httponly
cookie~httponly = flag
The HttpOnly attribute: 1 emits it,
0 — the default — does not.
samesite,
samesite=string = cookie~samesite
cookie~samesite = value
The SameSite attribute: Strict,
Lax or None, accepted case-insensitively and
stored in canonical spelling (the getter returns Strict,
Lax or None regardless of how it was
assigned). The null string — the default — omits the attribute;
assigning it clears a previously set value. SameSite=None
requires Secure; the rule is checked at serialisation, not
here, so the two may be assigned in either order.
makestringstring = cookie~makestring
Returns the value of the Set-Cookie header this cookie
will produce — exactly what addcookie will schedule and
commit will emit. The consistency rules above are checked
first: an inconsistent cookie does not serialise. Attributes appear in a
fixed order: name=value, then Max-Age,
Domain, Path, SameSite, then the
valueless Secure and HttpOnly:
id=v; Max-Age=3600; Domain=example.org; Path=/; SameSite=Lax; Secure; HttpOnly
Servlets rarely need makestring — addcookie
drives it — but it is convenient for logging or testing what a cookie
will look like on the wire.
HTTP.OutputStream
classAn HTTP.OutputStream collects the response body. The
servlet does not create it: the response object does, and
.response~output returns it. During servlet execution the
default output stream (.output) is redirected to it, so the
three statements below write to the same place:
Say "one" -- the usual way
.output~say("two") -- explicitly, via the default stream
.response~output~say("three") -- explicitly, via the response
Nothing reaches the client until the stream is flushed; the first flush commits the response — headers out first, then the buffered body. This buffering is what makes lazy headers possible, and it is the reason the class exists; the request/response model chapter tells that story. The servlet rarely needs to do anything: the processor flushes once after the servlet returns.
The class is a subclass of Stream and honours the stream
protocol as far as it makes sense for what it is: a write-only,
transient stream. It cannot be read (the input methods raise
NOTREADY) and it cannot be positioned. Lines written to it end in LF,
the web convention for text bodies.
arrayin,
charin, chars, linein,
lines, makearray, supplierThe stream is write-only: every input method raises the NOTREADY
condition, with a description naming the stream. If the condition is not
trapped, the usual Rexx semantics apply — the call is ignored and
returns a neutral value (the null string, 0, or
.nil, as appropriate to each method).
arrayout.response~output~arrayout( array, option )
.response~output~arrayout( array ) -- option defaults to "L"
Appends the elements of array — a single-dimensional array,
or an object that produces one when sent a request("ARRAY")
message — to the body. With option "LINES" (the
default; "L" will do) the elements are joined by LF, with
no LF after the last one; with "CHARS" ("C")
they are concatenated with no separator.
charoutrc = .response~output~charout( string )
Appends string to the body exactly as given — no line end.
The argument must have a string value. Returns 0 on
success. Called with no argument, charout closes the stream
(here: flushes it — see close). The stream protocol's
positioning argument is not accepted: this is a transient stream.
.response~output~charout("no trailing newline here")
close.response~output~close
Flushes any pending output. An HTTP output stream has no real notion of being closed — it remains writable afterwards; close is how the stream protocol spells "I am done for now", and here that means a flush.
command.response~output~command( string )
The stream-protocol command interface: the verbs OPEN,
CLOSE, FLUSH, QUERY,
SEEK and POSITION are accepted and dispatched
to the corresponding methods.
descriptionstring = .response~output~description
Returns the stream's state, followed by a description when there is something to describe — typically only when the underlying stream was not ready when the output stream was created.
flush.response~output~flush
The workhorse. Commits the response if it is not yet committed — this
is where the headers get written, in front of everything — then writes
the buffered body to the underlying CGI stream, flushes it, and empties
the buffer. .response~flush does the same thing through the
response object. Flushing explicitly mid-servlet is allowed, and fixes
the point past which headers can no longer change; what it does
not promise is client-visible streaming — see the
request/response model chapter.
initServlets do not construct output streams: the response object creates
one, wrapped around the real CGI output stream, when it is
created. The wrapped stream must be a Stream instance
proper — in particular, output streams do not nest: an
HTTP.OutputStream will not accept another one as its
underlying stream.
lineoutrc = .response~output~lineout( line )
Appends line, which must have a string value, followed by
LF. Returns 0 on success. Called with no argument, it
closes (flushes) the stream, like charout. The positioning
argument is not accepted.
open.response~output~open
An HTTP output stream is always open, unless its underlying stream reported a problem when the output stream was created — in which case it cannot be opened at all. The method returns the stream's state and changes nothing.
position, seekNot supported: transient streams cannot be positioned. Both raise.
qualifystring = .response~output~qualify
Returns the stream's name, the fixed label
HTTP.OutputStream:1.
queryvalue = .response~output~query( option )
The stream-protocol query interface, answered as befits a transient
stream that exists only for the duration of the request:
EXISTS returns the stream's label, SIZE the
null string, STREAMTYPE returns UNKNOWN,
DATETIME and TIMESTAMP return the stream's
creation time, and the POSITION/SEEK queries
return 1. HANDLE is not supported.
say.response~output~say( line )
.response~output~say -- emits an end-of-line
lineout with a default: called without an argument it
appends an empty line rather than closing the stream.
statestring = .response~output~state
The stream's state — READY in any response you will ever
want to send.
underlyingstreamstream = .response~output~underlyingstream
Returns the wrapped stream: the real CGI output, connected to the web
server. This is the response's plumbing — commit writes the
headers through it — and a servlet has no business writing to it
directly: anything sent there bypasses the buffer and lands in front of
content the response has not emitted yet, headers included.
HTTP.Request classAn HTTP.Request instance represents the request being
served. The servlet does not create it: the processor instantiates one
per incoming request, before the servlet runs, and hands it to the
servlet as its first argument; like the response, it is also published
in the global environment, as .request (the two forms are
equivalent — see the introduction to HTTP.Response).
The request object is read-only, and answers three kinds of
questions: what came with the request line and the headers (the
environment pool, and a set of accessors), what arguments the query
string or the request body carried (arg), and what cookies
the user agent sent (cookie).
By the time the servlet runs, the groundwork is done: the processor has parsed the arguments — reading the request body, for a POST — and applied the argument policy, so the arguments a servlet sees are always well-formed; and creating the request has set the current directory to the directory of the servlet file, so relative paths in the servlet resolve next to it.
CGI delivers a request as environment variables, and the request object embraces that: any message that does not match one of the methods below answers with the value of the environment variable of the same name.
agent = .request~http_user_agent -- the User-Agent request header
server = .request~server_name -- a standard CGI variable
raw = .request~http_cookie -- the raw Cookie header
A variable that is not set answers the null string. The pool is read-only: these messages accept no arguments, and there is no assignment form. The message name arrives uppercased — the usual Rexx behaviour — which is just what the upper-case names of the CGI world expect.
The lookup understands the deployment it lives in. Under an
Action-based deployment, Apache reaches the processor
through an internal subrequest, and hands some variables over under a
REDIRECT_ prefix. The request object absorbs that
detail:
HTTP_ — a request header, in the
form Apache passes them — is looked up as given.REDIRECT_ prefix. So the standard CGI
variables, which arrive unprefixed, are never shadowed, while a value
that exists only behind the Action subrequest's
REDIRECT_ is still found.So .request~remote_addr simply works, and a
SetEnv MYAPP_MODE production in an .htaccess
is .request~myapp_mode wherever Apache actually put the
value — prefixed or not. There is no special namespace: an
operator-defined variable is an ordinary pool name. If you ever want the
prefixed form explicitly, you can always name it:
.request~redirect_remote_user.
REXXHTTP_ARGPOLICY, the argument-policy switch, is read
through this same pool — which is why a per-directory
SetEnv is all it takes to configure it (next section).
Before the servlet runs, the processor parses the request arguments —
the query string of a GET, the
application/x-www-form-urlencoded body of a POST — and
applies the argument policy. The policy of the 1.0,
strict, the default and the only one, demands of every
parameter that:
=value — an empty
value (k=) is legitimate, a token without = is
not;arg anyway, since positions take precedence there);X and
x are the same name.These rules are applied to the decoded name — the one the
servlet will see and key on. Two spellings that decode to one name (say
user+name and user%20name) are therefore a
single name, and a request carrying both is rejected as a duplicate.
A request that breaks one of these rules is answered with
400 Bad Request, stating the reason, and the servlet never
runs. Stray &s — doubled, or trailing — are ignored,
not rejected. The policy is configured per directory or per servlet with
SetEnv REXXHTTP_ARGPOLICY in the Apache configuration; the
value is caseless, strict is the only value the 1.0
recognises, and an unknown value is answered with a 500
configuration error — an operator mistake, not a client one.
The payoff is what the servlet gets to assume: every argument it sees has a name and a value, and a name names exactly one argument.
Argument names and values alike are delivered decoded —
percent-escapes, and the +-for-space convention of form
encoding, applied to both sides of each pair as
application/x-www-form-urlencoded prescribes. So a field
named user name, which a form sends as
user+name (or user%20name), is reached as
arg("user name").
[]value = .request[name]
Bracket lookup: name, which must have a string value, is uppercased, its hyphens become underscores, and the result is looked up in the environment pool — always, never against a like-named method. The bracket form is the raw-variable door: it lets you reach an environment variable by its HTTP spelling even when a method shares the name.
length = .request["Content-Length"] -- the CONTENT_LENGTH variable, raw
agent = .request["HTTP-User-Agent"] -- the HTTP_USER_AGENT variable
This is the complement of the message form:
.request~content_length gives you the method (the parsed
length), while .request["Content-Length"] gives you the raw
environment variable of that name. Use ~name for the
library's view, [name] for the variable underneath.
There is no bracket assignment: the request is read-only.
argcount = .request~arg
value = .request~arg( n )
value = .request~arg( name )
value = .request~arg( n , option )
value = .request~arg( name, option )
Access to the request arguments, modelled on the built-in
Arg function. Called with no arguments, it returns how many
there are. Otherwise the first argument, which must be given and must
have a string value, selects one — by position, when it is a positive
whole number; by name, compared caselessly, in any other case — and
option says what to return about it. The option is identified
by its first nonblank character, caselessly, again in the manner of the
built-in:
Value — the default — returns the argument's value,
decoded; the null string when there is no such argument.Name returns the argument's name, in its original
spelling.Exists returns whether such an argument exists.Omitted returns the opposite.-- /servlet?q=hello&Lang=ca
.request~arg -- 2
.request~arg(2, "Name") -- "Lang" (the spelling is preserved...)
.request~arg("lang") -- "ca" (...but lookup is caseless)
.request~arg("page", "Exists") -- 0
Positional access reaches the arguments in the order the request sent
them; a position beyond the count yields the null string (and
Exists and Omitted answer accordingly). Under
the strict policy these are the only shapes a request can have: see the
previous section for what arg never has to cope with.
content_lengthlength = .request~content_length
The size of the request body, in bytes, as the request declared it; the null string when the request carried none (a GET, typically).
content_typetype = .request~content_type
The media type of the request body, as the request declared it — for
a form POST, application/x-www-form-urlencoded; the null
string when the request carried none.
cookiecount = .request~cookie
value = .request~cookie( n )
value = .request~cookie( name )
value = .request~cookie( n , option )
value = .request~cookie( name, option )
Access to the cookies the user agent sent, with the same shape as
arg: no arguments for the count, a first argument —
required, with a string value — that selects by position when it is a
positive whole number and by name in any other case, and an
option choosing among Name, Value
(the default), Exists and Omitted, with the
same meanings as in arg. Two differences, both deliberate.
Lookup by name is case-sensitive: session and
Session are different cookies, to the request object as to
the user agent. And the option is given as an abbreviation of the option
word — any leading part will do, caselessly — rather than by its first
character alone.
Cookie values are delivered percent-decoded, and only
percent-decoded: the +-for-space convention belongs to form
encoding, not to cookies, so a literal + in a value
survives the trip. This is the reading half of the round trip stated in
the HTTP.Cookie reference — encode on output, read plain on
input — and it is safe for the whole repertoire: an encoded
; (%3B) comes back as a ;
inside the value, never as a cookie separator.
When the request carries several cookies with the same name — the
same name under different Paths, say — positional access
reaches every one; lookup by name resolves to the last one sent.
If .request~cookie("session", "Exists") Then
session = .request~cookie("session")
filenamepath = .request~filename
The full filesystem path of the servlet file.
path_translated answers the same.
method,
request_methodverb = .request~method
The request method, as sent: GET, POST, and
so on. method and request_method are one
method under two names.
path_infoextra = .request~path_info
The extra path: what the request URI carries beyond the part that
locates the servlet, decoded, /-separated. A request for
/test/show/reports/2026, served by the servlet file
show, has the script_name
/test/show and the path_info
/reports/2026. The null string when there is none.
path_translatedpath = .request~path_translated
The full filesystem path of the servlet file — where the request URI
landed once Apache translated it. filename answers the
same.
post_stringbody = .request~post_string
The body of a POST request, exactly as received — still in its
transfer encoding; the processor has already read it by the time the
servlet runs. The null string for a GET. The parsed, decoded view of the
same data is arg.
query_stringquery = .request~query_string
The query string, exactly as received — still encoded, without the
?. The null string when there is none. The parsed, decoded
view of the same data is arg.
request_uri,
unparseduriraw = .request~request_uri
The request URI exactly as the client sent it: path and query string,
still encoded. unparseduri is the same method under a
second name.
script_namename = .request~script_name
The URI path that locates the servlet, decoded: uri
without the path_info.
system_versionversion = .request~system_version
The processor identification string — for this release,
REXXHTTP/1.0 20260611.
unknownThe environment pool described at the top of this reference is
implemented by unknown: a message that matches no method
answers the corresponding environment variable. The get form only — such
a message accepts no arguments.
uripath = .request~uri
The path of the request, decoded, query string excluded:
script_name plus path_info.
validateviolation = .request~validate
The processor's hook. Parses the arguments eagerly — reading the body
of a POST — and applies the argument policy, returning the null string
when the request is acceptable, and a short code:detail
description of the first violation otherwise. The processor calls it
before the servlet runs — this is where the 400 of the
strict policy comes from — so a servlet never needs to: by the time a
servlet runs, validate has already returned the null
string.
decodeURIComponenttext = .request~decodeURIComponent( enc )
text = .HTTP.Request~decodeURIComponent( enc )
Reverses percent-encoding: each %xx triple, where
xx is two hex digits, becomes the byte it names; everything
else passes through untouched. It is the inverse of
encodeURIComponent on the response object, and the routine
the processor uses internally to decode path and query components. A
lone % not followed by two hex digits is left as a literal
%, so malformed input degrades gracefully rather than
raising.
Like the encoders, it exists both as a class method
(.HTTP.Request~decodeURIComponent(s), no request in hand)
and as an instance method that delegates to it.
decodeFormtext = .request~decodeForm( enc )
text = .HTTP.Request~decodeForm( enc )
Decodes one field of an
application/x-www-form-urlencoded body: %xx
triples become their bytes, and a + becomes a
space. Since a conforming form serialiser emits a space as
+ but a hand-built query string may use %20,
it accepts both. It is the inverse of the response object's
encodeForm, and the routine the processor uses internally
to split a form body into its fields. The same class-and-instance
pairing applies.
HTTP.Response
classAn HTTP.Response instance represents the response being
built for the current request. The servlet does not create it: the
processor instantiates one response per request, before the servlet
runs, and hands it to the servlet as its second argument. It is also
published in the global environment, so both of these reach the same
object:
Use Arg request, response -- as the second argument...
...
.response~flush -- ...or as an environment symbol
The two are equivalent. The environment symbol has the advantage of
working in any scope — an internal routine, a ::Routine, a
method — without the variable having to travel there, and this manual
uses that form, .request and .response,
throughout.
A response is made of three parts: a set of headers, an
optional set of cookies, and a buffered output stream
that collects the body. The headers and cookies remain freely modifiable
while the servlet writes the body; they are serialised only when the
response is committed, which happens at the first flush (see
the request/response model chapter). After the servlet returns, the
processor issues a final flush, so a servlet that never touches the
response object beyond Say still produces a complete,
well-formed response.
Headers live in a directory keyed by the upper-cased header name. Two different syntaxes reach that directory, and they normalise the name differently — deliberately so, because each syntax has its own grammar:
Message form.
.response~cache_control = "no-store" and its read
counterpart .response~cache_control. A hyphen is not legal
inside a Rexx message name (it would parse as a subtraction), so the
message form uses an underscore wherever the HTTP name has a hyphen; the
response object translates underscores to hyphens to reach the real
header name.
Bracket form.
.response["Cache-Control"] = "no-store" and
.response["Cache-Control"]. Here the name is an ordinary
string, the hyphen poses no problem, and the name is used as written;
only its case is normalised.
Both routes converge on the same internal key
(CACHE-CONTROL), each starting from what its own grammar
allows. One practical consequence: a header whose real name contains an
underscore can only be reached through the bracket form, since the
message form would turn that underscore into a hyphen.
Header names are case-insensitive in HTTP. Internally they are stored
upper-cased — that is what makes the lookup case-insensitive, so that
the message form, the bracket form and any mix of case all reach the
same entry. On the way out, though, the name is title-cased for
readability: the response to the example above carries the header line
Cache-Control: no-store, not
CACHE-CONTROL: no-store. The rule is mechanical — each
hyphen-separated word gets an initial capital — so a few acronym headers
come out unconventionally (WWW-Authenticate would emit as
Www-Authenticate). RexxHTTP never sends those itself, and
since HTTP treats the names case-insensitively no client minds; only a
test that compares a raw header block case-sensitively need take
note.
Every response carries a Content-Type: the header
directory is seeded at creation time with the default
text/plain; charset=utf-8, which the servlet may overwrite
but not remove.
Four methods turn raw text into a form that is safe to drop into HTML
or into a URL. They are the counterpart to the two decoders on the
request object (decodeURIComponent and
decodeForm), which undo the URL forms on the way in; there
is deliberately no HTML decoder, since a servlet emits HTML but
is never handed HTML to parse.
Each method exists twice: as a class method, callable with no
response in hand (.HTTP.Response~encodeHTML(s)), and as an
instance method on a live response
(.response~encodeHTML(s)). The two are identical — the
instance form delegates to the class form — so a servlet uses whichever
is closer to hand. None of them touches the response; they are pure
string functions that happen to live here because emitting safe output
is the response's job.
encodeHTMLsafe = .response~encodeHTML( text )
safe = .HTTP.Response~encodeHTML( text )
Escapes the five characters that are unsafe in HTML, returning a
string fit to interpolate into either element content or a quoted
attribute value. The five are & →
&, < → <,
> → >, " →
" and ' → ';
& is converted first, so an escape is never
double-escaped. Escaping the two quote characters as well as the three
markup characters is what makes the result safe inside an attribute, not
only between tags — a value escaped for element content but interpolated
into title="..." would otherwise let a " break
out of the attribute. This is the method error uses for its
diagnostic line.
encodeURIComponentenc = .response~encodeURIComponent( text )
enc = .HTTP.Response~encodeURIComponent( text )
Percent-encodes text for use as a single component inside a
URL — a query field value, one path segment — following RFC 3986. Every
character outside the unreserved set (A–Z,
a–z, 0–9,
-, ., _, ~) is
replaced by % followed by its hexadecimal byte value; a
space becomes %20. Crucially it escapes the reserved
characters & / = ? # too, so a component carrying any
of them cannot break out and alter the structure of the URL it sits in.
Use it on the pieces, never on a whole URL — for that, see
encodeURI.
url = "/search?q=".response~encodeURIComponent(term)
encodeURIenc = .response~encodeURI( text )
enc = .HTTP.Response~encodeURI( text )
Percent-encodes an already-assembled URL, escaping only what
is illegal or unwise in a URL — control characters, space, the
high-ASCII bytes, and the literal characters
< > % " { } [ ] \ ^ ` — while leaving intact the
reserved characters that give a URL its structure
(/ ? # & = @ : + ; , $ ! ' ( ) *). A space becomes
%20. The contrast with encodeURIComponent is
the whole point of having both: that method escapes
& / = ? #, this one preserves them. The two carry the
same names as their JavaScript namesakes and divide the work the same
way — encodeURI for a complete URL,
encodeURIComponent for a value going inside one. Reach for
the one that matches the job; they are not interchangeable.
encodeFormenc = .response~encodeForm( text )
enc = .HTTP.Response~encodeForm( text )
Percent-encodes text for an
application/x-www-form-urlencoded body, the conforming
serialisation defined by the WHATWG URL Standard. It is the twin of
encodeURIComponent with the two differences the form media
type mandates: the safe set is narrower — only
A–Z, a–z,
0–9, *, -,
., _ stay raw, so ~ is
escaped here though it is not by encodeURIComponent — and a
space becomes + rather than %20. It is the
exact inverse of the request object's decodeForm; the two
are designed as a round-trip pair.
There is no first-class status method, and none is needed. Under CGI
the response status is set through the Status pseudo-header
(RFC 3875), which to the response object is a header like any other:
.response~status = "404 Not Found"
commit emits it with the rest of the headers; Apache
consumes it and builds the corresponding HTTP status line (the
Status line itself does not reach the client). A response
that sets no status is a 200 OK.
Setting status by hand is the low-level route. For the
common cases — sending an error page or redirecting —
error, 404 and redirect set the
status, the matching headers and (for errors) a body in one call, and
are written to be the last statement a servlet runs:
Exit .response~error(404).
error.response~error( status, detail )
.response~error( status ) -- detail is optional
Emits an error response with HTTP status status and a short
HTML page, and returns 0. status is a numeric
code; detail, if given, is shown on the page as a single
diagnostic line and is HTML-escaped (see the encoding methods below), so
a detail containing any of & < > " ' is displayed
literally rather than interpreted.
The reason phrase is supplied automatically for the codes a servlet
is likely to use (the 3xx redirects, 400,
401, 403, 404, 405,
500, 502, 503). A code outside
that set still works: it is given a generic phrase for its family —
Redirect, Client Error or Server Error — so
an unusual status is never refused.
error follows the buffering model. If the response has
not been committed, any body the servlet had written so
far is discarded, the status and a text/html content type
are set, and the error page replaces the buffered body; the normal flush
then sends it. If the response has been committed — the
head has already reached the client, so the status can no longer be
changed — error raises a Syntax condition
rather than pretend to do the impossible. This mirrors the servlet rule
for sendError on a committed response.
Because it returns 0, error reads naturally
as a servlet's final act:
If \record~exists Then Exit .response~error(404, "no such record")
404.response~404
.response~404( detail ) -- detail is optional
Shorthand for error(404 [, detail]) — the single most
common error from a servlet, given its own name. Same return value and
same buffering rules.
redirect.response~redirect( location, status )
.response~redirect( location ) -- status defaults to 302 (Found)
Emits a redirect to location and returns 0. It
sets the Location header and a 3xx status;
status defaults to 302 (Found), the
conventional temporary redirect. No body is sent.
A different code is requested explicitly. The two that come up in
practice: 301 (Moved Permanently) when a resource
has acquired its canonical URL and the new address should replace the
old — for example when an optional slug is added to a path — and
303 (See Other) after a form POST, so
that a refresh re-fetches the result page with GET instead
of resubmitting the form. Note that 301 is cached
aggressively by user agents; use it only for a move that is genuinely
permanent, never for one that might be undone.
Exit .response~redirect("/publishers/acme-press/", 301)
redirect obeys the same commit discipline as
error: it discards an uncommitted buffered body and raises
if the response is already committed.
[]value = .response[headername]
Returns the current value of header headername, or
.nil if no such header has been set. The name is
case-insensitive and is given literally, hyphens included.
headername must have a string value.
[]=.response[headername] = headervalue
Sets header headername to headervalue, creating or
overwriting it. Headers are single-valued: assigning to an existing name
replaces its value. Both arguments must have string values. Headers may
be set after body output has begun — that is the point of the design;
see the request/response model chapter — but must be set before the
first flush: once the response is committed the head is already on the
wire, so a late assignment cannot take effect and is treated as a
programming error rather than silently ignored — it raises (error
93.900, the response is already committed). This matches
error, redirect and addcookie,
which obey the same discipline; see Commit discipline in the
request/response model chapter.
addcookie.response~addcookie( cookie )
Schedules cookie, which must be an HTTP.Cookie
instance, for emission as a Set-Cookie header at commit
time. The cookie must be internally consistent: an inconsistent one —
say, SameSite=None without Secure — is not
accepted, and addcookie raises at once. The consistency
rules are given in the HTTP.Cookie reference.
A cookie scheduled after the response is committed could never be
emitted — the head is already on the wire — so, like []=,
addcookie treats this as a programming error and raises
(error 93.900) rather than dropping the cookie silently. The commit
check comes first, before the argument is even type- checked: past
commit the failure is already committed, not not a
cookie.
The cookie is stored by copy: changes made to the cookie
object after addcookie returns do not affect what will be
emitted. Cookies are keyed by the triple (name, Path,
Domain), matching the identity rules user agents apply:
adding a cookie with the same name but a different Path or
Domain yields an additional Set-Cookie line,
while re-adding one with the same triple replaces the previous one. The
relative order of the Set-Cookie lines in the response is
unspecified.
c = .HTTP.Cookie~new("session", "abc123")
c~httponly = 1
.response~addcookie(c)
commit.response~commit
Serialises the response head: writes the Content-Type
header, then one Set-Cookie line per scheduled cookie, then
the remaining headers, then the blank line that separates head from body
— all directly to the underlying CGI output stream, ahead of the
buffered body. Commit is idempotent: the first call emits the head and
marks the response committed; subsequent calls return immediately.
Servlets rarely call commit directly: the first
flush — explicit, or the processor's final one — commits
the response if needed. Once the head is committed it cannot be changed:
any later attempt to set a header ([]= or
response~name = value), schedule a cookie
(addcookie) or replace the response wholesale
(error, 404, redirect) raises
error 93.900 rather than failing silently. The body may still be written
and flushed; only the head is frozen.
committedstate = .response~committed
Returns .true if the response has been committed (its
headers have been emitted), .false otherwise. Takes no
arguments.
flush.response~flush
Commits the response if it is not yet committed, then flushes the
buffered output stream, writing the body collected so far to the
underlying CGI stream. Returns nothing. The processor calls it once
after the servlet returns; a servlet may also call it explicitly at any
point (see the request/response model chapter for what an early flush
does — and does not — buy you once mod_deflate sits
downstream).
initServlets do not create responses; the processor does, once per
request. A fresh response starts with exactly one header,
Content-Type: text/plain; charset=utf-8; no cookies; not
committed; and a new HTTP.OutputStream wrapped around the
current default output stream as its body buffer.
outputstream = .response~output
Returns the response's buffered output stream, an
HTTP.OutputStream instance. During servlet execution the
default output stream (.output) is redirected to this same
object, which is why a plain Say writes to the response
body; .response~output is the explicit handle, for stream
methods like ~charout or an explicit
~flush:
.response~output~charout("no trailing newline here")
unknownThe message forms described above —
.response~content_type = "text/html" -- set
type = .response~content_type -- get
— are not individual methods: they are resolved by
unknown, which maps any unknown message to the header of
the same name, translating underscores to hyphens (so
content_type reaches CONTENT-TYPE). The set
form requires exactly one value, which must have a string value; the get
form accepts no arguments. Reading a header that has not been set
returns .nil, exactly as with the bracket form.
Because every unknown message becomes a header access, the
response object cannot tell a header from a typo. A misspelled getter
that carries arguments fails fast (.response~addcokie(c)
raises, since the get form accepts no arguments), but a misspelled
assignment fails silently: .response~comitted = 1
quietly creates — and emits — a header named COMITTED. Mind
your spelling; that is the price of the convenience.
This chapter explains the design — not what RexxHTTP does, which the rest of the manual covers, but why it does it that way. The shape of the 1.0 is the result of a few decisions, most of them inherited from the 2006 original and kept because they are still right, a couple of them new and made by subtraction.
The largest decision is one of those subtractions, and the rest of
this chapter reads more clearly once it is on the table. The 2006
RexxHTTP tried to be portable along three axes at once: it ran under
both CGI and mod_rexx, on OS/2 and Windows as well as Unix, and aimed at
Microsoft IIS beside Apache. Much of the original rationale was the
bookkeeping of holding those axes together — reconciling the variables
CGI gave you with the ones mod_rexx gave you, working around an OS/2
Object Rexx that lacked a MutableBuffer, and so on. The 1.0
keeps one point in that space: CGI, on Apache, on a current ooRexx.
mod_rexx is no longer maintained upstream, IIS and OS/2 are not targets
anyone is asking for, and committing to a single gateway is what lets
the rest of the design be simple. Several of the decisions below were
originally compromises between two gateways; with only one gateway left,
they reduce to a single clear answer, and this chapter states the answer
rather than the old negotiation.
A servlet is an ordinary Rexx file, and the web server has to be told
to run it through RexxHTTP rather than serve it or execute it directly.
Apache's Action directive is the mechanism: it binds a
handler name to the RexxHTTP processor, and a small wrapper invokes that
handler for the servlet files you nominate. This is the same approach
the 2006 manual described, and it has aged well precisely because it
asks nothing unusual of Apache — Action is a stock
directive, and the routing is data in the configuration rather than
logic in the processor.
What has changed is the disposition. In 2006 the natural move was to
map broad extensions — .htm, .html,
.rsp — to the handler, because the point was to run whole
trees of pages, some of them compiled, through one gateway. The 1.0 has
no page compilers and no such ambition, and the Installation chapter recommends the opposite
default: a whitelist, so that only the files you explicitly mark are run
as servlets and everything else is served as a static file. On
infrastructure that has served tens of thousands of URLs for years, "run
as code only what is named" is the conservative and correct default;
opening an extension to the handler is the exception, made
deliberately.
CGI delivers a request as a set of environment variables, and
RexxHTTP takes that literally: the request object answers any message it
does not recognise with the value of the like-named environment
variable. This is the single most Rexx-like decision in the design. Rexx
programs reach their world through Value and through
variables; making request~remote_addr resolve to the
REMOTE_ADDR the server set means a servlet author touches
the CGI environment the same way they touch everything else, without a
lookup table or an import step. The mechanics are in the Class Reference
under The environment pool; the
reason is that the request object should feel like Rexx, not like a
wrapper bolted over Rexx.
This convenience raises one question. If
request~anything can name an environment variable, then a
variable could share a name with a method — present or future — and the
author needs a predictable answer to which one wins. The 2006 manual
settled it by carving out a namespace: operator-set variables had to
begin with an underscore, where no method would ever live. It worked,
but it asked the author to remember a naming rule, and the variables
that motivated it — timezone hints for the old Netscape-style cookies,
settings for the page compilers — are all gone from the 1.0. So the rule
went with them, in favour of something plainer: two doors instead of one
reserved name. request~name is the library's view, where a
method wins when one exists, exactly as Rexx method lookup already
works; request["name"] is the raw variable underneath,
which always reaches the environment pool and never a method. An
operator-set variable is then just an ordinary variable —
SetEnv MYAPP_MODE production is
request~myapp_mode, no underscore required — and if a
future method ever shadowed it, the bracket form reaches it regardless.
The collision question is answered not by reserving names but by giving
the author a way to ask for either side of one.
The trickiest part of the 2006 rationale was titled "the problem of request values," and the problem was real: what Apache actually invokes is the RexxHTTP processor, not your servlet, so the raw CGI variables describe the processor's invocation, not the request the servlet means to read. On top of that, the CGI specification is in places ambiguous or self-contradictory, and the 2006 system had to paper over differences between what CGI reported and what mod_rexx reported for the same notion.
With mod_rexx gone, the second half of that problem evaporates: there is one gateway, so there is nothing to reconcile. What remains is the first half — reconstructing the values a servlet should see from the values the processor is handed — and a commitment the 2006 design made and the 1.0 keeps: where CGI is inconsistent, RexxHTTP is consistent, and it documents what its methods mean rather than passing the server's quirks through. Three cases from the original are worth keeping, because the methods still behave as described and the reasoning is still the cleanest account of why:
path_translated always answers the full filesystem path
of the servlet file, whether or not there is extra path information
after it. The CGI specification's own definition of this variable is
incoherent — it promises a "translated version" of the extra path info,
which by construction has no physical mapping — and Apache, left to
itself, returns something useless. RexxHTTP picks the meaning that is
actually useful and holds to it.
script_name always answers the virtual path that locates
the servlet, with any extra path information stripped — what you want
for building self-referential URLs — and path_info answers
that extra path, decoded, or the null string when there is none. The
split between the two is clean by design, so a servlet can always ask
"where am I?" and "what came after me?" as separate questions.
request_uri (with its alias unparseduri)
answers the request URI exactly as the client sent it, still encoded;
uri answers the decoded path with the query string removed.
Naming both, with stable meanings, spares the servlet author from
guessing which encoding and which trimming a given server happened to
apply.
The thread through all three is the same: the request object is a specification the servlet can rely on, not a thin pass-through of whatever the gateway felt like reporting.
The 1.0 parses GET and POST arguments under a single, default, and
currently only argument policy, strict: every parameter
must be a genuine name=value pair, the
name must be a usable Rexx symbol and must be unique caselessly, and a
request that breaks any of these is answered
400 Bad Request before the servlet runs at all. The
mechanics are in Arguments,
and the strict policy; the decision is to make this the default
rather than an opt-in.
The reasoning is specific to the kind of service RexxHTTP exists to
run. For a public site with a large, stable set of URLs — the case
RexxHTTP was extracted from — malformed argument input is not a case to
be handled gracefully; it is a request that cannot have come from the
site's own pages, and the safe, predictable response is to reject it at
the door. Strict-by-default turns a class of ambiguous input into a
clear 400, and in exchange every servlet downstream gets to
assume that each argument it sees has a name and a value, and that a
name names exactly one argument. The strictness is a feature paid for
once, at the boundary, so that no servlet has to pay for it again.
RexxHTTP is free software, released under the Apache License, Version 2.0.
Copyright 2006–2026 Josep Maria Blasco
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
The complete text of the License accompanies the distribution, in the
file LICENSE at the root of the repository.
This release is the first published under the Apache License. The 2006 version was distributed under the Common Public License; the present 1.0, and the manual you are reading, replace it — the licence included.