This specification defines an HTTP client interface for XPath based languages, such as but not limited to, XQuery, XSLT, and XProc. The HTTP client interface is provided through extension functions which perform HTTP requests, and associated error codes which define client error states.

Version 2.0 is backward-compliant with the [[expath-http-client-10]] and has been designed to be compatible via [[!XPATH-31]] with [[!XQUERY-31]], and [[!XSLT-30]].

Introduction

The HTTP Client Module 2.0 provides an HTTP client interface for XPath 3.1 languages.

Version 2.0 makes the following design decisions over version 1.0:

An implementation of the module MUST be “backward-compliant” with version 1.0 of the HTTP Client Module. We define backward-compliant to mean that both versions of HTTP Client Module live in harmony side-by-side, that is to say that their functions, etc. are all in the same namespace. We have intentionally designed 2.0 so that it does not override, replace, or conflict, with any specific behavior of 1.0. Rather, 2.0 complements 1.0, providing a fresh API for newer versions of XPath. The design allows users to conveniently use either version, or to use both 2.0 and 1.0 versions together should they wish.

Namespaces and Prefixes

All functions and errors in this module are assigned to the http://expath.org/ns/http-client namespace. In this document, the http prefix is bound to this URI.

Supported HTTP versions

Implementations of this module are expected to support HTTP 1.0 [[!rfc1945]], 1.1 [[!rfc2616]], and 2.0 [[!rfc7540]]. Unless otherwise explicitly stated in this specification, client behavior is expected to conform to the appropriate HTTP RFC.

HTTP 1.0 Support

This module supports all features of HTTP 1.0. Although we do not provide explicit functions for the LINK and UNLINK HTTP methods, as in practice these methods do not appear to be widely used, they are still supported via the http:send function.

HTTP/2 Support

We acknowledge that HTTP/2 support is currently in its infancy. We have attempted to design for a future version of this specification to extend control for HTTP/2 via extension points such as $options and further arity functions. We await feedback from the users of this specification, with regards to revisions for further support of HTTP/2 features.

Functions

The functions of this document have several things in common:

The legacy function http:send-request is still available; it is defined in [[!expath-http-client-10]].

http:get

http:get($uri as xs:string) as map(xs:string, item())
http:get($uri as xs:string, $options as map(xs:string, item())) as map(xs:string, item())

Sends a GET request to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:post

http:post($uri as xs:string, $body as item()*) as map(xs:string, item())
http:post($uri as xs:string, $body as item()*, $options as map(xs:string, item())) as map(xs:string, item())

Sends a POST request with an optional $body to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:put

http:put($uri as xs:string, $body as item()*) as map(xs:string, item())
http:put($uri as xs:string, $body as item()*, $options as map(xs:string, item())) as map(xs:string, item())

Sends a PUT request with an optional $body to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:delete

http:delete($uri as xs:string) as map(xs:string, item())
http:delete($uri as xs:string, $options as map(xs:string, item())) as map(xs:string, item())

Sends a DELETE request to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:head

http:head($uri as xs:string) as map(xs:string, item())
http:head($uri as xs:string, $options as map(xs:string, item())) as map(xs:string, item())

Sends a HEAD request to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:options

http:options($uri as xs:string) as map(xs:string, item())
http:options($uri as xs:string, $options as map(xs:string, item())) as map(xs:string, item())

Sends an OPTIONS request to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:trace

http:trace($uri as xs:string, $body as item()*) as map(xs:string, item())
http:trace($uri as xs:string, $body as item()*, $options as map(xs:string, item())) as map(xs:string, item())

Sends a TRACE request with an optional $body to the supplied $uri and returns the response as a map. Additional $options can be supplied.

http:send

http:send($uri as xs:string, $method as xs:string) as map(xs:string, item())
http:send($uri as xs:string, $method as xs:string, $body as item()*) as map(xs:string, item())
http:send($uri as xs:string, $method as xs:string, $body as item()*, $options as map(xs:string, item())) as map(xs:string, item())

Sends a custom HTTP request with the supplied $method and an optional $body to the specified $uri and returns the response as a map. Additional $options can be supplied. The $method should be given according to the appropriate specification, for HTTP this means in upper-case characters, e.g. GET.

The Request

