commit 7abeb13ae856820658d16b34c2a1cd9520423965
parent 32b65f17938113f4f7b5a50abd97aea94dcccd7e
Author: Tomas Hlavaty <tom@logand.com>
Date: Sun, 6 Dec 2015 17:16:56 +0100
add demo-webserver
webserver for serving static files
Diffstat:
6 files changed, 416 insertions(+), 156 deletions(-)
diff --git a/demo-counter.lisp b/demo-counter.lisp
@@ -1,4 +1,4 @@
-;;; Copyright (C) 2013, 2014 Tomas Hlavaty <tom@logand.com>
+;;; Copyright (C) 2013, 2014, 2015 Tomas Hlavaty <tom@logand.com>
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
@@ -98,7 +98,9 @@
(rw.http:server (rw.socket:make-ipv4-address "0.0.0.0")
2349
'counter-handler
- :quit (lambda () nil)))
+ :quit (lambda () nil)
+ :allowed-methods '(:get :post)
+ :ignore-errors-p t))
;;(start)
diff --git a/demo-webserver.lisp b/demo-webserver.lisp
@@ -0,0 +1,148 @@
+;;; Copyright (C) 2015 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
+(defpackage :rw.demo.webserver
+ (:use :cl))
+
+(in-package :rw.demo.webserver)
+
+(defparameter *root* #p"/nix/store/l549rl2lmyk7dvsrv4mrrwgwbswf8q6l-logand-website/share/logandWebsite/data/")
+
+(defun part-reader (query)
+ (let ((r (rw:peek-reader (rw:reader (reverse query)))))
+ (lambda ()
+ (when (rw:peek r)
+ (prog1 (let ((x (rw:till r '(#\/))))
+ (if x
+ (coerce (nreverse x) 'string)
+ :nothing))
+ (rw:skip r '(#\/)))))))
+
+(defun query-pathname (query)
+ (let* ((tail (rw:till (rw:peek-reader (part-reader query))))
+ (head (pop tail)))
+ (merge-pathnames
+ (make-pathname :directory (cons :relative (nreverse tail))
+ :name (if (eq :nothing head)
+ "index"
+ (pathname-name head))
+ :type (if (eq :nothing head)
+ "html"
+ (pathname-type head)))
+ *root*)))
+
+(defun query-file (query) ;; TODO strip ?...
+ (when (every (lambda (c)
+ (or (char<= #\A c #\Z)
+ (char<= #\a c #\z)
+ (char<= #\0 c #\9)
+ (member c '(#\/ #\. #\- #\_))))
+ query)
+ (let ((f (probe-file (query-pathname query))))
+ (when f
+ (ignore-errors
+ (with-open-file (s f :if-does-not-exist nil)
+ f))))))
+
+(defun content-type (pathname)
+ (or (cdr (assoc (pathname-type pathname) rw.http:*mime-types* :test #'equalp))
+ rw.http:*default-mime-type*))
+
+(defun webserver-handler (msg stream method query protocol headers &optional body)
+ (declare (ignore stream protocol headers body))
+ (ecase msg
+ ;;(:read (rw:till (rw:peek-reader stream)))
+ (:write
+ (or (when (member method '(:get :head))
+ (let ((f (query-file query)))
+ (when f
+ `(:http-1.0
+ :code 200
+ :headers (("Connection" . "close")
+ ("Content-Type" . ,(content-type f)))
+ :body ,(and (eq :get method) f)))))
+ '(:http-1.0
+ :code 404
+ :headers (("Connection" . "close")
+ ("Content-Type" . "text/plain;charset=UTF-8"))
+ :body "404 Not Found")))))
+
+(defun start ()
+ (rw.http:server (rw.socket:make-ipv4-address "0.0.0.0")
+ 2341
+ 'webserver-handler
+ :quit (lambda () nil)
+ :allowed-methods '(:get :head)
+ :ignore-errors-p nil #+nil t))
+
+;;(start)
+
+(defun save-image ()
+ #-(or ccl cmucl sbcl)
+ (error "TODO RW.DEMO.WEBSERVER::SAVE-IMAGE")
+ #+clisp
+ (ext:saveinitmem "cl-rw-demo-webserver"
+ :executable t
+ :quiet t
+ :norc
+ :init-function (lambda ()
+ (handler-case
+ (progn
+ (start)
+ (loop (sleep 1)))
+ (condition ()
+ (quit 1)))))
+ #+ccl ;; TODO no debug on ^C
+ (ccl:save-application "cl-rw-demo-webserver"
+ :prepend-kernel t
+ :error-handler :quit-quietly
+ :toplevel-function (lambda ()
+ (handler-case
+ (progn
+ (start)
+ (loop (sleep 1)))
+ (condition ()
+ (ccl:quit 1)))))
+ #+cmu
+ (ext:save-lisp "cl-rw-demo-webserver"
+ :executable t
+ :batch-mode t
+ :print-herald nil
+ :process-command-line nil
+ :load-init-file nil
+ :init-function (lambda ()
+ (handler-case
+ (progn
+ (start)
+ (loop (sleep 1)))
+ (condition ()
+ (ext:quit)))))
+ #+sbcl
+ (sb-ext:save-lisp-and-die "cl-rw-demo-webserver"
+ :executable t
+ :toplevel (lambda ()
+ (handler-case
+ (progn
+ (start)
+ (loop (sleep 1)))
+ (condition ()
+ (sb-ext:exit :code 1 :abort t))))))
diff --git a/demo-zappel.lisp b/demo-zappel.lisp
@@ -1,4 +1,4 @@
-;;; Copyright (C) 2013, 2014 Tomas Hlavaty <tom@logand.com>
+;;; Copyright (C) 2013, 2014, 2015 Tomas Hlavaty <tom@logand.com>
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
@@ -188,7 +188,9 @@
(rw.http:server (rw.socket:make-ipv4-address "0.0.0.0")
2340
'zappel-handler
- :quit (lambda () nil)))
+ :quit (lambda () nil)
+ :allowed-methods '(:get :post)
+ :ignore-errors-p t))
;;(start)
diff --git a/http.lisp b/http.lisp
@@ -1,14 +1,126 @@
+;;; Copyright (C) 2013, 2014, 2015 Tomas Hlavaty <tom@logand.com>
+;;;
+;;; Permission is hereby granted, free of charge, to any person
+;;; obtaining a copy of this software and associated documentation
+;;; files (the "Software"), to deal in the Software without
+;;; restriction, including without limitation the rights to use, copy,
+;;; modify, merge, publish, distribute, sublicense, and/or sell copies
+;;; of the Software, and to permit persons to whom the Software is
+;;; furnished to do so, subject to the following conditions:
+;;;
+;;; The above copyright notice and this permission notice shall be
+;;; included in all copies or substantial portions of the Software.
+;;;
+;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+;;; NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+;;; HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+;;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+;;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+;;; DEALINGS IN THE SOFTWARE.
+
;; TODO remove all those coerce list<->string?
;; TODO !!! post parsing with multiline textarea
;; TODO !!! file(s) upload
(defpackage :rw.http
(:use :cl)
- (:export :client
+ (:export :*default-mime-type*
+ :*http-codes*
+ :*mime-types*
+ :client
:server))
(in-package :rw.http)
+(defparameter *mime-types*
+ '(("css" . "text/css;charset=UTF-8")
+ ("gif" . "image/gif")
+ ("html" . "text/html;charset=UTF-8")
+ ("js" . "application/javascript;charset=UTF-8")
+ ("png" . "image/png")
+ ("txt" . "text/plain;charset=UTF-8")))
+
+(defparameter *default-mime-type* "application/octet-stream")
+
+(defparameter *http-codes*
+ ;; https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+ '((200 . "OK")
+ (201 . "Created")
+ (202 . "Accepted")
+ (203 . "Non-Authoritative Information (since HTTP/1.1)")
+ (204 . "No Content")
+ (205 . "Reset Content")
+ (206 . "Partial Content")
+ (207 . "Multi-Status (WebDAV; RFC 4918)")
+ (208 . "Already Reported (WebDAV; RFC 5842)")
+ (226 . "IM Used (RFC 3229)")
+ (300 . "Multiple Choices")
+ (301 . "Moved Permanently")
+ (302 . "Found")
+ (303 . "See Other (since HTTP/1.1)")
+ (304 . "Not Modified")
+ (305 . "Use Proxy (since HTTP/1.1)")
+ (306 . "Switch Proxy")
+ (307 . "Temporary Redirect (since HTTP/1.1)")
+ (308 . "Permanent Redirect (approved as experimental RFC)[12]")
+ (400 . "Bad Request")
+ (401 . "Unauthorized")
+ (402 . "Payment Required")
+ (403 . "Forbidden")
+ (404 . "Not Found")
+ (405 . "Method Not Allowed")
+ (406 . "Not Acceptable")
+ (407 . "Proxy Authentication Required")
+ (408 . "Request Timeout")
+ (409 . "Conflict")
+ (410 . "Gone")
+ (411 . "Length Required")
+ (412 . "Precondition Failed")
+ (413 . "Request Entity Too Large")
+ (414 . "Request-URI Too Long")
+ (415 . "Unsupported Media Type")
+ (416 . "Requested Range Not Satisfiable")
+ (417 . "Expectation Failed")
+ (418 . "I'm a teapot (RFC 2324)")
+ (419 . "Authentication Timeout (not in RFC 2616)")
+ ;;(420 . "Method Failure (Spring Framework)")
+ ;;(420 . "Enhance Your Calm (Twitter)")
+ (422 . "Unprocessable Entity (WebDAV; RFC 4918)")
+ (423 . "Locked (WebDAV; RFC 4918)")
+ ;;(424 . "Failed Dependency (WebDAV; RFC 4918)")
+ ;;(424 . "Method Failure (WebDAV)[14]")
+ (425 . "Unordered Collection (Internet draft)")
+ (426 . "Upgrade Required (RFC 2817)")
+ (428 . "Precondition Required (RFC 6585)")
+ (429 . "Too Many Requests (RFC 6585)")
+ (431 . "Request Header Fields Too Large (RFC 6585)")
+ (444 . "No Response (Nginx)")
+ (449 . "Retry With (Microsoft)")
+ (450 . "Blocked by Windows Parental Controls (Microsoft)")
+ ;;(451 . "Unavailable For Legal Reasons (Internet draft)")
+ ;;(451 . "Redirect (Microsoft)")
+ (494 . "Request Header Too Large (Nginx)")
+ (495 . "Cert Error (Nginx)")
+ (496 . "No Cert (Nginx)")
+ (497 . "HTTP to HTTPS (Nginx)")
+ (499 . "Client Closed Request (Nginx)")
+ (500 . "Internal Server Error")
+ (501 . "Not Implemented")
+ (502 . "Bad Gateway")
+ (503 . "Service Unavailable")
+ (504 . "Gateway Timeout")
+ (505 . "HTTP Version Not Supported")
+ (506 . "Variant Also Negotiates (RFC 2295)")
+ (507 . "Insufficient Storage (WebDAV; RFC 4918)")
+ (508 . "Loop Detected (WebDAV; RFC 5842)")
+ (509 . "Bandwidth Limit Exceeded (Apache bw/limited extension)")
+ (510 . "Not Extended (RFC 2774)")
+ (511 . "Network Authentication Required (RFC 6585)")
+ (598 . "Network read timeout error (Unknown)")
+ (599 . "Network connect timeout error (Unknown)")))
+
(defun next-eol (reader)
(ecase (rw:next reader)
(#\newline :lf)
@@ -86,20 +198,22 @@
(defun next-body (reader) ;; TODO better, respect content-length!
(coerce (rw:till reader) 'string))
-(defun write-crlf (stream)
- (write-char (code-char 13) stream)
- (write-char (code-char 10) stream))
+(defun write-crlf (writer)
+ (rw:write-octets writer '(13 10)))
-(defun write-headers (headers stream)
+(defun write-headers (writer headers)
(dolist (x headers)
- (format stream "~a: ~a" (car x) (cdr x))
- (write-crlf stream)))
-
-(defun write-protocol (stream protocol)
- (write-string (ecase protocol
- (:http-1.0 "HTTP/1.0")
- (:http-1.1 "HTTP/1.1"))
- stream))
+ (rw:write-utf8-string writer (car x))
+ (rw:write-octets writer #.(rw.string:string-to-octets ": " :utf-8))
+ (rw:write-utf8-string writer (cdr x))
+ (write-crlf writer)))
+
+(defun write-protocol (writer protocol)
+ (rw:write-octets
+ writer
+ (ecase protocol
+ (:http-1.0 #.(rw.string:string-to-octets "HTTP/1.0" :utf-8))
+ (:http-1.1 #.(rw.string:string-to-octets "HTTP/1.1" :utf-8)))))
(defun write-query (stream method protocol path query-string)
(write-string (ecase method
@@ -164,20 +278,25 @@
;; <html><head><title>302 Moved Temporarily</title></head><body><h1>Moved Temporarily</h1>The document has moved <a href='http://NIL/?s=24rb7pccnd&a=0&c='>here</a><p><hr><address><a href='http://weitz.de/hunchentoot/'>Hunchentoot 1.2.3</a> <a href='http://openmcl.clozure.com/'>(Clozure Common Lisp Version 1.9-r15767 (LinuxARM32))</a></address></p></body></html>Connection closed by foreign host.
-(defun next-method (reader)
- (let ((x (cdr (assoc (rw:till reader '(#\G #\E #\T #\P #\O #\S) t)
+(defun next-method (reader allowed-methods)
+ (let ((x (cdr (assoc (rw:till (rw:peek-reader (rw:shorter-reader reader 5))
+ '(#\G #\E #\T
+ #\H #\A #\D
+ #\P #\O #\S)
+ t)
'(((#\G #\E #\T) . :get)
+ ((#\H #\E #\A #\D) . :head)
((#\P #\O #\S #\T) . :post))
:test #'equal))))
- (assert x)
+ (assert (member x allowed-methods))
x))
-(defun next-query (reader)
+(defun next-query (reader allowed-methods)
(unless (member (rw:peek reader) '(#\return #\newline))
(flet ((str (y)
(when y
(coerce y 'string))))
- (values (prog1 (next-method reader)
+ (values (prog1 (next-method reader allowed-methods)
(rw:skip reader))
(prog1 (str (rw:till reader '(#\space #\return #\newline)))
(unless (member (rw:peek reader) '(#\return #\newline))
@@ -185,90 +304,16 @@
(prog1 (next-protocol reader)
(next-eol reader))))))
-(defun write-status (stream protocol code message)
- (write-protocol stream protocol)
- (write-char #\space stream)
- (princ code stream)
- (write-char #\space stream)
- (write-string (or message
- ;; https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
- (ecase code
- (200 "OK")
- (201 "Created")
- (202 "Accepted")
- (203 "Non-Authoritative Information (since HTTP/1.1)")
- (204 "No Content")
- (205 "Reset Content")
- (206 "Partial Content")
- (207 "Multi-Status (WebDAV; RFC 4918)")
- (208 "Already Reported (WebDAV; RFC 5842)")
- (226 "IM Used (RFC 3229)")
- (300 "Multiple Choices")
- (301 "Moved Permanently")
- (302 "Found")
- (303 "See Other (since HTTP/1.1)")
- (304 "Not Modified")
- (305 "Use Proxy (since HTTP/1.1)")
- (306 "Switch Proxy")
- (307 "Temporary Redirect (since HTTP/1.1)")
- (308 "Permanent Redirect (approved as experimental RFC)[12]")
- (400 "Bad Request")
- (401 "Unauthorized")
- (402 "Payment Required")
- (403 "Forbidden")
- (404 "Not Found")
- (405 "Method Not Allowed")
- (406 "Not Acceptable")
- (407 "Proxy Authentication Required")
- (408 "Request Timeout")
- (409 "Conflict")
- (410 "Gone")
- (411 "Length Required")
- (412 "Precondition Failed")
- (413 "Request Entity Too Large")
- (414 "Request-URI Too Long")
- (415 "Unsupported Media Type")
- (416 "Requested Range Not Satisfiable")
- (417 "Expectation Failed")
- (418 "I'm a teapot (RFC 2324)")
- (419 "Authentication Timeout (not in RFC 2616)")
- ;;(420 "Method Failure (Spring Framework)")
- ;;(420 "Enhance Your Calm (Twitter)")
- (422 "Unprocessable Entity (WebDAV; RFC 4918)")
- (423 "Locked (WebDAV; RFC 4918)")
- ;;(424 "Failed Dependency (WebDAV; RFC 4918)")
- ;;(424 "Method Failure (WebDAV)[14]")
- (425 "Unordered Collection (Internet draft)")
- (426 "Upgrade Required (RFC 2817)")
- (428 "Precondition Required (RFC 6585)")
- (429 "Too Many Requests (RFC 6585)")
- (431 "Request Header Fields Too Large (RFC 6585)")
- (444 "No Response (Nginx)")
- (449 "Retry With (Microsoft)")
- (450 "Blocked by Windows Parental Controls (Microsoft)")
- ;;(451 "Unavailable For Legal Reasons (Internet draft)")
- ;;(451 "Redirect (Microsoft)")
- (494 "Request Header Too Large (Nginx)")
- (495 "Cert Error (Nginx)")
- (496 "No Cert (Nginx)")
- (497 "HTTP to HTTPS (Nginx)")
- (499 "Client Closed Request (Nginx)")
- (500 "Internal Server Error")
- (501 "Not Implemented")
- (502 "Bad Gateway")
- (503 "Service Unavailable")
- (504 "Gateway Timeout")
- (505 "HTTP Version Not Supported")
- (506 "Variant Also Negotiates (RFC 2295)")
- (507 "Insufficient Storage (WebDAV; RFC 4918)")
- (508 "Loop Detected (WebDAV; RFC 5842)")
- (509 "Bandwidth Limit Exceeded (Apache bw/limited extension)")
- (510 "Not Extended (RFC 2774)")
- (511 "Network Authentication Required (RFC 6585)")
- (598 "Network read timeout error (Unknown)")
- (599 "Network connect timeout error (Unknown)")))
- stream)
- (write-crlf stream))
+(defun write-status (writer protocol code message)
+ (write-protocol writer protocol)
+ (rw:write-u8 writer #.(char-code #\space))
+ (rw:write-utf8-string writer (princ-to-string code))
+ (rw:write-u8 writer #.(char-code #\space))
+ (rw:write-utf8-string writer
+ (or message
+ (cdr (assoc code *http-codes*))
+ (error "unknown http code ~s" code)))
+ (write-crlf writer))
(defun multipart-reader (reader boundary)
(let* ((start-boundary `(#\- #\- ,@boundary))
@@ -303,63 +348,82 @@
(when body
(coerce body 'string)))))))
-(defun server-handler (stream handler)
- (let ((r (rw:peek-reader (rw:char-reader stream))))
- (multiple-value-bind (method query protocol) (next-query r)
- (let ((form (let ((headers (next-headers r)))
+(defun server-read (breader creader method)
+ (let ((headers (next-headers creader)))
+ (values
+ headers
+ (when (eq :post method)
+ (rw:slurp
+ (multipart-reader
+ (rw:shorter-reader
+ breader
+ (cdr (assoc "Content-Length" headers :test #'equal)))
+ (coerce
+ (cdr (assoc "boundary"
+ (cdr (assoc "Content-Type" headers :test #'equal))
+ :test #'equal))
+ 'list)))))))
+
+(defun server-write (form writer)
+ (ecase (car form)
+ (:http-1.0
+ (destructuring-bind (&key code message headers body) (cdr form)
+ (write-status writer :http-1.0 code message)
+ (write-headers writer
+ (or headers
+ '(("Connection" . "close")
+ ;;("Date" . "")
+ ;;("Last-Modified" . "")
+ #+nil("Server" . "CL-RW"))))
+ (write-crlf writer)
+ (etypecase body
+ (null)
+ (string (rw:write-utf8-string writer body))
+ (pathname
+ (with-open-file (s body :element-type '(unsigned-byte 8))
+ (rw:copy (rw:byte-reader s) writer)))
+ (cons
+ (rw:write-utf8-string writer
+ (with-output-to-string (*standard-output*)
+ (rw.html:html body)))
+ #+nil(let ((*standard-output* stream)) (rw.html:html body))) ;; TODO xml, css...
+ (function (funcall body writer)))))))
+
+(defun server-handler (stream handler allowed-methods ignore-errors-p)
+ (flet ((body ()
+ (with-open-stream (stream stream)
+ (let* ((br (rw:byte-reader stream))
+ (cr (rw:peek-reader (rw:utf8-reader br :charp t))))
+ (multiple-value-bind (method query protocol)
+ (next-query cr allowed-methods)
+ (server-write
+ (multiple-value-bind (headers body)
+ (server-read br cr method)
(funcall handler :write stream method query protocol headers
- (when (eq :post method)
- (rw:slurp
- (multipart-reader
- (rw:shorter-reader
- r
- (cdr (assoc "Content-Length" headers :test #'equal)))
- (coerce
- (cdr (assoc "boundary"
- (cdr (assoc "Content-Type" headers :test #'equal))
- :test #'equal))
- 'list))))))))
- (ecase (car form)
- (:http-1.0
- (destructuring-bind (&key code message headers body) (cdr form)
- (write-status stream :http-1.0 code message)
- (write-headers (or headers
- '(("Connection" . "close")
- ;;("Date" . "")
- ;;("Last-Modified" . "")
- ("Server" . "CL-RW")))
- stream)
- (write-crlf stream)
- (etypecase body
- (null)
- (string (write-string body stream))
- (pathname
- (with-open-file (in body :element-type '(unsigned-byte 8))
- (rw:copy (rw:byte-reader in) (rw:byte-writer stream))))
- (cons (let ((*standard-output* stream)) (rw.html:html body))) ;; TODO xml, css...
- (function (funcall body stream))))))))))
-
-#-clisp
-(defun accept-loop (socket quit handler host port)
+ body))
+ (rw:byte-writer stream)))))))
+ (if ignore-errors-p
+ (ignore-errors (body))
+ (body))))
+
+(defun accept-loop (socket quit handler host port allowed-methods ignore-errors-p)
(do ((q (or quit (rw:reader '(nil t)))))
((funcall q))
(let ((c (rw.socket:accept socket)))
(rw.concurrency:make-thread
(format nil "RW.HTTP:SERVER-HANDLER ~s ~s" host port)
(lambda ()
- (ignore-errors
- (with-open-stream (c c)
- (server-handler c handler))))))))
+ (server-handler c handler allowed-methods ignore-errors-p))))))
;; TODO also without threads
;; TODO also thread limit
;; TODO also thread pool
-#-clisp
-(defun server (host port handler &key quit)
+(defun server (host port handler &key quit allowed-methods ignore-errors-p)
(let ((s (rw.socket:make-tcp-server-socket host port)))
(flet ((accept ()
(with-open-stream (s s)
- (accept-loop s quit handler host port))))
+ (accept-loop s quit handler host port allowed-methods
+ ignore-errors-p))))
(if (rw.concurrency:threads-supported-p)
(rw.concurrency:make-thread
(format nil "RW.HTTP:ACCEPT-LOOP ~s ~s" host port)
diff --git a/rw.lisp b/rw.lisp
@@ -1,4 +1,4 @@
-;;; Copyright (C) 2013, 2014 Tomas Hlavaty <tom@logand.com>
+;;; Copyright (C) 2013, 2014, 2015 Tomas Hlavaty <tom@logand.com>
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
@@ -78,6 +78,9 @@
:write-u32be
:write-u32le
:write-u8
+ :write-utf8-char
+ :write-utf8-codepoint
+ :write-utf8-string
:writer
:z0))
@@ -377,9 +380,14 @@
z))
(t (wrong))))))
-(defun utf8-reader (octet-reader)
- (lambda ()
- (next-utf8 octet-reader)))
+(defun utf8-reader (octet-reader &key charp)
+ (if charp
+ (lambda ()
+ (let ((x (next-utf8 octet-reader)))
+ (when x
+ (code-char x))))
+ (lambda ()
+ (next-utf8 octet-reader))))
;;(rw:till (rw:peek-reader (utf8-reader (rw:reader #(#x24)))))
;;(rw:till (rw:peek-reader (utf8-reader (rw:reader #(#xc2 #xa2)))))
@@ -486,6 +494,38 @@
;; TODO write-u64|128
;; TODO write-s8|16|32|64|128
+(defun write-utf8-codepoint (writer x) ;; TODO
+ (cond
+ ((<= 0 x #x7f)
+ (write-u8 writer x))
+ ((<= #x000080 x #x0007ff) ;; 110xxxxx 10xxxxxx
+ (write-u8 writer (logior #b11000000 (ash x -6)))
+ (write-u8 writer (logior #b10000000 (logand x #b00111111))))
+ ((or (<= #x000800 x #x00d7ff) ;; 1110xxxx 10xxxxxx 10xxxxxx
+ (<= #x00e000 x #x00ffff))
+ (write-u8 writer (logior #b11100000 (ash x -12)))
+ (write-u8 writer (logior #b10000000 (logand (ash x -6) #b00111111)))
+ (write-u8 writer (logior #b10000000 (logand x #b00111111))))
+ ((<= #x010000 x #x10ffff) ;; 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ (write-u8 writer (logior #b11110000 (ash x -18)))
+ (write-u8 writer (logior #b10000000 (logand (ash x -12) #b00111111)))
+ (write-u8 writer (logior #b10000000 (logand (ash x -6) #b00111111)))
+ (write-u8 writer (logior #b10000000 (logand x #b00111111))))
+ (t (error "wrong utf8 codepoint ~s" x))))
+
+;;(let ((*print-base* 16) (b (make-array 0 :adjustable t :fill-pointer 0))) (write-utf8-codepoint (writer b) #x24) (princ-to-string b)) => 24
+;;(let ((*print-base* 16) (b (make-array 0 :adjustable t :fill-pointer 0))) (write-utf8-codepoint (writer b) #xa2) (princ-to-string b)) => C2 A2
+;;(let ((*print-base* 16) (b (make-array 0 :adjustable t :fill-pointer 0))) (write-utf8-codepoint (writer b) #x20ac) (princ-to-string b)) => E2 82 AC
+;;(let ((*print-base* 16) (b (make-array 0 :adjustable t :fill-pointer 0))) (write-utf8-codepoint (writer b) #x10348) (princ-to-string b)) => F0 90 8D 88
+
+(defun write-utf8-char (writer x)
+ (write-utf8-codepoint writer (char-code x)))
+
+(defun write-utf8-string (writer x)
+ (loop
+ for e across x
+ do (write-utf8-char writer e)))
+
(defun line-reader (reader)
(let ((r (peek-reader reader)))
(lambda ()
diff --git a/socket.lisp b/socket.lisp
@@ -1,4 +1,4 @@
-;;; Copyright (C) 2013, 2014 Tomas Hlavaty <tom@logand.com>
+;;; Copyright (C) 2013, 2014, 2015 Tomas Hlavaty <tom@logand.com>
;;;
;;; Permission is hereby granted, free of charge, to any person
;;; obtaining a copy of this software and associated documentation
@@ -333,8 +333,11 @@
(car (sb-bsd-sockets:host-ent-addresses
(sb-bsd-sockets:get-host-by-name remote-host)))
remote-port)
- (sb-bsd-sockets:socket-make-stream x :input t :output t ;;:buffering :none
- :element-type :default))
+ (sb-bsd-sockets:socket-make-stream x
+ :input t
+ :output t
+ ;;:buffering :none
+ :element-type '(unsigned-byte 8)))
#+cmucl
(let ((x (ext:connect-to-inet-socket remote-host remote-port)))
(sys:make-fd-stream x :input x :output x :element-type '(unsigned-byte 8)))
@@ -397,6 +400,7 @@
(socket:socket-accept socket)
#+(or sbcl ecl mkcl)
(sb-bsd-sockets:socket-make-stream (sb-bsd-sockets:socket-accept socket)
+ :element-type '(unsigned-byte 8)
:input t
:output t
:auto-close t)