ML07-0002-0

Chapter 5. HTTP Behaviour and Limits

This chapter describes what HTTPD does with a request, which limits it enforces, and what it sends back. It covers the methods, the limits on the request, the decoding of the URL, the status codes and headers that the server itself produces, keep-alive and time-outs, chunked transfer, static files and their MIME types, character translation, and the limits of the worker model.

The chapter describes the server itself. A module that sends its own status line and headers decides its own response. The server only adds what this chapter names. The keywords that set the time-outs and task counts are in “The HTTPPRM Keywords” (Chapter 1, “The HTTPPRM Keywords”). The limits of a route line are in “Routes and Modules” (Chapter 2, “Routes and Authentication Policy”).

How a Request Is Handled

Every request passes the same stages, in this order.

  1. The server reads the request line and the header lines from the socket. This stage rejects a request that is too long or malformed (“Limits on the Request”).
  2. It parses the method, the path, the query string and, for a POST, the body. This stage rejects an unknown method and a body that is too large.
  3. It resolves the credential of the request and looks for the route that matches the path.
  4. The requests for /login and /logout are answered by the server before the authentication check. A GET or HEAD for a path that starts with /login. or /favicon. skips the check, too.
  5. The authentication check of the route gives a 401 or a 403, or lets the request go on.
  6. If the route names a program, the server runs that module. If there is no route, or the route is a LOC= route, the static file handler answers.

Methods

The server knows five methods: DELETE, GET, HEAD, POST and PUT. The comparison is case sensitive, as the method of an HTTP request is, so get is not the method GET. Any other method gets a 405. The 405 is sent before the authentication check and before the route lookup, so it is the same for every path. It carries no Allow header, the connection closes, and the body is 405 Method Not Allowed.

MethodRoute with a programStatic file or LOC= route
GETThe module runs.The server sends the file.
HEADThe module runs.The server sends the status and the headers of a GET, and no body.
POSTThe module runs.501 Not Implemented.
PUTThe module runs.501 Not Implemented.
DELETEThe module runs.501 Not Implemented.
Any other405 Method Not Allowed.405 Method Not Allowed.

Table 42. Dispatch by method

A route with a program has no restriction by method. The module decides which methods it accepts and sends its own response to the others.

A HEAD for a static file takes the path of a GET as far as the empty line that ends the headers, and stops there. The status and the headers are the ones that a GET sends. Content-Length is the length of the file, not 0. The response has no body. The client reads the headers and nothing else, so the connection stays usable for the next request.

$ 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

These rules hold for a HEAD.

Note: HTTPD 4.4.0 and earlier do not answer a HEAD for a static file with the headers of a GET. They answer 501 Not Implemented with no Content-Length, chunked transfer and the text 501 Not Implemented as the body. A HEAD response must not have a body, so the connection could not be used for the next request. The behaviour in this section is that of HTTPD 4.5.0.

An unknown method gets a 405 and the connection closes.

$ curl -i -X OPTIONS http://localhost:8080/hello.txt
HTTP/1.1 405 Method Not Allowed
Date: Tue, 27 Oct 2026 05:41:53 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: close
Cache-Control: no-store
Content-Type: text/plain
Transfer-Encoding: chunked

405 Method Not Allowed

Limits on the Request

The server reads a request into a buffer of 4000 bytes. Several limits come from that size.

The request line and the header lines

A line can hold at most 3998 characters. The CR in front of the LF is skipped and is not counted. A line that is longer than that is rejected. For the request line, the answer is 414 URI Too Long. For a header line, the answer is 400 Bad Request.

The server rejects a request in these cases, too. All of these responses close the connection, because keep-alive is not yet decided when they are sent.

