Skip to content

Commit b1f46d7

Browse files
net/http: Add js/wasm compatible DefaultTransport
Adds a new Transport type for the js/wasm target that uses the JavaScript Fetch API for sending HTTP requests. Support for streaming response bodies is used when available, falling back to reading the entire response into memory at once.
1 parent 65c365b commit b1f46d7

File tree

3 files changed

+408
-171
lines changed

3 files changed

+408
-171
lines changed

src/net/http/proxy.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright 2018 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package http
6+
7+
import (
8+
"errors"
9+
"fmt"
10+
"net"
11+
"net/url"
12+
"os"
13+
"strings"
14+
"sync"
15+
)
16+
17+
// ProxyFromEnvironment returns the URL of the proxy to use for a
18+
// given request, as indicated by the environment variables
19+
// HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions
20+
// thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https
21+
// requests.
22+
//
23+
// The environment values may be either a complete URL or a
24+
// "host[:port]", in which case the "http" scheme is assumed.
25+
// An error is returned if the value is a different form.
26+
//
27+
// A nil URL and nil error are returned if no proxy is defined in the
28+
// environment, or a proxy should not be used for the given request,
29+
// as defined by NO_PROXY.
30+
//
31+
// As a special case, if req.URL.Host is "localhost" (with or without
32+
// a port number), then a nil URL and nil error will be returned.
33+
func ProxyFromEnvironment(req *Request) (*url.URL, error) {
34+
var proxy string
35+
if req.URL.Scheme == "https" {
36+
proxy = httpsProxyEnv.Get()
37+
}
38+
if proxy == "" {
39+
proxy = httpProxyEnv.Get()
40+
if proxy != "" && os.Getenv("REQUEST_METHOD") != "" {
41+
return nil, errors.New("net/http: refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
42+
}
43+
}
44+
if proxy == "" {
45+
return nil, nil
46+
}
47+
if !useProxy(canonicalAddr(req.URL)) {
48+
return nil, nil
49+
}
50+
proxyURL, err := url.Parse(proxy)
51+
if err != nil ||
52+
(proxyURL.Scheme != "http" &&
53+
proxyURL.Scheme != "https" &&
54+
proxyURL.Scheme != "socks5") {
55+
// proxy was bogus. Try prepending "http://" to it and
56+
// see if that parses correctly. If not, we fall
57+
// through and complain about the original one.
58+
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
59+
return proxyURL, nil
60+
}
61+
62+
}
63+
if err != nil {
64+
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
65+
}
66+
return proxyURL, nil
67+
}
68+
69+
// ProxyURL returns a proxy function (for use in a Transport)
70+
// that always returns the same URL.
71+
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error) {
72+
return func(*Request) (*url.URL, error) {
73+
return fixedURL, nil
74+
}
75+
}
76+
77+
// canonicalAddr returns url.Host but always with a ":port" suffix
78+
func canonicalAddr(url *url.URL) string {
79+
addr := url.Hostname()
80+
if v, err := idnaASCII(addr); err == nil {
81+
addr = v
82+
}
83+
port := url.Port()
84+
if port == "" {
85+
port = portMap[url.Scheme]
86+
}
87+
return net.JoinHostPort(addr, port)
88+
}
89+
90+
// envOnce looks up an environment variable (optionally by multiple
91+
// names) once. It mitigates expensive lookups on some platforms
92+
// (e.g. Windows).
93+
type envOnce struct {
94+
names []string
95+
once sync.Once
96+
val string
97+
}
98+
99+
func (e *envOnce) Get() string {
100+
e.once.Do(e.init)
101+
return e.val
102+
}
103+
104+
func (e *envOnce) init() {
105+
for _, n := range e.names {
106+
e.val = os.Getenv(n)
107+
if e.val != "" {
108+
return
109+
}
110+
}
111+
}
112+
113+
// reset is used by tests
114+
func (e *envOnce) reset() {
115+
e.once = sync.Once{}
116+
e.val = ""
117+
}
118+
119+
var (
120+
httpProxyEnv = &envOnce{
121+
names: []string{"HTTP_PROXY", "http_proxy"},
122+
}
123+
httpsProxyEnv = &envOnce{
124+
names: []string{"HTTPS_PROXY", "https_proxy"},
125+
}
126+
noProxyEnv = &envOnce{
127+
names: []string{"NO_PROXY", "no_proxy"},
128+
}
129+
)
130+
131+
// useProxy reports whether requests to addr should use a proxy,
132+
// according to the NO_PROXY or no_proxy environment variable.
133+
// addr is always a canonicalAddr with a host and port.
134+
func useProxy(addr string) bool {
135+
if len(addr) == 0 {
136+
return true
137+
}
138+
host, _, err := net.SplitHostPort(addr)
139+
if err != nil {
140+
return false
141+
}
142+
if host == "localhost" {
143+
return false
144+
}
145+
if ip := net.ParseIP(host); ip != nil {
146+
if ip.IsLoopback() {
147+
return false
148+
}
149+
}
150+
151+
noProxy := noProxyEnv.Get()
152+
if noProxy == "*" {
153+
return false
154+
}
155+
156+
addr = strings.ToLower(strings.TrimSpace(addr))
157+
if hasPort(addr) {
158+
addr = addr[:strings.LastIndex(addr, ":")]
159+
}
160+
161+
for _, p := range strings.Split(noProxy, ",") {
162+
p = strings.ToLower(strings.TrimSpace(p))
163+
if len(p) == 0 {
164+
continue
165+
}
166+
if hasPort(p) {
167+
p = p[:strings.LastIndex(p, ":")]
168+
}
169+
if addr == p {
170+
return false
171+
}
172+
if len(p) == 0 {
173+
// There is no host part, likely the entry is malformed; ignore.
174+
continue
175+
}
176+
if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
177+
// no_proxy ".foo.com" matches "bar.foo.com" or "foo.com"
178+
return false
179+
}
180+
if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
181+
// no_proxy "foo.com" matches "bar.foo.com"
182+
return false
183+
}
184+
}
185+
return true
186+
}

