diff --git a/net-doc/net/scribblings/url.scrbl b/net-doc/net/scribblings/url.scrbl index 8f150d3fae..04f951105e 100644 --- a/net-doc/net/scribblings/url.scrbl +++ b/net-doc/net/scribblings/url.scrbl @@ -469,14 +469,18 @@ particular, it is closed if @racket[handle] raises an exception, or if the connection process is interruped by an asynchronous break exception.} -@defparam[current-proxy-servers mapping (listof (list/c string? string? (integer-in 0 65535)))]{ +@deftogether[( +@defparam[current-proxy-servers mapping (listof (list/c string? string? (integer-in 0 65535)))] +@defthing[proxiable-url-schemes (listof string?) #:value '("http")] + )]{ -A parameter that determines a mapping of proxy servers used for +The @racket[current-proxy-servers] parameter determines a mapping of proxy servers used for connections. Each mapping is a list of three elements: @itemize[ - @item{the URL scheme, such as @racket["http"];} + @item{the URL scheme, such as @racket["http"]; @racket[proxiable-url-schemes] lists the URL schemes + that can be proxied. Currently, the only proxiable scheme is @racket["http"].} @item{the proxy server address; and} @@ -484,16 +488,17 @@ connections. Each mapping is a list of three elements: ] -Currently, the only proxiable scheme is @racket["http"]. +@racket[current-proxy-servers] can be configured from the environment. If +it has no entry for a particular URL scheme, then the environment +variables @litchar["plt_http_proxy"] and @litchar["http_proxy"] are used. This is done via a promise +that is forced by @racket[proxy-server-for]. -The environment variable @litchar["PLT_HTTP_PROXY"] can by used to set -an initial value for @racket[current-proxy-servers]. It contains a -single URL of the form: @litchar{http://hostname:portno}. If any other -components of the URL are provided, an error will be logged to the -@racket[net/url] logger. +The environment variables contain a single URL of the form: +@litchar{http://hostname:portno}. If any other components of the URL are provided, +an error will be logged to the @racket[net/url] logger. -The default mapping (or the mapping if @litchar["PLT_HTTP_PROXY"] is -not a valid URL) is the empty list (i.e., no proxies).} +The default mapping is the empty list (i.e., no proxies). +} @defparam[current-no-proxy-servers dest-hosts-list (listof (or/c string? regexp?))]{ @@ -501,43 +506,61 @@ A parameter that determines which servers will be accessed directly i.e. without resort to @racket[current-proxy-servers]. It is a list of: @itemize[ + @item{strings that match host names exactly} + @item{regexps that match host by pattern} + ] If a proxy server is defined for a URL scheme, then the destination -host name is checked against @[current-no-proxy-servers]. The proxy +host name is checked against @racket[current-no-proxy-servers]. The proxy is used if and only if the host name does not match (by the definition above) any in the list. -The environment variable @litchar["PLT_NO_PROXY"] can by used to set -an initial value for @racket[current-no-proxy-servers]. It is a -comma-separated list of ``patterns''. A pattern from the environment -variable is one of: +@racket[current-no-proxy-servers] can be configured from the environment. +The environment variables @litchar["plt_no_proxy"] and @litchar["no_proxy"] +are used. This is done via a promise that is forced by @racket[proxy-server-for]. + +These environment variables are comma-separated list of ``patterns''. +A pattern from an environment variable is one of: @itemize[ - @item{a string beginning with a @litchar{.} . This is converted to a + + @item{a string beginning with a @litchar{.} (period). This is converted to a regexp which performs a suffix match on a destination host name, - e.g. @litchar[.racket-lang.org] will match destinations of - @litchar[doc.racket-lang.org], @litchar[pkgs.racket-lang.org], but - neither @litchar[doc.bracket-lang.org] nor - @litchar[pkgs.racket-lang.org.uk]. - @margin-note{This is consistent with the @litchar["NO_PROXY"] environment - variable used by other programs, albeit not consistent with the regexps + e.g. @litchar[".racket-lang.org"] will match destinations of + @litchar["doc.racket-lang.org"], @litchar["pkgs.racket-lang.org"], but + neither @litchar["doc.bracket-lang.org"] nor + @litchar["pkgs.racket-lang.org.uk"]. + @margin-note{This is consistent with the @litchar["no_proxy"] environment + variable used by other software, albeit not consistent with the regexps stored in @racket[current-no-proxy-servers]} } - @item{any other string -- is kept as a string to be matched exactly} -] - -} + + @item{any other string -- is converted to a regexp which matches the string exactly} + +]} @defproc[(proxy-server-for [url-schm string?] [dest-host-name (or/c false/c string?) #f]) (list/c string? string? (integer-in 0 65535))]{ -Returns the proxy server entry for the combination of @racket[url-schm] and @racket[host], or - @racket[#f] if no proxy is to be used. -} +Returns the proxy server entry for the combination of @racket[url-schm] +and @racket[host], or @racket[#f] if no proxy is to be used. + +The process that loads environment variables into @racket[current-proxy-servers] +and @racket[current-no-proxy-servers] is held on a promise that is forced by +@racket[proxy-server-for]. This means that: + + @itemize[ + + @item{calling @racket[proxy-server-for] might change the list of + @racket[current-proxy-servers] and @racket[current-no-proxy-servers]} + + @item{to resolve proxy/no-proxy values from the environment, call @racket[(proxy-server-for #f)]} + +]} @defproc[(url-exception? [x any/c]) boolean?]{ diff --git a/net-test/tests/net/url.rkt b/net-test/tests/net/url.rkt index 7438c3ce5e..89c602ce29 100644 --- a/net-test/tests/net/url.rkt +++ b/net-test/tests/net/url.rkt @@ -1,13 +1,139 @@ #lang racket -(require net/url tests/eli-tester) +(require net/url + tests/eli-tester) (provide tests) (module+ main (test do (tests))) (define (tests) + + (define envar-stash (environment-variables-copy (current-environment-variables))) + + (define (test-proxy-server-for #:current-proxy-servers [current-proxy-servers-val #f] + #:current-no-proxy-servers [current-no-proxy-servers-val #f] + schema + (host #f) + #:plt-http-proxy (plt-http-proxy #f) + #:http-proxy (http-proxy #f) + #:plt-no-proxy (plt-no-proxy #f) + #:no-proxy (no-proxy #f)) + (parameterize ([current-environment-variables envar-stash] + [current-namespace (make-base-namespace)]) + (define (put! name val) + (environment-variables-set! envar-stash + (string->bytes/locale name) + (and val (string->bytes/locale val)))) + (put! "plt_http_proxy" plt-http-proxy) + (put! "http_proxy" http-proxy) + (put! "plt_no_proxy" plt-no-proxy) + (put! "no_proxy" no-proxy) + (eval '(require net/url)) + (eval `(parameterize (,@(if current-proxy-servers-val + `([current-proxy-servers (quote ,current-proxy-servers-val)]) + null) + ,@(if current-no-proxy-servers-val + `([current-no-proxy-servers (quote ,current-no-proxy-servers-val)]) + null)) + (proxy-server-for ,schema ,host))))) + (test ;; Test the current-proxy-servers parameter can be set (parameterize ([current-proxy-servers '(("http" "proxy.com" 3128))]) (current-proxy-servers)) - => '(("http" "proxy.com" 3128)))) + => '(("http" "proxy.com" 3128)) + + ;; we have at least http + (member "http" proxiable-url-schemes) + + ;; by default, there are no proxy servers + (test-proxy-server-for "http") => #f + + ;; current-no-proxy-servers converts incoming strings to anchored regexps + (parameterize ([current-no-proxy-servers (list "test.racket-lang.org" + #rx".*\\.racket-lang\\.org")]) + (current-no-proxy-servers)) + => '(#rx"^test\\.racket-lang\\.org$" + #rx".*\\.racket-lang\\.org") + + ;; ------------------------------------------------------------------ + ;; Test Proxy Servers (loading from environment and proxy-server-for) + + ;; proxy servers set in current-proxy-servers are not overridden by environment + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:plt-http-proxy "http://proxy.net:1234" + #:http-proxy "http://proxy.net:1234" + "http" "test.racket-lang.org") + => '("http" "proxy.com" 3128) + + ;; plt_http_proxy is is prioritised over http_proxy + (test-proxy-server-for #:plt-http-proxy "http://proxy.net:3128" + #:http-proxy "http://proxy.net:3228" + "http" "test.racket-lang.org") + => '("http" "proxy.net" 3128) + + ;; otherwise fall back to http_proxy + (test-proxy-server-for #:http-proxy "http://proxy.net:3228" + "http" "test.racket-lang.org") + => '("http" "proxy.net" 3228) + + ;; --------------------------------------------------------------------- + ;; Test NO Proxy Servers (loading from environment and proxy-server-for) + ;; no proxy servers accumulate (they don't override), so test each one + ;; being inserted in turn + + ;; prove that we need a proxy if not otherwise told... + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + "http" "test.racket-lang.org") + => '("http" "proxy.com" 3128) + + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:current-no-proxy-servers '("test.racket-lang.org") + "http" "test.racket-lang.org") + => #f + + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:plt-no-proxy "test.racket-lang.org" + "http" "test.racket-lang.org") + => #f + + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:no-proxy "test.racket-lang.org" + "http" "test.racket-lang.org") + => #f + + ;; Pattern matching + ;; prove that we need a proxy if not otherwise told... + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:current-no-proxy-servers '(#rx".racket-lang.org") + "http" "test.racket-lang.org") + => #f + + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:plt-no-proxy ".racket-lang.org" + "http" "test.racket-lang.org") + => #f + + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:no-proxy ".racket-lang.org" + "http" "test.racket-lang.org") + => #f + + ;; Failed matches + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:plt-no-proxy ".racket-lang.org" + "http" "test.bracket-lang.org") + => '("http" "proxy.com" 3128) + + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:no-proxy ".racket-lang.org" + "http" "test.bracket-lang.org") + => '("http" "proxy.com" 3128) + + ;; Look at this... the no-proxes has a regexp which starts with a '.', a regexp + ;; any char... that will match the 'b' in bracket + (test-proxy-server-for #:current-proxy-servers '(("http" "proxy.com" 3128)) + #:current-no-proxy-servers '(#rx".racket-lang.org") + "http" "test.bracket-lang.org") + => #f + )) (module+ test (require (submod ".." main))) ; for raco test & drdr