ConditionCodeBody
The request line is longer than 3998 characters.414414 URI Too Long
A line has a CR that is not followed by an LF.400400 Bad Request
The request line has no version.400400 Bad Request
The version is not HTTP/1.0 or HTTP/1.1.505505 HTTP Version Not Supported
A header name has a character other than a letter, a digit or one of !#$%&'*+-.^_`|~.400400 Bad Request
A header value has a control character below the blank, other than the tab.400400 Bad Request
The request has two Host headers.400400 Bad Request
The Content-Length is empty or has a character that is not a digit.400400 Bad Request
An HTTP/1.1 request has no Host header.400400 Bad Request: Missing Host header

Table 43. Requests that the server rejects while it reads the headers

All these bodies are text/plain. A header line without a colon is skipped without a message.

If the request line is empty, or has a method but no URI, the server closes the connection without sending a response.

The headers

There is no limit on the number of header lines. If a header appears twice, the later one replaces the earlier one. The Host header is the exception, because two of them are an error.

The server turns the headers and some parts of the request into environment variables for the request. A module reads them with the API (Chapter 6, “The Server Module Interface”). The server builds the names this way.

The server also sets SERVER_ADDR, SERVER_PORT, SERVER_PROTOCOL and SERVER_SOFTWARE. The protocol is always HTTP/1.1. The software is HTTPD/ followed by the version.

A name and its value together can have at most 8192 bytes. If a module sets a larger variable, the call fails and the server writes message HTTPD905E (see Appendix A, “Messages”). A request cannot reach this size by itself, because a line is limited to 3998 characters. If setting a variable fails while the server parses a request, the request fails. On the first request of a connection, the server closes the connection without a response. On a later request, it closes the connection, too.

Parameters

A request can have at most 256 parameters. The query parameters and the form fields of the body count together. A request with more gets a 400 with the body 400 Bad Request.

The request body

The server reads a body only for a POST. It handles the body this way.

The server translates the body from ASCII to EBCDIC. If the content type is application/x-www-form-urlencoded, it sets POST_STRING to the body, and it sets one POST_ variable for each field. The values are decoded as described in “URL Decoding and Escaping”. For any other content type, it sets only POST_STRING. The comparison covers the whole value, in any case. A value with a parameter, such as application/x-www-form-urlencoded; charset=UTF-8, is therefore “any other content type”: the server sets POST_STRING and splits no fields. If the request has a body but no Content-Type header, the server does not read the body and sets nothing.

A PUT body is not read by the server. A module that handles PUT reads the body from the socket itself.

The 400 and the 413 that the parser sends carry Cache-Control: no-store and the type text/plain.

The server turns off keep-alive for every request that has a Content-Length or a Transfer-Encoding header. The response to a POST or a PUT therefore says Connection: close.

If the request is HTTP/1.1, has the header Expect: 100-continue and has a Content-Length or a Transfer-Encoding header, the server sends the interim response HTTP/1.1 100 Continue with an empty line once it has read all the headers. This happens before the authentication check.

URL Decoding and Escaping

The server splits the request target at the first question mark. The text in front is the path. The text behind is the query string. The server stores the raw query string in QUERY_STRING.

The path. The server decodes the path and stores the result in REQUEST_PATH. The decoding turns each %xx into the character for that byte. A plus sign stays a plus sign in the path. In the value of a query field it becomes a blank.

The query. The server splits the query string at each ampersand. It splits each field at the first equal sign. The name is not decoded. The value is decoded. A field without an equal sign gets an empty value.

Rules of the decoding.

Escaping in output. The server has one function that escapes text for HTML. It replaces the characters &, <, >, " and ' with &amp;, &lt;, &gt;, &quot; and &#39;. It always ends the result with a NUL character, and it cuts the result if the buffer is too small. The server uses it for the value of the Sec-Uri cookie when it writes the login form. The text of a 404 is text/plain and is not escaped.

Status Codes

The server itself sends these status codes. The body of every error response is one line of text/plain.

