λ

2 Tutorials

λ

2.1 Using built-in HTTP/2 client

[in package HTTP2/CLIENT]

There is a simple client in the package http2/client.

λ

2.2 Starting HTTP/2 server

[in package HTTP2-SERVER with nicknames HTTP2/SERVER]

Start server on foreground with RUN, or on background with START.

This creates (as of this version) a multithreaded server that serves 404 Not found responses on any request.

λ

2.3 Define content for HTTP/2 server

[in package HTTP2-SERVER with nicknames HTTP2/SERVER]

To server something else than 404 Not found, you need to define handlers for specific paths. Simple handler definition can look like

(define-exact-handler "/hello-world"
  (handler (foo :utf-8 nil)
    (with-open-stream (foo foo)
      (send-headers
       '((:status "200")
         ("content-type" "text/html; charset=utf-8")))
      (format foo "Hello World, this is random: ~a" (random 10)))))

This defines a handler on "/hello-world" path that sends reasonable headers, writes some text to the stream and closes the stream (via WITH-OPEN-STREAM). The text written is passed to the client as data (body).

In general, the handlers are set using DEFINE-PREFIX-HANDLER or DEFINE-EXACT-HANDLER, and are functions typically created by HANDLER macro, or (in simple cases) by REDIRECT-HANDLER or SEND-TEXT-HANDLER functions.

λ

2.4 Getting request details

[in package HTTP2-SERVER with nicknames HTTP2/SERVER]

Sometimes you need to get some data from the request.

These data can be carried by querying the HTTP/2 stream object involved. If you define handlers by HANDLER macro, it is available in a lexically bound STREAM variable.

(define-exact-handler "/body"
  (handler (foo :utf-8 nil)
    (with-open-stream (foo foo)
      (send-headers
       '((:status "200")
         ("content-type" "text; charset=utf-8")))
      (format foo "Hello World, this is a ~s request.~3%content~%~s~3%headers~%~s~%~3%body~%~s~%"
         (http2/core::get-method stream)
         (http-stream-to-string stream)
         (http2/core::get-headers stream)
         (http2/core::get-body stream)))))

λ

2.4.1 Body of the request

Sometimes there is a body in the client request.

When sending a such a request, you can use CONTENT parameter of the HTTP2/CLIENT:RETRIEVE-URL, together with CONTENT-TYPE.

 (http2/client:retrieve-url "https://localhost:8080/body" :content "Hello")
 (http2/client:retrieve-url "https://localhost:8080/body"
       :content #(1 2 3) :content-type "application/octet-stream")

When you write a handler for such a request, you should know if you want binary or text data. The vanilla class for the server streams looks at the headers, and if they look like UTF-8 (as per IS-UTF8-P), it processes the data as text, if not, they are collected as binary vector.

When your client systematically send headers that do not make it TEXT and you want to read text, as last resort change class of your streams to include FALLBACK-ALL-IS-ASCII (or improve IS-UTF8-P, or add some other decoding function).

If you do not want to see text at all, change class to NOT include UTF8-PARSER-MIXIN or any other conversion mixin.

λ

2.5 Customize client

[in package HTTP2/CLIENT]

RETRIEVE-URL is in fact a thin wrapper over FETCH-RESOURCE generic function.

You can customize its behaviour by creating subclasses for the GENERIC-REQUEST class and specialized methods for your new classes, as well as by changing documented variables.

λ

2.5.1 Client example: multiple requests

For example, this is how you can make a client that download several pages from same source.

First, you need a custom request class, both to have something to specialize FETCH-RESOURCE on and to keep the list of resources to fetch.

(defclass multi-url-request (simple-request)
  ((urls :accessor get-urls :initarg :urls)))

(defmethod fetch-resource ((connection client-http2-connection)
                           (request multi-url-request) args)

  (dolist (r (get-urls request))
    (fetch-resource connection r nil)))

Then you need a specialized stream class to specialize what to do when the responses arrive. Here it prints out the response body for simplicity, and ends if it got the last response.

(defclass my-client-stream (vanilla-client-stream)
  ())

(defmethod peer-ends-http-stream ((stream my-client-stream))
  (print (or (get-body stream) (http-stream-to-string stream)))
  (when (null (get-streams (get-connection stream)))
    (signal 'client-done))
  (terpri))

See GET-BODY, HTTP-STREAM-TO-STRING and CLIENT-DONE.

And finally, you need to pass these as the call parameter:

(http2/client:retrieve-url "https://localhost:8088/body"
   :request-class 'multi-url-request
   :urls '("https://localhost:8088/body" "https://localhost:8088/")
   :stream-class 'my-client-stream)

λ

2.5.2 Client reference

For a simple request without body, following documented methods are called in sequence: