Put handler-case for usocket:connection-aborted-error around the right
[hunchentoot.git] / headers.lisp
bloba94fd184df348c66de2ef35864cf7ce17c097e14
1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: HUNCHENTOOT; Base: 10 -*-
2 ;;; $Header: /usr/local/cvsrep/hunchentoot/headers.lisp,v 1.29 2008/03/27 08:08:31 edi Exp $
4 ;;; Copyright (c) 2004-2009, Dr. Edmund Weitz. All rights reserved.
6 ;;; Redistribution and use in source and binary forms, with or without
7 ;;; modification, are permitted provided that the following conditions
8 ;;; are met:
10 ;;; * Redistributions of source code must retain the above copyright
11 ;;; notice, this list of conditions and the following disclaimer.
13 ;;; * Redistributions in binary form must reproduce the above
14 ;;; copyright notice, this list of conditions and the following
15 ;;; disclaimer in the documentation and/or other materials
16 ;;; provided with the distribution.
18 ;;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESSED
19 ;;; OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ;;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 ;;; ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22 ;;; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 ;;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24 ;;; GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 ;;; INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26 ;;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 ;;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 ;;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 (in-package :hunchentoot)
32 (defun maybe-write-to-header-stream (key &optional value)
33 "Accepts a string KEY and an optional Lisp object VALUE and writes
34 them directly to the character stream *HEADER-STREAM* as an HTTP
35 header line \(or as a simple line if VALUE is NIL)."
36 (when *header-stream*
37 (format *header-stream* "~A~@[: ~A~]~%" key
38 (and value (regex-replace-all "[\\r\\n]" value " ")))
39 (force-output *header-stream*)))
41 (defgeneric write-header-line (key value)
42 (:documentation "Accepts a string KEY and a Lisp object VALUE and
43 writes them directly to the client as an HTTP header line.")
44 (:method (key (string string))
45 (let ((stream *hunchentoot-stream*))
46 (labels ((write-header-char (char)
47 (when *header-stream*
48 (write-char char *header-stream*))
49 (write-byte (char-code char) stream))
50 (write-header-string (string &key (start 0) (end (length string)))
51 (loop for i from start below end
52 do (write-header-char (aref string i)))))
53 (write-header-string key)
54 (write-header-char #\:)
55 (write-header-char #\Space)
56 (let ((start 0))
57 (loop
58 (let ((end (or (position #\Newline string :start start)
59 (length string))))
60 ;; skip empty lines, as they confuse certain HTTP clients
61 (unless (eql start end)
62 (unless (zerop start)
63 (write-header-char #\Tab))
64 (write-header-string string :start start :end end)
65 (write-header-char #\Return)
66 (write-header-char #\Linefeed))
67 (setf start (1+ end))
68 (when (<= (length string) start)
69 (return))))))))
70 (:method (key value)
71 (write-header-line key (princ-to-string value))))
73 (defun start-output (&key (content nil content-provided-p)
74 (request *request*))
75 "Sends all headers and maybe the content body to
76 *HUNCHENTOOT-STREAM*. Returns immediately and does nothing if called
77 more than once per request. Handles the supported return codes
78 accordingly. Called by PROCESS-REQUEST and/or SEND-HEADERS. Returns
79 the stream to write to."
80 ;; send headers only once
81 (when *headers-sent*
82 (return-from start-output))
83 (setq *headers-sent* t)
84 ;; Read post data to clear stream - Force binary mode to avoid OCTETS-TO-STRING overhead.
85 (raw-post-data :force-binary t)
86 (let* ((return-code (return-code*))
87 (chunkedp (and (acceptor-output-chunking-p *acceptor*)
88 (eq (server-protocol request) :http/1.1)
89 ;; only turn chunking on if the content
90 ;; length is unknown at this point...
91 (null (or (content-length*) content-provided-p))
92 ;; ...AND if the return code isn't one where
93 ;; Hunchentoot (or a user error handler) sends its
94 ;; own content
95 (member return-code *approved-return-codes*)))
96 (reason-phrase (reason-phrase return-code))
97 (request-method (request-method request))
98 (head-request-p (eq request-method :head))
99 content-modified-p)
100 (multiple-value-bind (keep-alive-p keep-alive-requested-p)
101 (keep-alive-p request)
102 (when keep-alive-p
103 (setq keep-alive-p
104 ;; use keep-alive if there's a way for the client to
105 ;; determine when all content is sent (or if there
106 ;; is no content)
107 (or chunkedp
108 head-request-p
109 (eql (return-code*) +http-not-modified+)
110 (content-length*)
111 content)))
112 ;; now set headers for keep-alive and chunking
113 (when chunkedp
114 (setf (header-out :transfer-encoding) "chunked"))
115 (cond (keep-alive-p
116 (setf *close-hunchentoot-stream* nil)
117 (when (and (acceptor-read-timeout *acceptor*)
118 (or (not (eq (server-protocol request) :http/1.1))
119 keep-alive-requested-p))
120 ;; persistent connections are implicitly assumed for
121 ;; HTTP/1.1, but we return a 'Keep-Alive' header if the
122 ;; client has explicitly asked for one
123 (setf (header-out :connection) "Keep-Alive"
124 (header-out :keep-alive)
125 (format nil "timeout=~D" (acceptor-read-timeout *acceptor*)))))
126 (t (setf (header-out :connection) "Close"))))
127 (unless (and (header-out-set-p :server)
128 (null (header-out :server)))
129 (setf (header-out :server) (or (header-out :server)
130 (server-name-header))))
131 (setf (header-out :date) (rfc-1123-date))
132 (unless reason-phrase
133 (setq content (escape-for-html
134 (format nil "Unknown http return code: ~A" return-code))
135 content-modified-p t
136 return-code +http-internal-server-error+
137 reason-phrase (reason-phrase return-code)))
138 (unless (or (not *handle-http-errors-p*)
139 (member return-code *approved-return-codes*))
140 ;; call error handler, if any - should return NIL if it can't
141 ;; handle the error
142 (let (error-handled-p)
143 (when *http-error-handler*
144 (setq error-handled-p (funcall *http-error-handler* return-code)
145 content (or error-handled-p content)
146 content-modified-p (or content-modified-p error-handled-p)))
147 ;; handle common return codes other than 200, which weren't
148 ;; handled by the error handler
149 (unless error-handled-p
150 (setf (content-type*)
151 "text/html; charset=iso-8859-1"
152 content-modified-p t
153 content
154 (format nil "<html><head><title>~D ~A</title></head><body><h1>~:*~A</h1>~A<p><hr>~A</p></body></html>"
155 return-code reason-phrase
156 (case return-code
157 ((#.+http-internal-server-error+) content)
158 ((#.+http-moved-temporarily+ #.+http-moved-permanently+)
159 (format nil "The document has moved <a href='~A'>here</a>"
160 (header-out :location)))
161 ((#.+http-authorization-required+)
162 "The server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials \(e.g., bad password), or your browser doesn't understand how to supply the credentials required.")
163 ((#.+http-forbidden+)
164 (format nil "You don't have permission to access ~A on this server."
165 (script-name request)))
166 ((#.+http-not-found+)
167 (format nil "The requested URL ~A was not found on this server."
168 (script-name request)))
169 ((#.+http-bad-request+)
170 "Your browser sent a request that this server could not understand.")
171 (otherwise ""))
172 (address-string))))))
173 ;; start with status line
174 (let ((first-line
175 (format nil "HTTP/1.1 ~D ~A" return-code reason-phrase)))
176 (write-sequence (map 'list #'char-code first-line) *hunchentoot-stream*)
177 (write-sequence +crlf+ *hunchentoot-stream*)
178 (maybe-write-to-header-stream first-line))
179 (when (and (stringp content)
180 (not content-modified-p)
181 (starts-with-one-of-p (or (content-type*) "")
182 *content-types-for-url-rewrite*))
183 ;; if the Content-Type header starts with one of the strings
184 ;; in *CONTENT-TYPES-FOR-URL-REWRITE* then maybe rewrite the
185 ;; content
186 (setq content (maybe-rewrite-urls-for-session content)))
187 (when (stringp content)
188 ;; if the content is a string, convert it to the proper external format
189 (setf content (string-to-octets content :external-format (reply-external-format*))))
190 (when content
191 ;; whenever we know what we're going to send out as content, set
192 ;; the Content-Length header properly; maybe the user specified
193 ;; a different content length, but that will wrong anyway
194 (setf (header-out :content-length) (length content)))
195 ;; write all headers from the REPLY object
196 (loop for (key . value) in (headers-out*)
197 when value
198 do (write-header-line (as-capitalized-string key) value))
199 ;; now the cookies
200 (loop for (nil . cookie) in (cookies-out*)
201 do (write-header-line "Set-Cookie" (stringify-cookie cookie)))
202 ;; all headers sent
203 (write-sequence +crlf+ *hunchentoot-stream*)
204 (maybe-write-to-header-stream "")
205 ;; access log message
206 (when-let (access-logger (acceptor-access-logger *acceptor*))
207 (funcall access-logger
208 :return-code return-code
209 :content content
210 :content-length (content-length*)))
211 ;; now optional content
212 (unless (or (null content) head-request-p)
213 (write-sequence content *hunchentoot-stream*))
214 (when chunkedp
215 ;; turn chunking on after the headers have been sent
216 (unless (typep *hunchentoot-stream* 'chunked-stream)
217 (setq *hunchentoot-stream* (make-chunked-stream *hunchentoot-stream*)))
218 (setf (chunked-stream-output-chunking-p *hunchentoot-stream*) t))
219 *hunchentoot-stream*))
221 (defun send-headers ()
222 "Sends the initial status line and all headers as determined by the
223 REPLY object *REPLY*. Returns a binary stream to which the body of
224 the reply can be written. Once this function has been called, further
225 changes to *REPLY* don't have any effect. Also, automatic handling of
226 errors \(i.e. sending the corresponding status code to the browser,
227 etc.) is turned off for this request. If your handlers return the
228 full body as a string or as an array of octets you should NOT call
229 this function."
230 (start-output))
232 (defun read-initial-request-line (stream)
233 "Reads and returns the initial HTTP request line, catching permitted errors
234 and handling *BREAK-EVEN-WHILE-READING-REQUEST-TYPE-P*. If no request could
235 be read, returns NIL. At this point, both an end-of-file as well as a
236 timeout condition are normal. end-of-file will occur when the client has
237 decided to not send another request but close the connection. A timeout
238 indicates that the connection timeout established by Hunchentoot has expired
239 and we do not want to wait for another request any longer."
240 (let ((*break-on-signals* (and *break-even-while-reading-request-type-p*
241 *break-on-signals*)))
242 (handler-case
243 (let ((*current-error-message* "While reading initial request line:"))
244 (with-mapped-conditions ()
245 (read-line* stream)))
246 ((or end-of-file
247 #-:lispworks usocket:timeout-error) ()
248 nil))))
250 (defun get-request-data (stream)
251 "Reads incoming headers from the client via STREAM. Returns as
252 multiple values the headers as an alist, the method, the URI, and the
253 protocol of the request."
254 (with-character-stream-semantics
255 (let ((first-line (read-initial-request-line stream)))
256 (when first-line
257 (destructuring-bind (method url-string &optional protocol)
258 (split "\\s+" first-line :limit 3)
259 (maybe-write-to-header-stream first-line)
260 (let ((headers (and protocol (read-http-headers stream *header-stream*))))
261 (unless protocol (setq protocol "HTTP/0.9"))
262 (when (equalp (cdr (assoc :expect headers :test #'eq)) "100-continue")
263 ;; handle 'Expect: 100-continue' header
264 (let ((continue-line
265 (format nil "HTTP/1.1 ~D ~A"
266 +http-continue+
267 (reason-phrase +http-continue+))))
268 (write-sequence (map 'list #'char-code continue-line) stream)
269 (write-sequence +crlf+ stream)
270 (write-sequence +crlf+ stream)
271 (force-output stream)
272 (maybe-write-to-header-stream continue-line)
273 (maybe-write-to-header-stream "")))
274 (values headers
275 (as-keyword method)
276 url-string
277 (as-keyword (trim-whitespace protocol)))))))))