remove *session-removal-hook* doc, add acceptor-remove-session doc
[hunchentoot.git] / acceptor.lisp
blob8bb5f94058a9ff2144a2475dcdb35a8c1b4c8d40
1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: CL-USER; Base: 10 -*-
3 ;;; Copyright (c) 2004-2010, Dr. Edmund Weitz. All rights reserved.
5 ;;; Redistribution and use in source and binary forms, with or without
6 ;;; modification, are permitted provided that the following conditions
7 ;;; are met:
9 ;;; * Redistributions of source code must retain the above copyright
10 ;;; notice, this list of conditions and the following disclaimer.
12 ;;; * Redistributions in binary form must reproduce the above
13 ;;; copyright notice, this list of conditions and the following
14 ;;; disclaimer in the documentation and/or other materials
15 ;;; provided with the distribution.
17 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
18 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 (in-package :hunchentoot)
31 (eval-when (:load-toplevel :compile-toplevel :execute)
32 (defun default-document-directory (&optional sub-directory)
33 (asdf:system-relative-pathname :hunchentoot (format nil "www/~@[~A~]" sub-directory))))
35 (defclass acceptor ()
36 ((port :initarg :port
37 :reader acceptor-port
38 :documentation "The port the acceptor is listening on. The
39 default is 80. Note that depending on your operating system you might
40 need special privileges to listen on port 80.")
41 (address :initarg :address
42 :reader acceptor-address
43 :documentation "The address the acceptor is listening on.
44 If address is a string denoting an IP address, then the server only
45 receives connections for that address. This must be one of the
46 addresses associated with the machine and allowed values are host
47 names such as \"www.zappa.com\" and address strings such as
48 \"72.3.247.29\". If address is NIL, then the server will receive
49 connections to all IP addresses on the machine. This is the default.")
50 (name :initarg :name
51 :accessor acceptor-name
52 :documentation "The optional name of the acceptor, a symbol.
53 This name can be utilized when defining \"easy handlers\" - see
54 DEFINE-EASY-HANDLER. The default name is an uninterned symbol as
55 returned by GENSYM.")
56 (request-class :initarg :request-class
57 :accessor acceptor-request-class
58 :documentation "Determines which class of request
59 objects is created when a request comes in and should be \(a symbol
60 naming) a class which inherits from REQUEST. The default is the
61 symbol REQUEST.")
62 (reply-class :initarg :reply-class
63 :accessor acceptor-reply-class
64 :documentation "Determines which class of reply
65 objects is created when a request is served in and should be \(a
66 symbol naming) a class which inherits from REPLY. The default is the
67 symbol REPLY.")
68 (taskmaster :initarg :taskmaster
69 :reader acceptor-taskmaster
70 :documentation "The taskmaster \(i.e. an instance of a
71 subclass of TASKMASTER) that is responsible for scheduling the work
72 for this acceptor. The default depends on the MP capabilities of the
73 underlying Lisp.")
74 (output-chunking-p :initarg :output-chunking-p
75 :accessor acceptor-output-chunking-p
76 :documentation "A generalized boolean denoting
77 whether the acceptor may use chunked encoding for output, i.e. when
78 sending data to the client. The default is T and there's usually no
79 reason to change this to NIL.")
80 (input-chunking-p :initarg :input-chunking-p
81 :accessor acceptor-input-chunking-p
82 :documentation "A generalized boolean denoting
83 whether the acceptor may use chunked encoding for input, i.e. when
84 accepting request bodies from the client. The default is T and
85 there's usually no reason to change this to NIL.")
86 (persistent-connections-p :initarg :persistent-connections-p
87 :accessor acceptor-persistent-connections-p
88 :documentation "A generalized boolean
89 denoting whether the acceptor supports persistent connections, which
90 is the default for threaded acceptors. If this property is NIL,
91 Hunchentoot closes each incoming connection after having processed one
92 request. This is the default for non-threaded acceptors.")
93 (read-timeout :initarg :read-timeout
94 :reader acceptor-read-timeout
95 :documentation "The read timeout of the acceptor,
96 specified in \(fractional) seconds. The precise semantics of this
97 parameter is determined by the underlying Lisp's implementation of
98 socket timeouts. NIL means no timeout.")
99 (write-timeout :initarg :write-timeout
100 :reader acceptor-write-timeout
101 :documentation "The write timeout of the acceptor,
102 specified in \(fractional) seconds. The precise semantics of this
103 parameter is determined by the underlying Lisp's implementation of
104 socket timeouts. NIL means no timeout.")
105 #+:lispworks
106 (process :accessor acceptor-process
107 :documentation "The Lisp process which accepts incoming
108 requests. This is the process started by COMM:START-UP-SERVER and no
109 matter what kind of taskmaster you are using this will always be a new
110 process different from the one where START was called.")
111 #-:lispworks
112 (listen-socket :initform nil
113 :accessor acceptor-listen-socket
114 :documentation "The socket listening for incoming
115 connections.")
116 #-:lispworks
117 (listen-backlog :initarg :listen-backlog
118 :reader acceptor-listen-backlog
119 :documentation "Number of pending connections
120 allowed in the listen socket before the kernel rejects
121 further incoming connections.")
122 (acceptor-shutdown-p :initform t
123 :accessor acceptor-shutdown-p
124 :documentation "A flag that makes the acceptor
125 shutdown itself when set to something other than NIL.")
126 (requests-in-progress :initform 0
127 :accessor accessor-requests-in-progress
128 :documentation "The number of
129 requests currently in progress.")
130 (shutdown-queue :initform (make-condition-variable)
131 :accessor acceptor-shutdown-queue
132 :documentation "A condition variable
133 used with soft shutdown, signaled when all requests
134 have been processed.")
135 (shutdown-lock :initform (make-lock "hunchentoot-acceptor-shutdown")
136 :accessor acceptor-shutdown-lock
137 :documentation "The lock protecting the shutdown-queue
138 condition variable and the requests-in-progress counter.")
139 (access-log-destination :initarg :access-log-destination
140 :accessor acceptor-access-log-destination
141 :documentation "Destination of the access log
142 which contains one log entry per request handled in a format similar
143 to Apache's access.log. Can be set to a pathname or string
144 designating the log file, to a open output stream or to NIL to
145 suppress logging.")
146 (message-log-destination :initarg :message-log-destination
147 :accessor acceptor-message-log-destination
148 :documentation "Destination of the server
149 error log which is used to log informational, warning and error
150 messages in a free-text format intended for human inspection. Can be
151 set to a pathname or string designating the log file, to a open output
152 stream or to NIL to suppress logging.")
153 (error-template-directory :initarg :error-template-directory
154 :accessor acceptor-error-template-directory
155 :documentation "Directory pathname that
156 contains error message template files for server-generated error
157 messages. Files must be named <return-code>.html with <return-code>
158 representing the HTTP return code that the file applies to,
159 i.e. 404.html would be used as the content for a HTTP 404 Not found
160 response.")
161 (document-root :initarg :document-root
162 :accessor acceptor-document-root
163 :documentation "Directory pathname that points to
164 files that are served by the acceptor if no more specific
165 acceptor-dispatch-request method handles the request."))
166 (:default-initargs
167 :address nil
168 :port 80
169 :name (gensym)
170 :request-class 'request
171 :reply-class 'reply
172 #-lispworks :listen-backlog #-lispworks 50
173 :taskmaster (make-instance (cond (*supports-threads-p* 'one-thread-per-connection-taskmaster)
174 (t 'single-threaded-taskmaster)))
175 :output-chunking-p t
176 :input-chunking-p t
177 :persistent-connections-p t
178 :read-timeout *default-connection-timeout*
179 :write-timeout *default-connection-timeout*
180 :access-log-destination *error-output*
181 :message-log-destination *error-output*
182 :document-root (load-time-value (default-document-directory))
183 :error-template-directory (load-time-value (default-document-directory "errors/")))
184 (:documentation "To create a Hunchentoot webserver, you make an
185 instance of this class and use the generic function START to start it
186 \(and STOP to stop it). Use the :PORT initarg if you don't want to
187 listen on the default http port 80. There are other initargs most of
188 which you probably won't need very often. They are explained in
189 detail in the docstrings of the slot definitions for this class.
191 Unless you are in a Lisp without MP capabilities, you can have several
192 active instances of ACCEPTOR \(listening on different ports) at the
193 same time."))
195 (defmethod print-object ((acceptor acceptor) stream)
196 (print-unreadable-object (acceptor stream :type t)
197 (format stream "\(host ~A, port ~A)"
198 (or (acceptor-address acceptor) "*") (acceptor-port acceptor))))
200 (defgeneric start (acceptor)
201 (:documentation "Starts the ACCEPTOR so that it begins accepting
202 connections. Returns the acceptor."))
204 (defgeneric stop (acceptor &key soft)
205 (:documentation "Stops the ACCEPTOR so that it no longer accepts
206 requests. If SOFT is true, and there are any requests in progress,
207 wait until all requests are fully processed, but meanwhile do
208 not accept new requests."))
210 (defgeneric start-listening (acceptor)
211 (:documentation "Sets up a listen socket for the given ACCEPTOR and
212 enables it to listen to incoming connections. This function is called
213 from the thread that starts the acceptor initially and may return
214 errors resulting from the listening operation \(like 'address in use'
215 or similar)."))
217 (defgeneric accept-connections (acceptor)
218 (:documentation "In a loop, accepts a connection and hands it over
219 to the acceptor's taskmaster for processing using
220 HANDLE-INCOMING-CONNECTION. On LispWorks, this function returns
221 immediately, on other Lisps it retusn only once the acceptor has been
222 stopped."))
224 (defgeneric initialize-connection-stream (acceptor stream)
225 (:documentation "Can be used to modify the stream which is used to
226 communicate between client and server before the request is read. The
227 default method of ACCEPTOR does nothing, but see for example the
228 method defined for SSL-ACCEPTOR. All methods of this generic function
229 must return the stream to use."))
231 (defgeneric reset-connection-stream (acceptor stream)
232 (:documentation "Resets the stream which is used to communicate
233 between client and server after one request has been served so that it
234 can be used to process the next request. This generic function is
235 called after a request has been processed and must return the
236 stream."))
238 (defgeneric process-connection (acceptor socket)
239 (:documentation "This function is called by the taskmaster when a
240 new client connection has been established. Its arguments are the
241 ACCEPTOR object and a LispWorks socket handle or a usocket socket
242 stream object in SOCKET. It reads the request headers, sets up the
243 request and reply objects, and hands over to PROCESS-REQUEST. This is
244 done in a loop until the stream has to be closed or until a connection
245 timeout occurs.
247 It is probably not a good idea to re-implement this method until you
248 really, really know what you're doing."))
250 (defgeneric handle-request (acceptor request)
251 (:documentation "This function is called once the request has been
252 read and a REQUEST object has been created. Its job is to set up
253 standard error handling and request logging.
255 Might be a good place for around methods specialized for your subclass
256 of ACCEPTOR which bind or rebind special variables which can then be
257 accessed by your handlers."))
259 (defgeneric acceptor-dispatch-request (acceptor request)
260 (:documentation "This function is called to actually dispatch the
261 request once the standard logging and error handling has been set up.
262 ACCEPTOR subclasses implement methods for this function in order to
263 perform their own request routing. If a method does not want to
264 handle the request, it is supposed to invoke CALL-NEXT-METHOD so that
265 the next ACCEPTOR in the inheritance chain gets a chance to handle the
266 request."))
268 (defgeneric acceptor-ssl-p (acceptor)
269 (:documentation "Returns a true value if ACCEPTOR uses SSL
270 connections. The default is to unconditionally return NIL and
271 subclasses of ACCEPTOR must specialize this method to signal that
272 they're using secure connections - see the SSL-ACCEPTOR class."))
274 ;; general implementation
276 (defmethod start ((acceptor acceptor))
277 (setf (acceptor-shutdown-p acceptor) nil)
278 (start-listening acceptor)
279 (let ((taskmaster (acceptor-taskmaster acceptor)))
280 (setf (taskmaster-acceptor taskmaster) acceptor)
281 (execute-acceptor taskmaster))
282 acceptor)
284 (defmethod stop ((acceptor acceptor) &key soft)
285 (setf (acceptor-shutdown-p acceptor) t)
286 (shutdown (acceptor-taskmaster acceptor))
287 (when soft
288 (with-lock-held ((acceptor-shutdown-lock acceptor))
289 (when (plusp (accessor-requests-in-progress acceptor))
290 (condition-variable-wait (acceptor-shutdown-queue acceptor)
291 (acceptor-shutdown-lock acceptor)))))
292 #-lispworks
293 (usocket:socket-close (acceptor-listen-socket acceptor))
294 #-lispworks
295 (setf (acceptor-listen-socket acceptor) nil)
296 #+lispworks
297 (mp:process-kill (acceptor-process acceptor))
298 acceptor)
300 (defmethod initialize-connection-stream ((acceptor acceptor) stream)
301 (declare (ignore acceptor))
302 ;; default method does nothing
303 stream)
305 (defmethod reset-connection-stream ((acceptor acceptor) stream)
306 (declare (ignore acceptor))
307 ;; turn chunking off at this point
308 (cond ((typep stream 'chunked-stream)
309 ;; flush the stream first and check if there's unread input
310 ;; which would be an error
311 (setf (chunked-stream-output-chunking-p stream) nil
312 (chunked-stream-input-chunking-p stream) nil)
313 ;; switch back to bare socket stream
314 (chunked-stream-stream stream))
315 (t stream)))
317 (defmethod process-connection :around ((*acceptor* acceptor) (socket t))
318 ;; this around method is used for error handling
319 (declare (ignore socket))
320 ;; note that this method also binds *ACCEPTOR*
321 (handler-bind ((error
322 ;; abort if there's an error which isn't caught inside
323 (lambda (cond)
324 (log-message* *lisp-errors-log-level*
325 "Error while processing connection: ~A" cond)
326 (return-from process-connection)))
327 (warning
328 ;; log all warnings which aren't caught inside
329 (lambda (cond)
330 (log-message* *lisp-warnings-log-level*
331 "Warning while processing connection: ~A" cond))))
332 (with-mapped-conditions ()
333 (call-next-method))))
335 (defun do-with-acceptor-request-count-incremented (*acceptor* function)
336 (with-lock-held ((acceptor-shutdown-lock *acceptor*))
337 (incf (accessor-requests-in-progress *acceptor*)))
338 (unwind-protect
339 (funcall function)
340 (with-lock-held ((acceptor-shutdown-lock *acceptor*))
341 (decf (accessor-requests-in-progress *acceptor*))
342 (when (acceptor-shutdown-p *acceptor*)
343 (condition-variable-signal (acceptor-shutdown-queue *acceptor*))))))
345 (defmacro with-acceptor-request-count-incremented ((acceptor) &body body)
346 "Execute BODY with ACCEPTOR-REQUESTS-IN-PROGRESS of ACCEPTOR
347 incremented by one. If the ACCEPTOR-SHUTDOWN-P returns true after
348 the BODY has been executed, the ACCEPTOR-SHUTDOWN-QUEUE condition
349 variable of the ACCEPTOR is signalled in order to finish shutdown
350 processing."
351 `(do-with-acceptor-request-count-incremented ,acceptor (lambda () ,@body)))
353 (defmethod process-connection ((*acceptor* acceptor) (socket t))
354 (let ((*hunchentoot-stream*
355 (initialize-connection-stream *acceptor* (make-socket-stream socket *acceptor*))))
356 (unwind-protect
357 ;; process requests until either the acceptor is shut down,
358 ;; *CLOSE-HUNCHENTOOT-STREAM* has been set to T by the
359 ;; handler, or the peer fails to send a request
360 (loop
361 (let ((*close-hunchentoot-stream* t))
362 (when (acceptor-shutdown-p *acceptor*)
363 (return))
364 (multiple-value-bind (headers-in method url-string protocol)
365 (get-request-data *hunchentoot-stream*)
366 ;; check if there was a request at all
367 (unless method
368 (return))
369 ;; bind per-request special variables, then process the
370 ;; request - note that *ACCEPTOR* was bound above already
371 (let ((*reply* (make-instance (acceptor-reply-class *acceptor*)))
372 (*session* nil)
373 (transfer-encodings (cdr (assoc* :transfer-encoding headers-in))))
374 (when transfer-encodings
375 (setq transfer-encodings
376 (split "\\s*,\\s*" transfer-encodings))
377 (when (member "chunked" transfer-encodings :test #'equalp)
378 (cond ((acceptor-input-chunking-p *acceptor*)
379 ;; turn chunking on before we read the request body
380 (setf *hunchentoot-stream* (make-chunked-stream *hunchentoot-stream*)
381 (chunked-stream-input-chunking-p *hunchentoot-stream*) t))
382 (t (hunchentoot-error "Client tried to use ~
383 chunked encoding, but acceptor is configured to not use it.")))))
384 (multiple-value-bind (remote-addr remote-port)
385 (get-peer-address-and-port socket)
386 (with-acceptor-request-count-incremented (*acceptor*)
387 (process-request (make-instance (acceptor-request-class *acceptor*)
388 :acceptor *acceptor*
389 :remote-addr remote-addr
390 :remote-port remote-port
391 :headers-in headers-in
392 :content-stream *hunchentoot-stream*
393 :method method
394 :uri url-string
395 :server-protocol protocol)))))
396 (finish-output *hunchentoot-stream*)
397 (setq *hunchentoot-stream* (reset-connection-stream *acceptor* *hunchentoot-stream*))
398 (when *close-hunchentoot-stream*
399 (return)))))
400 (when *hunchentoot-stream*
401 ;; as we are at the end of the request here, we ignore all
402 ;; errors that may occur while flushing and/or closing the
403 ;; stream.
404 (ignore-errors*
405 (finish-output *hunchentoot-stream*))
406 (ignore-errors*
407 (close *hunchentoot-stream* :abort t))))))
409 (defmethod acceptor-ssl-p ((acceptor t))
410 ;; the default is to always answer "no"
411 nil)
413 (defgeneric acceptor-log-access (acceptor &key return-code)
414 (:documentation
415 "Function to call to log access to the acceptor. The RETURN-CODE,
416 CONTENT and CONTENT-LENGTH keyword arguments contain additional
417 information about the request to log. In addition, it can use the
418 standard request accessor functions that are available to handler
419 functions to find out more information about the request."))
421 (defmethod acceptor-log-access ((acceptor acceptor) &key return-code)
422 "Default method for access logging. It logs the information to the
423 destination determined by (ACCEPTOR-ACCESS-LOG-DESTINATION ACCEPTOR)
424 \(unless that value is NIL) in a format that can be parsed by most
425 Apache log analysis tools.)"
427 (with-log-stream (stream (acceptor-access-log-destination acceptor) *access-log-lock*)
428 (format stream "~:[-~@[ (~A)~]~;~:*~A~@[ (~A)~]~] ~:[-~;~:*~A~] [~A] \"~A ~A~@[?~A~] ~
429 ~A\" ~D ~:[-~;~:*~D~] \"~:[-~;~:*~A~]\" \"~:[-~;~:*~A~]\"~%"
430 (remote-addr*)
431 (header-in* :x-forwarded-for)
432 (authorization)
433 (iso-time)
434 (request-method*)
435 (script-name*)
436 (query-string*)
437 (server-protocol*)
438 return-code
439 (content-length*)
440 (referer)
441 (user-agent))))
443 (defgeneric acceptor-log-message (acceptor log-level format-string &rest format-arguments)
444 (:documentation
445 "Function to call to log messages by the ACCEPTOR. It must accept
446 a severity level for the message, which will be one of :ERROR, :INFO,
447 or :WARNING, a format string and an arbitary number of formatting
448 arguments."))
450 (defmethod acceptor-log-message ((acceptor acceptor) log-level format-string &rest format-arguments)
451 "Default function to log server messages. Sends a formatted message
452 to the destination denoted by (ACCEPTOR-MESSAGE-LOG-DESTINATION
453 ACCEPTOR). FORMAT and ARGS are as in FORMAT. LOG-LEVEL is a
454 keyword denoting the log level or NIL in which case it is ignored."
455 (with-log-stream (stream (acceptor-message-log-destination acceptor) *message-log-lock*)
456 (format stream "[~A~@[ [~A]~]] ~?~%"
457 (iso-time) log-level
458 format-string format-arguments)))
460 (defun log-message* (log-level format-string &rest format-arguments)
461 "Convenience function which calls the message logger of the current
462 acceptor \(if there is one) with the same arguments it accepts.
464 This is the function which Hunchentoot itself uses to log errors it
465 catches during request processing."
466 (apply 'acceptor-log-message *acceptor* log-level format-string format-arguments))
468 ;; usocket implementation
470 #-:lispworks
471 (defmethod start-listening ((acceptor acceptor))
472 (when (acceptor-listen-socket acceptor)
473 (hunchentoot-error "acceptor ~A is already listening" acceptor))
474 (setf (acceptor-listen-socket acceptor)
475 (usocket:socket-listen (or (acceptor-address acceptor)
476 usocket:*wildcard-host*)
477 (acceptor-port acceptor)
478 :reuseaddress t
479 :backlog (acceptor-listen-backlog acceptor)
480 :element-type '(unsigned-byte 8)))
481 (values))
483 #-:lispworks
484 (defmethod accept-connections ((acceptor acceptor))
485 (usocket:with-server-socket (listener (acceptor-listen-socket acceptor))
486 (loop
487 (when (acceptor-shutdown-p acceptor)
488 (return))
489 (when (usocket:wait-for-input listener :ready-only t :timeout +new-connection-wait-time+)
490 (when-let (client-connection
491 (handler-case (usocket:socket-accept listener)
492 ;; ignore condition
493 (usocket:connection-aborted-error ())))
494 (set-timeouts client-connection
495 (acceptor-read-timeout acceptor)
496 (acceptor-write-timeout acceptor))
497 (handle-incoming-connection (acceptor-taskmaster acceptor)
498 client-connection))))))
500 ;; LispWorks implementation
502 #+:lispworks
503 (defmethod start-listening ((acceptor acceptor))
504 (multiple-value-bind (listener-process startup-condition)
505 (comm:start-up-server :service (acceptor-port acceptor)
506 :address (acceptor-address acceptor)
507 :process-name (format nil "Hunchentoot listener \(~A:~A)"
508 (or (acceptor-address acceptor) "*")
509 (acceptor-port acceptor))
510 ;; this function is called once on startup - we
511 ;; use it to check for errors
512 :announce (lambda (socket &optional condition)
513 (declare (ignore socket))
514 (when condition
515 (error condition)))
516 ;; this function is called whenever a connection
517 ;; is made
518 :function (lambda (handle)
519 (unless (acceptor-shutdown-p acceptor)
520 (handle-incoming-connection
521 (acceptor-taskmaster acceptor) handle)))
522 ;; wait until the acceptor was successfully started
523 ;; or an error condition is returned
524 :wait t)
525 (when startup-condition
526 (error startup-condition))
527 (mp:process-stop listener-process)
528 (setf (acceptor-process acceptor) listener-process)
529 (values)))
531 #+:lispworks
532 (defmethod accept-connections ((acceptor acceptor))
533 (mp:process-unstop (acceptor-process acceptor))
534 nil)
536 (defmethod acceptor-dispatch-request ((acceptor acceptor) request)
537 "Detault implementation of the request dispatch method, generates a +http-not-found+ error+."
538 (declare (ignore request))
539 (if (acceptor-document-root acceptor)
540 (handle-static-file (merge-pathnames (if (equal (script-name*) "/")
541 "index.html"
542 (subseq (script-name*) 1))
543 (acceptor-document-root acceptor)))
544 (setf (return-code *reply*) +http-not-found+)))
546 (defmethod handle-request ((*acceptor* acceptor) (*request* request))
547 "Standard method for request handling. Calls the request dispatcher
548 of *ACCEPTOR* to determine how the request should be handled. Also
549 sets up standard error handling which catches any errors within the
550 handler."
551 (handler-bind ((error
552 (lambda (cond)
553 ;; if the headers were already sent, the error
554 ;; happened within the body and we have to close
555 ;; the stream
556 (when *headers-sent*
557 (setq *close-hunchentoot-stream* t))
558 (throw 'handler-done
559 (values nil cond (get-backtrace)))))
560 (warning
561 (lambda (cond)
562 (when *log-lisp-warnings-p*
563 (log-message* *lisp-warnings-log-level* "~A" cond)))))
564 (with-debugger
565 (acceptor-dispatch-request *acceptor* *request*))))
567 (defgeneric acceptor-status-message (acceptor http-status-code &key &allow-other-keys)
568 (:documentation
569 "This function is called after the request's handler has been
570 invoked to convert the HTTP-STATUS-CODE to a HTML message to be
571 displayed to the user. If this function returns a string, that
572 string is sent to the client instead of the content produced by the
573 handler, if any.
575 If an ERROR-TEMPLATE-DIRECTORY is set in the current acceptor and
576 the directory contains a file corresponding to HTTP-STATUS-CODE
577 named <code>.html, that file is sent to the client after variable
578 substitution. Variables are referenced by ${<variable-name>}.
580 Additional keyword arguments may be provided which are made
581 available to the templating logic as substitution variables. These
582 variables can be interpolated into error message templates in,
583 which contains the current URL relative to the server and without
584 GET parameters.
586 In addition to the variables corresponding to keyword arguments,
587 the script-name, lisp-implementation-type,
588 lisp-implementation-version and hunchentoot-version variables are
589 available."))
591 (defun make-cooked-message (http-status-code &key error backtrace)
592 (labels ((cooked-message (format &rest arguments)
593 (setf (content-type*) "text/html; charset=iso-8859-1")
594 (format nil "<html><head><title>~D ~A</title></head><body><h1>~:*~A</h1>~?<p><hr>~A</p></body></html>"
595 http-status-code (reason-phrase http-status-code)
596 format (mapcar (lambda (arg)
597 (if (stringp arg)
598 (escape-for-html arg)
599 arg))
600 arguments)
601 (address-string))))
602 (case http-status-code
603 ((#.+http-moved-temporarily+
604 #.+http-moved-permanently+)
605 (cooked-message "The document has moved <a href='~A'>here</a>" (header-out :location)))
606 ((#.+http-authorization-required+)
607 (cooked-message "The server could not verify that you are authorized to access the document requested. ~
608 Either you supplied the wrong credentials \(e.g., bad password), or your browser doesn't ~
609 understand how to supply the credentials required."))
610 ((#.+http-forbidden+)
611 (cooked-message "You don't have permission to access ~A on this server."
612 (script-name *request*)))
613 ((#.+http-not-found+)
614 (cooked-message "The requested URL ~A was not found on this server."
615 (script-name *request*)))
616 ((#.+http-bad-request+)
617 (cooked-message "Your browser sent a request that this server could not understand."))
618 ((#.+http-internal-server-error+)
619 (if *show-lisp-errors-p*
620 (cooked-message "<pre>~A~@[~%~%Backtrace:~%~%~A~]</pre>"
621 (escape-for-html (princ-to-string error))
622 (when *show-lisp-backtraces-p*
623 (escape-for-html (princ-to-string backtrace))))
624 (cooked-message "An error has occurred")))
626 (when (<= 400 http-status-code)
627 (cooked-message "An error has occurred"))))))
629 (defmethod acceptor-status-message ((acceptor t) http-status-code &rest args &key &allow-other-keys)
630 (apply 'make-cooked-message http-status-code args))
632 (defmethod acceptor-status-message :around ((acceptor acceptor) http-status-code &rest args &key &allow-other-keys)
633 (handler-case
634 (call-next-method)
635 (error (e)
636 (log-message* :error "error ~A during error processing, sending cooked message to client" e)
637 (apply 'make-cooked-message http-status-code args))))
639 (defun string-as-keyword (string)
640 "Intern STRING as keyword using the reader so that case conversion is done with the reader defaults."
641 (let ((*package* (find-package :keyword)))
642 (read-from-string string)))
644 (defmethod acceptor-status-message ((acceptor acceptor) http-status-code &rest properties &key &allow-other-keys)
645 "Default function to generate error message sent to the client."
646 (labels
647 ((substitute-request-context-variables (string)
648 (let ((properties (append `(:script-name ,(script-name*)
649 :lisp-implementation-type ,(lisp-implementation-type)
650 :lisp-implementation-version ,(lisp-implementation-version)
651 :hunchentoot-version ,*hunchentoot-version*)
652 properties)))
653 (cl-ppcre:regex-replace-all "(?i)\\$\\{([a-z0-9-_]+)\\}"
654 string
655 (lambda (target-string start end match-start match-end reg-starts reg-ends)
656 (declare (ignore start end match-start match-end))
657 (let ((variable-name (string-as-keyword (subseq target-string
658 (aref reg-starts 0)
659 (aref reg-ends 0)))))
660 (escape-for-html (princ-to-string (getf properties variable-name variable-name))))))))
661 (file-contents (file)
662 (let ((buf (make-string (file-length file))))
663 (read-sequence buf file)
664 buf))
665 (error-contents-from-template ()
666 (let ((error-file-template-pathname (and (acceptor-error-template-directory acceptor)
667 (probe-file (make-pathname :name (princ-to-string http-status-code)
668 :type "html"
669 :defaults (acceptor-error-template-directory acceptor))))))
670 (when error-file-template-pathname
671 (with-open-file (file error-file-template-pathname :if-does-not-exist nil :element-type 'character)
672 (when file
673 (setf (content-type*) "text/html")
674 (substitute-request-context-variables (file-contents file))))))))
675 (or (unless (< 300 http-status-code)
676 (call-next-method)) ; don't ever try template for positive return codes
677 (error-contents-from-template) ; try template
678 (call-next-method)))) ; fall back to cooked message
680 (defgeneric acceptor-remove-session (acceptor session)
681 (:documentation
682 "This function is called whenever a session in ACCEPTOR is being
683 destroyed because of a session timout or an explicit REMOVE-SESSION
684 call."))
686 (defmethod acceptor-remove-session ((acceptor acceptor) (session t))
687 "Default implementation for the session removal hook function. This
688 function is called whenever a session is destroyed."
691 (defgeneric acceptor-server-name (acceptor)
692 (:documentation "Returns a string which can be used for 'Server' headers.")
693 (:method ((acceptor acceptor))
694 (format nil "Hunchentoot ~A" *hunchentoot-version*)))