CodePhraseWhen the server sends it
100ContinueThe interim response to Expect: 100-continue (“The request body”).
200OKA static file, the login form, the logout, and the pages of the display modules.
302Moved TemporarilyA GET on a route with form login and no session. The response has Location: /login and a Set-Cookie header for the cookie Sec-Uri. Only GET gets a 302 (“Other methods on a route with form login”).
303See OtherA successful login by POST. The response has a Location header and a Set-Cookie header for the cookie Sec-Token.
400Bad RequestA malformed request (“The request line and the header lines”, “Parameters”).
401UnauthorizedThe route needs a credential, and the request has none or a wrong one. The body is 401 Unauthorized.
403ForbiddenThe user is known, but the resource check of the route denies access. The body is 403 Forbidden.
404Not FoundNo file for the path. The body is 404 document "path" not found, with the decoded path.
405Method Not AllowedA method that the server does not know (“Methods”).
413Payload Too LargeA body of more than 3999 bytes, for a route without a program (“The request body”).
414URI Too LongA request line of more than 3998 characters.
500Internal Server ErrorThe server cannot build the response, for example because it cannot set the file name of a script, or a module asks for a status code that the server does not know. The body is 500 Internal Server Error.
501Not ImplementedPOST, PUT or DELETE for a static file.
503Service UnavailableA module does not run. The body is External program followed by the module name and the reason. If the module has already sent a status line, the text is appended to its output. See “When a Module Does Not Run” in Chapter 2, “Routes and Authentication Policy”.
505HTTP Version Not SupportedA version other than HTTP/1.0 and HTTP/1.1.

Table 44. Status codes that the server sends

The 401 adds a WWW-Authenticate header for a route with Basic authentication. It does not add it for a route with token or form authentication. It does not add it if the request has the header X-CSRF-ZOSMF-HEADER, because the browser would open its own credential dialog.

Other methods on a route with form login

On a route with form login and no session, every method that the server knows goes to the login step, not only GET. A GET gets the 302. A POST is treated as a login attempt, whatever its path. A PUT or DELETE gets the status 200 and a text/plain body Invalid REQUEST_METHOD "PUT", with the method that the client sent. A HEAD gets the same status 200 and the header Content-Type: text/plain, and no body. This holds for a HEAD for /login and /logout too.

The phrase table

A module calls the server to write the status line. The server knows the phrases of these 26 codes only.

CodePhraseCodePhrase
200OK404Not Found
201Created405Method Not Allowed
202Accepted409Conflict
204No Content410Gone
206Partial Content412Precondition Failed
301Moved Permanently413Payload Too Large
302Moved Temporarily414URI Too Long
303See Other429Too Many Requests
304Not Modified500Internal Server Error
400Bad Request501Not Implemented
401Unauthorized502Bad Gateway
403Forbidden503Service Unavailable
505HTTP Version Not Supported
507Insufficient Storage

Table 45. The status codes that the server can write

For any other code, the server writes message HTTPD909E, UNSUPPORTED HTTP
STATUS
followed by the code and REQUESTED, SENT 500, and it sends a 500 instead.

Response Headers

When a module or the server asks for a status line, the server writes these lines first, in this order.

LineContent
Status lineHTTP/1.1 or HTTP/1.0, the same as in the request. A request without a version gets HTTP/1.0.
DateThe current time in GMT, in the form Tue, 27 Oct 2026 05:41:42 GMT.
ServerHTTPD Server. The text is a constant and has no version.
X-Powered-ByMVSLOVERS.
Connectionkeep-alive or close (“Keep-Alive and Time-outs”).

Table 46. The lines the server writes with every status line

The server adds more headers in these cases.

The server supports no range requests and no conditional requests. It ignores the headers Range, If-Modified-Since and similar headers, and it sends the whole file with a 200.

$ curl -i -H "Range: bytes=0-9" http://localhost:8080/hello.txt
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

Hello from the HTTPD document root.
This file is plain text.
$ curl -i -H "If-Modified-Since: Tue, 27 Oct 2026 00:00:00 GMT" http://localhost:8080/hello.txt
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

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

