== call-with-query
A reasonable abstraction around e.g. fastcgi server-invocations: ports, environment, query
[[toc:]]
=== Motivation
Using FastCGI is relatively pain-in-the-ass; take this
contrived example, for instance, where we'd like create a server that
exports a database (given as a post-parameter) as JSON:
(fcgi-dynamic-server-accept-loop
(lambda (in out err env)
(out "Content-type: application/json\r\n\r\n")
(let* ((post-data (form-urldecode (fcgi-get-post-data in env)))
(database
(alist-ref/default post-data 'database (default-database))))
(out (with-output-to-string
(lambda () (json-write (database->json database))))))))
With {{call-with-query}}, however, we can do something like this:
(call-with-dynamic-fastcgi-query
(lambda (query)
(display-content-type-&c. 'json)
(json-write
(database->json (query-any query 'database (default-database))))))
Anything written to stdout appears in the request;
anything to stderr goes in the server logs; while {{display-content-type-&c.}}
takes care of the HTTP headers.
=== Documentation
==== {{display-content-type-&c.}}
(display-content-type-&c.) → unspecified
(display-content-type-&c. content-type-&c.) → unspecified
Write the content-type headers and e.g. XML prolog (if
necessary); do not, however, write the status (see {{display-status}}
and {{display-status-&c.}}).
Valid content-types are {{xhtml}}, {{html}}, {{text}}, {{json}}, {{png}}, {{xml}}.
; {{content-type-&c.}} : The content-type-and--prolog, e.g. {{xhtml}}
(define display-content-type-&c.
(case-lambda
(() (display-content-type-&c. (default-content-type-&c.)))
((content-type-&c.) ((alist-ref content-type-&cs. content-type-&c.)))))
==== {{display-status-&c.}}
(display-status-&c.) → unspecified
(display-status-&c. status) → unspecified
(display-status-&c. status content-type . rest) → unspecified
Display the status, content-type and prolog.
; {{status}} : Status code, e.g. {{status-no-content}} or 204
; {{content-type}} : Content-type, e.g. {{xhtml}}
; {{rest}} : Optional arguments to the status, e.g. {{location}} in the case of 300
(define display-status-&c.
(case-lambda
(() (display-status-&c. (default-status)))
((status) (display-status-&c. status (default-content-type)))
((status content-type . rest)
(display-status status)
(apply (alist-ref/default statuses status void) rest)
(display-content-type-&c. content-type))))
==== {{query-client-any}}
(query-client-any query key) → string
(query-client-any query key default) → string
Return the first client parameter (e.g. {get,post,cookie}-parameter)
corresponding to the key.
; {{key}} : The key whose value to extract
; {{default}} : A default value if {{key}} doesn't exist
(define query-client-any
(case-lambda
((query key) (alist-any (query-client query) key))
((query key default) (alist-any (query-client query) key default))))
==== {{query-client-all}}
(query-client-all query key) → list
(query-client-all query key default) → list
Return a list of client parameters (e.g. {get,post,cookie}-parameters)
corresponding to the key.
; {{key}} : The key whose value to extract
; {{default}} : A default value if {{key}} doesn't exist
(define query-client-all
(case-lambda
((query key) (alist-all (query-client query) key))
((query key default) (alist-all (query-client query) key default))))
==== {{query-server-any}}
(query-server-any query key) → string
(query-server-any query key default) → string
Return the first client parameter (e.g.
environment-variable) corresponding to the key.
; {{key}} : The key whose value to extract
; {{default}} : A default value if {{key}} doesn't exist
(define query-server-any
(case-lambda
((query key) (alist-any (query-server query) key))
((query key default) (alist-any (query-server query) key default))))
==== {{query-server-all}}
(query-server-all query key) → list
(query-server-all query key default) → list
Return a list of client parameters (e.g.
environment-variables) corresponding to the key.
; {{key}} : The key whose value to extract
; {{default}} : A default value if {{key}} doesn't exist
(define query-server-all
(case-lambda
((query key) (alist-all (query-server query) key))
((query key default) (alist-all (query-server query) key default))))
==== {{query-any}}
(query-any query key) → string
(query-any query key default) → string
Return the first client or server parameter (see above) corresponding to the key.
; {{key}} : The key whose value to extract
; {{default}} : A default value if {{key}} doesn't exist
(define query-any
(case-lambda
((query key) (alist-any (query-promiscuous query) key))
((query key default) (alist-any (query-promiscuous query) key default))))
==== {{query-all}}
(query-all query key) → list
(query-all query key default) → list
Return a list of client or server parameters (see above) corresponding to the key.
; {{key}} : The key whose value to extract
; {{default}} : A default value if {{key}} doesn't exist
(define query-all
(case-lambda
((query key) (alist-all (query-promiscuous query) key))
((query key default) (alist-all (query-promiscuous query) key default))))
==== {{call-with-dynamic-fastcgi-query}}
(call-with-dynamic-fastcgi-query quaerendum) → unspecified
Start a dynamic FastCGI server where output is bound to stdout;
and where a monadic function taking a query-record is called for every
request.
; {{quaerendum}} : A monadic function receiving a query parameter
(define (call-with-dynamic-fastcgi-query quaerendum)
(fcgi-dynamic-server-accept-loop
(lambda (in out err env)
(let ((environment
(map (match-lambda
((key . value) (cons (env-string->symbol key) value)))
(env)))
(cookies
(form-urldecode
(let ((cookies
(string-delete
char-set:whitespace
(env "HTTP_COOKIE" ""))))
(and (not (string-null? cookies)) cookies))))
(cookies2
(form-urldecode
(let ((cookies
(string-delete
char-set:whitespace
(env "HTTP_COOKIE2" ""))))
(and (not (string-null? cookies)) cookies))))
(post-data (form-urldecode (fcgi-get-post-data in env)))
(query (form-urldecode
(let ((query (env "QUERY_STRING")))
(and (not (string-null? query)) query)))))
(parameterize
((current-output-port
(make-output-port (lambda (scribendum) (out scribendum)) void))
(current-error-port
(make-output-port (lambda (errandum) (err errandum)) void)))
(quaerendum
(make-query
environment
(append cookies cookies2 post-data query))))))))
===== Examples
An authorization server
(call-with-auth-db
(lambda (connection)
(call-with-dynamic-fastcgi-query
(lambda (query)
(let ((user (query-server-any query 'remote-user))
(password (query-server-any query 'remote-passwd)))
(let ((status
(if (valid? connection user password "physician")
status-ok
status-unauthorized)))
(display-status-&c. status)))))))
==== {{call-with-cgi-query}}
(call-with-cgi-query quaerendum) → unspecified
Gather parameters (including post-variables, query-variables,
cookies, server-variables) into an association-list when called as a
CGI program.
; {{quaerendum}} : A monadic function receiving a query parameter
(define (call-with-cgi-query quaerendum)
(let ((environment
(alist-map
(lambda (key value) (cons (env-string->symbol key) value))
(get-environment-variables))))
(quaerendum
(make-query
environment
(remove-null-artifacts
(append
(form-urldecode-environment environment 'http-cookie)
(form-urldecode-environment environment 'http-cookie2)
(form-urldecode-environment environment 'query-string)
(form-urldecode-with-separator (read-all))))))))
===== Examples
Prints out the environment.
(call-with-cgi-query
(lambda (query) (display-content-type-&c. 'text) (display query)))
=== About this egg
==== Author
[[/users/klutometis|Peter Danenberg]]
==== Repository
[[https://github.com/klutometis/call-with-query]]
==== License
GPL-3
==== Dependencies
* [[alist-lib]]
* [[args]]
* [[call-with-environment-variables]]
* [[cock]]
* [[debug]]
* [[define-record-and-printer]]
* [[fastcgi]]
* [[format]]
* [[matchable]]
* [[regex]]
* [[setup-helper]]
* [[uri-common]]
==== Versions
; [[https://github.com/klutometis/call-with-query/releases/tag/0.1|0.1]] : First release
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2|0.2]] : Record-printer for queries
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.1|0.2.1]] : JSON and PNG
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.2|0.2.2]] : HTML5
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.3|0.2.3]] : Remove dependency on regex.
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.4|0.2.4]] : Fix irregex.
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.5|0.2.5]] : Add pdf.
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.6|0.2.6]] : Add XML.
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.7|0.2.7]] : Remove the dependency on setup-helper-cock.
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.8|0.2.8]] : Remove debug.
; [[https://github.com/klutometis/call-with-query/releases/tag/0.2.9|0.2.9]] : Add call-with-cgi-query.
==== Colophon
Documented by [[/egg/cock|cock]].