ML07-0001-0

Chapter 3. A First Request

This chapter sends a few requests to a running HTTPD with curl and reads what comes back: the welcome page, a plain text file, a file that is not there, and two requests on one connection. The requests ask for static files from the document root. Routes, server modules and logins come in later chapters.

You need a running HTTPD (Chapter 2, “Installing HTTPD”, Chapter 8, “Operating HTTPD”), the host name of the MVS system and the port of HTTPD. The examples run curl on a machine that reaches HTTPD as localhost on port 8080, which is the default of PORT. Replace localhost:8080 with the host name and port of your system. The web root image that ships with HTTPD holds the welcome page and nothing else. The other files in the examples (hello.txt, data.json and a few more) are small test files. To repeat an example, put a file of that name into your web root (Chapter 7, “Serving Files”).

What the Console Shows

Before the first request, look at the console. A healthy start ends with message HTTPD001I. The lines before it show what HTTPD has set up:

S HTTPD
$HASP100 HTTPD    ON STCINRDR
$HASP373 HTTPD    STARTED
IEF403I HTTPD - STARTED - TIME=05.44.51
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 MVSMF REGISTERED FOR /zosmf/info
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/services/authenticate
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/restconsoles/*
HTTPD036I MODULE MVSMF REGISTERED FOR /zosmf/*
HTTPD054I LISTENING ON ANY PORT 8080
HTTPD061I STARTING SOCKET THREAD    TCB(9CD7C8) TASK(152FC8) STACKSIZE(32768)
HTTPD061I STARTING WORKER(164D00)   TCB(9BBEB8) TASK(166FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(164C00)   TCB(9BBB88) TASK(177FC8) STACKSIZE(65536)
HTTPD061I STARTING WORKER(164B80)   TCB(9BB858) TASK(188FC8) STACKSIZE(65536)
HTTPD001I HTTPD 4.5.1 READY - SERVING /www

The lines have this meaning:

HTTPD000I
The start. It names the version and the build of the program.
HTTPD005I
The C runtime library that HTTPD is linked with.
HTTPD002I
How the started task got its authorization: by the library it was fetched from, or by an SVC (the case in the capture).
HTTPD004I
The started task has given up the default identity that RAKF gives to every started task. It runs under the user ID and the group from the STCUSER and STCGRP values of the procedure (here HTTPD and USER).
RAKF0004
A line from RAKF, not from HTTPD. The server logs on once with a user ID that cannot exist, to test the security product, and RAKF refuses it. A line HTTPD042W in its place is not a good start (“A Security System That Accepts Any User ID”).
HTTPD036I
One line for each MOD= route: the server module and the path it is registered for. A configuration without MOD= routes has none of these lines.
HTTPD054I
HTTPD listens on the port. ANY means all interfaces.
HTTPD061I
A thread has started: the socket thread first, then the workers. The default is three workers.
HTTPD001I
HTTPD is ready. The text after SERVING is the document root.

Do not send requests before HTTPD001I has been written. How to start, stop and watch HTTPD is the subject of Chapter 8, “Operating HTTPD”. A message that is not in this list is described under “Messages” in the HTTPD Reference.

Choosing a Client

Any HTTP client can talk to HTTPD. A browser shows the page. curl shows more, and four forms of it are used in this book:

$ curl -I http://localhost:8080/hello.txt
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 17:11:59 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 61

The Content-Length is the size of the file. HTTPD 4.4.0 and earlier answer this request with 501 Not Implemented. A HEAD request for a path that belongs to a server module goes to the module, and the module decides (Chapter 5, “Routes and Server Modules”).

Getting the Welcome Page

A request for / asks for a directory. HTTPD looks in that directory for index.html and then for default.html (see “Directory Requests”). The web root that ships with HTTPD has an index.html, the welcome page. Fetch it:

curl http://localhost:8080/

The command prints the HTML text of the page. The page has the title “Welcome to HTTPD”, the line “It works.” and the text “You are connected to HTTPD, an HTTP/1.1 server running on MVS 3.8j.” A browser at the same URL shows it as a page. The headers are of more interest. This command prints them and nothing else:

$ curl -s -D - -o /dev/null http://localhost:8080/
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:41:41 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/html
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked

The option -s keeps curl quiet about its progress, -D - writes the response headers to the standard output, and -o /dev/null throws the body away.

Reading the Response

The first line is the status line: the HTTP version, the status code and a short text. HTTPD answers in the version of the request. A request in HTTP/1.1 gets HTTP/1.1, and a request in HTTP/1.0 gets HTTP/1.0. The headers follow, one on each line, up to an empty line. The body comes after it.

Every response that HTTPD writes starts with the same four headers:

Date
The date and time at which HTTPD built the response, in the form of RFC 1123.
Server
Always HTTPD Server. It does not name the version.
X-Powered-By
Always MVSLOVERS.
Connection
keep-alive if HTTPD takes another request on this connection, close if it closes the connection after this response.

A response for a static file adds more headers. The examples show them in the order in which HTTPD writes them:

Cache-Control
no-store, sent for HTML documents. It keeps a browser from keeping a copy of the page. A text file has no such header.
Content-Type
The type of the file, chosen from the end of its name (“Content Types”).
X-Content-Type-Options
nosniff. It tells a browser to believe the Content-Type.
Content-Length
The size of the file in bytes. A response without it uses Transfer-Encoding: chunked instead.

The welcome page above has Transfer-Encoding: chunked and no Content-Length. HTTPD scans an HTML document for server side includes while it sends it, so it does not know the length in advance (“Server Side Includes”). A text file has a Content-Length:

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

Hello from the HTTPD document root.
This file is plain text.

The file is 61 bytes long. The body follows the empty line. To see the whole conversation and not only the response, use curl -v:

$ curl -v http://localhost:8080/hello.txt
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET /hello.txt HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.14.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Tue, 27 Oct 2026 05:41:42 GMT
< Server: HTTPD Server
< X-Powered-By: MVSLOVERS
< Connection: keep-alive
< Content-Type: text/plain
< X-Content-Type-Options: nosniff
< Content-Length: 61
< 
{ [61 bytes data]
* Connection #0 to host localhost left intact
Hello from the HTTPD document root.
This file is plain text.

The lines with > are what curl sent. A request in HTTP/1.1 must have a Host header. HTTPD refuses one without it with a 400 response.

A File That Is Not There

A request for a path that is not in the document root is answered with 404 Not Found:

$ curl -i http://localhost:8080/nothere.html
HTTP/1.1 404 Not Found
Date: Tue, 27 Oct 2026 05:41:42 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

404 document "/nothere.html" not found

The body is one line of plain text. It repeats the path as HTTPD decoded it (“Paths”). The same answer comes for a directory without an index file and for a path with two dots in it. The answer does not say which of these it was. The 404 response has Cache-Control: no-store and a body of plain text. The status codes of HTTPD are in “HTTP Behaviour and Limits” in the HTTPD Reference.

Two Requests on One Connection

A client that wants two files can send both requests on one connection. Give curl two URLs:

$ curl -v http://localhost:8080/hello.txt http://localhost:8080/css/site.css
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET /hello.txt HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.14.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Tue, 27 Oct 2026 05:41:53 GMT
< Server: HTTPD Server
< X-Powered-By: MVSLOVERS
< Connection: keep-alive
< Content-Type: text/plain
< X-Content-Type-Options: nosniff
< Content-Length: 61
< 
{ [61 bytes data]
* Connection #0 to host localhost left intact
Hello from the HTTPD document root.
This file is plain text.
* Re-using existing http: connection with host localhost
> GET /css/site.css HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.14.1
> Accept: */*
> 
* Request completely sent off
< HTTP/1.1 200 OK
< Date: Tue, 27 Oct 2026 05:41:53 GMT
< Server: HTTPD Server
< X-Powered-By: MVSLOVERS
< Connection: keep-alive
< Content-Type: text/css
< X-Content-Type-Options: nosniff
< Content-Length: 67
< 
{ [67 bytes data]
* Connection #0 to host localhost left intact
body { font-family: sans-serif; margin: 2em; }
h1 { color: #036; }

The line “Re-using existing http: connection” shows it. The first response has Connection: keep-alive, so HTTPD waits for the next request. It waits for KEEPALIVE_TIMEOUT seconds (5 by default), and KEEPALIVE_MAX limits the number of requests on one connection (100 by default, plus the one that is answered with Connection: close). Both are described in “The HTTPPRM Keywords” in the HTTPD Reference. When the limit is reached, HTTPD answers with Connection: close and closes the connection after the response.

An HTTP/1.0 client gets a different answer. In HTTP/1.0 a connection ends after one response, unless the client asks for Connection: keep-alive:

$ curl -i --http1.0 http://localhost:8080/hello.txt
HTTP/1.0 200 OK
Date: Tue, 27 Oct 2026 05:41:53 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: close
Content-Type: text/plain
X-Content-Type-Options: nosniff
Content-Length: 61

Hello from the HTTPD document root.
This file is plain text.

The status line says HTTP/1.0 and the response has Connection: close. HTTPD closes the connection after the body.

A request in HTTP/1.1 can end the connection too. The client can send Connection: close, and a request that has a body (a Content-Length or a Transfer-Encoding header) is always the last one on its connection. “What Is Not Supported” shows examples.

What Can Go Wrong