An HTTP request is made to a URI, and consists of headers and an optional entity body.

The Request URI

The request URI is always specified in the $uri parameter to each function. The request URI MUST be a valid URI according to the RFC of the HTTP version that is being used for the request. If the URI is invalid, the error http:invalid-uri MUST be raised.

Request Options

The following options can be specified via the $options parameter:

Request Options
Option Key XDM Type Default Description
http-version xs:string 1.1

The HTTP Version to use for the request.

Supported versions are 1.0, 1.1, and 2.0.

headers map(xs:string, xs:string) empty-sequence() HTTP header fields.
content-encoding xs:string empty-sequence()

Uses a specific content encoding for the request body.

Supported encodings are gzip, compress, and deflate.

NOTE: Setting this option will override any Content-Encoding HTTP header.

transfer-encoding xs:string
  • empty-sequence() for HTTP 1.0 and 2.0
  • chunked for HTTP 1.1

Uses a specific transfer encoding for the request body.

Supported encodings are none and chunked.

NOTE: Setting this option will override any Transfer-Encoding HTTP header.

NOTE: chunked is not supported by HTTP 1.0 or 2.0, such use MUST raise the http:version error.

multipart xs:boolean false()

Indicates if the entity body in $body is a multipart entity body.

permit-expired-ssl-certificate xs:boolean false()

This allows HTTPS requests to still work when the server provides an SSL certificate which has expired.

permit-untrusted-ssl-certificate xs:boolean false()

This allows HTTPS requests to still work when the server provides an SSL certificate which is not trusted, this may be because the CA (Certificate Authority) is unknown.

follow-redirect xs:integer 20

Follow HTTP redirects from the server.

0 indicates that redirects are not to be followed, -1 indicates that redirects are to be followed indefinitely, a specific number indicates the maximum number of redirects to follow.

timeout xs:decimal 120

Maximum number of seconds to wait for a response. Milliseconds may be specified using the fractional digits of an xs:decimal e.g. 0.200 is 200 milliseconds.

The value 0 indicates that no timeout should be imposed.

If a negative value is specified, the error code http:invalid-option MUST be raised.

When a timeout is set, if the server fails to respond within the timeout period, the error http:timeout MUST be raised.

certificates map(xs:string, item()+)+ empty-sequence()

Options for authenticating the client with the server using certificate(s). The type and use of this option is implementation defined.

NOTE: Java implementations SHOULD use the definition defined in .

auth-method xs:string Basic

HTTP Authentication method.

Supported values are: Basic, and Digest.

Implementations are free to define additional authentication methods.

preemptive-auth xs:boolean false

Indicates that HTTP authentication should be attempted preemptively, i.e. before a challenge from the server.

If the chosen auth-method does not support preemptive authentication, then the error http:invalid-option MUST be raised.

username xs:string

Authentication: username.

password xs:string

Authentication: password.

status-only xs:boolean false()

Ignore the headers and body in the HTTP response.

This may be used to save resources on parsing the response when the user is only interested in the response code.

parse-response-entity-body xs:boolean true()

Indicates if the response bodies will be parsed (see ).

When set to true(), if status-only is also set to true(), the error http:invalid-option MUST be raised.

          http:get('http://expath.org/xyz', map {
            'status-only': true(),
            'headers': map { 'User-Agent': 'EXPath/1.0' }
          })
        

Authentication data is processed if a non-empty username is supplied:

          http:delete('http://expath.org/xyz', map {
            'username': 'john',
            'password': '****'
          })
        

The http:invalid-option error will be raised if the value of an option is invalid, or if it has an invalid type.

The Request Entity Body

Some HTTP methods (such as POST, PUT, and others that are less common) transmit an entity body (often colloquially shortened to the "Request Body"). The functions in this module use the $body parameter to supply this.

If the value supplied as the $body parameter is an empty sequence, the corresponding request’s entity body will be omitted.

If the supplied value, or the value(s) of a multipart entity body, have any other type than described in ths section, the http:body error MUST be raised. The http:serialize error MUST be raised if an error occurs whilst serializing an item (e.g. because a supplied map cannot be serialized as JSON).

Single Entity Body Request

By default, or if the multipart option is set to false, a single entity will be sent in the request’s entity body.

