guile-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Add support for HTTP proxies


From: Mark H Weaver
Subject: [PATCH] Add support for HTTP proxies
Date: Fri, 07 Jun 2013 01:23:10 -0400

Here's a preliminary patch to add proxy support to our HTTP client.
The API is as follows:

* If the 'http_proxy' environment variable is set to a string such as
  "http://myproxy:8123/";, then existing code should do the right thing
  without any changes.

* To override the environment variable within an arbitrary dynamic
  extent, you can parameterize the new 'current-http-proxy' parameter.
  Valid settings include #f to disable use of the proxy, 'auto to
  consult the environment variable, or a non-empty string.

* Finally, the relevant procedures such as 'http-get' will accept a
  keyword argument #:http-proxy, which accepts the same settings as the
  parameter except that 'auto falls back to the parameter setting.  This
  allows us to avoid the pitfalls of fluids/parameters when possible.

There's also the question of what to do if an existing port is passed to
'http-get' etc.  It needs to know whether the port is connected to a
proxy.  The solution implemented here is two-pronged.  First, there's an
object property 'http-proxy-port?' that is set by 'open-socket-for-uri'
and consulted by various procedures.  This should make existing code
"just work".  Second, the relevant procedures accept a keyword argument
'#:using-proxy?'.

Still to be done: documentation and tests.  However, before spending
time on that I wanted to ask for feedback on this general approach.

What do you think?

    Mark


>From 0e1928e957e09c8822e0ea8ac9199381e1657bc2 Mon Sep 17 00:00:00 2001
From: Mark H Weaver <address@hidden>
Date: Fri, 7 Jun 2013 00:47:33 -0400
Subject: [PATCH] Add support for HTTP proxies (PRELIMINARY)

* module/web/http.scm (http-proxy-port?): New exported object property.
  (current-http-proxy): New exported parameter.
  (write-request-line): Add 'using-proxy?' keyword argument.  If set,
  write an absolute-URI in the request line.

* module/web/request.scm (write-request): Add 'using-proxy?' keyword
  argument.  Pass it through to 'write-request-line'.

* module/web/client.scm: Import (web http).
  (choose-http-proxy): New procedure.
  (open-socket-for-uri): Add 'http-proxy' keyword argument.  If
  'chosen-http-proxy' returns a true value, connect to the proxy instead
  of the URI host, and set the 'http-proxy-port?' object property on the
  socket to true.
  (request, http-get, http-get*, http-head, http-post, http-put,
  http-delete, http-trace, http-options): Add 'http-proxy' and
  'using-proxy?' keyword arguments.
  (define-http-verb): Add 'http-proxy' and 'using-proxy?' keyword
  arguments to the defined verb.
---
 module/web/client.scm  |   44 ++++++++++++++++++++++++++++++++++----------
 module/web/http.scm    |   26 ++++++++++++++++++++++++--
 module/web/request.scm |    5 +++--
 3 files changed, 61 insertions(+), 14 deletions(-)

diff --git a/module/web/client.scm b/module/web/client.scm
index 7d5ea49..6d7b765 100644
--- a/module/web/client.scm
+++ b/module/web/client.scm
@@ -39,6 +39,7 @@
   #:use-module (web request)
   #:use-module (web response)
   #:use-module (web uri)
+  #:use-module (web http)
   #:use-module (srfi srfi-1)
   #:export (open-socket-for-uri
             http-get
@@ -56,9 +57,21 @@
    ((uri? uri-or-string) uri-or-string)
    (else (error "Invalid URI" uri-or-string))))
 
-(define (open-socket-for-uri uri-or-string)
+(define* (choose-http-proxy #:optional (http-proxy 'auto))
+  (define (valid-proxy? p)
+    (or (not p)
+        (and (string? p)
+             (not (string-null? p)))))
+  (define (identity p) p)
+  (cond (http-proxy            valid-proxy? => identity)
+        ((current-http-proxy)  valid-proxy? => identity)
+        ((getenv "http_proxy") valid-proxy? => identity)
+        (else #f)))
+
+(define* (open-socket-for-uri uri-or-string #:key (http-proxy 'auto))
   "Return an open input/output port for a connection to URI."
-  (define uri (ensure-uri uri-or-string))
+  (define chosen-proxy (choose-http-proxy http-proxy))
+  (define uri (ensure-uri (or chosen-proxy uri-or-string)))
   (define addresses
     (let ((port (uri-port uri)))
       (delete-duplicates
@@ -84,6 +97,8 @@
           (setvbuf s _IOFBF)
           ;; Enlarge the receive buffer.
           (setsockopt s SOL_SOCKET SO_RCVBUF (* 12 1024))
+          ;; If this is proxy server, associate that fact with the port.
+          (when http-proxy (set! (http-proxy-port? s) #t))
           s)
         (lambda args
           ;; Connection failed, so try one of the other addresses.
@@ -192,8 +207,10 @@ as is the case by default with a request returned by 
`build-request'."
 
 ;; We could expose this to user code if there is demand.
 (define* (request uri #:key
+                  (http-proxy 'auto)
                   (body #f)
-                  (port (open-socket-for-uri uri))
+                  (port (open-socket-for-uri uri #:http-proxy http-proxy))
+                  (using-proxy? (http-proxy-port? port))
                   (method "GET")
                   (version '(1 . 1))
                   (keep-alive? #f)
@@ -211,7 +228,7 @@ as is the case by default with a request returned by 
`build-request'."
                     #:port port)))
   (call-with-values (lambda () (sanitize-request request body))
     (lambda (request body)
-      (let ((request (write-request request port)))
+      (let ((request (write-request request port #:using-proxy? using-proxy?)))
         (when body
           (write-request-body request body))
         (force-output (request-port request))
@@ -236,8 +253,10 @@ as is the case by default with a request returned by 
`build-request'."
                           body))))))))))
 
 (define* (http-get uri #:key
+                   (http-proxy 'auto)
                    (body #f)
-                   (port (open-socket-for-uri uri))
+                   (port (open-socket-for-uri uri #:http-proxy http-proxy))
+                   (using-proxy? (http-proxy-port? port))
                    (version '(1 . 1)) (keep-alive? #f)
                    ;; #:headers is the new name of #:extra-headers.
                    (extra-headers #f) (headers (or extra-headers '()))
@@ -275,11 +294,13 @@ true)."
   (request uri #:method "GET" #:body body
            #:port port #:version version #:keep-alive? keep-alive?
            #:headers headers #:decode-body? decode-body?
-           #:streaming? streaming?))
+           #:streaming? streaming? #:using-proxy? using-proxy?))
 
 (define* (http-get* uri #:key
+                    (http-proxy 'auto)
                     (body #f)
-                    (port (open-socket-for-uri uri))
+                    (port (open-socket-for-uri uri #:http-proxy http-proxy))
+                    (using-proxy? (http-proxy-port? port))
                     (version '(1 . 1)) (keep-alive? #f)
                     ;; #:headers is the new name of #:extra-headers.
                     (extra-headers #f) (headers (or extra-headers '()))
@@ -290,12 +311,15 @@ true)."
    "Instead, use `http-get' with the #:streaming? #t keyword argument.")
   (http-get uri #:body body
             #:port port #:version version #:keep-alive? keep-alive?
-            #:headers headers #:decode-body? #t #:streaming? #t))
+            #:headers headers #:decode-body? #t #:streaming? #t
+            #:using-proxy? using-proxy?))
 
 (define-syntax-rule (define-http-verb http-verb method doc)
   (define* (http-verb uri #:key
+                      (http-proxy 'auto)
                       (body #f)
-                      (port (open-socket-for-uri uri))
+                      (port (open-socket-for-uri uri #:http-proxy http-proxy))
+                      (using-proxy? (http-proxy-port? port))
                       (version '(1 . 1))
                       (keep-alive? #f)
                       (headers '())
@@ -306,7 +330,7 @@ true)."
              #:body body #:method method
              #:port port #:version version #:keep-alive? keep-alive?
              #:headers headers #:decode-body? decode-body?
-             #:streaming? streaming?)))
+             #:streaming? streaming? #:using-proxy? using-proxy?)))
 
 (define-http-verb http-head
   "HEAD"
diff --git a/module/web/http.scm b/module/web/http.scm
index 35169ef..27d6a41 100644
--- a/module/web/http.scm
+++ b/module/web/http.scm
@@ -66,7 +66,10 @@
             write-response-line
 
             make-chunked-input-port
-            make-chunked-output-port))
+            make-chunked-output-port
+
+            http-proxy-port?
+            current-http-proxy))
 
 
 (define (string->header name)
@@ -1113,10 +1116,26 @@ three values: the method, the URI, and the version."
         (display #\? port)
         (display (uri-query uri) port))))
 
-(define (write-request-line method uri version port)
+(define* (write-request-line method uri version port
+                             #:key (using-proxy? (http-proxy-port? port)))
   "Write the first line of an HTTP request to PORT."
   (display method port)
   (display #\space port)
+  (when using-proxy?
+    (let ((scheme (uri-scheme uri))
+          (host (uri-host uri))
+          (host-port (uri-port uri)))
+      (when (and scheme host)
+        (display scheme port)
+        (display "://" port)
+        (if (string-index host #\:)
+            (begin (display #\[ port)
+                   (display host port)
+                   (display #\] port))
+            (display host port))
+        (unless ((@@ (web uri) default-port?) scheme host-port)
+          (display #\: port)
+          (display host-port port)))))
   (let ((path (uri-path uri))
         (query (uri-query uri)))
     (if (not (string-null? path))
@@ -1958,3 +1977,6 @@ KEEP-ALIVE? is true."
     (unless keep-alive?
       (close-port port)))
   (make-soft-port (vector put-char put-string flush #f close) "w"))
+
+(define http-proxy-port? (make-object-property))
+(define current-http-proxy (make-parameter 'auto))
diff --git a/module/web/request.scm b/module/web/request.scm
index 7ced076..665d022 100644
--- a/module/web/request.scm
+++ b/module/web/request.scm
@@ -206,13 +206,14 @@ requests."
       (make-request method uri version (read-headers port) meta port))))
 
 ;; FIXME: really return a new request?
-(define (write-request r port)
+(define* (write-request r port #:key (using-proxy? (http-proxy-port? port)))
   "Write the given HTTP request to PORT.
 
 Return a new request, whose ‘request-port’ will continue writing
 on PORT, perhaps using some transfer encoding."
   (write-request-line (request-method r) (request-uri r)
-                      (request-version r) port)
+                      (request-version r) port
+                      #:using-proxy? using-proxy?)
   (write-headers (request-headers r) port)
   (display "\r\n" port)
   (if (eq? port (request-port r))
-- 
1.7.10.4


reply via email to

[Prev in Thread] Current Thread [Next in Thread]