Keep-Alive and Time-outs

Keep-alive

The server decides after it has read the request headers.

After a response, the server frees the request and waits for the next one on the same connection. The response header Connection shows the decision.

The next screens are a run with KEEPALIVE_MAX=2. The first two responses say keep-alive. The third says close.

PORT=8080
DOCROOT=/www
KEEPALIVE_MAX=2
KEEPALIVE_TIMEOUT=5
$ curl -sv -o /dev/null -o /dev/null -o /dev/null http://localhost:8080/hello.txt http://localhost:8080/hello.txt http://localhost:8080/hello.txt 2>&1 | grep -E "Connected|Re-using|Connection|^< HTTP|Closing|keep-alive|Keep-Alive|left intact"
* Connected to localhost (::1) port 8080
< HTTP/1.1 200 OK
< Connection: keep-alive
* Connection #0 to host localhost left intact
* Re-using existing http: connection with host localhost
< HTTP/1.1 200 OK
< Connection: keep-alive
* Connection #0 to host localhost left intact
* Re-using existing http: connection with host localhost
< HTTP/1.1 200 OK
< Connection: close

An HTTP/1.0 request gets a closed connection and a Content-Length.

$ 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.

Time-outs

The server waits for each line of a request for a limited time. The timer starts again for each line. The limit is KEEPALIVE_TIMEOUT seconds for the second and each later request of a connection. It is CLIENT_TIMEOUT seconds for the first request.

When the time is up, the server closes the connection. It sends no HTTP response. If the server has received at least one character of the request and CLIENT_TIMEOUT_MSG is on, it writes a line to the console. If CLIENT_TIMEOUT_DUMP is on, a dump of the received text follows. Both keywords are on by default. The line has no message ID.

The next screens show a client that sends a part of a request line and then waits. The Parmlib sets the time-out to 3 seconds.

PORT=8080
DOCROOT=/www
CLIENT_TIMEOUT=3
CLIENT_TIMEOUT_MSG=1
CLIENT_TIMEOUT_DUMP=1
$ exec 3<>/dev/tcp/localhost/8080
$ printf 'GET /hello.tx' >&3
$ sleep 6
$ exec 3>&-
client 00144FC8 on socket 1 timed out after 3 seconds
Dump of 00145028 "Receive Buffer" (13 bytes)
+00000 C7C5E340 61888593 93964BA3 A7       :GET /hello.tx   :

The defaults and the allowed values are these. “The HTTPPRM Keywords” (Chapter 1, “The HTTPPRM Keywords”) has the keywords in full.

KeywordDefaultRange
CLIENT_TIMEOUT101 to 255. Zero, a negative value and text that is not a number are ignored, and the default stays.
KEEPALIVE_TIMEOUT51 to 255.
KEEPALIVE_MAX1001 to 255.

Table 47. Defaults and ranges of the time-out keywords

A stalled send

When the client does not take data, the server tries again every 0.1 second. It gives up after 100 tries without progress, that is, after 10 seconds. It then abandons the client and writes message HTTPD065W. The server writes this message at most once in 60 seconds. It counts the messages that it suppresses and shows the count in the next one. A server that is stopping does not wait for the 10 seconds.

The limit holds for a chunked response and for the body of a static file that the server sends itself. After the server has given up, it does not keep the connection for another request, and the worker is free for the next client.

Note: HTTPD 4.4.0 and earlier have this limit for a chunked response only. A client that stops reading a static file keeps its worker (httpd440). The behaviour in this section is that of HTTPD 4.5.0.

If the server cannot read a file from the UFS in the middle of a response, it writes message HTTPD045E, cuts the response, and closes the connection. The server writes this message at most once in 60 seconds, too, and the next one shows the count of the errors that it held back. For an error in a file that a server-side include pulled in (“Server-side includes”), the message names that file.

Chunked Transfer

The server uses chunked transfer when it does not know the length of a response. When the server writes the empty line that ends the headers, it adds the header Transfer-Encoding: chunked if all of these are true.

