CGI installation
This site is distributed as a zipfile which contains all the programs to run the Rexx Parser and its child project programs, such as the Rexx Highlighter, as well as the related documentation: the file you are reading now, for example, is part of this documentation.
The documentation consists of a set of Markdown files.
On the reference distribution site, https://rexx.epbcn.com/rexx-parser/, Markdown is
translated on-the-fly and then served as HTML by a Rexx CGI program,
CGI.markdown.rex
, reproduced at the
end of this page.
[CGI stands for Common Gateway Interface, and it is a language-neutral specification that allows the development of dynamic web pages using any computer language --- including Rexx! You can read more about CGI in this Apache tutorial.]
How does CGI.markdown.rex work? ┌────────────────────────────────────────┐ │ Markdown source file │ │ May also contain: │ (1) │ • Standard HTML/CSS/JavaScript │ │ • Bootstrap HTML and CSS │ │ • Rexx fenced code blocks │ └───────────────────┬────────────────────┘ ▼ ╔═══════════════════╧════════════════════╗ ║ bin/FencedCode.cls ║ (2) ║ • Processes Rexx code blocks ║ ║ • Produces standard Markdown ║ ╚═══════════════════╤════════════════════╝ ▼ ╔═══════════════════╧════════════════════╗ ║ Pandoc ║ (3) ║ • Processes Markdown file ║ ║ • Produces standard HTML ║ ╚═══════════════════╤════════════════════╝ ▼ ╔═══════════════════╧════════════════════╗ ║ cgi/rexx.epbcn.com.optional.cls ║ (4) ║ Adds rexx.epbcn.com localisms: ║ ║ headers, footers, sidebars, etc. ║ ╚═══════════════════╤════════════════════╝ ▼ ┌───────────────────┴────────────────────┐ │ Final HTML file │ (5) │ → cgi/CGI.markdown.rex sends the │ │ resulting HTML to the browser │ └────────────────────────────────────────┘
Such a workflow has a number of advantages:
- Your web pages are written in Markdown. Markdown is much easier to write, learn, teach, remember and maintain than HTML/CSS + a framework like Bootstrap.
- Nevertheless, Since we are using Bootstrap as the underlying framework plus Pandoc to translate the Markdown sources to HTML, we can always resort to use Bootstrap-enriched HTML+CSS in our Markdown files when we really need it, almost without limitations (GitHub Markdown, on the other hand, is very aggresive and limiting regarding which subsets of HTML and CSS you can use in your Markdown pages)
- Our CGI makes automatic use of the Rexx Highlighter, as it highlights all Rexx fenced code blocks. We can use all the possible customizations that the Highlighter allows (like extra letters, selectable styles, style patches, etc.) on a fenced block-by-fenced block basis.
- If we are using a minimally decent Unicode-supporting text editor (something relatively simple like Notepad++ will do), we can generate web pages containing highlighted Rexx mixed with non-latin alphabets, as well as emojis, with absolute ease.
CGI.markdown.rex
and the accompanying files were
specifically designed to support the https://rexx.epbcn.com/ site only, but they are provided
as a reference, since you may want to install them locally, to play and
to experiment with them, and then eventually adapt them for your own
use.
Installing
a local copy of the rexx-parser
tree: a Windows
tutorial
We will learn how to install a working copy of the whole
rexx-parser
subtree of https://rexx.epbcn.com/ on a Windows machine; installing
under Linux or Mac should be similar. Everything should work as in the
original site, except for the logo (you can always substitute it by one
of your own instead).
Prerequisites
We will need:
Bootstrap 3, which you can download at https://getbootstrap.com/docs/3.4/.
Bootstrap defines a set of CSS classes and Javascript procedures that allow the easy development of mobile-first, responsive web pages.
Pandoc, which you can download at https://pandoc.org/.
We will use Pandoc to dynamically produce HTML files from Markdown sources.
A version of the Apache httpd web server. In this readme, we will be using Apache Lounge, which you can download at https://www.apachelounge.com/download/, but other distributions should also work.
The Visual C++ Redistributable installation files. You can find download links in the Apache Lounge download page.
The Rexx Parser distribution itself. You can download it here.
And of course ooRexx.
Step-by-step instructions
Install Visual C++ Redistributable Visual Studio 2015-2022.
This is needed to run Apache Lounge; other distributions of Apache httpd may not have this dependency. You may also find that the redistributables are already installed, as they are a prerequisite of many other programs.
Install the Apache httpd web server.
- Open the zipped Apache httpd distribution, and copy the
Apache24
directory to yourC:
disk: this will create aC:\Apache24
directory (the included readme contains instructions that you can follow if you prefer to install it in a different directory). - Execute
C:\Apache24\bin\httpd.exe
. You will get a prompt from the Windows firewall. Allow net access. - Open a web browser, and point to
localhost
. You should see a page that contains the words "It works!". You can now close the Apache window you opened in the previous step.
- Open the zipped Apache httpd distribution, and copy the
Install Pandoc.
Create your new local server directory.
You will need to create or select a directory to store the copy of the Rexx Parser distribution that will be served by your newly installed Apache web server. In this tutorial, we will assume that this directory is
C:\Parser
.Install Bootstrap
- Open the Bootstrap zip file.
- Create
C:\Parser\css
and copycss\bootstrap.min.css
andcss\bootstrap.theme.min.css
from the Bootstrap zip file. - Create
C:\Parser\js
and copyjs\bootstrap.min.js
. - Create
C:\Parser\fonts
and copy all the content of thefonts
subdirectory.
Install the Rexx Parser files.
Create
C:\Parser\rexx-parser
, and copy all the contents of the zipped Rexx Parser distribution there.Customize the Apache httpd configuration file.
Open
C:\Apache24\conf\httpd.conf
with a text editor.Locate the "DocumentRoot" line. You will find two lines that read
DocumentRoot "${SRVROOT}/htdocs" <Directory "${SRVROOT}/htdocs">
Substitute
${SRVROOT}/htdocs
by the server directory you just created:DocumentRoot "C:/Parser" <Directory "C:/Parser">
[Note the forward slashes, and beware of the quotes.]
The "document root" is where our served files will reside.
Just after the
DocumentRoot "C:/Parser"
line, add the following lines:Action RexxCGIMarkdown "/cgi-bin/cgi.rex" <Files *.md> SetHandler RexxCGIMarkdown </Files>
This defines an "Action", and associates it with a "Handler". The handler is fired whenever a Markdown file ("*.md") is requested; the handler then invokes the corresponding action, which is our "cgi.rex" Rexx program.
We must now create the
cgi.rex
program file.Locate the
C:\Apache24\cgi-bin
directory, and create acgi.rex
file containing exactly the following two lines:#!rexx Call "C:\Parser\rexx-parser\cgi\CGI.markdown.rex"
The first line ensures that the ooRexx interpreter will be called. The second one delegates the work to
cgi\CGI.markdown.rex
. This indirection mechanism greatly simplifies setup of the httpd server, and it allows us to distributeCGI.markdown.rex
as part of the Rexx Parser tree.Locate a line that reads
DirectoryIndex index.html
. Change that line so that it readsDirectoryIndex readme.md slides.md article.md index.html
When a URL that refers to a directory (that is, a URL ending with "/", like the root directory) is requested, we will automatically serve a
readme.md
file, if one is found; if not, we will try to serve aslides.md
file; and so on.
Start the Apache httpd server we just configured.
Point your browser to http://localhost/rexx-parser/. You should see a copy of the Rexx Parser web page (without the EPBCN logo, which is not included).
You can find a copy of an updated httpd.conf
file here for your reference. Please note that this was
generated on 20250510 against the
Apache 2.4.63-250207 Win64
version of Apache Lounge, and
may not work in your installation, depending on your choices and on the
versions used.
CSS Paged Media
The workflow defined above can be extended with a further last step: by using the W3C CSS Paged Media Level 3 specification, we can prepare special Markdown pages such that, when displayed on the browser and then printed, we obtain, automatically, a set of slides (or an article). The procedure and the rules to follow are explained in detail in a separate page.
CGI.markdown.rex
Location:
[installation directory]/cgi/CGI.markdown.rex
.
#!/usr/bin/env rexx
/******************************************************************************/
/* */
/* CGI.markdown.rex -- Sample Apache CGI markdown processor */
/* ======================================================== */
/* */
/* This program is part of the Rexx Parser package */
/* [See https://rexx.epbcn.com/rexx-parser/] */
/* */
/* Copyright (c) 2024-2025 Josep Maria Blasco <josep.maria.blasco@epbcn.com> */
/* */
/* License: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0) */
/* */
/* Requirements: */
/* */
/* + Apache */
/* + Pandoc */
/* + Use the "Action" directive to define an action pointing to */
/* this CGI routine, for example */
/* Action Markdown /cgi-bin/CGI.markdown.rex */
/* + Tell to Apache which files you want to process using the action handler */
/* you have defined. For example, */
/* */
/* <Files *.md> */
/* SetHandler Markdown */
/* </Files> */
/* */
/* Version history: */
/* */
/* Date Version Details */
/* -------- ------- --------------------------------------------------------- */
/* 20241220 0.1c First public release */
/* 20241230 0.1e Switch to a local copy of Bootstrap 3 to allow for colors */
/* in code sections to be printed correctly. */
/* 20250222 0.2 Add drive to path for requires */
/* 20250222 Change Address PATH to Address COMMAND for pandoc */
/* 20250317 Allow style=(dark|light) query string */
/* 20250328 Main dir is now rexx-parser instead of rexx[.]parser */
/* */
/******************************************************************************/
Signal On Syntax
--------------------------------------------------------------------------------
-- ::REQUIRES does not work well with "../" paths --
--------------------------------------------------------------------------------
package = .context~package
local = package~local
mypath = FileSpec( "Drive", package~name )FileSpec( "Path", package~name )
local ~ . = .File~new( mypath"../" )~absolutePath -- Creates ".."
Call Requires .."/bin/FencedCode.cls"
Call Requires mypath"rexx.epbcn.com.optional.cls"
Signal SkipOverRequiresAndSyntaxHandler
Requires:
package~addPackage( .Package~new( Arg(1) ) )
Return
--------------------------------------------------------------------------------
-- Error handling in CGIs is complicated, we'd better have a decent handler --
--------------------------------------------------------------------------------
Syntax:
.output~destination( .stdOut )
ConditionObject = Condition("O")
major = ConditionObject~rc
code = ConditionObject~code
message1 = ConditionObject~errorText
message2 = ConditionObject~message
program = ConditionObject~program
line = ConditionObject~position
traceBack = ConditionObject~traceBack
Say "Content-type: text/plain; charset=utf8"
Say ""
Say "Syntax error" major "in" program "line" line": " message1
Say "Error" code":" message2
Say ""
Say "Traceback follows:"
Do line Over traceBack
Say line
End
Exit
--------------------------------------------------------------------------------
SkipOverRequiresAndSyntaxHandler:
--------------------------------------------------------------------------------
-- We will collect all our output in an array. To this effect, we use --
-- a small class that subclasses .Array and at the same time inherits from --
-- .OutputStream. Indeed, we only need to implement the SAY method. --
-- --
-- We change the destination of the output monitor to point to this --
-- hybrid array. --
--------------------------------------------------------------------------------
arrayOutput = .ArrayOutputStream~new
.output~destination( arrayOutput )
--------------------------------------------------------------------------------
-- We now create .request and .response objects to encapsulate the --
-- complexities of the CGI protocol. --
--------------------------------------------------------------------------------
.environment~request = .Http.Request~new
.environment~response = .Http.Response~new
--------------------------------------------------------------------------------
-- PATH_TRANSLATED should point to the markdown file we have to process, --
-- and REQUEST_URI should contain the request URI. --
--------------------------------------------------------------------------------
file = .request~PATH_TRANSLATED
url = .request~REQUEST_URI
--------------------------------------------------------------------------------
-- We need to ensure that the CGI processor has not been called directly; --
-- if this happens, the environment strings will be empty. In that case, --
-- we produce a soft 404. --
--------------------------------------------------------------------------------
If file == "" Then Exit .Response~404
If url == "" Then Exit .Response~404
--------------------------------------------------------------------------------
-- We only accept one optional query, of the form style=(light|dark) --
--------------------------------------------------------------------------------
style = "dark"
If url~contains("?") Then Do
Parse Var url url"?"query
ok = 0
If query~startsWith("style=") Then Do
Parse Var query "style="style
If style == "dark" | style == "light" Then ok = 1
End
If \ok Then Exit .Response~404
End
--------------------------------------------------------------------------------
-- We don't want strangely formatted URIs --
--------------------------------------------------------------------------------
If url~contains("//") Then Exit .Response~404
If url~contains("/.") Then Exit .Response~404
If url~contains("..") Then Exit .Response~404
--------------------------------------------------------------------------------
-- We are using "readme.md" as one of the index pages, using the Apache --
-- DirectoryIndex directive. --
--------------------------------------------------------------------------------
-- In case we need to form canonical URLs
If url~endsWith("readme.md") Then url = Left(url, Length(url) - 9)
--------------------------------------------------------------------------------
-- The file should exist in the filesystem; if not, that's a 404 too. --
--------------------------------------------------------------------------------
resolved = Stream(file,"C","Query exists")
If resolved == "" Then Exit .Response~404
--------------------------------------------------------------------------------
-- There is a bug in the Linux version of ooRexx by which trailing slashes --
-- are wrongly accepted at the end of a filename. We don't want that. --
-- See https://sourceforge.net/p/oorexx/bugs/1940/ --
--------------------------------------------------------------------------------
If file~endsWith("/") Then Do
file2 = Strip(file,"T","/")
resolved2 = Stream(file2,"C","Query exists")
If resolved2 == resolved Then Exit .Response~404
End
--------------------------------------------------------------------------------
-- See if an accompanying extra style .css file exists --
-- This is is a file with the same name as the cgi, with ".css" added at --
-- the end. It is useful for specifying variables that are file-dependent, --
-- like the running header and footer (this should be done with the --
-- string-set property, but it is not properly implemented in the major --
-- browsers).
--------------------------------------------------------------------------------
Select Case FileSpec("Name",file)
When "slides.md" Then ownStyle = "slides"
When "article.md" Then ownStyle = "article"
Otherwise ownStyle = ""
End
extraStyle = Stream(resolved".css","c","Q exists")
If extraStyle \== "" Then Do
p = LastPos(.File~separator,extraStyle)
extraStyle = SubStr(extraStyle,p+1)
End
--------------------------------------------------------------------------------
-- Ok, now we have a file to process. Read it into an array --
--------------------------------------------------------------------------------
file = resolved
source = CharIn( file, 1, Chars(file) )~makeArray
Call Stream file, "c", "close"
--------------------------------------------------------------------------------
-- We process Rexx fenced code blocks first --
--------------------------------------------------------------------------------
source = FencedCode( file, source, style )
--------------------------------------------------------------------------------
-- Now call pandoc. It will transform markdown into html by default --
--------------------------------------------------------------------------------
contents = .Array~new
Address COMMAND 'pandoc --from markdown-smart+footnotes' -
'--reference-location=section' -
With Input Using (source) Output Using (contents)
--------------------------------------------------------------------------------
-- As the document title, pick the contents of the first h1 header --
--------------------------------------------------------------------------------
title = "Missing title"
chunk = contents~makeString("L"," ")
If chunk~contains("<h1") Then
Parse Var chunk "<h1" ">"title"</h1>"
--------------------------------------------------------------------------------
-- Our output will be html --
--------------------------------------------------------------------------------
.response~"Content-Type" = "text/html"
--------------------------------------------------------------------------------
-- Copy the HTML resource, with some substitutions --
--------------------------------------------------------------------------------
template = .Resources~HTML
Do line Over template
Select
When line = "%title%" Then Say title
When line = "%header%" Then Call OptionalCall PageHeader, title
When line = "%contents%" Then Say contents
When line = "%footer%" Then Call OptionalCall PageFooter
When line = "%sidebar%" Then Call OptionalCall Sidebar, url
When line = "%contentheader%" Then Call OptionalCall ContentHeader, url
When line = "%extrastyle%" Then
If extraStyle \== "" Then
Say " <link rel='stylesheet' media='print' href='"extraStyle"'>"
When line = "%ownstyle%" Then
If ownStyle \== "" Then
Say " <link rel='stylesheet'" -
"href='/rexx-parser/css/print/"ownstyle".css'>"
Otherwise Say line
End
End
--------------------------------------------------------------------------------
-- We are done. We only have to revert to normal .stdout, ... --
--------------------------------------------------------------------------------
.output~destination
--------------------------------------------------------------------------------
-- ... emit the stored HTTP headers ... --
--------------------------------------------------------------------------------
.response~printHeaders
--------------------------------------------------------------------------------
-- ... and an empty line to separate http headers and html body ... --
--------------------------------------------------------------------------------
Say ""
--------------------------------------------------------------------------------
-- ...and we can finally emit the body, by dumping the whole array at once --
--------------------------------------------------------------------------------
Say arrayOutput
Exit 0
--------------------------------------------------------------------------------
-- This allows us to optionally implement headers, footers, breadcrumbs and --
-- sidebars. --
--------------------------------------------------------------------------------
OptionalCall: Procedure
Signal On Syntax Name OptionalRoutineMissing
routineName = Markdown"."Arg(1)
Call (routineName) Arg(2)
Return
OptionalRoutineMissing:
code = Condition("O")~code
If code == 43.1, Condition("A")[1] = routineName Then Return
Raise Propagate
--------------------------------------------------------------------------------
::Class Http.Request
::Method unknown
Return Value(Arg(1),,"ENVIRONMENT")
--------------------------------------------------------------------------------
::Class Http.Response
::Method 404
.output~destination( .stdOut )
Say 'Content-Type: text/html; charset=iso-8859-1'
Say 'Status: 404 Not Found'
Say ""
Say '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'
Say '<html><head>'
Say '<title>404 Not Found</title>'
Say '</head><body>'
Say '<h1>Not Found</h1>'
Say '<p>The requested URL was not found on this server.</p>'
Say '<hr>'
Say Value('SERVER_SIGNATURE',,"ENVIRONMENT")'</body></html>'
Return 0 -- Necessary to allow EXIT syntax
::Method init
Expose headers order
order = .Array~new
headers = .Directory~new
::Method printHeaders
Expose headers order
Do header Over order
Say header":" headers[header]
End
::Method unknown
Expose headers order
messageName = Arg(1)
If messageName~endsWith("=") Then Do
messageName = messageName[1, Length(messageName) - 1]
messageName = Process(messageName)
order~append( messageName )
headers[ messageName ] = Arg(2)
Return
End
messageName = Process(messageName)
If \headers~hasIndex(messageName) Then Return ""
Return headers[messageName]
Process:
array = messageName~translate(" ","-_")~space~makeArray(" ")
Do i = 1 To array~items
array[i] = Upper( array[i][1] )Lower( SubStr(array[i],2) )
End
Return array~makeString("Line","-")
--------------------------------------------------------------------------------
::Class ArrayOutputStream SubClass Array Inherit OutputStream
::Method say
Use Strict Arg string -- We make string not optional
self~append(string)
Return 0
--------------------------------------------------------------------------------
-- Structure of a page:
--
-- +------------------------------------------+
-- | page header, inc. title |
-- +------------------------------------------+
-- | content header | side bar |
-- +---------------------------+ |
-- | | |
-- | contents | |
-- | | |
-- | [9 columns] | [3 columns] |
-- +------------------------------------------+
-- | page footer |
-- +------------------------------------------+
--
--------------------------------------------------------------------------------
-- We are loading a local copy of Bootstrap 3, customized to eliminate
-- print media styles, and then we add our own media styles css.
::Resource HTML
<!doctype html>
<html lang='en'>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
%title%
</title>
<link rel="stylesheet" href="/css/bootstrap.min.css">
<link rel='stylesheet' href='/rexx-parser/css/rexx-light.css'>
<link rel='stylesheet' href='/rexx-parser/css/rexx-dark.css'>
<link rel='stylesheet' href='/rexx-parser/css/markdown.css'>
%ownstyle%
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Questrial&display=swap" rel="stylesheet">
%extrastyle%
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class='container bg-white' lang='en'>
%header%
<div class='row'>
<div class='col-md-9'>
%contentheader%
<div class='content'>
%contents%
</div>
</div>
<div class="col-md-3 text-center text-larger">
%sidebar%
</div>
</div>
%footer%
</div>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<script src="/js/bootstrap.min.js"></script>
</body>
</html>
::END