For a single entity, the value for the $body parameter MUST have one of the following types:

xs:base64Binary, xs:hexBinary
Value will be sent as binary data, the media type will be application/octet-stream.
xs:string
The string will be serialized as UTF-8 octets, the media type will be text/plain.
node()
The node will be serialized as XML, the media type will be application/xml.
map(*), array(*)
The map will be serialized as JSON, the media type will be application/json.

The evaluated media type will be assigned as the value of the Content-Type header unless a value has been supplied by the user in headers. For multipart requests, each media type will be sent as the Content-Type header in the corresponding multipart header section.

            http:put('http://expath.org/xyz', 'plain and simple')
          

If no implicit conversion is available for the desired media type, the body can be serialized to a string or binary item in advance, and a custom Content-Type header value can be supplied:

            http:post(
              'http://expath.org/xyz',
              file:read-text('persons.csv'),
              map { 'headers': 'Content-Type': 'text/csv;charset=UTF-8' }
            )
          

Multipart Entity Body Request

Multipart Types provide for the encapsulation of several entities (aka parts) within the request's entity body.

An entity body of multiple parts, wherein each part is itself an entity, will be sent if the multipart option is set to true.

The $body parameter allows one or more entities to be provided; Each entity MUST have the type map(xs:string, item()), and the format: map { 'headers': $part-headers, 'body': $part-body }. If the body entry is not supplied, or if it is an empty sequence, the error http:body MUST be raised.

The $part-body value will be parsed as a Single Entity. If no Content-Type header is supplied in the for the specific part, the resulting media type will be assigned.

(
  map {
    'headers': map {
      'Content-Disposition': 'form-data; name="file"; filename="covering-letter.md"',
      'Content-Type': 'text/markdown'
    },
    'body': fn:unparsed-text('covering-letter.md')
  },
  map {
    'headers': map {
      'Content-Disposition': 'form-data; name="application-name"'
    },
    'body': 'John Smith'
  },
  map {
    'headers': map {
      'Content-Disposition': 'form-data; name="email"'
    },
    'body': 'john@smith.com'
  }
)
          
          http:send('http://expath.org/xyz', 'POST', (
            (: Body 1, implicit Content-Type: application/xml :)
            map {
              'body': <persons>...</persons>,
            }
            (: Body 2, explicit Content-Type: application/x-object :)
            map {
              'headers': map {
                'Content-Disposition': 'form-data; name="uploadedfile"; filename="hello.o"',
                'Content-Type': 'application/x-object'
              },
              'body': xs:hexBinary('414243')
            }
          ))
        

The Response

When the HTTP request is made to the server indicated by the request $uri, if the server processes the request and responds to the client within the timeout then the HTTP response from the server is returned as the result of the function as a map.

If the server fails to respond to an HTTP request within the timeout then the error http:timeout MUST be raised. If the HTTP request cannot be made to the server then the error http:network MUST be raised.

The Response Map

The HTTP response map contains the following entries. Optional entries are entirely absent from the map if the HTTP response does not contain the corresponding data, for example no body is returned, there will be no body entry.

Response Entries
Response Key XDM Type Optional Description
http-version xs:string No

The HTTP Version of the response. This may differ from the http-version requested!

status xs:string No

The HTTP status code.

message xs:string No

HTTP reason phrase.

headers map(xs:string, xs:string) Yes HTTP response header fields.
body item()+ Yes HTTP response body.
          map {
            "http-version": "1.1",
            "status": "200",
            "message": "OK",
            "headers": map {
              "Date": "Fri, 31 Dec 1999 23:59:59 GMT",
              "Server": "Apache",
              "Content-Type": "text/plain"
            },
            "body": "Happy new year"
          }
        

The Response Entity Body

HTTP requests sent to the server are under complete the control of the client, as such, the client can carefully dictate the data sent in the request. The opposite is true of the HTTP response returned from the server, over which the client has no control. The majority of the time we can rely on the server to perform as expected and return a meaningful response to our request. However, regardless of whether the server is trusted or not by the user, we cannot rely on it to always operate correctly. Servers can, and often do, return HTTP responses which are not in the spirit of the HTTP request that was made. For example, a server is under no obligation to return data in a format that the client can understand or even read.

The request functions of this module are intentionally biased towards the majority case where the server is responding inline with the requests made to it. This bias takes the form of implicitly parsing the response entity body from the server before it is returned in the response map. This approach eases the job of the developer, by allowing them to work with higher-level XDM type representations of the response body as opposed to the raw HTTP response data.

For those cases where the server is not responding faithfully to the request, or the developer needs a greater level of control over the response, the implicit parsing of the response entity body can be disabled via the parse-response-entity-body request option, see .

Implicit Parsing

When present in the HTTP response, the entity body will be implicitly parsed to an XDM type based on the media type set in the Content-Type header of the HTTP response headers.

If a response entity body cannot be parsed according to the Conversions detailed below, the error http:parse MUST be raised.

Implicit Parsing Conversions (ordered - most significant first)
Media Type Pattern(s) XDM Type Description
multipart/* map(xs:string, item())*

Multipart body, returned as map(xs:string, item())*. The format of the map items is identical to the request format: map { 'headers': $part-headers, 'body': $part-body }. A part body will have one of the remaining types of this list.

  • application/xml
  • application/xml-external-parsed-entity
  • text/xml
  • text/xml-external-parsed-entity
  • */*+xml
document-node()

Parsed as XML.

application/json
  • map(*)
  • array(*)
  • xs:string
  • xs:double
  • xs:boolean
  • empty-sequence()

Parsed as JSON.

text/* xs:string

Parsed as text.

*/* xs:base64Binary

No parsing.

          map {
            "status": "200",
            "message": "OK",
            "headers": map {
              "Content-Type": "multipart/mixed"
            },
            "body": [
              map {
                "headers" : map {
                  "Content-Type": "application/json"
                },
                "body": map { "creator": "John", "year": 2018 }
              },
              map {
                "headers" : map {
                  "Content-Type": "image/png"
                },
                "body": xs:base64Binary('abcdefgh')
              }
            ]
          }
        

Explicit Parsing

Implicit parsing of the response body can be disabled via the parse-response-entity-body request option. All bodies of single and multipart responses will be returned as binary items of type xs:base64Binary, and the values can be processed (stored, parsed, forwarded) in a second step.

            (:~
             : Parses a response and returns the parsed body values.
             : @param $response response (top-level, multipart)
             : @return flat sequence with parsed bodies
             :)
            declare function local:parse(
              $response as map(xs:string, item())
            ) as item()* {
              let $type := $response?headers?Content-Type
              let $body := $response?body
              return if(ends-with($type, '/csv') then (
                csv:parse($body)
              ) else if(ends-with($type, '/json') then (
                json:parse($body)
              ) else if(ends-with($type, '/html') then (
                html:parse($body)
              ) else if(starts-with($type, 'multipart/') then (
                (: parse multipart entries recursively :)
                for $part in $body
                return local:parse($part)
              ) else (
                error(xs:QName('http:parse'), 'Type not supported: ' || $type)
              )
            };

            local:parse(
              http:get('http://expath.org/xyz',
                  map { 'parse-response-entity-body': false() })
            )
          

Errors

This section defines and describes the error codes that may be raised by the module.

http:body
A request body argument has an invalid type.
http:version
A feature is not supported by the specified HTTP version.
http:invalid-uri
The URI specified in the $uri parameter to the function is invalid with respect to the HTTP version in use.
http:invalid-option
The value of an option is invalid, or has an invalid type.
http:parse
A response body could not be parsed.
http:serialize
A request body could not be serialized.
http:timeout
The server did not respond to the request within the timeout period.
http:network
A network error occurred whilst making the HTTP request.

Certificates Option format for Java implementations

This appendix provides an example of how the certificates option SHOULD be specified for Java implementations.

"certificates": (
  map {
    "trust": map {
      "keystore": "/x/y/my-trust-store.jks",
      "keystore-password": "trust-store-password",
      "alias": "trust-certificate"
    },
    "client": map {
      "keystore": "/x/y/my-client-store.jks",
      "keystore-password": "client-store-password",
      "alias": "my-certificate",
      "password": "my-certificate-password"
    }
  }
)
      

NOTE: In the above example the alias options are OPTIONAL.

Acknowledgements

Many thanks to Robin Berjon for making the production of this specification much simpler by starting the ReSpec tool project.