Quickstart
A first server, one step at a time. Each step is a whole program: save it,
run it with make run PROGRAM=your-file.sps, and try it with curl from
another terminal. Ctrl-C stops it.
1. Answer every request
(import (rnrs) (http-server))
(serve
(lambda (request)
(make-response 200 #f '() (string->utf8 "Hello\n"))))
servestarts the server and runs until you stop it. It listens on127.0.0.1:8080and printslistening 127.0.0.1 8080.- The handler, the
lambda, gets each request and returns a response. make-responsetakes a status, a reason phrase (#fmeans the usual one, hereOK), a list of headers, and the body as bytes.
$ curl -i http://127.0.0.1:8080/
HTTP/1.1 200 OK
Date: Fri, 16 Oct 2026 12:00:00 GMT
Content-Length: 6
Hello
The server added Date and Content-Length for you.
2. Look at the request
(import (rnrs) (http-server))
(define (text status s)
(make-response status #f
(list (cons (string->utf8 "Content-Type") (string->utf8 "text/plain")))
(string->utf8 s)))
(serve
(lambda (request)
(let ((path (request-path request)))
(cond ((string=? path "/") (text 200 "home\n"))
((string=? path "/hello")
(text 200 (string-append "hello, " (or (request-query request) "stranger") "\n")))
(else (text 404 "not found\n"))))))
$ curl 'http://127.0.0.1:8080/hello?you'
hello, you
$ curl -i http://127.0.0.1:8080/nope
HTTP/1.1 404 Not Found
...
A handler routes with plain Scheme: here a cond on the path. Headers are
pairs of bytevectors, (name . value).
3. Read a body
A request with a body reaches your handler before its body has arrived. To read it, answer with a reader instead of a response:
(import (rnrs) (http-server))
(serve
(lambda (request)
(read-whole-body
(lambda (body)
(make-response 200 #f '()
(string->utf8
(string-append "got " (number->string (bytevector-length body))
" bytes\n")))))))
$ curl --data 'hello there' http://127.0.0.1:8080/
got 11 bytes
read-whole-body collects the body, up to 1 MiB by default, then calls your
procedure with it. For bodies of any size, read them piece by piece with
read-body (Bodies and streaming).
4. Stream a response
When you don't have the whole body up front, give a producer: a procedure the server calls for each next piece, until it returns the eof object.
(import (rnrs) (http-server))
(serve
(lambda (request)
(let ((n 0))
(make-response 200 #f '()
(lambda ()
(set! n (+ n 1))
(if (> n 3)
(eof-object)
(string->utf8 (string-append "line " (number->string n) "\n"))))))))
The server sends it chunked, and asks for more only as fast as the client
reads. If you know the length, wrap the producer in sized-body and the
server sends Content-Length instead
(Streaming with a length).
5. Change where it listens
Settings come from the environment:
CHEZ_HTTP_PORT=3000 make run PROGRAM=your-file.sps
Every setting, from the port to each timeout, is in Configuration.