http_client
The http_client library provides a request-oriented client layer on
top of the http_core, url, and http_socket libraries. It is
the request-oriented client-side entry point: it builds normalized
requests from absolute http:// URLs plus options and delegates
transport to the sockets-backed layer.
The low-level stream primitives previously exposed as the main API now
live in the http_client_core object. That object remains the
transport-neutral stream-processing layer used internally by
http_socket.
This library can be used with backend Prolog systems that supports the
sockets library: ECLiPSe, GNU Prolog, SICStus Prolog, SWI-Prolog,
Trealla Prolog, and XVM.
API documentation
Open the ../../apis/library_index.html#http_client link in a web browser.
Loading
To load the library, load the loader.lgt file:
| ?- logtalk_load(http_client(loader)).
Testing
To test this library, load the tester.lgt file:
| ?- logtalk_load(http_client(tester)).
Usage examples
The examples below are self-contained. They use a local http_socket
listener so they can be reproduced without any external service. They
assume a backend with thread support because threaded_once/2 and
threaded_exit/2 are used to run the local server concurrently with
the client call.
Define a small echo handler once:
:- object(notes_http_client_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.
Start a local listener, send a request, and inspect the normalized response:
| ?- http_socket::open_listener('127.0.0.1', Port, Listener, []),
threaded_once(http_socket::serve_once(Listener, notes_http_client_echo_handler, _), Tag),
atomic_list_concat(['http://127.0.0.1:', Port, '/echo'], URL),
http_client::post(URL, content('text/plain', text(hello)), Response, []),
threaded_exit(http_socket::serve_once(Listener, notes_http_client_echo_handler, _), Tag),
http_socket::close_listener(Listener).
Response = response(http(1,1), status(200, 'OK'), _, content('text/plain', text(hello)), _).
For a multipart form-data request, define a handler that inspects the
normalized request body using http_multipart:
:- object(notes_http_client_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.
| ?- http_socket::open_listener('127.0.0.1', Port, Listener, []),
threaded_once(http_socket::serve_once(Listener, notes_http_client_multipart_handler, _), Tag),
atomic_list_concat(['http://127.0.0.1:', Port, '/upload'], URL),
http_client::post(
URL,
form_data([
field(title, 'Logtalk', []),
file(upload, 'notes.txt', 'text/plain', text(hello), [])
]),
Response,
[]
),
threaded_exit(http_socket::serve_once(Listener, notes_http_client_multipart_handler, _), Tag),
http_socket::close_listener(Listener).
Response = response(http(1,1), status(200, 'OK'), _, content('text/plain', text('title=Logtalk; upload=notes.txt')), _).
For a WebSocket opening handshake, define a handler that accepts the upgrade:
:- object(notes_http_client_websocket_handler,
implements(http_handler_protocol)).
handle(Request, Response) :-
http_server::accept_websocket(Request, Response, [protocol(chat)]).
:- end_object.
| ?- http_socket::open_listener('127.0.0.1', Port, Listener, []),
threaded_once(http_socket::serve_once(Listener, notes_http_client_websocket_handler, _), Tag),
atomic_list_concat(['ws://127.0.0.1:', Port, '/socket'], URL),
http_client::open_websocket(URL, Connection, Response, [protocols([chat]), key('dGhlIHNhbXBsZSBub25jZQ==')]),
http_socket::close_connection(Connection),
threaded_exit(http_socket::serve_once(Listener, notes_http_client_websocket_handler, _), Tag),
http_socket::close_listener(Listener).
Response = response(http(1,1), status(101, 'Switching Protocols'), _, empty, _).
The Connection term returned by open_websocket/4 remains open.
After the handshake, either close it explicitly with
http_socket::close_connection/1 or hand it to the
http_websocket, http_websocket_messages, or
http_websocket_session libraries.
Current scope
The initial request-oriented implementation provides:
request/4for one-shot requests against absolutehttp://URLs.open_websocket/4for validated WebSocket opening handshakes against absolutews://URLs, returning the upgraded reusable connection handle and the101response.request/5for requests over an already openhttp_socketconnection or connection pool handle, with endpoint validation against the supplied URL.get/3-4,head/3-4,delete/3-4,post/4-5,put/4-5, andpatch/4-5convenience predicates.
Supported request options are:
headers(Headers)to supply normalized request headers.body(Body)to supply a normalized request body. The request-oriented facade also acceptsbody(form_data(Items))as a convenience descriptor for multipart form-data requests; in that case it builds the multipart body viahttp_multipartand injects a generated boundary property unless the caller already provided one inproperties/1.query(Pairs)to append URL-encoded query pairs to the URL query string.version(Version)to override the defaulthttp(1,1)version.properties(Properties)to supply additional normalized request properties.
Supported WebSocket opening-handshake options are:
headers(Headers)to supply additional normalized handshake request headers other than the handshake-managed headers.query(Pairs)to append URL-encoded query pairs to the URL query string.version(Version)to override the defaulthttp(1,1)version. The opening handshake requires HTTP/1.1 or later.protocols(Protocols)to request one or more subprotocol tokens.key(Key)to provide an explicitSec-WebSocket-Keyvalue. When omitted, a fresh key is generated automatically.connection_options(Options)to pass socket options through tohttp_socket::open_connection/4.
Multipart workflow
The current multipart workflow is intentionally small but practical:
For generic multipart bodies, callers can keep using normalized
content(MediaType, multipart(Parts))terms.For common form-data requests, callers can use
form_data(Items)through the existing body path, includingpost/4-5,put/4-5, andpatch/4-5.The
Itemsdescriptors are the samefield(Name, Value, Parameters)andfile(Name, Filename, MediaType, Payload, Parameters)descriptors supported by thehttp_multipart::form_data_body/2helper.In both descriptor shapes,
Parametersis the ordered list of extraContent-Disposition: form-dataparameters to preserve or generate.The reserved
nameandfilenameparameters stay explicit helper arguments and must not be repeated in theParameterslist.When using the convenience descriptor, the client facade adds a multipart boundary property automatically so the request can be generated on the wire without extra caller bookkeeping.
For example, callers can send extra disposition parameters explicitly:
| ?- http_client::post(
URL,
form_data([
field(title, 'Logtalk', [charset-utf8]),
file(upload, 'notes.txt', 'text/plain', text(hello), [creation_date-'2026-06-08'])
]),
Response,
[]
).
WebSocket workflow
The current WebSocket workflow is intentionally limited to the opening handshake:
Call
open_websocket/4with an absolutews://URL.The helper opens a dedicated reusable
http_socketconnection, sends a normalized opening-handshake request, and validates the server101response including theSec-WebSocket-Acceptvalue.When the call succeeds, the returned connection handle remains open so a higher layer can take over the upgraded stream, typically by calling
http_socket::connection_streams/3and then using thehttp_websocketframe predicates, thehttp_websocket_messagesmessage predicates, or the statefulhttp_websocket_sessionpredicates with explicit close-state and automatic control-message handling, including the higher-levelrun_session/3-4callback loop.When client-side code wants one entry point for handshake plus session execution, the
http_websocket_client_session::open/4-5predicates layer on top ofopen_websocket/4, can write optional initial outbound messages, and then run the callback-driven session loop.
Callers are responsible for eventually closing that connection using
http_socket::close_connection/1if a later layer does not take ownership of it. Thehttp_websocket_session::run_session/3-4predicates do take ownership of the upgraded connection and close it automatically when the session loop finishes, as do the higher-levelhttp_websocket_client_session::open/4-5predicates.
The http_client_core object continues to provide the original
stream-based predicates:
write_request/2read_response/2exchange/4exchange_connection/4
Framing rules
The http_client_core object supports response framing for:
status codes that never carry a body (
1xx,204,205, and304)fixed-length bodies via
Content-Lengthchunked bodies via
Transfer-Encoding: chunked, including trailersclose-delimited bodies terminated by end-of-file
Request-aware exchanges performed by http_client_core::exchange/4
and http_client_core::exchange_connection/4 also support HEAD
responses. In that case the normalized response body is empty and
the response is annotated with the metadata properties
body_omitted(head) and, when applicable,
omitted_body_length(Length).
Close-delimited responses returned by
http_client_core::read_response/2 and
http_client_core::exchange/4 are annotated with the property
body_framing(close_delimited). Sequential exchanges can only use a
close-delimited response as the final response in the sequence because
the connection is consumed while reading the body.
Current limitations
Only absolute
http://URLs are supported by the request-oriented facade.https://URLs are rejected until transport-level TLS support is added.The WebSocket helper supports only plain
ws://opening handshakes.wss://remains out of scope until TLS support exists.The
open_websocket/4predicate is limited to opening-handshake validation. See the dedicated WebSocket libraries for frame parsing, message reassembly, and related functionality.Only the transport coding sequence
[chunked]is recognized when reading streamed response bodies inhttp_client_core.Close-delimited response bodies can only be used as the final response in an
http_client_core::exchange_connection/4sequence.The
http_client_coreobject assumes binary streams for both input and output.