src/net/http/transport.go

Lines changed: 2 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
// This is the low-level Transport implementation of RoundTripper.
88
// The high-level interface is in client.go.
99

10+
// +build !js
11+
1012
package http
1113

1214
import (
@@ -255,66 +257,6 @@ func (t *Transport) onceSetNextProtoDefaults() {
255257
}
256258
}
257259

258-
// ProxyFromEnvironment returns the URL of the proxy to use for a
259-
// given request, as indicated by the environment variables
260-
// HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions
261-
// thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https
262-
// requests.
263-
//
264-
// The environment values may be either a complete URL or a
265-
// "host[:port]", in which case the "http" scheme is assumed.
266-
// An error is returned if the value is a different form.
267-
//
268-
// A nil URL and nil error are returned if no proxy is defined in the
269-
// environment, or a proxy should not be used for the given request,
270-
// as defined by NO_PROXY.
271-
//
272-
// As a special case, if req.URL.Host is "localhost" (with or without
273-
// a port number), then a nil URL and nil error will be returned.
274-
func ProxyFromEnvironment(req *Request) (*url.URL, error) {
275-
var proxy string
276-
if req.URL.Scheme == "https" {
277-
proxy = httpsProxyEnv.Get()
278-
}
279-
if proxy == "" {
280-
proxy = httpProxyEnv.Get()
281-
if proxy != "" && os.Getenv("REQUEST_METHOD") != "" {
282-
return nil, errors.New("net/http: refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
283-
}
284-
}
285-
if proxy == "" {
286-
return nil, nil
287-
}
288-
if !useProxy(canonicalAddr(req.URL)) {
289-
return nil, nil
290-
}
291-
proxyURL, err := url.Parse(proxy)
292-
if err != nil ||
293-
(proxyURL.Scheme != "http" &&
294-
proxyURL.Scheme != "https" &&
295-
proxyURL.Scheme != "socks5") {
296-
// proxy was bogus. Try prepending "http://" to it and
297-
// see if that parses correctly. If not, we fall
298-
// through and complain about the original one.
299-
if proxyURL, err := url.Parse("http://" + proxy); err == nil {
300-
return proxyURL, nil
301-
}
302-
303-
}
304-
if err != nil {
305-
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
306-
}
307-
return proxyURL, nil
308-
}
309-
310-
// ProxyURL returns a proxy function (for use in a Transport)
311-
// that always returns the same URL.
312-
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error) {
313-
return func(*Request) (*url.URL, error) {
314-
return fixedURL, nil
315-
}
316-
}
317-
318260
// transportRequest is a wrapper around a *Request that adds
319261
// optional extra headers to write and stores any error to return
320262
// from roundTrip.
@@ -573,47 +515,6 @@ func (t *Transport) cancelRequest(req *Request, err error) {
573515
// Private implementation past this point.
574516
//
575517

576-
var (
577-
httpProxyEnv = &envOnce{
578-
names: []string{"HTTP_PROXY", "http_proxy"},
579-
}
580-
httpsProxyEnv = &envOnce{
581-
names: []string{"HTTPS_PROXY", "https_proxy"},
582-
}
583-
noProxyEnv = &envOnce{
584-
names: []string{"NO_PROXY", "no_proxy"},
585-
}
586-
)
587-
588-
// envOnce looks up an environment variable (optionally by multiple
589-
// names) once. It mitigates expensive lookups on some platforms
590-
// (e.g. Windows).
591-
type envOnce struct {
592-
names []string
593-
once sync.Once
594-
val string
595-
}
596-
597-
func (e *envOnce) Get() string {
598-
e.once.Do(e.init)
599-
return e.val
600-
}
601-
602-
func (e *envOnce) init() {
603-
for _, n := range e.names {
604-
e.val = os.Getenv(n)
605-
if e.val != "" {
606-
return
607-
}
608-
}
609-
}
610-
611-
// reset is used by tests
612-
func (e *envOnce) reset() {
613-
e.once = sync.Once{}
614-
e.val = ""
615-
}
616-
617518
func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectMethod, err error) {
618519
if port := treq.URL.Port(); !validPort(port) {
619520
return cm, fmt.Errorf("invalid URL port %q", port)
@@ -1235,63 +1136,6 @@ func (w persistConnWriter) Write(p []byte) (n int, err error) {
12351136
return
12361137
}
12371138

1238-
// useProxy reports whether requests to addr should use a proxy,
1239-
// according to the NO_PROXY or no_proxy environment variable.
1240-
// addr is always a canonicalAddr with a host and port.
1241-
func useProxy(addr string) bool {
1242-
if len(addr) == 0 {
1243-
return true
1244-
}
1245-
host, _, err := net.SplitHostPort(addr)
1246-
if err != nil {
1247-
return false
1248-
}
1249-
if host == "localhost" {
1250-
return false
1251-
}
1252-
if ip := net.ParseIP(host); ip != nil {
1253-
if ip.IsLoopback() {
1254-
return false
1255-
}
1256-
}
1257-
1258-
noProxy := noProxyEnv.Get()
1259-
if noProxy == "*" {
1260-
return false
1261-
}
1262-
1263-
addr = strings.ToLower(strings.TrimSpace(addr))
1264-
if hasPort(addr) {
1265-
addr = addr[:strings.LastIndex(addr, ":")]
1266-
}
1267-
1268-
for _, p := range strings.Split(noProxy, ",") {
1269-
p = strings.ToLower(strings.TrimSpace(p))
1270-
if len(p) == 0 {
1271-
continue
1272-
}
1273-
if hasPort(p) {
1274-
p = p[:strings.LastIndex(p, ":")]
1275-
}
1276-
if addr == p {
1277-
return false
1278-
}
1279-
if len(p) == 0 {
1280-
// There is no host part, likely the entry is malformed; ignore.
1281-
continue
1282-
}
1283-
if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
1284-
// no_proxy ".foo.com" matches "bar.foo.com" or "foo.com"
1285-
return false
1286-
}
1287-
if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
1288-
// no_proxy "foo.com" matches "bar.foo.com"
1289-
return false
1290-
}
1291-
}
1292-
return true
1293-
}
1294-
12951139
// connectMethod is the map key (in its String form) for keeping persistent
12961140
// TCP connections alive for subsequent HTTP requests.
12971141
//
@@ -2118,19 +1962,6 @@ var portMap = map[string]string{
21181962
"socks5": "1080",
21191963
}
21201964

2121-
// canonicalAddr returns url.Host but always with a ":port" suffix
2122-
func canonicalAddr(url *url.URL) string {
2123-
addr := url.Hostname()
2124-
if v, err := idnaASCII(addr); err == nil {
2125-
addr = v
2126-
}
2127-
port := url.Port()
2128-
if port == "" {
2129-
port = portMap[url.Scheme]
2130-
}
2131-
return net.JoinHostPort(addr, port)
2132-
}
2133-
21341965
// bodyEOFSignal is used by the HTTP/1 transport when reading response
21351966
// bodies to make sure we see the end of a response body before
21361967
// proceeding and reading on the connection again.

0 commit comments

Comments
 (0)