ML07-0001-0

Chapter 5. Routes and Server Modules

A route ties a path of a request to what HTTPD does with it. Without a route, HTTPD serves every path as a file. With a route, a path can go to a program (a server module), or it can stay a file and ask for a login first. This chapter shows how to write the routes in the Parmlib member, in which order, and what happens when a module is not there. The login that a route asks for is the subject of Chapter 6, “Authentication and Security”. The full syntax and the full list of messages are in ‘Routes and Authentication Policy’ in the HTTPD Reference.

The Two Kinds of Route

A route is one line in the member. There are two forms.

LineForm
MODMOD=program [pattern] [AUTH=mode] [RES=class:resource]
LOCLOC=pattern [AUTH=mode] [RES=class:resource]

Table 15. The route lines

A MOD route names a program. HTTPD links to the program, and the program answers the request. A LOC route has no program. HTTPD serves the path as a file from the document root, as it does without a route. The only difference is the policy: a LOC route can ask for a login or a RACF permission, and a path without a route cannot.

Both forms have the same options. AUTH= says how HTTPD asks a client for a login. RES= names a RACF resource that the user must be allowed to read. Both are explained in Chapter 6, “Authentication and Security”. A route without any option is public.

MOD= Routes

The first word after MOD= is the name of the load module. HTTPD changes it to upper case and keeps at most 8 characters. A longer name is cut without a message. The second word is the pattern: the path that the route claims.

