рдкрд┐рдЫрд▓реЗ рд▓реЗрдЦ рдореЗрдВ, рд╣рдордиреЗ рдЕрдкрдирд╛ рд╡реЗрдм рд╕рд░реНрд╡рд░ рд╡рд┐рдХрд╕рд┐рдд рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд┐рдпрд╛ред рдЙрдкрдпреЛрдЧ рдХреЗ рд╕рд╛рде рдЬрд╛рд░реА рд░рдЦреЗрдВред рд▓рд┐рд╕реНрдк рдлрд╝рд╛рдЗрд▓ред рдЗрд╕ рдкреИрдХреЗрдЬ рдореЗрдВ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЗ рд▓рд┐рдП рд╣рдорд╛рд░реЗ рд╕рднреА рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдп рд╢рд╛рдорд┐рд▓ рд╣реЛрдВрдЧреЗред рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдЪрд░ * рд░реЗрдЦрд╛ * рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ, рд╣рдореЗрдВ рднрд╡рд┐рд╖реНрдп рдореЗрдВ рдЗрд╕рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реЛрдЧреАред
(defvar *new-line* (concatenate 'string (string #\Return) (string #\Newline)))
рд╣рдореЗрдВ рдПрдХ рдлрд╝рдВрдХреНрд╢рди рднреА рдЪрд╛рд╣рд┐рдП рдЬреЛ utf-8 рдореЗрдВ рд╕реНрдЯреНрд░реАрдо рд╕реЗ рдмрд╛рдЗрдЯреНрд╕ рдкрдврд╝реЗрдЧрд╛ рдФрд░
рддреБрдЪреНрдЫ-рдпреВрдЯреАрдПрдл -8 рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрдиреНрд╣реЗрдВ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдореЗрдВ рдмрджрд▓ рджреЗрдЧрд╛
: utf-8-рдмрд╛рдЗрдЯреНрд╕-рдЯреВ-рд╕реНрдЯреНрд░рд┐рдВрдЧ ред
(defun read-utf-8-string (stream &optional (end 0)) (let ((byte -1) (buffer (make-array 1 :fill-pointer 0 :adjustable t))) (handler-case (loop do (setq byte (read-byte stream)) (if (/= byte end) (vector-push-extend byte buffer)) while (/= byte end)) (end-of-file ())) (trivial-utf-8:utf-8-bytes-to-string buffer)))
рд╣рдо рдмрд╕ рдмрд╛рдЗрдЯреНрд╕ рдХреЛ рдкрдврд╝рддреЗ рд╣реИрдВ рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рд╣рдо рдмрд╛рдЗрдЯ рдХреЛ рдорд╛рди рдЕрдВрдд рдХреЗ рд╕рд╛рде рдкреНрд░рд╛рдкреНрдд рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рдмрд╛рдЗрдЯреНрд╕ рдХреЗ рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк рд╕рд░рдгреА рдХреЛ рдПрдХ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдореЗрдВ рдмрджрд▓ рджреЗрддреЗ рд╣реИрдВред рдпрд╣ рдлрд╝рдВрдХреНрд╢рди рджреВрд╕рд░реЗ рддрд░реАрдХреЗ рд╕реЗ рд▓рд┐рдЦрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ (рдЕрдзрд┐рдХ рдХреБрд╢рд▓рддрд╛ рд╕реЗ), рд▓реЗрдХрд┐рди рдореБрдЭреЗ рдпрд╣рд╛рдВ рдРрд╕рд╛ рд╡рд┐рдХрд▓реНрдк рдорд┐рд▓рд╛ред рдпрджрд┐ рдЖрдкрдХреЗ рдкрд╛рд╕ рдХреЛрдИ рдЕрдЪреНрдЫрд╛ рд╡рд┐рдЪрд╛рд░ рд╣реИ, рддреЛ рдореБрдЭреЗ рдЯрд┐рдкреНрдкрдгрд┐рдпреЛрдВ рдореЗрдВ рдЙрдиреНрд╣реЗрдВ рджреЗрдЦрдХрд░ рдЦреБрд╢реА рд╣реЛрдЧреАред рдПрдХ рдФрд░ рд╕рдорд╛рд░реЛрд╣ рдШреЛрд╖рд┐рдд рдХрд░реЗрдВ
(defun response-write (text stream) (trivial-utf-8:write-utf-8-bytes text stream))
рд╡рд╣ рдХреНрд▓рд╛рдЗрдВрдЯ рдХреЛ рдЙрд╕реА рдкреНрд░рд╛рд░реВрдк рдореЗрдВ рдЙрддреНрддрд░ рд▓рд┐рдЦрдиреЗ рдореЗрдВ рд╣рдорд╛рд░реА рдорджрдж рдХрд░реЗрдЧрд╛ (utf-8)
рд╣рдорд╛рд░рд╛ рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХреЗрд╡рд▓ GET рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЛ рд╕рдВрднрд╛рд▓рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдЧрд╛ред рдЕрдЧрд░ рдХрд┐рд╕реА рдХреЛ рджрд┐рд▓рдЪрд╕реНрдкреА рд╣реИ, рддреЛ рд╡рд╣ POST рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЗ рдкреНрд░рд╕рдВрд╕реНрдХрд░рдг рдХреЛ рд▓рд┐рдЦ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдЕрдм рд╣рдо GET рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЗ рд▓рд┐рдП рдЦреБрдж рдХреЛ рд╕реАрдорд┐рдд рдХрд░ рд▓реЗрдВрдЧреЗред рдПрдХ рд╡рд┐рд╢рд┐рд╖реНрдЯ HTTP GET рдЕрдиреБрд░реЛрдз рдХреБрдЫ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ
GET /path/to/a/resource?param1=paramvalue1┬╢m1=paramvalu2 HTTP/1.1 \r\n HeaderName: HeaderValue \r\n .... HeaderName: HeaderValue \r\n \r\n
рдкрд╣рд▓реА рдмрд╛рдд рдпрд╣ рд╣реИ рдХрд┐ рд╣рдореЗрдВ рдкрддрд╛ рд╣реИ рдХрд┐ рд╡реЗрдм рд╕рд░реНрд╡рд░ рдкрд░ рд╣рдореЗрдВ рдХрд┐рд╕ рдкреНрд░рдХрд╛рд░ рдХрд╛ рдЕрдиреБрд░реЛрдз рдорд┐рд▓рд╛ рд╣реИред
(defun parse-request (stream) (let ((header (read-utf-8-string stream 10))) (if (eq (length header) 0) '() (if (equal (subseq header 0 4) "POST") (parse-post-header header stream) (parse-get-header header stream)))))
рд╣рдо POST рдЕрдиреБрд░реЛрдзреЛрдВ рдХреЗ рд▓рд┐рдП рдХреБрдЫ рднреА рдирд╣реАрдВ рдХрд░рдиреЗ рдЬрд╛ рд░рд╣реЗ рд╣реИрдВ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдПрдХ рд╕рд╛рдзрд╛рд░рдг рдлрд╝рдВрдХреНрд╢рди рд▓рд┐рдЦреЗрдВрдЧреЗ
(defun parse-post-header (header stream) (cons "POST" nil))
GET рдЕрдиреБрд░реЛрдз рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рдЕрдиреБрд░реЛрдзрд┐рдд рд╕рдВрд╕рд╛рдзрди рдФрд░ рдЕрдиреНрдп рд╕рднреА рд╢реАрд░реНрд╖ рд▓реЗрдЦреЛрдВ рдХрд╛ рдорд╛рд░реНрдЧ рдкреНрд░рд╛рдкреНрдд рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдП
(defun parse-get-header (header stream) (cons "GET" (cons (parse-path (subseq header (position #\/ header) (position #\Space header :from-end t))) (parse-headers stream))))
рдЗрд╕рдХреЗ рд▓рд┐рдП рд╣рдо рдлрд╝рдВрдХреНрд╢рди
рдкрд╛рд░реНрд╕-рдкрд╛рде рдФрд░
рдкрд╛рд░реНрд╕-рд╣реЗрдбрд░реНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ
рд╢реБрд░реБрдЖрдд рдХрд░рддреЗ рд╣реИрдВ
рдкрд╛рд░реНрд╕-рдкрд╛рде рд╕реЗ (defun parse-path (path) (if (position #\? path) (cons (subseq path 0 (position #\? path)) (parse-params (subseq path (1+ (position #\? path))))) (cons path nil)))
рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдпрд╣рд╛рдВ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд╣рдо рдкреИрд░рд╛рдореАрдЯрд░реНрд╕ рдХреЛ рдЕрд▓рдЧ рдХрд░рддреЗ рд╣реИрдВ рдФрд░
рдкреИрд░рд╛рд╕реЗ-рдкрд╛рд░рдореЗрд╕ рдлрдВрдХреНрд╢рди рдХреЗ рд╕рд╛рде рдкреИрд░рд╛рдореАрдЯрд░реНрд╕ рдХреЛ рдЕрд▓рдЧ
- рдЕрд▓рдЧ рдХрд░рддреЗ рд╣реИрдВ
рдЗрд╕рд╕реЗ рдкрд╣рд▓реЗ рдХрд┐ рд╣рдо рдорд╛рдкрджрдВрдбреЛрдВ рдХреЛ рдкрд╛рд░реНрд╕ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВ, рд╣рдореЗрдВ рдЕрдкрдиреЗ рддрддреНрдХрд╛рд▓ рдореВрд▓реНрдпреЛрдВ рдХреЗ рд▓рд┐рдП рд╣реЗрдХреНрд╕рд╛рдбреЗрд╕рд┐рдорд▓ рдореЗрдВ рдЙрдкрдпреЛрдЧ рдХрд┐рдП рдЬрд╛рдиреЗ рд╡рд╛рд▓реЗ рдкрд╛рддреНрд░реЛрдВ рдХреЛ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдФрд░ рд╕рд╣рд╛рдпрдХ рдлрд╝рдВрдХреНрд╢рди рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред
(defun http-char (c1 c2 &optional (default #\Space)) (let ((code (parse-integer (coerce (list c1 c2) 'string) :radix 16 :junk-allowed t))) (if code (code-char code) default)))
рдЗрд╕ рдлрд╝рдВрдХреНрд╢рди рдХреЛ
http-char-decode рдХрд╣рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ
рдЕрдм рдпрд╣ рд╣рдорд╛рд░реЗ рдорд╛рдкрджрдВрдбреЛрдВ рдХреЛ рдмрджрд▓ рджреЗрддрд╛ рд╣реИред
(defun parse-params (s) (let ((params (decode-params s))) (remove-duplicates params :test (lambda (x1 x2) (equal (car x1) (car x2))) :from-end nil))) (defun decode-params (s) (let ((p1 (position #\& s))) (if p1 (cons (decode-kv (subseq s 0 p1)) (parse-params (subseq s (1+ p1)))) (list (decode-kv s))))) (defun decode-kv (s) (let ((p1 (position #\= s))) (if p1 (cons (decode-param (subseq s 0 p1)) (decode-param (subseq s (1+ p1)))) (cons (decode-param s) nil)))) (defun decode-param (s) (labels ((f (1st) (when 1st (case (car 1st) (#\% (cons (http-char (cadr 1st) (caddr 1st)) (f (cdddr 1st)))) (#\+ (cons #\Space (f (cdr 1st)))) (otherwise (cons (car 1st) (f (cdr 1st)))))))) (coerce (f (coerce s 'list)) 'string)))
рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд╣рдо рдЗрд╕рдХреЗ рд▓рд┐рдП
рдбреАрдХреЛрдб-рдкреИрд░рд╛рдореНрд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ, рдЬреЛ рдмрджрд▓реЗ рдореЗрдВ рдлрд┐рд░ рд╕реЗ
рдкрд╛рд░реНрд╕-рдкрд░рдо рдХреЛ рдХреЙрд▓ рдХрд░рддреЗ рд╣реИрдВ, рдЬреЛ
рдбрд┐рдХреЛрдб-рдХреЗрд╡реА рдХреЗ рд╕рд╛рде рдирд╛рдо = рдорд╛рди рдкреИрд░рд╛рдореАрдЯрд░ рдХреЛ рдкреВрд░реНрд╡-рдкрд╛рд░реНрд╕ рдХрд░рдиреЗ рдХреЗ рдмрд╛рдж рдкреБрди: рдкрд╛рд░реНрд╕ рдХрд░рддрд╛ рд╣реИред рдЕрдВрдд рдореЗрдВ, рд╣рдо рд╕рд╣рд╛рдпрдХ рдлрд╝рдВрдХреНрд╢рди
рдбрд┐рдХреЛрдб-рдкрд░рдо рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ, рдЬреЛ рд╡рд┐рд╢реЗрд╖ http рд╡рд░реНрдгреЛрдВ рдХреЛ рдЕрд▓рдЧ рдХрд░рддрд╛ рд╣реИ рдФрд░ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА http-char рд░рд┐рдЯрд░реНрдирд┐рдВрдЧ рдбреЗрдЯрд╛ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрдиреНрд╣реЗрдВ рдкрд░рд┐рд╡рд░реНрддрд┐рдд рдХрд░рддрд╛ рд╣реИ
рд╣рдорд╛рд░рд╛
рдкрд╛рд░реНрд╕-рдкрд░рдо рддреИрдпрд╛рд░ рд╣реИ, рдпрд╣
рдкрд╛рд░реНрд╕-рд╣реЗрдбрд░реНрд╕ рдлрд╝рдВрдХреНрд╢рди рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдмрдирд╛ рд╣реБрдЖ рд╣реИ, рдпрд╣рд╛рдВ рд╕рдм рдХреБрдЫ рдмрд╣реБрдд рд╕рд░рд▓ рд╣реИ
(defun parse-headers (stream) (let ((headers nil) (header nil)) (loop do (setq header (read-utf-8-string stream 10)) (if (> (length header) 2) (setq headers (cons (parse-header header) headers))) while (> (length header) 2)) (reverse headers))) (defun parse-header (header) (let ((pos (position #\: header))) (if pos (cons (string-downcase (subseq header 0 pos)) (string-trim (concatenate 'string (string #\Space) (string #\Return)) (subseq header (1+ pos)))))))
рд╣рдо рдкрд╣рд▓реА рдмрд╛рд░ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реИрдВ (рд░реАрдб-рдпреВрдЯреАрдПрдл-8-рд╕реНрдЯреНрд░рд┐рдВрдЧ рд╕реНрдЯреНрд░реАрдо 10), рдЬрд╣рд╛рдВ рдПрдПрд╕рд╕реАрдЖрдИрдЖрдИ рдореЗрдВ 10 рдорд╛рди \ n рд╣реИ рдФрд░ рдпрджрд┐ рдпрд╣ рджреЛ рд╕реЗ рдЕрдзрд┐рдХ рдЕрдХреНрд╖рд░ рд╣реИ, рддреЛ рдЗрд╕реЗ рдкрд╛рд░реНрд╕-рд╣реЗрдбрд░ рдХреЗ рд╕рд╛рде рдкрд╛рд░реНрд╕ рдХрд░реЗрдВред рдирддреАрдЬрддрди, рд╣рдо рд╕рднреА рд╢реАрд░реНрд╖рдХреЛрдВ рд╕реЗ рдЕрд▓рдЧ рд╣реЛ рдЬрд╛рддреЗ рд╣реИрдВред
рдЗрд╕ рдкрд░,
рдкрд╛рд░реНрд╕-рдЧреЗрдЯ-рд╣реЗрдбрд░ рддреИрдпрд╛рд░ рд╣реИ рдФрд░ рдЙрд╕реЗ рдкреНрд░рдХрд╛рд░ рдХреА рд╕рдВрд░рдЪрдирд╛ рд╡рд╛рдкрд╕ рдХрд░рдиреА рдЪрд╛рд╣рд┐рдП
'("GET" ("path/to/file" (("param1" . "value1") ("param2" . "value2"))) (("header1" . "value1") ("header2" . "value2")))
рдЗрд╕ рд╕рдВрд░рдЪрдирд╛ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреА рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рд▓рд┐рдП, рд╣рдо рджреЛ рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдпреЛрдВ рдХреЛ рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ
(defun get-param (name request) (cdr (assoc name (cdadr request) :test #'equal))) (defun get-header (name request) (cdr (assoc (string-downcase name) (cddr request) :test #'equal)))
рдЕрдм рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдПрдХ рдЕрдиреБрд░реЛрдз рд╣реИ, рд╣рдо рдЧреНрд░рд╛рд╣рдХ рдХреЛ рдПрдХ рдкреНрд░рддрд┐рдХреНрд░рд┐рдпрд╛ рднреЗрдЬ рд╕рдХрддреЗ рд╣реИрдВред рдПрдХ рдареЗрда рдЬрд╡рд╛рдм рдХреБрдЫ рдЗрд╕ рддрд░рд╣ рджрд┐рдЦрддрд╛ рд╣реИ
HTTP/1.1 200 OK HeaderName: HeaderValue \r\n .... HeaderName: HeaderValue \r\n \r\n Data
рд╣рдо рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рдПрдХ рдЬреЛрдбрд╝реЗ рдХреЛ рд▓рд┐рдЦреЗрдВрдЧреЗ рдЬреЛ рдЙрддреНрддрд░ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдореЗрдВ рд╣рдорд╛рд░реА рдорджрдж рдХрд░реЗрдВрдЧреЗ
(defun http-response (code headers stream) (response-write (concatenate 'string "HTTP/1.1 " code *new-line*) stream) (mapcar (lambda (header) (response-write (concatenate 'string (car header) ": " (cdr header) *new-line*) stream)) headers) (response-write *new-line* stream)) (defun http-404-not-found (message stream) (http-response "404 Not Found" nil stream) (response-write message stream))
рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рдпрд╣рд╛рдВ рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд╕рдм рдХреБрдЫ рд╕рд░рд▓ рднреА рд╣реИред
рдЕрдм рдпрд╣ рдПрдХ рдлрд╝рдВрдХреНрд╢рди рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рд╣реИ рдЬреЛ рд╣рдореЗрдВ
рд╡реЗрдм рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд╕реЗ рдлрд╛рдЗрд▓реЗрдВ рджреЗрдЧрд╛
(defun file-response (filename type request stream) (handler-case (with-open-file (in (concatenate 'string "web/" filename) :element-type '(unsigned-byte 8)) (if (equal (get-header "if-modified-since" request) (format-timestring nil (universal-to-timestamp (file-write-date in)) :format +asctime-format+)) (http-response "304 Not Modified" nil stream) (progn (http-response "200 OK" (cons (cons "Last-Modified" (format-timestring nil (universal-to-timestamp (file-write-date in)) :format +asctime-format+)) (cons (cons "Content-Type" type) nil)) stream) (let ((buf (make-array 4096 :element-type (stream-element-type in)))) (loop for pos = (read-sequence buf in) while (plusp pos) do (write-sequence buf stream :end pos))) ))) (file-error () (http-404-not-found "404 File Not Found" stream) )))
рдпрд╣ рд╣рдорд╛рд░реЗ рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХреЛ рдЫрд╡рд┐рдпреЛрдВ рдФрд░ HTML рдкреГрд╖реНрдареЛрдВ рдЬреИрд╕реА рдлрд╝рд╛рдЗрд▓реЛрдВ рдХреЛ рд╡рд╛рдкрд╕ рдХрд░рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рджреЗрдЧрд╛ред рдЙрд╕реА рд╕рдордп, рд╣рдо рд▓рд╛рд╕реНрдЯ-рдореЙрдбрд┐рдлрд╛рдЗрдб рд╣реЗрдбрд░ рдХреЛ рднреА рд▓рд╛рд╕реНрдЯ рдлрд╛рдЗрд▓ рдореЙрдбрд┐рдлрд┐рдХреЗрд╢рди рдХреА рддрд╛рд░реАрдЦ рд╕реЗ рд▓реМрдЯрд╛рддреЗ рд╣реИрдВред рдпрджрд┐ рд╣рдореЗрдВ рд╣реЗрдбрд░ рдХреЗ рд╕рд╛рде рджреВрд╕рд░реА рдмрд╛рд░ рдЙрд╕реА рдлрд╛рдЗрд▓ рдХреЗ рд▓рд┐рдП рдЕрдиреБрд░реЛрдз рдорд┐рд▓рддрд╛ рд╣реИ рдпрджрд┐ рд╕рдВрд╢реЛрдзрд┐рдд-рдЪреВрдВрдХрд┐, рддреЛ рд╣рдо рдлрд╝рд╛рдЗрд▓ рдХреЗ рдЕрдВрддрд┐рдо рд╕рдВрд╢реЛрдзрди рдХреА рддрд╛рд░реАрдЦ рдХреЗ рд╕рд╛рде рддрд╛рд░реАрдЦ рдХреЛ рдлреНрд░реАрдЬ рдХрд░ рджреЗрдВрдЧреЗред рдпрджрд┐ рддрд╛рд░реАрдЦ рдирд╣реАрдВ рдмрджрд▓реА рдЧрдИ рд╣реИ, рддреЛ рдЗрд╕рдХрд╛ рдорддрд▓рдм рд╣реИ рдХрд┐ рд╡реЗрдм рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдХреЗ рдкрд╛рд╕ рдЕрдкрдиреЗ рдХреИрд╢ рдореЗрдВ рдлрд╝рд╛рдЗрд▓ рдХрд╛ рдирд╡реАрдирддрдо рд╕рдВрд╕реНрдХрд░рдг рд╣реИ, рдЗрд╕рд▓рд┐рдП рд╣рдо рдХреЗрд╡рд▓ рдХреЛрдб 304 рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░рддреЗ рд╣реИрдВ
рдЕрдм рд╣рдо рджреВрд╕рд░рд╛
html- рдЯреЗрдореНрдкреНрд▓реЗрдЯ рдлрд╝рдВрдХреНрд╢рди рд▓рд┐рдЦреЗрдВрдЧреЗ, рдЬреЛ
рд╡реЗрдм рдирд┐рд░реНрджреЗрд╢рд┐рдХрд╛ рд╕реЗ рдХреЛрдИ рднреА рдЯреЗрдХреНрд╕реНрдЯ рдлрд╝рд╛рдЗрд▓ рд▓реЗрдЧрд╛ рдФрд░ рд╕рдорд╛рди рдирд╛рдореЛрдВ рдХреЗ рд╕рд╛рде рдПрд▓рд┐рд╕реНрдЯ рд╕реВрдЪреА рдореЗрдВ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдорд╛рдиреЛрдВ рдХреЗ рд╕рд╛рде $ {name} рдХреЗ рдорд╛рдиреЛрдВ рдХреЛ рдмрджрд▓ рджреЗрдЧрд╛ред рдПрдХ рдкреНрд░рдХрд╛рд░ рдХрд╛ рдЖрджрд┐рдо рдЯреЗрдореНрдкрд▓реЗрдЯ рдЗрдВрдЬрди
(defun html-template (filename type params request stream) (handler-case (with-open-file (in (concatenate 'string "web/" filename) :element-type '(unsigned-byte 8)) (loop for line = (read-utf-8-string in 10) while (and line (> (length line) 0)) do (progn (mapcar (lambda (i) (let* ((key (concatenate 'string "${" (car i) "}"))) (loop for pos = (search key line) while pos do (setq line (concatenate 'string (subseq line 0 pos) (cdr i) (subseq line (+ pos (length key))))) ) )) params) (response-write line stream) (response-write (string #\Return) stream)) ) ) (file-error () (http-404-not-found "404 File Not Found" stream) )))
рдЗрд╕ рдкрд░ рд╣рдорд╛рд░рд╛ рдЙрдкрдпреЛрдЧред
рд▓рд┐рд╕реНрдк рд▓рдЧрднрдЧ рддреИрдпрд╛рд░ рд╣реИ, рдпрд╣ рдХреЗрд╡рд▓ рд▓реЙрдЧ рдХреЗ рд▓рд┐рдП рдлрд╝рдВрдХреНрд╢рди рд▓рд┐рдЦрдиреЗ рдХреЗ рд▓рд┐рдП рд░рд╣рддрд╛ рд╣реИред рдЪрд▓реЛ
log.lisp рдлрд╝рд╛рдЗрд▓ рдореЗрдВ
рд╕реАрдПрд▓-рд▓реЙрдЧ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░реЗрд╢рди рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ
(setf (log-manager) (make-instance 'log-manager :message-class 'formatted-message)) (start-messenger 'text-file-messenger :filename "log/web.log") (defmethod format-message ((self formatted-message)) (format nil "~a ~a ~?~&" (local-time:format-timestring nil (local-time:universal-to-timestamp (timestamp-universal-time (message-timestamp self)))) (message-category self) (message-description self) (message-arguments self)))
рдпрд╣рд╛рдВ рд╕рдм рдХреБрдЫ рдорд╛рдирдХ рд╣реИ, рдХреЗрд╡рд▓ рдПрдХ рдЪреАрдЬ рдЬреЛ рд╣рдордиреЗ рдмрджрд▓реА рд╣реИ рд╡рд╣ рд╣реИ рдкреНрд░рд╛рд░реВрдк-рд╕рдВрджреЗрд╢ рдЬрд╣рд╛рдВ рд╣рдо рдХреЗрд╡рд▓ рдПрдХ рд╕реНрд╡рд░реВрдкрд┐рдд рд░реВрдк рдореЗрдВ рджрд┐рдирд╛рдВрдХ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рддреЗ рд╣реИрдВред
рдЕрдм рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд▓реЙрдЧрд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдПрдХ рдлрд╝рдВрдХреНрд╢рди рдЬреЛрдбрд╝реЗрдВред рд▓рд┐рд╕реНрдк рдЬреЛ рдПрдХ рдЕрд▓рдЧ рдереНрд░реЗрдб рдореЗрдВ рдПрдХ рд╣реА рд╕рдордп рдореЗрдВ рдкреНрд░рддрд┐ рд╕реЗрдХрдВрдб 1 рд╕реЗ рдЕрдзрд┐рдХ рд╕рдордп рдореЗрдВ рд╕рдВрджреЗрд╢ рд▓реЙрдЧ рдХрд░реЗрдЧрд╛ред рд╕реАрдзреЗ рд▓реЙрдЧрд┐рдВрдЧ рд╕реЗ рд▓реЛрдбрд┐рдВрдЧ рдХреЛ рд╣рдЯрд╛рдиреЗ рдХреА рдЕрдиреБрдорддрд┐ рдХреНрдпрд╛ рд╣реЛрдЧреА
(defvar *log-queue-lock* (bt:make-lock)) (defvar *log-queue-cond* (bt:make-condition-variable)) (defvar *log-queue-cond-lock* (bt:make-lock)) (defvar *log-queue* nil) (defvar *log-queue-time* (get-universal-time)) (defun log-worker () (bt:with-lock-held (*log-queue-lock*) (progn (mapcar (lambda (i) (if (cdr i) (cl-log:log-message (car i) (cdr i)))) (reverse *log-queue*)) (setq *log-queue* nil) )) (bt:with-lock-held (*log-queue-cond-lock*) (bt:condition-wait *log-queue-cond* *log-queue-cond-lock*) ) (log-worker)) (bt:make-thread #'log-worker :name "log-worker")
рдЗрд╕рдХреЗ рд▓рд┐рдП рд╣рдо рд╕рд╣рд╛рдпрдХ рд▓реЙрдЧрд┐рдВрдЧ рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВрдЧреЗ
(defun log-info (message) (bt:with-lock-held (*log-queue-lock*) (progn (push (cons :info message) *log-queue*) (if (> (- (get-universal-time) *log-queue-time*) 0) (bt:condition-notify *log-queue-cond*)) ))) (defun log-warning (message) (bt:with-lock-held (*log-queue-lock*) (progn (push (cons :warning message) *log-queue*) (if (> (- (get-universal-time) *log-queue-time*) 0) (bt:condition-notify *log-queue-cond*)) ))) (defun log-error (message) (bt:with-lock-held (*log-queue-lock*) (progn (push (cons :error message) *log-queue*) (if (> (- (get-universal-time) *log-queue-time*) 0) (bt:condition-notify *log-queue-cond*)) )))
рдпрд╣ рд╣реИрдВрдбрд▓рд░.рд▓рд┐рд╕реНрдк рдореЗрдВ
рдкреНрд░рдХреНрд░рд┐рдпрд╛-рдЕрдиреБрд░реЛрдз рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдФрд░ рд╣рдорд╛рд░реЗ рдХрд╛рд░реНрдпреЛрдВ рдХрд╛ рдкреНрд░рдпрд╛рд╕ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдиреА рд╣реБрдИ рд╣реИ
(defun process-request (request stream) (let ((path (caadr request))) (cond ((equal path "/logo.jpg") (myweb.util:file-response "logo.jpg" "image/jpeg" request stream)) (t (process-index request stream))))) (defun process-index (request stream) (let ((name (myweb.util:get-param "name" request))) (if (and name (> (length name) 0)) (myweb.util:html-template "index.html" "text/html;encoding=UTF-8" `(("name" . ,name)) request stream) (myweb.util:html-template "name.html" "text/html;encoding=UTF-8" nil request stream) )))
рд╡реЗрдм рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдПрдХ
index.html рдлрд╝рд╛рдЗрд▓ рдмрдирд╛рдПрдБ
<html> <head> <title>myweb</title> </head> <body> <image src="logo.jpg"> <h1>Hello ${name}</h1> </body> </html>
рдФрд░
name.html рдлрд╝рд╛рдЗрд▓
<html> <head> <title>myweb</title> </head> <body> <image src="logo.jpg"> <h2>Hello stranger. What's your name?</h2> <form action="/" method="GET"> Name: <input type="text" name="name"> <input type="submit" value="Submit"> </form> </body> </html>
рдФрд░ рд╡рд╣рд╛рдБ рдПрдХ рд╕реБрдВрджрд░
logo.jpg рд░рдЦрдирд╛ рдордд рднреВрд▓рдирд╛
рд╡реЗрдм рд╕рд░реНрд╡рд░ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╢реБрд░реВ рдХрд░реЗрдВ (myweb: start-http "localhost" 8080) рдФрд░ рд╕реНрдерд╛рдиреАрдпрд╣реЛрд╕реНрдЯ рдкрд░ рдЬрд╛рдПрдВ: 8080 рдмреНрд░рд╛рдЙрдЬрд╝рд░
рдЖрдкрдХрд╛ рдзреНрдпрд╛рди рджреЗрдиреЗ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рджред