Each chunk is the length in hexadecimal digits, a CR LF, the data, and a CR LF. The server ends the response with a chunk of length 0. If the server cannot send a chunk, it turns off the chunk framing and keep-alive. An HTTP/1.0 response is never chunked.

This applies to the responses of modules, too. A module only has to send a Content-Length header if it wants to avoid chunking. The responses of the server to errors, such as the 404 in the next screen, are sent chunked.

$ 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

Static Files

The server reads static files from the UFS only. It does not read them from a data set. If the server has no UFS, it finds no file, and every request for a static path gets a 404. A static file is served for a GET, and a HEAD gets the headers of the GET (“Methods”). POST, PUT and DELETE get a 501.

The path

The server joins the document root and the decoded path. It applies these rules.

$ curl -i http://localhost:8080/docs/
HTTP/1.1 200 OK
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/html
X-Content-Type-Options: nosniff
Transfer-Encoding: chunked

<!DOCTYPE html>
<html><head><title>Documents</title></head>
<body><h1>Documents</h1>
<p>This is docs/index.html.</p></body></html>
$ curl -i http://localhost:8080/docs
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 "/docs" not found
$ curl -i http://localhost:8080/empty/
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 "/empty/" not found

MIME types

The server takes the type from the extension of the file name. The extension is the text after the last dot in the last part of the path. The comparison ignores case. A file name with no extension, or with an extension that is not in the table, gets text/plain and is sent as text. A file data.json is an example.

$ curl -i http://localhost:8080/data.json
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: 34

{"name": "httpd", "static": true}

The table has a column “Body”. Text means that the server opens the file as text and translates it from EBCDIC to ASCII. Binary means that the server opens the file in binary mode and sends the bytes as they are. The table lists the types as they are in the server, also the ones where the setting is not what the name of the type suggests (for example sh and tex are binary).

ExtensionsContent-TypeBody
aiapplication/postscriptBinary
aif, aifc, aiffaudio/x-aiffBinary
au, sndaudio/basicBinary
avivideo/x-msvideoBinary
bcpioapplication/x-bcpioBinary
binapplication/octet-streamBinary
c, h, lua, rex, rexx, txttext/plainText
cdf, ncapplication/x-netcdfBinary
cpioapplication/x-cpioBinary
cshapplication/x-cshBinary
csstext/cssText
dviapplication/x-dviBinary
eps, psapplication/postscriptText
etxtext/x-setextText
gifimage/gifBinary
gtarapplication/x-gtarBinary
hdfapplication/x-hdfBinary
htm, htmltext/htmlText
ico, iconimage/x-iconBinary
iefimage/iefBinary
jpe, jpeg, jpgimage/jpegBinary
jsapplication/x-javascriptText
latexapplication/x-latexBinary
lodapplication/x-loadBinary
manapplication/x-troff-manText
meapplication/x-troff-meText
mifapplication/x-mifBinary
mov, qtvideo/quicktimeBinary
movievideo/x-sgi-movieBinary
mpe, mpeg, mpgvideo/mpegBinary
msapplication/x-troff-msBinary
odaapplication/odaBinary
pbmimage/x-portable-bitmapBinary
pdfapplication/pdfBinary
pgmimage/x-portable-graymapBinary
pngimage/pngBinary
pnmimage/x-portable-anymapBinary
ppmimage/x-portable-pixmapBinary
rasimage/x-cmu-rasterBinary
rgbimage/x-rgbBinary
rng, xmltext/xmlText
roff, t, trapplication/x-troffBinary
rtfapplication/rtfBinary
rtxtext/richtextBinary
shapplication/x-shBinary
sharapplication/x-sharBinary
shtm, shtmltext/x-server-parsed-htmlText
srcapplication/x-wais-sourceText
sv4cpioapplication/x-sv4cpioBinary
sv4srcapplication/x-sv4crcBinary
svgimage/svg+xmlText
tarapplication/x-tarBinary
tclapplication/x-tclBinary
texapplication/x-texBinary
texi, texinfoapplication/x-texinfoBinary
tif, tiffimage/tiffBinary
tsvtext/tab-separated-valuesText
ustarapplication/x-ustarBinary
wavaudio/x-wavBinary
xbmimage/x-xbitmapBinary
xhtmlapplication/xhtml+xmlText
xpmimage/x-xpixmapBinary
xsl, xslttext/xslText
xwdimage/x-xwindowdumpBinary
zipapplication/zipBinary

