Author: iratqq
Date: Fri Feb  6 01:17:10 2009
New Revision: 5825

Modified:
   trunk/scm/http-client.scm

Log:
* scm/http-client.scm (http:read-chunk)
  (http:header-field-search, http:chunked?, http:content-length?)
  (http:parse-header):
  - New functions.
  (http:make-get-request-string):
  - Fix user agent string (rfc-2616).
  (http:get):
  - Use chunk parser.

  Partial support HTTP/1.1.


Modified: trunk/scm/http-client.scm
==============================================================================
--- trunk/scm/http-client.scm   (original)
+++ trunk/scm/http-client.scm   Fri Feb  6 01:17:10 2009
@@ -32,6 +32,7 @@
 (require-extension (srfi 1 2))
 (require "posix.scm")
 (require "socket.scm")
+(require "input-parse.scm")

 (define (http:open hostname servname)
   (call-with-getaddrinfo-hints
@@ -61,6 +62,94 @@
           (hex-format2 (char->integer c)))
         (string->list str))))

+(define (http:read-chunk port)
+  (define (hex-decode str)
+ (define hex-alist '((#\0 . 0) (#\1 . 1) (#\2 . 2) (#\3 . 3) (#\4 . 4) + (#\5 . 5) (#\6 . 6) (#\7 . 7) (#\8 . 8) (#\9 . 9)
+                        (#\a . 10) (#\A . 10) (#\b . 11) (#\B . 11)
+                        (#\c . 12) (#\C . 12) (#\d . 13) (#\D . 13)
+                        (#\e . 14) (#\E . 14) (#\f . 15) (#\F . 15)))
+    (let ((n (reverse
+              (map (lambda (c)
+                     (assq-cdr c hex-alist))
+                   (string->list str)))))
+      (let loop ((l n)
+                 (sum 0))
+        (if (null? l)
+            sum
+            (loop (map (lambda (x) (* 16 x)) (cdr l))
+                  (+ sum (car l)))))))
+  (define (http:drop-cr line)
+    (apply string-append (string-split line "\r")))
+  (define (http:drop-space line)
+    (apply string-append (string-split line " ")))
+
+ (let loop ((len-str (http:drop-space (http:drop-cr (file-read-line port))))
+             (rest '()))
+    (let ((len (guard (err
+                       (else #f))
+                      (hex-decode len-str))))
+      (if (or (not len) (= len 0))
+          (apply string-append (reverse rest))
+          (let ((buf (file-read-buffer port len)))
+            (file-read-line port) ;; blank
+ (loop (http:drop-cr (file-read-line port)) (cons buf rest)))))))
+
+(define (http:header-field-search l h)
+  (find (lambda (x)
+          (and (string? (car x))
+               (string-ci=? (car x) h))) l))
+
+(define (http:chunked? l)
+  (and-let* ((f (http:header-field-search l "transfer-encoding"))
+             (l (string-split (cdr f) ";"))
+             (ent (find (lambda (ent)
+                          (string=? "chunked" ent))
+                        l)))
+            #t))
+(define (http:content-length? l)
+  (and-let* ((ret (http:header-field-search l "content-length")))
+            (guard (err
+                    (else #f))
+                   (string->number (cdr ret)))))
+
+(define (http:parse-header lines)
+  (let loop ((lines lines)
+             (state '(status header))
+             (rest '()))
+    (if (null? lines)
+        (reverse rest)
+        (call-with-input-string
+         (car lines)
+         (lambda (port)
+           (cond ((eq? 'status (car state))
+                  (let ((version
+                         (find-string-from-port?
+                          "HTTP/"
+                          port))
+                        (version-number
+ (next-token '(#\space #\.) '(#\space) "Invalid header"
+                                     port))
+                        (status-code
+ (next-token '(#\space) '(#\space) "Invalid header" port))
+                        (reason-phrase
+ (next-token '(#\space) '(#\space #\return *eof*) "Invalid header" port)))
+                    (loop (cdr lines)
+                          (cdr state)
+                          (cons (cons 'header
+ (list (cons 'version-number version-number)
+                                            (cons 'status-code status-code)
+ (cons 'reason-phrase reason-phrase)))
+                                rest))))
+                 ((eq? 'header (car state))
+                  (let ((field-name
+ (next-token '(#\space #\tab) '(#\:) "Invalid header" port))
+                        (field-value
+ (next-token '(#\: #\space #\tab) '(#\return *eof*) "Invalid header" port)))
+                    (loop (cdr lines)
+                          state
+ (cons (cons field-name field-value) rest))))))))))
+
 (define (http:read-header port)
   (let loop ((str (file-read-line port))
              (rest '()))
@@ -69,20 +158,6 @@
         (reverse rest)
         (loop (file-read-line port) (cons str rest)))))

-(define (http:find-body-length l)
-  (and-let* ((req (map (lambda (q)
-                         (string-split
-                          (car (string-split q "\r"))
-                          ": "))
-                       l))
-             (cl (find (lambda (q)
-                         (and (= 2 (length q))
-                              (string-ci=? "content-length" (car q))))
-                       req)))
-    (guard (err
-            (else #f))
-           (string->number (cadr cl)))))
-
 (define (http:make-request-string request-alist)
   (string-append
    (apply
@@ -94,21 +169,25 @@

 (define (http:make-get-request-string hostname path request-alist)
   (string-append
-   (format "GET ~a HTTP/1.0\n" path)
+   (format "GET ~a HTTP/1.1\n" path)
    (format "Host: ~a\n" hostname)
-   (format "User-Agent: uim-~a\n" (uim-version))
+   (format "User-Agent: uim/~a\n" (uim-version))
    (http:make-request-string request-alist)))

 (define (http:get hostname path servname request-alist)
-    (and-let* ((socket (http:open hostname servname))
-               (port (open-file-port socket))
- (request (http:make-get-request-string hostname path request-alist))
-               (nr (file-display request port))
-               (header (http:read-header port)))
-      (let* ((body-length (http:find-body-length header))
-             (body (if body-length
-                       (file-read-buffer port body-length)
-                       (file-get-buffer port))))
-        (file-close socket)
-        body)))
-
+  (let ((file (http:open hostname servname)))
+    (call-with-open-file-port
+     file
+     (lambda (port)
+ (and-let* ((request (http:make-get-request-string hostname path request-alist))
+                  (nr (file-display request port))
+                  (header (http:read-header port))
+                  (parsed-header (http:parse-header header)))
+
+     (let ((content-length (http:content-length? parsed-header)))
+       (cond (content-length
+              (file-read-buffer port content-length))
+             ((http:chunked? parsed-header)
+              (http:read-chunk port))
+             (else
+              (file-get-buffer port)))))))))

Reply via email to