commit 716a88e1e80ed8c5673d3588108d05e7e2d27c20
parent 6c6504e9313ac0d19cca34338ebb6f05910dcaee
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun, 10 Jan 2016 20:12:59 +0100
better proxy
Diffstat:
| M | demo-webserver.lisp | | | 44 | ++++++++++++++++++++++++++++++++------------ | 
| M | http.lisp | | | 88 | ++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------- | 
2 files changed, 88 insertions(+), 44 deletions(-)
diff --git a/demo-webserver.lisp b/demo-webserver.lisp
@@ -99,18 +99,38 @@
 
 (defun serve-proxy (host port method query protocol headers body)
   (or (ignore-errors
-        (let ((host "ondoc.logand.com")
-              (port 80))
-          (with-open-stream (s (rw.socket:make-tcp-client-socket host port))
-            #+nil
-            (rw.http::%client1 s host port query #+nil path query-string
-                               (update-headers headers
-                                               `(("Host" . ,host)))))))
+        (with-open-stream (s (rw.socket:make-tcp-client-socket host port))
+          (multiple-value-bind (protocol code message headers2 body)
+              ;; TODO forward upload body
+              (rw.http::%client1 s host port method protocol query nil ;;TODO split into path query-string
+                                 (update-headers headers
+                                                 `(("Host" . ,host))))
+            (declare (ignore protocol))
+            ;;(print (list :@@@ headers2))
+            (list
+             :http-1.0
+             :code code
+             :message message
+             :headers (flet ((allow (k)
+                               (let ((x (find k headers2
+                                              :test #'equalp :key #'car)))
+                                 (when x
+                                   (list x)))))
+                        (append
+                         '(("Connection" . "close"))
+                         ;;(allow "Cache-Control")
+                         (allow "Content-Disposition")
+                         (allow "Content-Length")
+                         (allow "Content-Type")
+                         ;;(allow "Last-Modified")
+                         (allow "Location")
+                         (allow "Set-Cookie")))
+             :body body))))
       '(:http-1.0
-        :code 200
+        :code #1=502
         :headers (("Connection" . "close")
                   ("Content-Type" . "text/plain;charset=UTF-8"))
-        :body "TODO SERVE-PROXY")))
+        :body #.(format nil "~a ~a" #1# (cdr (assoc #1# rw.http:*http-codes*))))))
 
 (defun serve-not-found ()
   '(:http-1.0
@@ -129,7 +149,7 @@
               (rw:next-z0 r)))))
 
 (defun webserver-handler (msg stream method query protocol headers &optional body)
-  (declare (ignore stream #+nil protocol #+nil body))
+  (declare (ignore stream protocol #+nil body))
   (ecase msg
     ;;(:read (rw:till (rw:peek-reader stream)))
     (:write
@@ -140,8 +160,8 @@
                   #+nil(equalp "127.0.0.1" host))
               ;; proxy_set_header Host $host;
               ;; proxy_set_header Gate "$scheme $remote_addr";
-              ;; proxy_pass       http://127.0.0.1:1431;
-              (serve-proxy host port method query protocol headers body))
+              (assert (not body))
+              (serve-proxy "ondoc.logand.com" 1431 method query :http-1.0 headers body))
              ;; TODO ?rewrite ondoc.logand.com$ -> http://ondoc.logand.com/$1
              ((or (not host)
                   (equalp "logand.com" host)
diff --git a/http.lisp b/http.lisp
@@ -196,18 +196,38 @@
     (next-eol reader)))
 
 (defun next-body (reader) ;; TODO better, respect content-length!
-  (coerce (rw:till reader) 'string))
+  (coerce (rw:till reader) '(vector (unsigned-byte 8))))
 
 (defun write-crlf (writer)
   (rw:write-octets writer '(13 10)))
 
-(defun write-headers (writer headers)
-  (dolist (x headers)
-    (rw:write-utf8-string writer (car x))
+(defun write-header (writer k v)
+  (when v
+    (rw:write-utf8-string writer k)
     (rw:write-octets writer #.(rw.string:string-to-octets ": " :utf-8))
-    (rw:write-utf8-string writer (cdr x))
+    (etypecase v
+      (string (rw:write-utf8-string writer v))
+      (integer (rw:write-utf8-string writer (princ-to-string v)))
+      (cons
+       (loop
+          for part in v
+          for i from 0
+          do (progn
+               (when (plusp i)
+                 (rw:write-octets writer #.(rw.string:string-to-octets ";" :utf-8)))
+               (etypecase part
+                 (string (rw:write-utf8-string writer part))
+                 (cons
+                  (rw:write-utf8-string writer (car part))
+                  (when (cdr part)
+                    (rw:write-octets writer #.(rw.string:string-to-octets "=" :utf-8))
+                    (rw:write-utf8-string writer (cdr part)))))))))
     (write-crlf writer)))
 
+(defun write-headers (writer headers)
+  (dolist (x headers)
+    (write-header writer (car x) (cdr x))))
+
 (defun write-protocol (writer protocol)
   (rw:write-octets
    writer
@@ -215,32 +235,35 @@
      (:http-1.0 #.(rw.string:string-to-octets "HTTP/1.0" :utf-8))
      (:http-1.1 #.(rw.string:string-to-octets "HTTP/1.1" :utf-8)))))
 
-(defun write-query (stream method protocol path query-string)
-  (write-string (ecase method
-                  (:get "GET")
-                  (:post "POST"))
-                stream)
-  (write-char #\space stream)
-  (write-string (or path "/") stream)
+(defun write-query (writer method protocol path query-string)
+  (rw:write-utf8-string writer (ecase method
+                                 (:get "GET")
+                                 (:head "HEAD")
+                                 (:post "POST")
+                                 (:put "PUT")))
+  (rw:write-utf8-char writer #\space)
+  (rw:write-utf8-string writer (or path "/"))
   (when query-string
-    (write-char #\? stream)
-    (write-string query-string stream))
-  (write-char #\space stream)
-  (write-protocol stream protocol)
-  (write-crlf stream))
-
-(defun %client1 (stream host port path query-string headers)
-  (write-query stream :get :http-1.0 path query-string)
-  (write-headers (or headers
-                     `(("Host" . ,(if port
-                                      (format nil "~a:~a" host port)
-                                      host))))
-                 stream)
-  (write-crlf stream)
+    (rw:write-utf8-char writer #\?)
+    (rw:write-utf8-string writer query-string))
+  (rw:write-utf8-char writer #\space)
+  (write-protocol writer protocol)
+  (write-crlf writer))
+
+(defun %client1 (stream host port method protocol path query-string headers)
+  (let ((w (rw:byte-writer stream)))
+    (write-query w method protocol path query-string)
+    (write-headers w (or headers
+                         `(("Host" . ,(if port
+                                          (format nil "~a:~a" host port)
+                                          host)))))
+    (write-crlf w))
   (finish-output stream)
-  (let ((r (rw:peek-reader (rw:char-reader stream))))
-    (multiple-value-bind (protocol code message) (next-status r)
-      (values protocol code message (next-headers r) (next-body r)))))
+  (assert (eq :http-1.0 protocol)) ;; TODO chunked encoding with http-1.1
+  (let* ((rb (rw:peek-reader (rw:byte-reader stream)))
+         (rc (rw:peek-reader (rw:utf8-reader rb :charp t))))
+    (multiple-value-bind (protocol code message) (next-status rc)
+      (values protocol code message (next-headers rc) (next-body rb)))))
 
 (defun client1 (url headers)
   (destructuring-bind (&key scheme host port path query-string fragment)
@@ -250,7 +273,7 @@
     (declare (ignore fragment))
     (assert (equal "http" scheme))
     (with-open-stream (s (rw.socket:make-tcp-client-socket host (or port 80)))
-      (%client1 s host port path query-string headers))))
+      (%client1 s host port :get :http-1.0 path query-string headers))))
 
 (defun client (url &key headers (redirect 5))
   (do (protocol code message headers2 body)
@@ -261,9 +284,8 @@
         (setq url (cdr (assoc "Location" headers2 :test #'equalp))) ;; TODO update "Host" header
         (return-from client (values protocol code message headers2 body)))))
 
-;;(client "http://127.0.0.1:1234/")
+;;(client "http://127.0.0.1:2341")
 ;;(client "http://logand.com")
-;;(client "http://logand.com:2234")
 
 
 
@@ -382,6 +404,8 @@
          (pathname
           (with-open-file (s body :element-type '(unsigned-byte 8))
             (rw:copy (rw:byte-reader s) writer)))
+         ((vector (unsigned-byte 8))
+          (rw:copy (rw:reader body) writer))
          (cons
           (rw:write-utf8-string writer
                                 (with-output-to-string (*standard-output*)