MOD=MVSMF /zosmf/* AUTH=TOKEN

This line sends every path that starts with /zosmf/ to the module MVSMF. The patterns are described in “The Order of the Routes and the First Match”.

If you leave out the pattern, HTTPD builds one from the program name: *. and the name in lower case. The line MOD=REXX becomes the pattern *.rexx. A pattern that starts with *. makes an extension route. For a request on an extension route HTTPD sets the variable SCRIPT_FILENAME to the document root followed by the path of the request, so that the module knows which file the client asked for. No other route gets this variable.

Each MOD line adds one route. Many routes can use the same module, as the examples in this chapter show. HTTPD reads at most 8 words of a line. Words after the eighth are ignored without a message.

LOC= Routes

A LOC route is the way to protect part of the document tree. The route does not change what HTTPD serves. It decides who may get it.

LOC=/private/*   AUTH=BASIC
LOC=/pub/*       AUTH=NONE

The first line asks for a user and a password for every path below /private/. The second line makes every path below /pub/ public explicitly.

Two details prevent surprises:

The serving of the files is described in Chapter 7, “Serving Files”.

The Order of the Routes and the First Match

HTTPD keeps all routes, MOD and LOC together, in one table in the order of the lines. For a request it walks the table from the top and uses the first route that matches. It never looks for the best match. The order of the lines is the only thing that sets the priority. The policy of the first route is the whole policy of the request. A later route that also matches is not asked.

Write the specific routes first and the general ones last.

What a Pattern Matches

HTTPD matches the path of the request, without the query string. A pattern is of one of two kinds.

KindRules
Plain pathThe path and the pattern must be equal. The case of the letters does not count. /status matches /STATUS and does not match /status/.
With * or ?The whole path must match. The case of the letters counts. * stands for any characters, also a slash. At the end of the pattern it also stands for no character, so /pub/* matches /pub/. Anywhere else it stands for at least one character. ? stands for one character.

Table 16. The two kinds of pattern

The captures below use this member. It has two plain-path routes, both protected by Basic authentication:

PORT=8080
DOCROOT=/www
REALM=MVS.EXAMPLE
LOC=/hello.txt                  AUTH=BASIC
LOC=/pub                        AUTH=BASIC

A plain path claims exactly that path and ignores the query string. The request for /hello.txt asks for a login, and so does the request with a query:

$ curl -i http://localhost:8080/hello.txt
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:53:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized
$ curl -i "http://localhost:8080/hello.txt?x=1"
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:53:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

The route LOC=/pub does not claim a path below it. The file /pub/notice.txt is public:

$ curl -i http://localhost:8080/pub/notice.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:53:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 23

This notice is public.

A path that no route claims is public and is served as a file:

$ curl -i http://localhost:8080/README.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:53:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 36

notes without a file name extension

If you want to protect everything below a directory, write LOC=/pub/*. A route for a directory and for everything below it needs two lines, one for the directory and one with the trailing /*.

The First Line Wins

Two routes for the same path show the rule. In the first member the Basic route stands first:

PORT=8080
DOCROOT=/www
REALM=MVS.EXAMPLE
LOC=/private/*                  AUTH=BASIC
LOC=/private/*                  AUTH=NONE

The first route claims the path. The second route is never used, and the request needs a login:

$ curl -i http://localhost:8080/private/secret.txt
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:50:48 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

401 Unauthorized

In the second member the public route stands first:

PORT=8080
DOCROOT=/www
REALM=MVS.EXAMPLE
LOC=/private/*                  AUTH=NONE
LOC=/private/*                  AUTH=BASIC

Now the same request is public. The route that asks for the login is dead, and HTTPD gives no message:

$ curl -i http://localhost:8080/private/secret.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:50:40 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 43

private: any logged-in user may read this.
Note: A general route above a specific one hides it. A line such as LOC=/* AUTH=NONE makes all routes below it unreachable. If it stands above a protected route, that route protects nothing. HTTPD does not warn about this. After a change of the routes, request the protected path without a login and check that you get the 401.

The Fixed Paths

The paths /login and /logout belong to HTTPD. A request for one of them goes to the login code of HTTPD whatever the routes say. Do not write a route for them. The paths that match /login.* and /favicon.* are exempt from the login for the methods GET and HEAD, so that the login page can load its parts (Chapter 6, “Authentication and Security”).

The Standard Module: mvsMF

The module that HTTPD is mostly used for is mvsMF. It answers the REST API of z/OSMF below the path /zosmf/. mvsMF is a separate product. Its load module is called MVSMF. This is the member that the captures of this guide use. It has the routes for mvsMF, and it routes the display modules (Chapter 8, “Operating HTTPD”).

# HTTPD lab configuration (ug-ops, ug-security, ug-routes)
PORT=8080
DOCROOT=/www
REALM=MVS.EXAMPLE
MOD=MVSMF /zosmf/info                    AUTH=NONE
MOD=MVSMF /zosmf/services/authenticate   AUTH=NONE
MOD=MVSMF /zosmf/restconsoles/*          AUTH=TOKEN RES=FACILITY:MVSMF.CONSOLE
MOD=MVSMF /zosmf/*                       AUTH=TOKEN
MOD=HTTPDSRV /.dsrv                      AUTH=FORM
MOD=HTTPDM /.dm                          AUTH=BASIC
MOD=HTTPDMTT /.dmtt                      AUTH=BASIC
LOC=/pub/*                               AUTH=NONE
LOC=/private/*                           AUTH=BASIC
LOC=/form/*                              AUTH=FORM
LOC=/token/*                             AUTH=TOKEN
LOC=/admin/*                             AUTH=BASIC RES=FACILITY:HTTPD.ADMIN
LOC=/denied/*                            AUTH=BASIC RES=FACILITY:HTTPD.DENIED

Read the four MVSMF routes from top to bottom.

$ curl -i http://localhost:8080/zosmf/info
HTTP/1.1 401 Unauthorized
Date: Tue, 27 Oct 2026 05:53:01 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Length: 0
WWW-Authenticate: Basic realm="MVS.EXAMPLE"
Cache-Control: no-store
Pragma: no-cache
X-Content-Type-Options: nosniff
Content-Language: en

The order matters. The route for the console stands before /zosmf/*, because the general route also matches the console path. If you swap the two lines, the console path falls to the general route, and the check of the resource is lost.

The meaning of TOKEN and of RES= is in Chapter 6, “Authentication and Security”.

Where the Module Must Be

HTTPD links to a module by name, in the task of the server. The system finds the module in the STEPLIB of the procedure or in the link list. A module in another library is not found. Put the module into the library that the STEPLIB names, or add the library to the STEPLIB.

Whether the library is APF-authorized decides the message HTTPD002I and the protection key in which the system fetches the module. This is described in ‘The Started Task and the Package’ in the HTTPD Reference. Because of that key, a module must not change its own static storage (Chapter 9, “Writing a Server Module”).

Modules Held in Storage: RESIDENT

By default HTTPD loads each module of a MOD route once, when it has read the member and before it serves a request. It holds the copy until it stops. A request then does not fetch the module from the library. HTTPD does this once for each program name, however many routes use the name. The member keyword is RESIDENT, with the values YES (the default) and NO.

D C shows the number of modules held in the line HTTPD137I. The member above uses four programs, and the line shows MODULES HELD 4.

A module that HTTPD cannot hold still works. Each request fetches it, as with RESIDENT=NO. The start tells you why:

MessageMeaning
HTTPD440WHTTPD did not find the module at the start. It does not hold it. If the module is in the library later, the route works.
HTTPD441WThe module is not reentrant and not serially reusable. HTTPD does not hold it.
HTTPD442WThe load failed. The message gives the abend code and the reason code.

Table 17. The messages for resident modules

With RESIDENT=NO HTTPD does not look at the modules at the start. A missing module is then silent until the first request (see “A Route to a Missing Module”).

Context Blocks for Modules

A server module must not keep data in writable static storage. When a module needs data that lives from one request to the next, it asks HTTPD for a context block. The module calls the function http_cgictx_get with an 8-byte name (an eyecatcher) and a size. The first call for a name creates the block. Every later call with the same name, from any worker, returns the same block. The block stays until the server stops. HTTPD never frees it.

The keyword CGI_CONTEXT_POINTERS is the size of the table in which HTTPD records these blocks. Each name takes one entry. The table has one entry more than the number. The default is 255, which is also the largest value. If the table is full, the call returns no block (NULL) and the module has to deal with that. A module does not need the keyword to run. Leave it at the default unless the documentation of a module tells you to change it. How a module uses the block is described in Chapter 9, “Writing a Server Module” and in ‘The Server Module Interface’ in the HTTPD Reference.

A Route to a Missing Module

A route to a module that is not in the library is not an error of the member. HTTPD registers the route and tells you at the start:

PORT=8080
DOCROOT=/www
MOD=NOSUCH /nosuch/*            AUTH=NONE
S HTTPD,M=PRMNOSU
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=05.34.49
HTTPD000I HTTPD 4.5.1 (nnnnnnn) STARTING
HTTPD005I LIBC370 2.26.0 (3AA3B28)
HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)
HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT
RAKF0004 INVALID ATTEMPT TO ACCESS SYSTEM:  USER:HTTPPRB
HTTPD036I MODULE NOSUCH REGISTERED FOR /nosuch/*
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD440W MODULE NOSUCH NOT FOUND AT STARTUP, NOT HELD
HTTPD061I STARTING SOCKET THREAD    TCB(9CDB00) TASK(100FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(0DD7F0)   TCB(9CD410) TASK(114FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD100)   TCB(9CD0E0) TASK(125FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD080)   TCB(9BBCF8) TASK(136FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1 READY - SERVING /www
IEA703I 806- 4 HTTPD    HTTPD    MODULE ACCESSED NOSUCH
HTTPD908E EXTERNAL PROGRAM NOSUCH was not found

The message HTTPD036I says that the route exists. The message HTTPD440W says that the module is not found and not held. The server starts. The failure comes with the first request:

$ curl -i http://localhost:8080/nosuch/x
HTTP/1.1 503 Service Unavailable
Date: Tue, 27 Oct 2026 05:50:25 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
Transfer-Encoding: chunked

External program NOSUCH was not found

The client gets the status 503 and the text External program NOSUCH was
not found
. The console shows the system message IEA703I 806- 4 and, after it, HTTPD908E (the last two lines of the start capture above). The completion code S806 means that the system found the module neither in the STEPLIB nor in the link list. If you see this, check the name in the route, then the library (“Where the Module Must Be”). The other reasons for a failure are listed in ‘Routes and Authentication Policy’ in the HTTPD Reference.

With RESIDENT=NO the start is silent about the module:

PORT=8080
DOCROOT=/www
RESIDENT=NO
MOD=NOSUCH /nosuch/*            AUTH=NONE
S HTTPD,M=PRMRESN
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=05.34.34
HTTPD000I HTTPD 4.5.1 (nnnnnnn) STARTING
HTTPD005I LIBC370 2.26.0 (3AA3B28)
HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)
HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT
RAKF0004 INVALID ATTEMPT TO ACCESS SYSTEM:  USER:HTTPPRB
HTTPD036I MODULE NOSUCH REGISTERED FOR /nosuch/*
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD061I STARTING SOCKET THREAD    TCB(9CDB00) TASK(100FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(0DD7F0)   TCB(9CD410) TASK(114FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD100)   TCB(9CD0E0) TASK(125FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD080)   TCB(9BBCF8) TASK(136FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1 READY - SERVING /www

Warnings and Refusals While HTTPD Reads the Routes

HTTPD reads the whole member before it decides whether to start. Most mistakes in a route give a warning, and the route is registered. One example shows the common ones. The member has a bad RESIDENT value and four routes with options that HTTPD does not accept:

PORT=8080
DOCROOT=/www
RESIDENT=MAYBE
MOD=NOSUCH /nosuch/*            AUTH=NONE
LOC=/a/*                        AUTH=NONE RES=FACILITY:HTTPD.ADMIN
LOC=/b/*                        AUTH=BASIC RES=NOCOLON
LOC=/c/*                        AUTH=NONE BOGUS=1
LOC=/d/*                        RES=FACILITY:HTTPD.NOPROFILE
S HTTPD,M=PRMRTE
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=05.35.14
HTTPD000I HTTPD 4.5.1 (nnnnnnn) STARTING
HTTPD005I LIBC370 2.26.0 (3AA3B28)
HTTPD002I AUTHORIZED BY SVC (MODULE KEY 8)
HTTPD004I STC IDENTITY SET TO HTTPD/USER VIA RACINIT
RAKF0004 INVALID ATTEMPT TO ACCESS SYSTEM:  USER:HTTPPRB
HTTPD443W INVALID RESIDENT VALUE "MAYBE" (YES|NO)
HTTPD036I MODULE NOSUCH REGISTERED FOR /nosuch/*
HTTPD414W AUTH=NONE IGNORES RES=FACILITY:HTTPD.ADMIN (PUBLIC ROUTE)
HTTPD417I LOCATION /a/* REGISTERED
HTTPD412W IGNORING MALFORMED RES= 'NOCOLON' (NEED CLASS:RESOURCE)
HTTPD417I LOCATION /b/* REGISTERED
HTTPD413W IGNORING UNKNOWN ROUTE OPTION 'BOGUS=1'
HTTPD417I LOCATION /c/* REGISTERED
HTTPD417I LOCATION /d/* REGISTERED
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD425W NO PROFILE FOR FACILITY:HTTPD.NOPROFILE -- /d/* NOT GATED
HTTPD440W MODULE NOSUCH NOT FOUND AT STARTUP, NOT HELD
HTTPD061I STARTING SOCKET THREAD    TCB(9CDB00) TASK(100FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(0DD188)   TCB(9CD410) TASK(114FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD088)   TCB(9CD0E0) TASK(125FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(0DD008)   TCB(9BBCF8) TASK(136FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1 READY - SERVING /www

Read the console lines one after the other.

HTTPD checks only the words it knows. A line such as MOD=ADMIN /admin/* AUTH BASIC has the words AUTH and BASIC separately. HTTPD gives HTTPD413W twice and registers a public route. Look for HTTPD413W in the log after every change.

In some cases HTTPD refuses to start, because a route that asked for a login would otherwise turn public. An AUTH= value that does not exist gives HTTPD411E, HTTPD419E and HTTPD420E, and the return code is 8. The capture for that is in Chapter 4, “Configuring HTTPD”. A line in which an option stands where the program name or the path belongs also refuses the start, if it asks for a login or a resource. A line with only AUTH=NONE, or with no option, gives HTTPD421W for MOD or HTTPD415W for LOC, and the start goes on.

A change in a route takes effect when you stop and start HTTPD. To look at the routes that run now, use the display module HTTPDSRV (Chapter 8, “Operating HTTPD”).