Put handler-case for usocket:connection-aborted-error around the right
[hunchentoot.git] / test / test-handlers.lisp
blob8d81a6f61a71efea748e75fb165fbaa256f95aea
1 ;;; -*- Mode: LISP; Syntax: COMMON-LISP; Package: HUNCHENTOOT; Base: 10 -*-
2 ;;; $Header: /usr/local/cvsrep/hunchentoot/test/test.lisp,v 1.24 2008/03/06 07:46:53 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-test)
32 (defvar *this-file* (load-time-value
33 (or #.*compile-file-pathname* *load-pathname*)))
35 (defmacro with-html (&body body)
36 `(with-html-output-to-string (*standard-output* nil :prologue t)
37 ,@body))
39 (defun hunchentoot-link ()
40 (with-html-output (*standard-output*)
41 (:a :href "http://weitz.de/hunchentoot/" "Hunchentoot")))
43 (defun menu-link ()
44 (with-html-output (*standard-output*)
45 (:p (:hr
46 (:a :href "/hunchentoot/test" "Back to menu")))))
48 (defmacro with-lisp-output ((var) &body body)
49 `(let ((*package* (find-package :hunchentoot-test-user)))
50 (with-output-to-string (,var #+:lispworks nil
51 #+:lispworks :element-type
52 #+:lispworks 'lw:simple-char)
53 ,@body)))
55 (defmacro info-table (&rest forms)
56 (let ((=value= (gensym))
57 (=first= (gensym)))
58 `(with-html-output (*standard-output*)
59 (:p (:table :border 1 :cellpadding 2 :cellspacing 0
60 (:tr (:td :colspan 2
61 "Some Information "
62 (hunchentoot-link)
63 " provides about this request:"))
64 ,@(loop for form in forms
65 collect `(:tr (:td :valign "top"
66 (:pre :style "padding: 0px"
67 (esc (with-lisp-output (s) (pprint ',form s)))))
68 (:td :valign "top"
69 (:pre :style "padding: 0px"
70 (esc (with-lisp-output (s)
71 (loop for ,=value= in (multiple-value-list ,form)
72 for ,=first= = t then nil
73 unless ,=first=
74 do (princ ", " s)
75 do (pprint ,=value= s))))))))))
76 (menu-link))))
78 (defun authorization-page ()
79 (multiple-value-bind (user password)
80 (authorization)
81 (cond ((and (equal user "nanook")
82 (equal password "igloo"))
83 (with-html
84 (:html
85 (:head (:title "Hunchentoot page with Basic Authentication"))
86 (:body
87 (:h2 (hunchentoot-link)
88 " page with Basic Authentication")
89 (info-table (header-in* :authorization)
90 (authorization))))))
92 (require-authorization)))))
94 (defparameter *test-image*
95 (load-time-value
96 (with-open-file (in (make-pathname :name "fz" :type "jpg" :version nil
97 :defaults *this-file*)
98 :element-type 'flex:octet)
99 (let ((image-data (make-array (file-length in)
100 :element-type 'flex:octet)))
101 (read-sequence image-data in)
102 image-data))))
104 (defun image-ram-page ()
105 (setf (content-type*) "image/jpeg")
106 *test-image*)
108 (let ((count 0))
109 (defun info ()
110 (with-html
111 (:html
112 (:head (:title "Hunchentoot Information"))
113 (:body
114 (:h2 (hunchentoot-link) " Information Page")
115 (:p "This page has been called "
117 (fmt "~[~;once~;twice~:;~:*~R times~]" (incf count)))
118 " since its handler was compiled.")
119 (info-table (host)
120 (acceptor-address *acceptor*)
121 (acceptor-port *acceptor*)
122 (remote-addr*)
123 (remote-port*)
124 (real-remote-addr)
125 (request-method*)
126 (script-name*)
127 (query-string*)
128 (get-parameters*)
129 (headers-in*)
130 (cookies-in*)
131 (user-agent)
132 (referer)
133 (request-uri*)
134 (server-protocol*)))))))
136 (defun oops ()
137 (with-html
138 (log-message :error "Oops \(error log level).")
139 (log-message :warning "Oops \(warning log level).")
140 (log-message :info "Oops \(info log level).")
141 (error "Errors were triggered on purpose. Check your error log.")
142 (:html
143 (:body "You should never see this sentence..."))))
145 (defun redir ()
146 (redirect "/hunchentoot/test/info.html?redirected=1"))
148 (defun forbidden ()
149 (setf (return-code*) +http-forbidden+)
150 nil)
152 (defun cookie-test ()
153 (set-cookie "pumpkin" :value "barking")
154 (no-cache)
155 (with-html
156 (:html
157 (:head (:title "Hunchentoot cookie test"))
158 (:body
159 (:h2 (hunchentoot-link)
160 " cookie test")
161 (:p "You might have to reload this page to see the cookie value.")
162 (info-table (cookie-in "pumpkin")
163 (mapcar 'car (cookies-in*)))))))
165 (defun session-test ()
166 (let ((new-foo-value (post-parameter "new-foo-value")))
167 (when new-foo-value
168 (setf (session-value 'foo) new-foo-value)))
169 (let ((new-bar-value (post-parameter "new-bar-value")))
170 (when new-bar-value
171 (setf (session-value 'bar) new-bar-value)))
172 (no-cache)
173 (with-html
174 (:html
175 (:head (:title "Hunchentoot session test"))
176 (:body
177 (:h2 (hunchentoot-link)
178 " session test")
179 (:p "Use the forms below to set new values for "
180 (:code "FOO")
181 " or "
182 (:code "BAR")
183 ". You can later return to this page to check if
184 they're still set. Also, try to use another browser at the same
185 time or try with cookies disabled.")
186 (:p (:form :method :post
187 "New value for "
188 (:code "FOO")
189 ": "
190 (:input :type :text
191 :name "new-foo-value"
192 :value (or (session-value 'foo) ""))))
193 (:p (:form :method :post
194 "New value for "
195 (:code "BAR")
196 ": "
197 (:input :type :text
198 :name "new-bar-value"
199 :value (or (session-value 'bar) ""))))
200 (info-table (session-cookie-name *acceptor*)
201 (cookie-in (session-cookie-name *acceptor*))
202 (mapcar 'car (cookies-in*))
203 (session-value 'foo)
204 (session-value 'bar))))))
206 (defun parameter-test (&key (method :get) (charset :iso-8859-1))
207 (no-cache)
208 (recompute-request-parameters :external-format
209 (flex:make-external-format charset :eol-style :lf))
210 (setf (content-type*)
211 (format nil "text/html; charset=~A" charset))
212 (with-html
213 (:html
214 (:head (:title (fmt "Hunchentoot ~A parameter test" method)))
215 (:body
216 (:h2 (hunchentoot-link)
217 (fmt " ~A parameter test with charset ~A" method charset))
218 (:p "Enter some non-ASCII characters in the input field below
219 and see what's happening.")
220 (:p (:form
221 :method method
222 "Enter a value: "
223 (:input :type :text
224 :name "foo")))
225 (case method
226 (:get (info-table (query-string*)
227 (map 'list 'char-code (get-parameter "foo"))
228 (get-parameter "foo")))
229 (:post (info-table (raw-post-data)
230 (map 'list 'char-code (post-parameter "foo"))
231 (post-parameter "foo"))))))))
233 (defun parameter-test-latin1-get ()
234 (parameter-test :method :get :charset :iso-8859-1))
236 (defun parameter-test-latin1-post ()
237 (parameter-test :method :post :charset :iso-8859-1))
239 (defun parameter-test-utf8-get ()
240 (parameter-test :method :get :charset :utf-8))
242 (defun parameter-test-utf8-post ()
243 (parameter-test :method :post :charset :utf-8))
245 ;; this should not be the same directory as *TMP-DIRECTORY* and it
246 ;; should be initially empty (or non-existent)
247 (defvar *tmp-test-directory*
248 #+(or :win32 :mswindows) #p"c:\\hunchentoot-temp\\test\\"
249 #-(or :win32 :mswindows) #p"/tmp/hunchentoot/test/")
251 (defvar *tmp-test-files* nil)
253 (let ((counter 0))
254 (defun handle-file (post-parameter)
255 (when (and post-parameter
256 (listp post-parameter))
257 (destructuring-bind (path file-name content-type)
258 post-parameter
259 (let ((new-path (make-pathname :name (format nil "hunchentoot-test-~A"
260 (incf counter))
261 :type nil
262 :defaults *tmp-test-directory*)))
263 ;; strip directory info sent by Windows browsers
264 (when (search "Windows" (user-agent) :test 'char-equal)
265 (setq file-name (cl-ppcre:regex-replace ".*\\\\" file-name "")))
266 (rename-file path (ensure-directories-exist new-path))
267 (push (list new-path file-name content-type) *tmp-test-files*))))))
269 (defun clean-tmp-dir ()
270 (loop for (path . nil) in *tmp-test-files*
271 when (probe-file path)
272 do (ignore-errors (delete-file path)))
273 (setq *tmp-test-files* nil))
275 (defun upload-test ()
276 (let (post-parameter-p)
277 (when (post-parameter "file1")
278 (handle-file (post-parameter "file1"))
279 (setq post-parameter-p t))
280 (when (post-parameter "file2")
281 (handle-file (post-parameter "file2"))
282 (setq post-parameter-p t))
283 (when (post-parameter "clean")
284 (clean-tmp-dir)
285 (setq post-parameter-p t))
286 (when post-parameter-p
287 ;; redirect so user can safely use 'Back' button
288 (redirect (script-name*))))
289 (no-cache)
290 (with-html
291 (:html
292 (:head (:title "Hunchentoot file upload test"))
293 (:body
294 (:h2 (hunchentoot-link)
295 " file upload test")
296 (:form :method :post :enctype "multipart/form-data"
297 (:p "First file: "
298 (:input :type :file
299 :name "file1"))
300 (:p "Second file: "
301 (:input :type :file
302 :name "file2"))
303 (:p (:input :type :submit)))
304 (when *tmp-test-files*
305 (htm
307 (:table :border 1 :cellpadding 2 :cellspacing 0
308 (:tr (:td :colspan 3 (:b "Uploaded files")))
309 (loop for (path file-name nil) in *tmp-test-files*
310 for counter from 1
311 do (htm
312 (:tr (:td :align "right" (str counter))
313 (:td (:a :href (format nil "files/~A?path=~A"
314 (url-encode file-name)
315 (url-encode (namestring path)))
316 (esc file-name)))
317 (:td :align "right"
318 (str (ignore-errors
319 (with-open-file (in path)
320 (file-length in))))
321 " Bytes"))))))
322 (:form :method :post
323 (:p (:input :type :submit :name "clean" :value "Delete uploaded files")))))
324 (menu-link)))))
326 (defun send-file ()
327 (let* ((path (get-parameter "path"))
328 (file-info (and path
329 (find (pathname path) *tmp-test-files*
330 :key 'first :test 'equal))))
331 (unless file-info
332 (setf (return-code*) +http-not-found+)
333 (return-from send-file))
334 (handle-static-file path (third file-info))))
336 (defparameter *headline*
337 (load-time-value
338 (format nil "Hunchentoot test menu (see file <code>~A</code>)"
339 (truename (merge-pathnames (make-pathname :type "lisp") *this-file*)))))
341 (defvar *utf-8* (flex:make-external-format :utf-8 :eol-style :lf))
343 (defvar *utf-8-file* (merge-pathnames "UTF-8-demo.html" *this-file*)
344 "Demo file stolen from <http://www.w3.org/2001/06/utf-8-test/>.")
346 (defun stream-direct ()
347 (setf (content-type*) "text/html; charset=utf-8")
348 (let ((stream (send-headers))
349 (buffer (make-array 1024 :element-type 'flex:octet)))
350 (with-open-file (in *utf-8-file* :element-type 'flex:octet)
351 (loop for pos = (read-sequence buffer in)
352 until (zerop pos)
353 do (write-sequence buffer stream :end pos)))))
355 (defun stream-direct-utf-8 ()
356 (setf (content-type*) "text/html; charset=utf-8")
357 (let ((stream (flex:make-flexi-stream (send-headers) :external-format *utf-8*)))
358 (with-open-file (in (merge-pathnames "UTF-8-demo.html" *this-file*)
359 :element-type 'flex:octet)
360 (setq in (flex:make-flexi-stream in :external-format *utf-8*))
361 (loop for line = (read-line in nil nil)
362 while line
363 do (write-line line stream)))))
365 (defun stream-direct-utf-8-string ()
366 (setf (content-type*) "text/html; charset=utf-8"
367 (reply-external-format*) *utf-8*)
368 (with-open-file (in (merge-pathnames "UTF-8-demo.html" *this-file*)
369 :element-type 'flex:octet)
370 (let ((string (make-array (file-length in)
371 :element-type #-:lispworks 'character #+:lispworks 'lw:simple-char
372 :fill-pointer t)))
373 (setf in (flex:make-flexi-stream in :external-format *utf-8*)
374 (fill-pointer string) (read-sequence string in))
375 string)))
377 (define-easy-handler (easy-demo :uri "/hunchentoot/test/easy-demo.html"
378 :default-request-type :post)
379 (first-name last-name
380 (age :parameter-type 'integer)
381 (implementation :parameter-type 'keyword)
382 (meal :parameter-type '(hash-table boolean))
383 (team :parameter-type 'list))
384 (with-html
385 (:html
386 (:head (:title "Hunchentoot \"easy\" handler example"))
387 (:body
388 (:h2 (hunchentoot-link)
389 " \"Easy\" handler example")
390 (:p (:form :method :post
391 (:table :border 1 :cellpadding 2 :cellspacing 0
392 (:tr
393 (:td "First Name:")
394 (:td (:input :type :text
395 :name "first-name"
396 :value (or first-name "Donald"))))
397 (:tr
398 (:td "Last name:")
399 (:td (:input :type :text
400 :name "last-name"
401 :value (or last-name "Duck"))))
402 (:tr
403 (:td "Age:")
404 (:td (:input :type :text
405 :name "age"
406 :value (or age 42))))
407 (:tr
408 (:td "Implementation:")
409 (:td (:select :name "implementation"
410 (loop for (value option) in '((:lispworks "LispWorks")
411 (:allegro "AllegroCL")
412 (:cmu "CMUCL")
413 (:sbcl "SBCL")
414 (:openmcl "OpenMCL"))
415 do (htm
416 (:option :value value
417 :selected (eq value implementation)
418 (str option)))))))
419 (:tr
420 (:td :valign :top "Meal:")
421 (:td (loop for choice in '("Burnt weeny sandwich"
422 "Canard du jour"
423 "Easy meat"
424 "Muffin"
425 "Twenty small cigars"
426 "Yellow snow")
427 do (htm
428 (:input :type "checkbox"
429 :name (format nil "meal{~A}" choice)
430 :checked (gethash choice meal)
431 (esc choice))
432 (:br)))))
433 (:tr
434 (:td :valign :top "Team:")
435 (:td (loop for player in '("Beckenbauer"
436 "Cruyff"
437 "Maradona"
438 ;; without accent (for SBCL)
439 "Pele"
440 "Zidane")
441 do (htm
442 (:input :type "checkbox"
443 :name "team"
444 :value player
445 :checked (member player team :test 'string=)
446 (esc player))
447 (:br)))))
448 (:tr
449 (:td :colspan 2
450 (:input :type "submit"))))))
451 (info-table first-name
452 last-name
454 implementation
455 (loop :for choice :being :the :hash-keys :of meal :collect choice)
456 (gethash "Yellow snow" meal)
457 team)))))
460 (defun menu ()
461 (with-html
462 (:html
463 (:head
464 (:link :rel "shortcut icon"
465 :href "/hunchentoot/test/favicon.ico" :type "image/x-icon")
466 (:title "Hunchentoot test menu"))
467 (:body
468 (:h2 (str *headline*))
469 (:table :border 0 :cellspacing 4 :cellpadding 4
470 (:tr (:td (:a :href "/hunchentoot/test/info.html?foo=bar"
471 "Info provided by Hunchentoot")))
472 (:tr (:td (:a :href "/hunchentoot/test/cookie.html"
473 "Cookie test")))
474 (:tr (:td (:a :href "/hunchentoot/test/session.html"
475 "Session test")))
476 (:tr (:td (:a :href "/hunchentoot/test/parameter_latin1_get.html"
477 "GET parameter handling with LATIN-1 charset")))
478 (:tr (:td (:a :href "/hunchentoot/test/parameter_latin1_post.html"
479 "POST parameter handling with LATIN-1 charset")))
480 (:tr (:td (:a :href "/hunchentoot/test/parameter_utf8_get.html"
481 "GET parameter handling with UTF-8 charset")))
482 (:tr (:td (:a :href "/hunchentoot/test/parameter_utf8_post.html"
483 "POST parameter handling with UTF-8 charset")))
484 (:tr (:td (:a :href "/hunchentoot/test/redir.html"
485 "Redirect \(302) to info page above")))
486 (:tr (:td (:a :href "/hunchentoot/test/authorization.html"
487 "Authorization")
488 " (user 'nanook', password 'igloo')"))
489 (:tr (:td (:a :href "/hunchentoot/code/test-handlers.lisp"
490 "The source code of this test")))
491 (:tr (:td (:a :href "/hunchentoot/test/image.jpg"
492 "Binary data, delivered from file")
493 " \(a picture)"))
494 (:tr (:td (:a :href "/hunchentoot/test/image-ram.jpg"
495 "Binary data, delivered from RAM")
496 " \(same picture)"))
497 (:tr (:td (:a :href "/hunchentoot/test/easy-demo.html"
498 "\"Easy\" handler example")))
499 (:tr (:td (:a :href "/hunchentoot/test/utf8-binary.txt"
500 "UTF-8 demo")
501 " \(writing octets directly to the stream)"))
502 (:tr (:td (:a :href "/hunchentoot/test/utf8-character.txt"
503 "UTF-8 demo")
504 " \(writing UTF-8 characters directly to the stream)"))
505 (:tr (:td (:a :href "/hunchentoot/test/utf8-string.txt"
506 "UTF-8 demo")
507 " \(returning a string)"))
508 (:tr (:td (:a :href "/hunchentoot/test/upload.html"
509 "File uploads")))
510 (:tr (:td (:a :href "/hunchentoot/test/forbidden.html"
511 "Forbidden \(403) page")))
512 (:tr (:td (:a :href "/hunchentoot/test/oops.html"
513 "Error handling")
514 " \(output depends on "
515 (:a :href "http://weitz.de/hunchentoot/#*show-lisp-errors-p*"
516 (:code "*SHOW-LISP-ERRORS-P*"))
517 (fmt " \(currently ~S))" *show-lisp-errors-p*)))
518 (:tr (:td (:a :href "/hunchentoot/foo"
519 "URI handled by")
521 (:a :href "http://weitz.de/hunchentoot/#*default-handler*"
522 (:code "*DEFAULT-HANDLER*")))))))))
524 (setq *dispatch-table*
525 (nconc
526 (list 'dispatch-easy-handlers
527 (create-static-file-dispatcher-and-handler
528 "/hunchentoot/test/image.jpg"
529 (make-pathname :name "fz" :type "jpg" :version nil
530 :defaults *this-file*)
531 "image/jpeg")
532 (create-static-file-dispatcher-and-handler
533 "/hunchentoot/test/favicon.ico"
534 (make-pathname :name "favicon" :type "ico" :version nil
535 :defaults *this-file*))
536 (create-folder-dispatcher-and-handler
537 "/hunchentoot/code/"
538 (make-pathname :name nil :type nil :version nil
539 :defaults *this-file*)
540 "text/plain"))
541 (mapcar (lambda (args)
542 (apply 'create-prefix-dispatcher args))
543 '(("/hunchentoot/test/form-test.html" form-test)
544 ("/hunchentoot/test/forbidden.html" forbidden)
545 ("/hunchentoot/test/info.html" info)
546 ("/hunchentoot/test/authorization.html" authorization-page)
547 ("/hunchentoot/test/image-ram.jpg" image-ram-page)
548 ("/hunchentoot/test/cookie.html" cookie-test)
549 ("/hunchentoot/test/session.html" session-test)
550 ("/hunchentoot/test/parameter_latin1_get.html" parameter-test-latin1-get)
551 ("/hunchentoot/test/parameter_latin1_post.html" parameter-test-latin1-post)
552 ("/hunchentoot/test/parameter_utf8_get.html" parameter-test-utf8-get)
553 ("/hunchentoot/test/parameter_utf8_post.html" parameter-test-utf8-post)
554 ("/hunchentoot/test/upload.html" upload-test)
555 ("/hunchentoot/test/redir.html" redir)
556 ("/hunchentoot/test/oops.html" oops)
557 ("/hunchentoot/test/utf8-binary.txt" stream-direct)
558 ("/hunchentoot/test/utf8-character.txt" stream-direct-utf-8)
559 ("/hunchentoot/test/utf8-string.txt" stream-direct-utf-8-string)
560 ("/hunchentoot/test/files/" send-file)
561 ("/hunchentoot/test" menu)))
562 (list 'default-dispatcher)))