This skill targets RexxHTTP 1.0, running on ooRexx 5.3.0 or later and Apache 2.4 or later with the
cgidandactionsmodules. It was written and tested against that combination.Requires the
apache-cgi-oorexxskill, which in turn requiresoorexx-run. This skill assumes you already have ooRexx installed (rexx -vworks) and Apache serving ooRexx CGI scripts. It does not repeat that setup — do those two first. What this skill adds is the RexxHTTP processor on top: deploying its sources, wiring Apache to it, writing a servlet, and verifying the whole pipeline answers.Environment note. The paths below (
/var/www/html/...) and the commands (apt,a2enconf,/var/log/apache2/) assume a Debian/Ubuntu Apache layout. Substitute the locations that match your machine.
RexxHTTP is a servlet processor for ooRexx: a servlet is an ordinary
ooRexx program that receives a request and a
response object and produces its body with plain
Say or CharOut. The processor builds those
objects, runs your servlet, and flushes the response. The central design
idea is lazy headers — you can write body content before fixing
the response headers, and they are emitted on the first flush.
The pipeline, end to end: Apache routes a request to a one-line
wrapper, which Calls
RexxHTTP.rex (the processor); the processor builds the
request/response objects, runs the servlet named by the request path,
and flushes. You configure that route once; after that, adding a servlet
is one line.
The sources are distributed as a zip. Download it, and extract the processor sources plus one example servlet to use for verification:
curl -sO https://rexx.epbcn.com/rexxhttp/rexxhttp-latest.zip
unzip -qo rexxhttp-latest.zip -d /tmp/rexxhttp
You need two things from it: everything under src/ (the
processor RexxHTTP.rex and its four classes — they load
each other via ::Requires, so they travel together) and one
servlet to test with. Each example is a servlet file named
index inside its own subfolder, so the hello-world servlet
is examples/hello-world/index — a one-liner
(Say "Hello, world!") and ideal for verification. (Don't
confuse it with examples/index, which is the examples
catalogue, not a servlet.)
The processor sources live in one directory; the servlets you serve live in another. Deploy the five sources, then create the wrapper:
sudo mkdir -p /var/www/html/RexxHTTP
sudo cp /tmp/rexxhttp/src/RexxHTTP.rex \
/tmp/rexxhttp/src/HTTP.Request.cls \
/tmp/rexxhttp/src/HTTP.Response.cls \
/tmp/rexxhttp/src/HTTP.OutputStream.cls \
/tmp/rexxhttp/src/HTTP.Cookie.cls \
/var/www/html/RexxHTTP/
The wrapper is a one-line ooRexx script whose only
job is to Call the processor. It must be saved with
LF line endings and have a clean #!
shebang, because the kernel exec's it directly:
sudo tee /var/www/html/RexxHTTP/wrapper > /dev/null << 'WRAP'
#!/usr/bin/env rexx
Call "/var/www/html/RexxHTTP/RexxHTTP.rex"
WRAP
sudo chmod 755 /var/www/html/RexxHTTP/wrapper
Why the wrapper at all: the .rex/.cls
sources are stored and checked out as CRLF (they are
edited on Windows; ooRexx reads CRLF fine). A kernel cannot exec a
script whose #! line ends in CR — it would look for
/usr/bin/env rexx\r and fail with not found
(exit 127). The wrapper sidesteps this: its own shebang is clean LF, and
RexxHTTP.rex is reached through Call, never
exec'd, so its CRLF shebang is just an inert comment. Do
not "fix" the sources to LF — keep them CRLF and let
the wrapper do its job.
Servlets live in their own directory, gated by a whitelist
.htaccess:
sudo mkdir -p /var/www/html/test
sudo cp /tmp/rexxhttp/examples/hello-world/index /var/www/html/test/hello-world
The .htaccess lists exactly which files are
handled as servlets; everything else in the directory is served as a
static file. This is a whitelist on purpose — see the warning under
"Adding more servlets".
sudo tee /var/www/html/test/.htaccess > /dev/null << 'HT'
<Files hello-world>
SetHandler RexxHTTPCGI
</Files>
HT
This wires Apache to the processor. The Action points at
the wrapper, never at RexxHTTP.rex
directly (for the CRLF-shebang reason above). Write it as a conf snippet
and enable it:
sudo tee /etc/apache2/conf-available/rexxhttp.conf > /dev/null << 'CONF'
ServerName localhost
<Directory "/var/www/html/RexxHTTP">
Options +ExecCGI
AddHandler cgi-script .rex
Require all granted
</Directory>
ScriptAlias /RexxHTTP/ /var/www/html/RexxHTTP/
# Action points at the wrapper (one-line LF Rexx that Calls RexxHTTP.rex),
# not at RexxHTTP.rex directly: the wrapper's LF shebang keeps the kernel
# happy, and RexxHTTP.rex is reached via Call so its CRLF shebang is inert.
Action RexxHTTPCGI /RexxHTTP/wrapper
<Directory "/var/www/html/test">
AllowOverride All
Options +ExecCGI
Require all granted
</Directory>
CONF
sudo a2enconf rexxhttp
sudo service apache2 restart
AllowOverride All on the servlet directory is what lets
the .htaccess take effect. Without it, the whitelist is
ignored and your servlets are served as static text.
Request the servlet. You should get HTTP 200 and the body:
curl -s -i http://127.0.0.1/test/hello-world
Expected:
HTTP/1.1 200 OK
...
Content-Type: text/plain; charset=utf-8
Hello, world!
Note the response convention: headers are CRLF, the body is
LF (per RFC for the headers; the body carries whatever the
servlet wrote). A plain Say produces
text/plain; charset=utf-8 by default — a servlet sets
response~content_type = "text/html" when it wants HTML.
If you get the source code of the servlet back instead of
Hello, world!, the .htaccess is not taking
effect — check AllowOverride All and that
actions is enabled. If you get a 500, look in
/var/log/apache2/error.log.
This is where the two-directory montage pays off. To add a servlet,
drop the file in /var/www/html/test/ and add one stanza to
the .htaccess:
<Files my-servlet>
SetHandler RexxHTTPCGI
</Files>
.htaccess is read per request, so a new servlet is live
immediately — no Apache reload. The existing servlets
keep working untouched.
The whitelist is a safety boundary, not a formality.
A file that is not listed is served as a static file — its
source code goes out verbatim. Never rely on "it's in the servlets
directory" to mean "it runs": if it is not in the
.htaccess, its source is public. List every servlet, and
keep nothing secret in that directory that is not listed.
A servlet is ordinary ooRexx, but a few non-obvious rules will catch you:
The invocation contract is frozen. A servlet
receives Use Arg request, response; .request
and .response also exist in the environment;
Say and CharOut write the body. Production
servlets rely on this exactly — don't expect a different
signature.
Line endings: .rex/.cls are
CRLF, the wrapper is LF. Keep the processor sources CRLF
(ooRexx reads them fine; the wrapper protects the exec path). Only the
wrapper must be LF.
& does not short-circuit in
ooRexx. For an "exists and matches" guard, use a comma
in the If, not &:
If request~exists("q"), request~arg("q") \== "" Then. With
& both sides evaluate and a missing value blows up at
runtime — and a test outside Apache may not reproduce it.
JSON needs its own ::Requires.
.json is not one of the four classes the processor loads. A
servlet that parses or builds JSON must end with
::Requires "json.cls", or it dies with Error 97, method
not found. (A Signal On Syntax handler can mask this
as a parse failure — if JSON "won't parse", check the requires
first.)
Linux is case-sensitive.
::Requires "HTTP.Request.cls" must match the filename on
disk exactly. http.request.cls is a different
file.
Output encoding. The response object offers
encoders for output (encodeHTML for the five HTML entities
& < > " ', encodeURIComponent,
encodeURI, encodeForm) and the request offers
the matching decoders (decodeURIComponent,
decodeForm). Escape any external value you echo into a page
with encodeHTML. See the manual's Class Reference for the
full set.
.htaccess not taking effect. Check
AllowOverride All on the servlet
<Directory>, that the file is listed in the
whitelist, and that a2enmod actions was run.error.log shows exit 127 / "not
found" — the CRLF-shebang trap. The Action is
pointing at RexxHTTP.rex directly, or the wrapper was saved
CRLF. The wrapper must be LF and the Action must point at
it.Error 43: Routine not found — a
source did not deploy. All five files (RexxHTTP.rex + the
four HTTP.*.cls) must sit together in the processor
directory.Error 97: ... json — a JSON
servlet without ::Requires "json.cls". Add it.RexxHTTP.rex guards against being run as a servlet directly
and returns 404. Requests must go through the wrapper to a servlet, not
to the processor.The montage above is the real two-directory pattern, kept minimal.
For a complete, multi-servlet deployment with environment overrides and
an automated montage — the same shape, exercised end to end — see
tests/http/ in the package: RunHTTPTests.rex
deploys the five sources plus the wrapper, writes this exact Apache
configuration, and runs a suite of fixture servlets against the live
stack. Its readme.md documents the montage and the
fixtures.