Table 48. The MIME types of the server

Two screens show the headers of a CSS file and of a PNG file.

$ curl -i http://localhost:8080/css/site.css
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/css
X-Content-Type-Options: nosniff
Content-Length: 67

body { font-family: sans-serif; margin: 2em; }
h1 { color: #036; }
$ curl -s -D - -o /dev/null http://localhost:8080/logo.png
HTTP/1.1 200 OK
Date: Tue, 27 Oct 2026 05:41:43 GMT
Server: HTTPD Server
X-Powered-By: MVSLOVERS
Connection: keep-alive
Content-Type: image/png
X-Content-Type-Options: nosniff
Content-Length: 70

Server-side includes

The server processes server-side includes in a file with the MIME type text/html or text/x-server-parsed-html. These are the extensions htm, html, shtm and shtml. The response is chunked on HTTP/1.1, because it has no Content-Length. The directory index in “The path” shows this.

The server knows three directives. A directive starts with <!--# and ends at the first blank-and-marker, -->, so a blank must stand in front of the closing marker. A line can hold several directives.

DirectiveEffect
<!--#echo var="X" -->Inserts the value of the request variable X. If there is none, the server looks for an environment variable of its own, of that name (see below). If there is neither, it inserts the comment <!-- Oops: we didn't find var="X" -->.
<!--#include virtual="path" -->Inserts the file. The form with file= is the same.
<!--#printenv -->Inserts one HTML table, headed Server Variables, with a row for each variable of the request (“The Request Variables”). It lists nothing of the environment of the server.

Table 49. Server-side include directives

The name in an echo directive is looked up in two places, in this order. First the server looks at the request variables (“The Request Variables”). If the request has no variable of that name, the server looks at its own environment, the one that the server task runs with, and inserts the variable of that name from it. So a page can print a single variable of the server environment by name. The name comes from the page, never from the request: a client cannot choose it and cannot make the server read any other variable. The directive can therefore be used only by someone who can put a page into the document root. A client cannot do it. The directive printenv never reads the server environment.

Note: The server drops the second look in HTTPD 4.6.0 (httpd421). From that version echo reads the request variables only, and a variable that is not among them gives the Oops comment. A page that prints a variable of the server environment must not rely on this.

The server writes every value that echo inserts, and every name and value that printenv lists, as HTML text. The five characters &, <, >, " and ' become the entities &amp;, &lt;, &gt;, &quot; and &#39;. A header value from the client therefore reaches the page as text and not as markup. The server never cuts a value to do this. Text that comes from the page itself, from the file or from a file that it includes, is sent as it is.

Text that the server does not recognize as a directive is sent as it is. That covers an unknown directive name and a missing blank in front of the closing marker. A directive that has a known name but a bad argument is not sent as it is. The server writes a comment in its place: <!-- Unable to parse 'echo' request --> or <!-- Unable to parse 'include' request -->.

Includes can be nested up to 10 levels. Below that level the server writes <!-- Oops: Maximum Server Side Include levels exceeded --> in place of the include. An include of a file that does not exist gets <!-- Oops: we didn't find "path" -->, and an include of a file with a binary MIME type gets a comment that starts with <!-- Oops: we can't
include a binary file
. The comment that the server writes in place of a directive is limited to 4095 characters. A value that echo or printenv writes is not cut at that limit. A read error in an included file ends the response as it does in the page itself (“A stalled send”). The includes that follow on the same line are not opened.

The server reads the file line by line and looks for directives in each line. A line can be of any length. The server reads a long line in pieces and sends it as one line. A directive that a read cuts in two is joined again, and the server recognizes it, if it is at most 384 characters long. A longer directive that a read cuts in two is not recognized in every case. The server then sends it as text, like any other text that is not a directive. The same happens when the output buffer of the response is still full after the server has tried to send it, at the moment the rest of a long line is due: the part of the line that was held back goes out as text, and a directive that spans that cut is not recognized.

Character Translation

The network carries ASCII. The server works in EBCDIC. The Parmlib keyword CODEPAGE selects the table: CP037 (the default), IBM1047 or LEGACY. The name can be in any case. An unknown name gives message HTTPD070E, and the server uses CP037. See “Files and Character Set” in Chapter 1, “The HTTPPRM Keywords”.

The server translates from ASCII to EBCDIC with that table in these places.

It translates from EBCDIC to ASCII with that table in these places.

Two kinds of data are not translated with the table of the server.

Workers and Threads

The server has one socket thread and a pool of worker threads. The socket thread has a stack of 32 KB. Each worker has a stack of 64 KB.

The socket thread waits for a connection. It accepts the connection, sets the socket to non-blocking, allocates the control block of the client, and puts it in the queue of the pool. A worker takes one connection from the queue and serves it until the connection is closed. A connection with keep-alive therefore holds one worker until the connection closes, also while it is idle. It holds the worker for at most KEEPALIVE_TIMEOUT seconds between two requests.

The pool has at least MINTASK and at most MAXTASK workers. The defaults are 3 and 9. Both values can be 1 to 255 in the Parmlib. A value of 0 or below is ignored. The console commands SET MAXTASK and SET MINTASK change the values while the server runs, in the range 1 to 100 (see “Console Commands” in Chapter 3, “Console Commands”). The Parmlib keywords are in Chapter 1, “The HTTPPRM Keywords”.

There is no limit on the queue of connections that wait for a worker, except the storage of the region. The keyword LISTEN_QUEUE sets the length of the queue of the TCP/IP stack for the listener. If the pool is stopping, the server closes the new connection without a response.

MessageCause
HTTPD052EThe accept call fails.
HTTPD053EThe server cannot set the socket to non-blocking.
HTTPD999EThe server cannot allocate the control block of the client. It closes the socket.
HTTPD901EA worker has gone through its loop 1000 times for one connection without progress.
HTTPD062EA worker abends. The recovery closes the connection, and the worker carries on.
HTTPD063EThe recovery for a worker could not be set up.

Table 50. Events in the socket thread and the workers

The texts of the messages are in Appendix A, “Messages”.

Other Hard Limits

ItemLimit
A request line or header line3998 characters (“The request line and the header lines”)
Header lines in a requestNo limit.
Parameters of a request256 (“Parameters”)
Body of a request that the server reads3999 bytes (“The request body”)
Name and value of an environment variable8192 bytes together
Output of one call of the printf function of the server5119 characters. The server cuts the rest.
Path of a static file255 characters, and 255 for the joined name
Nesting of server-side includes10 levels
RoutesSee Chapter 2, “Routes and Authentication Policy”.
Statistics countersThe counters for requests, errors, bytes sent and active connections are 32-bit unsigned numbers. They wrap at 4294967295.

Table 51. Fixed limits of the server

The counter of the errors counts each response with a status of 400 or more. The counter of the bytes counts what the server hands to the send function. That includes the headers and the chunk framing. A response that does not reach its end, for example after a time-out, is not counted. The console command D STATS shows the counters (see “Console Commands” in Chapter 3, “Console Commands”).

The read of the request

The server reads the request line and the headers one byte at a time, because the TCP/IP stack of MVS 3.8j corrupts data when a read of several bytes crosses the wrap of its ring buffer.