http_server
The http_server library provides a portable stream-oriented server
layer on top of the http_core library. It is designed for the first
orchestration step above the wire parser and generator: read exactly one
request from an existing binary stream, dispatch it to a handler object
implementing the http_handler_protocol protocol, and write exactly
one response.
API documentation
Open the ../../apis/library_index.html#http_server link in a web browser.
Loading
To load the library, load the loader.lgt file:
| ?- logtalk_load(http_server(loader)).
Testing
To test this library, load the tester.lgt file:
| ?- logtalk_load(http_server(tester)).
Usage examples
The examples below use in-memory streams or local files so they can be reproduced directly.
Define a simple handler object once:
:- object(notes_http_server_echo_handler,
implements(http_handler_protocol)).
handle(Request, Response) :-
http_core::version(Request, Version),
http_core::body(Request, Body),
http_core::response(Version, status(200, 'OK'), [], Body, [], Response).
:- end_object.
To serve a single request from existing binary streams:
| ?- Request = 'POST /echo HTTP/1.1\r\nhost: example.com\r\ncontent-type: text/plain\r\ncontent-length: 5\r\n\r\nhello',
atom_codes(Request, RequestBytes),
open('request.tmp', write, RequestStream, [type(binary)]),
forall(member(RequestByte, RequestBytes), put_byte(RequestStream, RequestByte)),
close(RequestStream),
open('request.tmp', read, Input, [type(binary)]),
open('response.tmp', write, Output, [type(binary)]),
http_server::serve(Input, Output, notes_http_server_echo_handler),
close(Input),
close(Output),
http_core::parse_response(file('response.tmp'), Response).
Response = response(http(1,1), status(200, 'OK'), _, content('text/plain', text(hello)), _).
To keep serving requests on the same stream pair until persistence rules
say to stop, write several requests into the same input stream and call
serve_connection/3 once:
| ?- Requests = 'POST /echo HTTP/1.1\r\nhost: example.com\r\ncontent-type: text/plain\r\ncontent-length: 3\r\n\r\nonePOST /echo HTTP/1.1\r\nhost: example.com\r\ncontent-type: text/plain\r\ncontent-length: 3\r\n\r\ntwo',
atom_codes(Requests, RequestBytes),
open('requests.tmp', write, RequestStream, [type(binary)]),
forall(member(RequestByte, RequestBytes), put_byte(RequestStream, RequestByte)),
close(RequestStream),
open('requests.tmp', read, Input, [type(binary)]),
open('responses.tmp', write, Output, [type(binary)]),
http_server::serve_connection(Input, Output, notes_http_server_echo_handler),
close(Input),
close(Output).
The generated responses.tmp file contains two consecutive normalized
HTTP responses, one for one and one for two.
For multipart request inspection, define a handler that uses
http_multipart on the normalized request body:
:- object(notes_http_server_multipart_handler,
implements(http_handler_protocol)).
handle(Request, Response) :-
http_core::version(Request, Version),
http_core::body(Request, Body),
http_multipart::fields(Body, [field(title, Title, _FieldParameters)]),
http_multipart::files(Body, [file(upload, Filename, 'text/plain', text(hello), _FileParameters)]),
atomic_list_concat(['title=', Title, '; upload=', Filename], Text),
http_core::response(Version, status(200, 'OK'), [], content('text/plain', text(Text)), [], Response).
:- end_object.
That handler can be used unchanged with serve/3 or
serve_connection/3 because this library exposes multipart requests
in the same normalized form produced by http_core::parse_request/2.
The multipart helper predicates used by handlers return the
parameter-aware field(Name, Value, Parameters) and
file(Name, Filename, MediaType, Payload, Parameters) descriptors
defined by the http_multipart library.
In both descriptor shapes, Parameters is the ordered list of extra
Content-Disposition: form-data parameters. The reserved name and
filename parameters stay explicit helper arguments and are not
repeated in that list.
For a WebSocket opening handshake, define a handler that delegates to
accept_websocket/3:
:- object(notes_http_server_websocket_handler,
implements(http_handler_protocol)).
handle(Request, Response) :-
http_server::accept_websocket(Request, Response, [protocol(chat)]).
:- end_object.
| ?- Request = 'GET /socket HTTP/1.1\r\nhost: example.com\r\nconnection: Upgrade\r\nupgrade: websocket\r\nsec-websocket-key: dGhlIHNhbXBsZSBub25jZQ==\r\nsec-websocket-version: 13\r\nsec-websocket-protocol: chat\r\n\r\n',
atom_codes(Request, RequestBytes),
open('ws_request.tmp', write, RequestStream, [type(binary)]),
forall(member(RequestByte, RequestBytes), put_byte(RequestStream, RequestByte)),
close(RequestStream),
open('ws_request.tmp', read, Input, [type(binary)]),
open('ws_response.tmp', write, Output, [type(binary)]),
http_server::serve_websocket(Input, Output, notes_http_server_websocket_handler, Outcome),
close(Input),
close(Output).
Outcome = accepted(RequestTerm, Response),
Response = response(http(1,1), status(101, 'Switching Protocols'), _, empty, _).
After serve_websocket/4 succeeds, higher layers must take ownership
of the underlying upgraded connection or streams. This library stops at
the HTTP opening handshake.
Current scope
The current implementation provides seven predicates:
read_request/2reads exactly one request from a binary stream without requiring end-of-file.write_response/2writes exactly one normalized response to a binary stream.write_response/2streams file-backed response bodies directly after writing the generated header block when the response does not use chunked transfer coding.dispatch/3calls a handler object and normalizes handler failures into500 Internal Server Errorresponses.accept_websocket/3validates a normalized WebSocket opening-handshake request and builds the corresponding101 Switching Protocolsresponse, includingSec-WebSocket-Acceptand optional subprotocol selection.serve_websocket/4reads one request, dispatches one response, writes it to the output stream, and reports whether the exchange completed with a valid WebSocket opening-handshake response.serve/3connects request reading, handler dispatch, and response writing, returning400 Bad Requestfor malformed input. ForHEADrequests it sends the generated response headers but suppresses the response body bytes, including for file-backed responses.serve_connection/3repeatedly serves requests on the same stream pair using HTTP persistence rules and stops on end-of-file orConnection: closesemantics. ForHEADrequests it sends the generated response headers but suppresses the response body bytes, including for file-backed responses.
Because incoming request bodies are normalized by the http_core
library, handler objects can inspect multipart/form-data requests
directly using the http_multipart predicates after reading or
serving a request.
This layer intentionally stays transport-neutral. It does not accept sockets, listen for connections, or manage concurrency. Those concerns belong to higher layers or backend-specific integration built on top of this stream contract.
Framing rules
Request framing currently supports:
header termination using
CRLF CRLFfixed-length bodies via
Content-Lengthchunked request bodies via
Transfer-Encoding: chunked, including trailers
When neither Content-Length nor Transfer-Encoding is present,
the request is read as having an empty body so that subsequent bytes
remain available for the next request on the stream.
Multipart workflow
This library does not introduce a separate multipart server API.
Multipart requests are exposed to handlers as the same normalized
request bodies produced by http_core::parse_request/2, which means
handlers can use http_multipart::fields/2 and
http_multipart::files/2 directly on the request body.
WebSocket handshake workflow
This layer now provides a focused helper for the HTTP opening handshake:
Handlers can call
accept_websocket/3to validate an incoming normalized handshake request and build the matching101response.Servers that need to stop after one opening handshake can call
serve_websocket/4and inspect whether it returnedaccepted(Request, Response)orrejected(Response).The optional
protocol(Protocol)option selects one offered subprotocol.The helper is intentionally transport-neutral: it does not take ownership of the upgraded stream or begin frame processing.
Higher layers that need to continue on the upgraded connection must stop at a single served message and then take over the underlying stream or socket.
The
http_websocket_server_session::serve_once/6-7predicates build on top of this handshake helper when server-side code wants a single entry point for handshake plus callback-driven session execution.
Current limitations
Only the transport coding sequence
[chunked]is recognized when reading streamed request bodies.The
accept_websocket/3helper is limited to the HTTP opening handshake. This layer does not hand upgraded connections off to a frame-processing loop, andserve_connection/3remains HTTP-request oriented.The library does not provide socket accept loops, connection pooling, or concurrency management.
The library assumes binary streams for both input and output.