86fd9633078c
More
author | Steve Losh <steve@stevelosh.com> |
---|---|
date | Tue, 22 Jun 2021 11:24:13 -0400 |
parents | 01ff28d9de54 |
children | 65b7fdace3da |
branches/tags | (none) |
files | lisp/drivethru.lisp vim/custom-dictionary.utf-8.add |
Changes
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/drivethru.lisp Tue Jun 22 11:24:13 2021 -0400 @@ -0,0 +1,115 @@ +(eval-when (:compile-toplevel :load-toplevel :execute) + (ql:quickload + '(:adopt :with-user-abort :iterate :losh :hunchentoot :lparallel) + :silent t)) + +(defpackage :drivethru + (:use :cl :iterate :losh) + (:export :toplevel :*ui*)) + +(in-package :drivethru) + +;;;; State -------------------------------------------------------------------- +(defvar *acceptor* nil) + +;; in: (req . promise) +;; ↑ +;; out: (status . body) +(defvar *q* (lparallel.queue:make-queue)) + + +;;;; Server ------------------------------------------------------------------- +(defun prompt (message) + (write-string message) + (finish-output)) + +(defun handle () + (let ((p (lparallel:promise))) + (lparallel.queue:push-queue (cons hunchentoot:*request* p) *q*) + (destructuring-bind (status . body) (lparallel:force p) + (setf (hunchentoot:return-code*) status) + body))) + +(defun serve (r) + (destructuring-bind (request . promise) r + (let ((auth (multiple-value-list (hunchentoot:authorization request)))) + (format t "~%~C[1;96m;;;; ~A ~A --------------~%~C[0m" + #\esc + (hunchentoot:request-method request) + (hunchentoot:request-uri request) + #\esc) + (when auth + (format t ";; User: ~S~%;; Pass: ~S~%" (first auth) (second auth)))) + (let ((status (progn (prompt "Reponse code: ") + (parse-integer (read-line)))) + (body (progn (prompt "Reponse body: ") + (read-line)))) + (lparallel:fulfill promise (cons status body))))) + +(defun dispatch (request) + (declare (ignore request)) + #'handle) + + +;;;; Run ---------------------------------------------------------------------- +(defun run (port) + (setf hunchentoot:*dispatch-table* '(dispatch) + *acceptor* (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor + :port port))) + (unwind-protect (loop (serve (lparallel.queue:pop-queue *q*))) + (hunchentoot:stop *acceptor*))) + + +;;;; User Interface ----------------------------------------------------------- +(defparameter *option-help* + (adopt:make-option 'help + :help "Display help and exit." + :long "help" + :short #\h + :reduce (constantly t))) + +(defparameter *option-port* + (adopt:make-option 'port + :help "Listen on port N (default 33455)." + :parameter "N" + :long "port" + :short #\p + :reduce #'adopt:last + :key #'parse-integer + :initial-value 33455)) + +(adopt:define-string *help-text* + "drivethru listens for HTTP requests and lets you give responses by hand on ~ + the fly, as a low-tech mock when testing an API.") + +(defparameter *examples* + '(("Run on port 80:" . "drivethru -p 80"))) + +(defparameter *ui* + (adopt:make-interface + :name "drivethru" + :usage "[OPTIONS]" + :summary "serve HTTP requests by hand" + :help *help-text* + :examples *examples* + :contents (list *option-port* + *option-help*))) + + +(defmacro exit-on-ctrl-c (&body body) + `(handler-case + (with-user-abort:with-user-abort (progn ,@body)) + (with-user-abort:user-abort () (adopt:exit 130)))) + + +(defun toplevel () + (exit-on-ctrl-c + (multiple-value-bind (arguments options) (adopt:parse-options-or-exit *ui*) + (handler-case + (if (gethash 'help options) + (adopt:print-help-and-exit *ui*) + (progn + (assert (null arguments)) + (run (gethash 'port options)))) + (error (e) (adopt:print-error-and-exit e)))))) +