- Overview
- Getting started
- Handling requests
- Built-in handlers
- Logging and tuning
- Starting and terminating the server
- Add-ons
- Examples
Gauche-makiki is a simple multi-threaded http server intended for
applications that want to provide http server capability easily.
The main functionalities are available by just one file, makiki.scm
,
so you can either install it as an ordinary Gauche extension library,
or you can just copy the file into your application.
You need Gauche 0.9.7 or later to use Gauche-makiki. To serve over secure connection (https), you need Gauche 0.9.14 or later with TLS support.
You'll get the idea by looking at the minimal server:
(use makiki)
(define (main args) (start-http-server :port 6789))
(define-http-handler "/"
(^[req app] (respond/ok req "<h1>It worked!</h1>")))
Basically, you register a handler for a path (or a pattern for a path), and the server dispatches matching request to the handler, which is expected to return the content.
The req
argument holds the information of the request, and
the app
argument holds the application state you pass to
start-http-server
. (The above example isn't using application
state. See this BBS example for simple
usage of application state.)
See also development.md to develop server script interactively with REPL.
Gauche-makiki isn't an all-in-one framework; rather, we provide simple and orthogonal parts that can be combined together as needed.
To use the server, you should define http-handler using
define-http-handler
macro:
(define-http-handler [METHODS] PATTERN [? GUARD-PROC] HANDLER-PROC)
Or, handlers can be added procedurally using add-http-handler!
:
(add-http-handler! PATTERN HANDLER-PROC :optional GUARD-PROC METHODS)
METHODS
is a list of symbols (GET
, POST
, etc.) that this handler
accepts. You can define different handler with the same PATTERN
as far as METHODS
don't overlap. When omitted,
(GET HEAD POST)
is assumed.
PATTERN
is to specify routing from the request path to the handler.
It is explained in the Routing section below.
REQUEST
is a request record, explained below.
APP-DATA
is an application-specific data given at the time the server
is started. Gauche-makiki treats APP-DATA
as opaque data; it's
solely up to the application how to use it.
The optional GUARD-PROC
is a procedure called right after the
server finds the request path matches PATTERN
.
It is called with two arguments,
REQUEST
and APP-DATA
. If the guard proc returns false,
the server won't call the corresponding handler and look for
another match instead.
It is useful to refine the condition the handler is called.
If the guard procedure returns a non-false value, it is stored in the
guard-value
slot of the request record, and available to the
handler procedure.
See examples/session.scm for an example
of using a guard procedure and the request's guard-value
slot.
PATTERN
argument of define-http-handler
can be a string,
a list, or a regexp,
- If it is a string, it matches with the exactly same request path. It is suitable for resources with a fixed path.
- If it is a list, each element must be either a string or a symbol.
The request path is split with
/
into a list of path components, and each component is compared to the corresponding element ofPATTERN
. If the pattern's element is a string, it must exactly match the path component. If the element is a symbol, it matches unconditionally to the corresponding component, and the match is saved torequest-path-match
. If the list is a dotted list, the lastcdr
must be a symbol, and it matches any remaining components. - If it is a regexp, it is matched against the entire request path.
If matched, the match object is saved to
request-path-match
so that submatches can be retrieved later.
For each incoming request, the server matches its path of
the request uri against PATTERN
. Note: For component-wise match
with a list pattern, trailing slashes of request path is ignored; that is,
("foo" x)
matches "/foo/bar"
and "/foo/bar/"
.
When the request path matches, the server calls HANDLER-PROC
with
two arguments:
(handler-proc REQUEST APP-DATA)
Some routing examples:
;; Fixed path. The handler is called when the client requests
;; /favicon.ico.
(define-http-handler "/favicon.ico" handler)
;; Path with parameter. Paths such as "/usr/bob" or "/usr/alice"
;; match, and the variable part can be retrieved with
;; ((request-path-match req) 'user-id).
(define-http-handler ("user" user-id) handler)
;; Path with parameter and conversion. Obj-id part only matches
;; a string valid as an integer, e.g. "/obj/233515". The matched
;; part is converted to an integer with the procedure path:int.
(define-http-handler ("obj" (path:int obj-id)) handler)
;; If the pattern is a dotted-list, all the rest path components
;; are saved with the name of the last cdr. The following route
;; matches "/src/a/b/c.txt", for example, and
;; ((request-path-match req) 'path) gives "a/b/c.txt".
(define-http-handler ("src" . path) handler)
There are a few utility procedures to help parsing a path component. They take a path component as a string, and returns #f if it is not accepted, or an object if accepted.
(path:int COMPONENT)
If the path component string COMPONENT is a valid exact decimal integer representation, returns that exact integer; otherwise returns #f.
(path:hex COMPONENT)
If the path component string COMPONENT is a valid exact hexadecimal integer representation, returns that exact integer; otherwise returns #f.
(path:uuid COMPONENT)
If the path component string COMPONENT is a valid representation of UUOD, returns an UUID object; otherwise returns #f. See the document of rfc.uuid module for the details of UUID representation.
The value of the matched component can be retrieved with passing
the key value to (request-path-match req)
, or, more conveniently,
using request-path-ref
. See below.
A request record passed to the handlers and guard procedures has the following slots (only public slots are shown):
line ; request line
socket ; client socket (#<socket>)
remote-addr ; remote address (sockaddr)
method ; request method (symbol in upper cases, e.g. GET)
uri ; request uri
http-version ; requested version (e.g. "1.1")
server-host ; request host (string)
server-port ; request port (integer)
secure ; whether the communication is secure
; This can be
; - #t (we have direct TLS connection),
; - 'forwarded (we have reverse proxy that has
; secure connection to the client),
; - #f (otherwise)
path ; request path (string, url decoded)
path-match ; proc to extract matched path component
guard-value ; the result of guard procedure
query ; unparsed query string
params ; query parameters (result of cgi-parse-parameters)
headers ; request headers (result of rfc822-read-headers)
(response-error) ; #f if response successfully sent, #<error> otherwise.
; set by respond/* procedures. The handler can check
; this slot and take actions in case of an error.
The following convenience procedures are available on the request record.
(request-iport REQ) ; input port to read from the client
(request-oport REQ) ; output port to write to the client.
; NB: the handler proc shouldn't write
; to this port normally---one of the
; 'respond' procedures below takes care of
; writing response line and headers.
(request-params REQ) ; List of query-string parameters, in the form
; of ((NAME VALUE ...) ...) where NAMEs and VALUEs
; are all strings. It is compatible to the
; reutrn value of cgi-parse-parameters.
(request-param-ref REQ PARAM-NAME . keys)
; Retrieve request query-string parameter with
; PARAM-NAME. KEYS are a keyword-value list
; passed to cgi-get-parameter in www.cgi.
; See also `let-params` below for easier access.
(request-headers REQ) ; List of request headers, in the form of
; ((NAME VALUE) ...). It is compatible to the
; header list used in rfc.822 module.
(request-header-ref REQ HEADER-NAME :optional (DEFAULT #f))
; retrieve the value from the request headers.
; See also `let-params` below for easier access.
(request-cookies REQ) ; returns parsed cookie list (see rfc.cookie)
; in the request.
(request-cookie-ref REQ COOKIE-NAME :optional (DEFAULT #f))
; returns one entry of the parsed cookie with
; the given COOKIE-NAME. The returned value
; is the result of `parse-cookie-string` of
; `rfc.cookie`, i.e.
; `(<name> <value> <cookie-parameters> ...)`
; See also `let-params` below for easier access.
(request-path-ref REQ KEY :optional (DEFAULT #f))
; returns the value matched to the path component
; designated with KEY. If the input path did not
; match they component with KEY, DEFAULT is
; returned instead.
The handler procedure can set/modify response headers using the following procedures.
(response-header-push! REQ HEADER-NAME VALUE)
(response-header-delete! REQ HEADER-NAME)
(response-header-replace! REQ HEADER-NAME VALUE)
(response-cookie-add! REQ NAME VALUE . COOKIE-OPTIONS)
(response-cookie-delete! REQ NAME)
If a header with the same name is pushed more than once, they all
appear in the response. If you want to override the previously
pushed header, use response-header-replace!
.
If a cookie with the same name is added, it replaces the previous
one if any. Note that response-cookie-delete!
does send back
a cookie to the client, but with the past expiration time so that
the client will delete it on its end.
By default, secure
attribute of the cookie is automatically added
if request-secure
has a true value. You can turn off this behavior
by setting the parameter add-secure-cookie
to false.
After a handler is selected according to the request url and method,
query parameters in the url is parsed and saved in request-params
.
(Note: Parameters passed via POST
request body are not
parsed automatically; see Handling POST request body below.)
It is often the case that the server needs to look at several
different places to see what parameters the client provides;
most commonly they are passed via query parameters in url
or form-encoded in POST body, but can also be via request path
component (often the case in REST API) or sometimes via cookies
or even via request headers. The let-params
macro provides
an easy and convenient way to access those parameters.
(let-params REQ (VAR-SPEC ...) BODY ...)
REQ
should be the request record. Each VAR-SPEC
specify
a variable and its source, in one of the following forms:
(var source kv-args ...)
(var)
var
Where var
is a symbol (variable name), source
is a string,
and kv-args
is keyword-value list. This form extracts parameters
according to source
and binds its value to var
, then executes
body
.... The latter two forms are a shorthand for (var "q")
.
The source
string can have either <kind>:<name>
or just <kind>
,
where <kind>
is a single character specifying where the value
should be taken.
q - Query parameters
p - Path regexp matches
c - Cookies
h - Request headers
The optional <name>
part specifies the parameter's name as sent
from the client, e.g. query name, cookie name or header name.
For path regexp match, <name>
can be a word for named subgroup,
or an integer for unnamed subgroups. If <name>
is omitted, the
name of var
is assumed.
The following keyword arguments are accepted in kv-args
.
:default <value> Specifies the default value when the
parameter isn't provided from the client.
The default default value is `#f`, except
for the list query parameters, in which case
the default default value is `()`.
:convert <proc> Specifies the converter procedure `<proc>`,
which should take a string and convert
it to suitable type of object. Note:
if the value isn't provided, `<proc>`
is never called and the default value is
directly used.
:list <flag> This is only effective for query parameters.
If `<flag>` is a true value, the client
can specify multiple instances of the same
name of query parameters, and all the values
are gathered to a list. If `:convert` is
also given, the convert procedure is applied
to each value. If `:default` is also given,
its value is only used when there's no
parameter for this name is provided.
Suppose you have in the following code:
(define-http-handler #/^\/resource\/(\d+)\/edit$/
(^[req app]
(let-params req ([name "q"]
[comment "q:c"]
[resource-id "p:1" :convert x->integer]
[sess "c:sessionid"])
...)))
And if the client sends this request:
http://..../resource/33525/edit?name=foo&c=bar%20baz
Then the code gets name
to be bound to "foo"
, comment
to
be bound to "bar"
, resource-id
to be bound to 33525. (Sess
would depend on whether the client send a cookie for "sessionid"
.)
A query string in a request url is automatically parsed and
accessible via request-query
, request-params
and request-param-ref
,
but the parameters passed via POST/PUT body aren't processed by default.
The following procedure returns a handler that parses POST request body
and put the parsed result to request-params
:
(with-post-parameters INNER-HANDLER :key PART-HANDLERS)
It can handle the body with both multipart/form-data
and
application/x-www-form-urlencoded
content types.
The REQUEST structure the INNER-HANDLER receives got parsed parameters (If the original request also has a query string in url, that will be overwritten.)
PART-HANDLERS specifies how to handle each parameter is handled
according to its name. By default, all parameter values are
read into strings. However, you might not want that behavior if
you're accepting large file updates. See the documentation of
www.cgi
module
for the meaning of PART-HANDLERS.
For Web APIs, it is convenient to receive request in json. Here's a convenience wrapper:
(with-post-json INNER-HANDLER :key ON-ERROR)
It parses the request body as json, and set the parsed value in
request-params
with the key "json-body". That is, INNER-HANDLER
can access the parsed json by (request-param-ref req "json-body")
.
If the client didn't pass the body, the "json-body" parameter is #f
.
Json dictionary becomes an alist, and json array becomes a vector.
See the documentation of
rfc.json
, for
the details.
ON-ERROR is a procedure that takes three argument, as
(on-error req app condition)
, and called when a <json-parse-error>
is raised during parsing. It must either return an alternative value
to be used as the value of "json-body", or call request-error
to
notify the client the error. If omitted or #f
, the procedure
returns 400 error to the client.
If you don't use one of the POST handlers above, the request body
hasn't been read when the handler is called. The easiest way to
retrieve the request body at once is using read-request-body
:
(read-request-body req)
This reads the request body into a fresh u8vector and returns it.
It can return #f
if the request has no body.
If the body has already read (even partially), or ended prematurely
(i.e. the data is smaller than the size stated by content-length),
this procedure returns #<eof>
.
If you need to handle request body specially (for example, if the client
sends huge binary data, you don't want to read everything into memory),
you can handle it by yourself. When the handler is called, the request
body is available to be read from (request-iport req)
. Check
the content-type header first, for it must specify the size of the request
body in octets.
HANDER-PROC
should call one of the following respond procedure at
the tail position. NB: These must be extended greatly to support
various types of replies.
(respond/ok REQ BODY :key CONTENT-TYPE)
; This returns 200 response to the client,
; with BODY as the response body. See below
; for allowed values in BODY.
; CONTENT-TYPE argument can override the default
; content-type inferred from BODY.
(respond/ng REQ CODE :key BODY CONTENT-TYPE)
; This returns CODE response to the client.
; If BODY keyword arg is omitted, the body
; consists of the description of the HTTP code.
; See below for allowed values in BODY.
(respond/redirect REQ URI :optional (CODE 302))
; Send back a redirection message using Location
; header. URI can be an absolute uri or
; just a path component; in the latter case,
; protocol, host and port components are
; automatically added.
These procedures return after the entire message is sent. If an error occurs during sending the message (most likely because the client has disconnected prematurely), an error condition is stored in (request-response-error REQ).
The response body for respond/ok
and respond/ng
can be one of
the following forms. The content type can be overridden by
CONTENT-TYPE
keyword argument.
-
string : A string is sent back as
text/plain; charset=utf-8
. -
text-tree : A tree of strings; see
text.tree
. Concatenated string is sent back astext/plain; charset=utf-8
. -
u8vector : The content of the vector is sent back as
application/binary
. -
(
file
filename) : The content of the named file is sent back. Content-type is determined by the file's extension by default. See the description offile-handler
below for the details of content-type handling. -
(
plain
lisp-object) : The lisp object is converted to a string bywrite-to-string
, then sent back astext/plain; charset=utf-8
. -
(
json
alist-or-vector) : The argument is converted to a JSON byconstruct-json-string
(seerfc.json
), then sent back asapplication/json; charset=utf-8
. -
(
sxml
sxml) : The SXML tree is rendered by to XML or HTML. (If the root node of sxml ishtml
, thensxml:sxml->html
is used to render to HTML with content typetext/html; charset=utf-8
. Otherwisesxml:sxml->xml
is used to render to XML, with content typeapplication/xml
. -
(
chunks
string-or-u8vector ...) : Chunks are concatenated and sent back asapplication/octet-stream
. This form allows you to pass a lazy list, so that you can avoid creating entire content in memory.
Check out scripts in examples
directory for some concrete examples.
You can raise a condition <request-error>
to notify the client that
you encounter an error during processing the request. Use the
request-error
procedure to raise the condition:
(request-error :key status body content-type)
The status
keyword argument is used as the http response status,
defaulted by 400. The body
and content-type
arguments are
the same as in respond/ok
and respond/ng
. This is useful to
abort the processing of a request deep in the stack.
For example, you can return an error message in JSON form to the client as follows:
(unless (parameter-is-valid?)
(request-error :body `(json (("message" . "Invalid parameter")))))
If you raise an unhandled condition other than <request-error>
from
the handler, it is captured by makiki and the client receives
500 Internal Server Error
response, and the error itself is logged
to the error log.
The content-type and the body of such 500 response is determined by the "Accept" request header; if the request has "Accept: application/json", the error response is in JSON. For the time being, we recognize application/json and text/html, and for all other content-type we return a plain text (text/plain).
By default, we only return "Internal Server Error" to the client,
which is a bit inconvenient during development. If the environment
variable MAKIKI_DEBUGGING
is set when the server is run, we add
the error message in the error response as well. You can also turn on
the debugging feature by setting the parameter debugging
to a true
value. Make sure you don't
set the environment variable in the production environment, so that you
wouldn't reveal any internal information accidentally.
Response headers and cookies are sent back to a client inside one of
response procedures such as respond/ok
. When you're writing a
wrapper handler, there can be a case that you want to add response
headers or cookies after the internal handler generates content, but
before it is sent back to the client. For example, you might send
a cookie depending on the parameter value which may be modified by
the internal handler.
You can register callbacks for that purpose.
(respond-callback-add! (^[req code content-type] ...))
The callback list is empty when the handler is called. If callbacks are added during handling, they are called in the order of addition, with three arguments: The request record, integer response code, and string content-type. At the time the callback is called, the content of the response is already generated, but no information is sent back to the client yet. You can modify response headers and cookies there. You can't change the response content, though.
The callback must return #f
. We may allow other return values in future.
For typical tasks, we provide convenience procedures to build a
suitable handler. The following procedures return a procedure
that can be directly passed to define-http-handler
; for example,
the following handler definition serves files under document-root
:
(define-http-handler "/" (file-handler))
Some handler-builders takes another handler procedure and returns a new handler that augments the original handler.
See examples for more usages.
For the convenience, file-handler can be used to create a handler procedure suitable for define-http-handler to return a file on the server.
(file-handler :key (directory-index '("index.html" #t))
(path-trans request-path)
(root #f))
PATH-TRANS
should be a procedure that takes REQUEST
and returns
the server-side file path. The returned path should start from
slash, and the document-root directory passed to the start-http-server
is prepended to it. It is not allowed to go above the document
root directory by "/../../.."
etc---403 error message would results.
The file to be served is determined as this: First, the path part
is extracted from the request with PATH-TRANS
, then the directory
name given to ROOT
argument is appended. If ROOT
is #f
, the
value of the parameter document-root
is used.
The DIRECTORY-INDEX
keyword argument specifies the behavior when
given path is a directory. It must be a list of either filename or
#t
. The list is examined from left to right; if it is a filename,
and the named file exists in the directory, the content of the file
is returned. If it is a filename but does not exist, next element
is examined. If it is #t
, the list of the entries in the directory
is returned.
Makiki uses some heuristics to determine content-type
of the file,
but that's far from complete. You can use a parameter file-mime-type
to customize the association of content-type and files; it must be a
procedure that takes one argument, the pathname of the file, and it
must return a mime-type in string, or #f
to delegate the association
to the makiki's default handler.
(document-root)
A parameter that holds the current path of the document root (the one
given to start-http-server
; "."
by default.)
The Last-modified
response header is generated by this handler
automatically, based on the the timestamp of the file.
There's an experimental support to call a CGI script written
in Gauche. Instead of spawning a child process, we load
Gauche program and call its main routine "in process".
You have to (use makiki.cgi)
to use this feature.
(cgi-script FILE :key ENTRY-POINT SCRIPT-NAME LOAD-EVERY-TIME FORWARDED)
Loads the cgi script in FILE, and creates and returns an http handler that
calls a procedure named by ENTRY-POINT inside the script (main
by default).
The return value can be used as the handler argument for define-http-handler
,
as follows:
(define-http-handler #/\/foo.cgi(\/.*)?/
(cgi-script (build-path (sys-dirname (current-load-path))
"foo.cgi")
:script-name "/foo.cgi" :forwarded #t))
To avoid interference with makiki itself, the script is loaded into an independent, anonymous module.
Loading is done only once unless LOAD-EVERY-TIME is true. Usually, loading only once cuts the overhead of script loading for repeating requests. However, if the cgi script sets some global state, it should be loaded for every request---a script can be executed concurrently by multiple threads, so any code relying on a shared mutable global state will fail. Note also that we assume the script itself isn't written inside a specific module; if it has it's own define-module and select-module, the module will be shared for every load, and we won't have enough isolation.
The cgi script should access to cgi metavariables through
cgi-get-metavariable
(in www.cgi
module), not directly
from the environment variables.
SCRIPT-NAME is the path to the script in the URL. That is,
if the script is accessible via http://example.com/foo/bar/baz.cgi
,
then it should be "/foo/bar/baz.cgi"
. It doesn't need to be
related to the actual pathname of the cgi script file. The value
becomes the value of SCRIPT_NAME
CGI metavariable, and also
used to calculate PATH_INFO
CGI metavariable.
If you're running Makiki server behind a reverse proxy, give a true value to FORWARDED. Then Makiki adjust SERVER_NAME, SERVER_PORT, and HTTPS cgi metavariables according to the external connection (between the client and the proxy server), instead of the internal connection (between the proxy and Makiki). It is important to produce output using what the client directly sees; e.g. a link to the same site must use the external hostname and port, otherwise the client can't follow the link.
(cgi-handler PROC :key SCRIPT-NAME FORWARDED)
This is the low-level procedure that creates an http handler that
sets up the cgi metavariables and calls PROC, that takes one
argument (as in main
procedure of the usual script; though
most cgi scripts won't use the argument).
PROC would write out the response to its stdout; which will be captured by the created handler and returned to the client.
(with-header-handler inner-handler header value ...)
This returns a handler that first adds extra response headers then calls INNER-HANDLER.
Header is a keyword representing the header name; value can be a string for header value, a procedure to take request and app-data and to return a string header value, or #f to omit the header. For example, the following call returns a handler that adds "Cache-control: public" header to the file response.
(with-header-handler (file-handler) :cache-control "public")
Since that the headers are added before the inner handler is called, they may be overwritten by inner-handler.
If you write out logs inside an http handler, you can use those macros:
(access-log FMT ARGS ...)
(error-log FMT ARGS ...)
FMT and ARGS are the same as log-format
in gauche.logger
.
The destination of logs are set by the keyword arguments
of start-http-server
described below.
You can run Gauche's built-in sampling profiler during handling a request. There are two ways to do it.
If the environment variable MAKIKI_PROFILER_OUTPUT
is set
when makiki.scm
is loaded, Makiki automatically profiles all
handlers. The value of MAKIKI_PROFILER_OUTPUT
is used as a
filename to which the profiling result is written out. If the
file already exists, the result is appended to it. Each result is preceded
by the request path.
Alternatively, you can selectively profile specific handlers
by wrapping the handler with with-profiling-handler
:
(with-profiling-handler OUTPUT-FILE INNER-HANDLER)
This returns a procedure suitable to be a handler. When called,
it runs INNER-HANDLER
with profiler running, and then write out
the result to OUTPUT-FILE
. When OUTPUT-FILE
already exists,
the result is appended to it.
You should use either one of the above method, but not both;
MAKIKI_PROFILER_OUTPUT
tries to profile every handler, even it
is already wrapped by with-post-parameters
.
Note: If multiple handlers run simultaneously in multiple threads, the profiling result becomes less reliable, for you don't know which thread the sampling picks---the profiling is recorded per thread, but the sampling timer is shared. Make sure to issue one request at a time during profiling.
See the Using profiler section of the Gauche reference manual for the details of Gauche's built-in profiler.
Finally, to start the server, call start-http-server
.
(start-http-server :key app-data host port tls-port path
document-root num-threads max-backlog
access-log error-log
tls-settings
forwarded? auto-secure-cookie
startup-callback shutdown-callback
control-channel)
It takes the following keyword arguments:
app-data
- an opaque data passed to the request handler as is.host
(#f
or string),port
(#f
or integer) - Passed tomake-server-sockets
ofgauche.net
to open the server socket. If none ofport
,tls-port
andpath
arguments are given, port 8080 is used for the convenience.tls-port
(#f
or integer) - Opens and listens TLS connection on this port. See Running https server for how to run https server.path
(#f
or string) - If a string is given, it specifies the path to a Unix-domain socket on which the server listens.document-root
- used to specify the root of the document served byfile-handler
. The default is the process's working directory.num-threads
- number of threads to serve the request. Currently threads are created when the server is started. The default is 5.max-backlog
- max number of request queued when all threads are busy. When a request comes while the queue is full, 503 (server busy) response is returned to the client. The default is 10.access-log
,error-log
- specify the destination of logs.#f
(no log),#t
(stdout), string (filename) or<log-drain>
object. For access log,<log-drain>
is better not to have prefix, for timestamp is included in the message. The default is#f
.tls-settings
(keyword-value list): Pathnames for certificates and private keys. See Running https server for details.forwarded?
- specify true if you use makiki behind a reverse-proxy httpd, and access-log uses the value ofx-forwarded-for
header if exists, instead of the client's address.auto-secure-cookie
- if this is true, 'secure' attribute of cookies are automatically turned on when the request is over a secure channel. The default is#t
.startup-callback
- a procedure to be called after the server opened sockets, but before start processing any requests. A list of server sockets are passed as the only argument. Logging procedures are already active.shutdown-callback
- a thunk to be called after all the server operations are shut down. If given, this is the last thingstart-http-server
does before returning.control-channel
- an opaque object, through which you can request the server loop to shutdown. Seemake-server-control-channel
andterminate-server-loop
below.
Note that start-http-server
enters the server loop and won't return
by default. There are two ways to shut down the server loop.
- Send
SIGINT
orSIGTERM
. By default, signals sent to a Gauche process is handled by the main thread. So if you calledstart-http-server
in the main thread, this is the easiest way. - If you run
start-http-server
outside of the main thread, or don't want to rely on signals, you need a bit of setup. (1) Create a server control channel bymake-server-control-channel
and pass it to:control-channel
argument of thestart-http-server
. (2) When you want to request the server loop to shutdown, callterminate-server-loop
with the control channel. See below for further description. See this test code as an example.
Here's the API for controlling the server loop.
(make-server-control-channel)
Returns an opaque object through which you can request the server loop to shut down. You need one channel for each server loop, if you have more than one loop.
(terminate-server-loop CHANNEL EXIT-CODE)
Calling this function causes start-http-server
that has CHANNEL
to break the loop, does cleaning up (including finishing request that are
already being processed), and returns EXIT-CODE.
You can pass any object to EXIT-CODE, but the supposed way to call
start-http-server
is the tail position of the main
function; in
that case, EXIT-CODE becomes the exit code of the server.
(define (main args)
...
(start-http-server ...))
Some less frequently used features are provided in separate modules.
-
makiki.connect
: HandlingCONNECT
http request. See simple proxy example. -
makiki.cgi
: Handling CGI scripts. See the section Calling CGI scripts above. -
makiki.dev
: Interactive REPL development while running a server. See development.md for the details.
The examples directory contains some simple server scripts, each one shows how to implement a specific functionality.