[tbnl-devel] Patch for handling If-Modified-Since
Stefan Scholl
stesch at no-spoon.de
Thu Jul 22 15:51:52 UTC 2004
Hi!
A patch for handling If-Modified-Since is attached to this
e-mail. Maybe it could be useful.
I've only tested it with Mozilla 1.7.1 (with help from
<http://livehttpheaders.mozdev.org/>). So additional tests would
be fine.
New function rfc1123-date for generating a date string for http
headers.
Function handle-if-modified-since wants a time and handles almost
everything by itself. Checks against If-Modified-Since and
returns a 304 return code if necessary.
You have to set the Last-Modified header by yourself because you
don't know if you can produce some output at this point. See
create-static-file-dispatcher-and-handler in html.lisp for an
example.
I've improved create-static-file-dispatcher-and-handler by adding
cache functionality with handle-if-modified-since and setting
Last-Modified, and a simple check if the file exists. (==> 404 if
not). You see that Last-Modified isn't set when the file doesn't
exist.
Regards,
Stefan
-------------- next part --------------
diff -ru --exclude='*.fasl' --exclude='*~' tbnl-0.2.5/html.lisp tbnl-0.2.5-stesch/html.lisp
--- tbnl-0.2.5/html.lisp 2004-07-21 00:58:28.000000000 +0200
+++ tbnl-0.2.5-stesch/html.lisp 2004-07-22 17:40:42.000000000 +0200
@@ -141,12 +141,22 @@
(when (equal (script-name request) uri)
;; the handler
(lambda ()
- (setf (content-type) content-type)
- (with-output-to-string (out)
- (with-open-file (file path
- :direction :input
- #+:tbnl-bivalent-streams :element-type
- #+:tbnl-bivalent-streams '(unsigned-byte 8))
- (loop for pos = (read-sequence buf file)
- until (zerop pos)
- do (write-sequence buf out :end pos)))))))))
\ No newline at end of file
+ (let ((time (or (file-write-date path)
+ (get-universal-time))))
+ (handle-if-modified-since time)
+ (setf (content-type) content-type)
+ (let ((str
+ (with-output-to-string (out)
+ (with-open-file (file path
+ :direction :input
+ #+:tbnl-bivalent-streams :element-type
+ #+:tbnl-bivalent-streams '(unsigned-byte 8)
+ :if-does-not-exist nil)
+ (unless file ; does not exist
+ (setf (return-code) +http-not-found+)
+ (throw 'tbnl-handler-done nil))
+ (loop for pos = (read-sequence buf file)
+ until (zerop pos)
+ do (write-sequence buf out :end pos))))))
+ (setf (header-out "Last-Modified") (rfc1123-date time))
+ str)))))))
diff -ru --exclude='*.fasl' --exclude='*~' tbnl-0.2.5/request.lisp tbnl-0.2.5-stesch/request.lisp
--- tbnl-0.2.5/request.lisp 2004-05-07 16:10:40.000000000 +0200
+++ tbnl-0.2.5-stesch/request.lisp 2004-07-22 17:01:53.000000000 +0200
@@ -261,3 +261,13 @@
returned. Search is case-sensitive."
(or (get-parameter name request)
(post-parameter name request)))
+
+(defun handle-if-modified-since (time &optional (request *request*))
+ "Handles the If-Modified-Since header of the REQUEST. Date string is
+compared to the one generated from the supplied TIME."
+ (let ((if-modified-since (header-in "If-Modified-Since" request))
+ (time-string (rfc1123-date time)))
+ ;; Simple string compare is sufficient. See RFC 2616 14.25
+ (when (and if-modified-since (equal if-modified-since time-string))
+ (setf (return-code) +http-not-modified+)
+ (throw 'tbnl-handler-done nil))))
diff -ru --exclude='*.fasl' --exclude='*~' tbnl-0.2.5/specials.lisp tbnl-0.2.5-stesch/specials.lisp
--- tbnl-0.2.5/specials.lisp 2004-07-19 14:22:09.000000000 +0200
+++ tbnl-0.2.5-stesch/specials.lisp 2004-07-22 15:37:29.000000000 +0200
@@ -46,6 +46,7 @@
(defconstant +http-ok+ 200)
(defconstant +http-moved-permanently+ 301)
(defconstant +http-moved-temporarily+ 302)
+(defconstant +http-not-modified+ 304)
(defconstant +http-authorization-required+ 401)
(defconstant +http-forbidden+ 403)
(defconstant +http-not-found+ 404)
diff -ru --exclude='*.fasl' --exclude='*~' tbnl-0.2.5/tbnl.asd tbnl-0.2.5-stesch/tbnl.asd
--- tbnl-0.2.5/tbnl.asd 2004-05-12 19:05:09.000000000 +0200
+++ tbnl-0.2.5-stesch/tbnl.asd 2004-07-22 16:56:19.000000000 +0200
@@ -41,7 +41,7 @@
(:file "util" :depends-on ("specials"))
(:file "log" :depends-on ("util"))
(:file "cookie" :depends-on ("util"))
- (:file "request" :depends-on ("util"))
+ (:file "request" :depends-on ("util" "reply" "specials"))
(:file "reply" :depends-on ("util"))
(:file "session" :depends-on ("cookie" "log"))
(:file "html" :depends-on ("session" "request" "util"))
diff -ru --exclude='*.fasl' --exclude='*~' tbnl-0.2.5/util.lisp tbnl-0.2.5-stesch/util.lisp
--- tbnl-0.2.5/util.lisp 2004-07-19 14:22:09.000000000 +0200
+++ tbnl-0.2.5-stesch/util.lisp 2004-07-22 16:38:42.000000000 +0200
@@ -66,6 +66,7 @@
((#.+http-ok+) "OK")
((#.+http-moved-permanently+) "Moved Permanently")
((#.+http-moved-temporarily+) "Moved Temporarily")
+ ((#.+http-not-modified+) "Not Modified")
((#.+http-authorization-required+) "Authorization Required")
((#.+http-forbidden+) "Forbidden")
((#.+http-not-found+) "Not Found")
@@ -210,4 +211,18 @@
(defun get-backtrace (error)
(declare (ignore error))
(format nil "Output of backtrace currently not implemented for ~A"
- (lisp-implementation-type)))
\ No newline at end of file
+ (lisp-implementation-type)))
+
+(defun rfc1123-date (&optional (time (get-universal-time)))
+ "Generate time string according to RFC 1123. Default is current time."
+ (multiple-value-bind
+ (second minute hour date month year day-of-week)
+ (decode-universal-time time 0)
+ (format nil "~A, ~2d ~A ~4d ~2,'0d:~2,'0d:~2,'0d GMT"
+ (svref +day-names+ day-of-week)
+ date
+ (svref +month-names+ (1- month))
+ year
+ hour
+ minute
+ second)))
More information about the Tbnl-devel
mailing list