With this web 2.0 thing, nowadays it's not uncommon to have javascript library weighting over 500k (with extreme case like dojo which adds up to several megabytes).
If you google a bit you'll find that this doesn't concern the toolkit authors because they'll say that if you minimize (eliminate whitespaces) the javascript file and turn on gzip encoding it's a non-issue.
It was a low-hanging fruit to add gzip-encoding to hunchentoot since the hard work has already been done by Edi's (flexi-stream), Zach (Salza 1) and Sean Ross (gzip-stream 2).
In fact it was only a few lines of changes.
Using the network monitor in FireBug , my initial testing (with a 790 KB javascript file) shows that the response time is slower with gzip turned on (I'm guessing that it might be related to flexi-stream.)
262 KB 828ms 875ms 735ms 703ms 719ms 593ms 734ms
790 KB 625ms 359ms 672ms 328ms 610ms 641ms 594ms
Since I'm testing with the loopback interface, there's virtually no network overhead.
So I launch another firefox in vmware and limit the download rate of the network interface to 150k (the most common DSL speed offered in the states)
262 KB 1.72s 1.72s 1.64s 1.79s
790 KB 5.16s 5.21s 5.11s 5.21s
Now we're talking. And we can't ignore the 56k line users, can't we?
262 KB 8.39s 5.07s 4.97s 5.07s
790 KB 15.36s … point taken, I don't want to repeat this :-)
So now, the questions remain:
Should we add this to hunchentoot or make it an extension module? (this will introduce two new dependencies to hunchentoot)
If we add this to hunchentoot, should we add a flag to turn on/off gzip encoding? (probably yes)
And again, since there's flexi-stream overhead (my guess), should we add a flag to control what content-type we should disable gzip encoding? (probably wasting cpu cycle to gzip jpeg, mp3 and png, etc).
Suggestions?
-- Mac
2: http://common-lisp.net/project/gzip-stream/
(defun handle-static-file (path &optional content-type) "A function which acts like a Hunchentoot handler for the file denoted by PATH. Send a content type header corresponding to CONTENT-TYPE or (if that is NIL) tries to determine the content type via the file's suffix." (unless (or (pathname-name path) (pathname-type path)) ;; not a file (setf (return-code) +http-bad-request+) (throw 'handler-done nil)) (unless (probe-file path) ;; does not exist (setf (return-code) +http-not-found+) (throw 'handler-done nil)) (let ((time (or (file-write-date path) (get-universal-time))) (accept-gzip-p (search "gzip" (header-in :accept-encoding)))) (setf (content-type) (or content-type (mime-type path) "application/octet-stream")) (handle-if-modified-since time) (with-open-file (file path :direction :input :element-type '(unsigned-byte 8) :if-does-not-exist nil) (setf (header-out "Last-Modified") (rfc-1123-date time)) (if accept-gzip-p (setf (header-out "Content-Encoding") "gzip") (setf (content-length) (file-length file))) (let ((out (send-headers))) (when accept-gzip-p (setq out (gzip-stream:make-gzip-output-stream out))) (loop with buf = (make-array +buffer-length+ :element-type '(unsigned-byte 8)) for pos = (read-sequence buf file) until (zerop pos) do (write-sequence buf out :end pos) (finish-output out))))))
On Sat, 26 May 2007 03:24:31 -0700, "Mac Chan" emailmac@gmail.com wrote:
Using the network monitor in FireBug , my initial testing (with a 790 KB javascript file) shows that the response time is slower with gzip turned on (I'm guessing that it might be related to flexi-stream.)
I suppose gzip itself will also take some time, won't it? Why do you think flexi-streams is the culprit in this case?
Should we add this to hunchentoot or make it an extension module? (this will introduce two new dependencies to hunchentoot)
I'm not against it per se, but I wouldn't want to add it just for static files. I think static files aren't something we should be particularly concerned with. If you have lots of static files and Hunchentoot can't serve them fast enough for your purposes, let Apache serve them, or put them behind mod_lisp and use mod_gzip. Besides, clients will cache Javascript files, so they'll have to read your big Ajax library only once.
If, OTOH, you also want to allow gzip encoding for dynamic content generated with Hunchentoot, it gets a lot more complicated, particularly because gzip-streams are binary only. I thought about this for some time, and I don't see an easy and elegant way around this - unless you want to add yet another flexi-streams layer which would result in four (!) layers of Gray streams atop each other.
If we add this to hunchentoot, should we add a flag to turn on/off gzip encoding? (probably yes)
Yes, definitely.
And again, since there's flexi-stream overhead (my guess), should we add a flag to control what content-type we should disable gzip encoding? (probably wasting cpu cycle to gzip jpeg, mp3 and png, etc).
Yes. This has nothing to do with whether flexi-streams adds overhead or not. It is generally a bad idea to compress formats that are already compressed... :)
(accept-gzip-p (search "gzip" (header-in :accept-encoding)))
I think that's a bit too simplistic. The header could be one of
Accept-Encoding: x-frogzipper Accept-Encoding: gzip; q=0 Accept-Encoding: *
Something like this maybe (untested):
(defun parse-qvalue (string) "Checks if the string STRING is a `qvalue' according to RFC 2616 and returns the corresponding numeric value. Signals an error otherwise." (unless (ppcre:scan "^(?:[0](?:\.[0-9]{0,3})?|[1](?:\.0{0,3})?)$" string) (error "Not a qvalue: ~S" string)) (case (char string 0) (#\1 1) (#\0 (let ((length (length string))) (case length ((1 2) 0) (otherwise (/ (parse-integer string :start 2) (case length (3 10) (4 100) (5 1000)))))))))
(defun accepts-gzip-p () "Checks whether the user agent accepts gzip encoding according to the incoming headers." (when-let (accept-encoding-header (header-in :accept-encoding)) (dolist (part (ppcre:split "," accept-encoding-header)) (with-input-from-string (stream part) (let* ((*current-error-message* "Corrupted Accept-Encoding header:") (coding (read-token stream)) (parameters (read-name-value-pairs stream)) (quality (cdr (assoc "q" parameters)))) (when (and (member coding '("gzip" "*") :test #'string=) (or (null quality) (plusp (handler-case (parse-qvalue quality) (error (condition) (log-message :warn "While parsing Accept-Encoding header: ~A" condition) 0))))) (return-from accepts-gzip-p t)))))))
Cheers, Edi.
On 5/26/07, Edi Weitz edi@agharta.de wrote:
I suppose gzip itself will also take some time, won't it? Why do you think flexi-streams is the culprit in this case?
Because it was convenient to blame flexi-steams ;-) (re: the upload performance thread)
I think static files aren't something we should be particularly concerned with. If you have lots of static files and Hunchentoot can't serve them fast enough for your purposes, let Apache serve them, or put them behind mod_lisp and use mod_gzip. Besides, clients will cache Javascript files, so they'll have to read your big Ajax library only once.
I wasn't too concerned about scalability. Like you said, if it is a public facing high-traffic website, putting hunchentoot behind mod-lisp would be a safer bet.
However, even in intranet application, downloading 790k verse 220k is a huge difference in user experience.
And not to mention that you don't have to deal with apache setup and configuration.
Imagine hacking httpd.conf and installing mods verse writing your own mod-alias mod-rewrite in lisp, changing the rules in runtime without restarting the web server. Which one is more fun? ;)
(accept-gzip-p (search "gzip" (header-in :accept-encoding)))
I think that's a bit too simplistic. The header could be one of
Accept-Encoding: x-frogzipper Accept-Encoding: gzip; q=0 Accept-Encoding: *
Yeah, you're right. I was just trying to do some experiments and fish for ideas from the mailing list.
Something like this maybe (untested):
See? I threw in a brick and got some quality code back. Not bad at all :-)
I'm not against it per se, but I wouldn't want to add it just for static files.
Agreed. I don't think we should put half baked solution into hunchentoot. For now I'll adopt it into lsp:create-lsp-folder-dispatcher-and-handler.
Thanks, -- Mac