MaxBytesReader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// MaxBytesReader is similar to io.LimitReader but is intended for
// limiting the size of incoming request bodies. In contrast to
// io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a
// non-EOF error for a Read beyond the limit, and closes the
// underlying reader when its Close method is called.
//
// MaxBytesReader prevents clients from accidentally or maliciously
// sending a large request and wasting server resources.
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
	return &maxBytesReader{w: w, r: r, n: n}
}

maxBytesReader函数返回了一个io.ReadCloser类型的对象,maxBytesReader类型对象实现了io.ReadCloser的全部接口。它主要用于检查服务端收到的request的长度是否超过我们预设的限制。

NotFound

1
2
// NotFound replies to the request with an HTTP 404 not found error.
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }

notfound方法将404的状态码写入到ResponseWriter里面,并输出404相关错误信息

ParseHTTPVersion

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ParseHTTPVersion parses a HTTP version string.
// "HTTP/1.0" returns (1, 0, true).
func ParseHTTPVersion(vers string) (major, minor int, ok bool) {
	const Big = 1000000 // arbitrary upper bound
	switch vers {
	case "HTTP/1.1":
		return 1, 1, true
	case "HTTP/1.0":
		return 1, 0, true
	}
	if !strings.HasPrefix(vers, "HTTP/") {
		return 0, 0, false
	}
	dot := strings.Index(vers, ".")
	if dot < 0 {
		return 0, 0, false
	}
	major, err := strconv.Atoi(vers[5:dot])
	if err != nil || major < 0 || major > Big {
		return 0, 0, false
	}
	minor, err = strconv.Atoi(vers[dot+1:])
	if err != nil || minor < 0 || minor > Big {
		return 0, 0, false
	}
	return major, minor, true
}

ParseHTTPVersion通过传递进来的字符串来判断http协议版本,里面的逻辑比较简单,几乎是硬编码的方式去做判断。除了1.1和1.0是明确判断之外,最后两个if语句也是可以判断出一些格式合法,但是主副版本号没有在1.1和1.0之内的协议版本。

ParseTime

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ParseTime parses a time header (such as the Date: header),
// trying each of the three formats allowed by HTTP/1.1:
// TimeFormat, time.RFC850, and time.ANSIC.
func ParseTime(text string) (t time.Time, err error) {
	for _, layout := range timeFormats {
		t, err = time.Parse(layout, text)
		if err == nil {
			return
		}
	}
	return
}

ParseTime函数将使用time.Parse函数将传递进来的string解析成相应的时间。如果按照预定的时间格式解析成功则返回结果。

ProxyEnvironment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// ProxyFromEnvironment returns the URL of the proxy to use for a
// given request, as indicated by the environment variables
// HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions
// thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https
// requests.
//
// The environment values may be either a complete URL or a
// "host[:port]", in which case the "http" scheme is assumed.
// An error is returned if the value is a different form.
//
// A nil URL and nil error are returned if no proxy is defined in the
// environment, or a proxy should not be used for the given request,
// as defined by NO_PROXY.
//
// As a special case, if req.URL.Host is "localhost" (with or without
// a port number), then a nil URL and nil error will be returned.
func ProxyFromEnvironment(req *Request) (*url.URL, error) {
	var proxy string
	if req.URL.Scheme == "https" {
		proxy = httpsProxyEnv.Get()
	}
	if proxy == "" {
		proxy = httpProxyEnv.Get()
		if proxy != "" && os.Getenv("REQUEST_METHOD") != "" {
			return nil, errors.New("net/http: refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")
		}
	}
	if proxy == "" {
		return nil, nil
	}
	if !useProxy(canonicalAddr(req.URL)) {
		return nil, nil
	}
	proxyURL, err := url.Parse(proxy)
	if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
		// proxy was bogus. Try prepending "http://" to it and
		// see if that parses correctly. If not, we fall
		// through and complain about the original one.
		if proxyURL, err := url.Parse("http://" + proxy); err == nil {
			return proxyURL, nil
		}
	}
	if err != nil {
		return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
	}
	return proxyURL, nil
}

ProxyFromEnvironment这个函数单从输入输出上来看,是根据request的信息,返回给我们一个url.URL的对象。这个对象里保存着根据我们系统的环境变量生成的代理信息。这个代理信息一般都是真正的服务器地址,也就是说,使用golang实现的服务器会起到一个代理的作用,客户端真正需要的内容需要到另外一台机器上去取。

这个函数工作当中确实没有用过,一直在做cdn管理系统相关的服务,客户端的请求的内容最终都会在我们本地的服务得到应答。所以我们先来看一个对我们来说比较陌生的类型:url.URL

url.URL

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// A URL represents a parsed URL (technically, a URI reference).
// The general form represented is:
//
//	scheme://[userinfo@]host/path[?query][#fragment]
//
// URLs that do not start with a slash after the scheme are interpreted as:
//
//	scheme:opaque[?query][#fragment]
//
// Note that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.
// A consequence is that it is impossible to tell which slashes in the Path were
// slashes in the raw URL and which were %2f. This distinction is rarely important,
// but when it is, code must not use Path directly.
//
// Go 1.5 introduced the RawPath field to hold the encoded form of Path.
// The Parse function sets both Path and RawPath in the URL it returns,
// and URL's String method uses RawPath if it is a valid encoding of Path,
// by calling the EscapedPath method.
//
// In earlier versions of Go, the more indirect workarounds were that an
// HTTP server could consult req.RequestURI and an HTTP client could
// construct a URL struct directly and set the Opaque field instead of Path.
// These still work as well.
type URL struct {
	Scheme     string
	Opaque     string    // encoded opaque data
	User       *Userinfo // username and password information
	Host       string    // host or host:port
	Path       string
	RawPath    string // encoded path hint (Go 1.5 and later only; see EscapedPath method)
	ForceQuery bool   // append a query ('?') even if RawQuery is empty
	RawQuery   string // encoded query values, without '?'
	Fragment   string // fragment for references, without '#'
}

粗略的看了一下该类型的定义就可以知道,这个类型内的成员就是一个合法的url中各个组成部分,我们通过一些方法可以把一条url中的不同元素解析到这个类型的成员里以便我们使用。文档中对一个url进行了划分,并且golang中为我们考虑到了url path在编解码上的问题。URL类型中的Path成员存放的是已经解码的path,而在go1.5版本之后,添加了RawPath这个成员,用于保存已经被编码过的path。

既然ProxyFromEnvironment函数是根据系统的环境变量来确定代理地址的,那么首先就需要获取到我们系统上有关代理的系统变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var (
	httpProxyEnv = &envOnce{
		names: []string{"HTTP_PROXY", "http_proxy"},
	}
	httpsProxyEnv = &envOnce{
		names: []string{"HTTPS_PROXY", "https_proxy"},
	}
	noProxyEnv = &envOnce{
		names: []string{"NO_PROXY", "no_proxy"},
	}
)

代码中我们可以看出,golang中已经预定义了系统中关于Http/Https代理的系统变量名。这些变量名作为envOnce这个类型当中names成员的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// envOnce looks up an environment variable (optionally by multiple
// names) once. It mitigates expensive lookups on some platforms
// (e.g. Windows).
type envOnce struct {
	names []string
	once  sync.Once
	val   string
}

func (e *envOnce) Get() string {
	e.once.Do(e.init)
	return e.val
}

func (e *envOnce) init() {
	for _, n := range e.names {
		e.val = os.Getenv(n)
		if e.val != "" {
			return
		}
	}
}

// reset is used by tests
func (e *envOnce) reset() {
	e.once = sync.Once{}
	e.val = ""
}

envOnce这个类型有三个方法,我们在ProxyEnvironment方法中使用了envOnce.Get。envOnce的Get方法将会通过sync.Once类型变量的Do方法来执行init方法,init方法逻辑上通过遍历预定义的环境变量名来找出该环境变量在我们的系统中被设置的值并保存于val成员内。那么为什么我们不直接调用init方法,反而要把它传递给sync.Once类型的Do方法呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Once is an object that will perform exactly one action.
type Once struct {
	m    Mutex
	done uint32
}

// Do calls the function f if and only if Do is being called for the
// first time for this instance of Once. In other words, given
// 	var once Once
// if once.Do(f) is called multiple times, only the first call will invoke f,
// even if f has a different value in each invocation. A new instance of
// Once is required for each function to execute.
//
// Do is intended for initialization that must be run exactly once. Since f
// is niladic, it may be necessary to use a function literal to capture the
// arguments to a function to be invoked by Do:
// 	config.once.Do(func() { config.init(filename) })
//
// Because no call to Do returns until the one call to f returns, if f causes
// Do to be called, it will deadlock.
//
// If f panics, Do considers it to have returned; future calls of Do return
// without calling f.
//
func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 1 {
		return
	}
	// Slow-path.
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

原来sync.Once为我们提供了一种机制:在同一个sync.Once类型的对象下,只有Do方法第一次被调用的时候,才会执行参数传递进来的f函数。从第二次开始,无论f传递进来是什么值都不会再调用它。而且要注意到的是,sync.Once类型并没有提供任何方法可以改变done的值,也就是说,只要是Do被执行了一次之后,除了通过新定义一个sync.Once的对象之外,就再也没有别的方式可以对f函数进行二次调用了。这也就为什么在envOnce实现的reset方法中对val成员重新赋值了一个新的sync.Once对象,但是这个reset方法是没导出的,不应该在外部使用。由此可见,sync.Once类型的Do方法是为了那些需要某个函数必须且仅能执行一次的情况。这种机制应用在我们获取系统变量的场景下是非常合适的,因为它可以防止频繁的系统调用

在ProxyFromEnvironment中获取了系统关于代理环境变量的内容之后,我们将会对获取的结果进行判断,如果是空的话,那么直接返回,不需要代理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
	if proxy == "" {
		return nil, nil
	}
	if !useProxy(canonicalAddr(req.URL)) {
		return nil, nil
	}
	proxyURL, err := url.Parse(proxy)
	if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
		// proxy was bogus. Try prepending "http://" to it and
		// see if that parses correctly. If not, we fall
		// through and complain about the original one.
		if proxyURL, err := url.Parse("http://" + proxy); err == nil {
			return proxyURL, nil
		}
	}
	if err != nil {
		return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
	}
	return proxyURL, nil

否则我们将会调用useProxy来判断一个被格式化的请求url是否应该使用我们找到的代理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
func useProxy(addr string) bool {
	if len(addr) == 0 {
		return true
	}
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return false
	}
	if host == "localhost" {
		return false
	}
	if ip := net.ParseIP(host); ip != nil {
		if ip.IsLoopback() {
			return false
		}
	}

	no_proxy := noProxyEnv.Get()
	if no_proxy == "*" {
		return false
	}

	addr = strings.ToLower(strings.TrimSpace(addr))
	if hasPort(addr) {
		addr = addr[:strings.LastIndex(addr, ":")]
	}

	for _, p := range strings.Split(no_proxy, ",") {
		p = strings.ToLower(strings.TrimSpace(p))
		if len(p) == 0 {
			continue
		}
		if hasPort(p) {
			p = p[:strings.LastIndex(p, ":")]
		}
		if addr == p {
			return false
		}
		if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
			// no_proxy ".foo.com" matches "bar.foo.com" or "foo.com"
			return false
		}
		if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
			// no_proxy "foo.com" matches "bar.foo.com"
			return false
		}
	}
	return true
}

我们首先通过检测host和解析成ip之后是否是本地的地址,如果是的话就不应该使用代理了。紧接着会获取no_proxy环境变量,通过判断addr是否能够匹配到no_proxy中的某一条内容来决定是否使用代理。 如果需要使用代理的话,ProxyFromEnvironment函数会调用url.Parse来将Proxy解析成一个合法的url.URL值返回给调用者。

ProxyURL

ProxyURL函数和ProxyFromEnvironment有着一样的形式,他们都可以赋值给TransPort类型中的Proxy成员,只不过ProxyURL是原样返回了作为参数传递进来的url.URL。

1
2
3
4
5
6
7
// ProxyURL returns a proxy function (for use in a Transport)
// that always returns the same URL.
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error) {
	return func(*Request) (*url.URL, error) {
		return fixedURL, nil
	}
}

Redirect

Redirect函数将根据我们提供的urlStr,将重定向后的Location字段中的url写入到ResponseHeader中。需要注意的是,location字段的url必须是以绝对路径的形式出现的。如果不是以绝对路径出现的话,我们要通过request的url值提取host和scheme部分加到urlStr之前。

1
func Redirect(w ResponseWriter, r *Request, urlStr string, code int)

Serve

1
2
3
4
func Serve(l net.Listener, handler Handler) error {
 		srv := &Server{Handler: handler}
  		return srv.Serve(l)
 }

Serve函数和ListenAndServe函数实际的效果是一样的,只不过Serve函数允许我们自己传递一个listener对象,而ListenAndServe函数是自己new了一个监听特定端口的tcp链接的listerner。

ServeFile

ServeFile函数将根据name参数提供的文件路径所指向的文件内容发送给客户端作为请求的响应。在通过ServeFile处理请求的时候,会根据一定的规则来判断是否要返回给客户端一个重定向的响应:

  1. 如果请求的url路径是以index.html来结尾的,那么重定向到其所在的目录下。
  2. 如果name所指向的文件的类型是一个目录,且请求的url最后一个字符不是*/*,此时也需要将请求重定向到name所指向目录下
  3. 调用者主动开启重定向功能,根据name所指向的文件类型和url最后一个字符来构造重定向响应中的Location字段。比如,name所指向的文件是目录的话,那么url的最后一个字符必须是 */, 不然就需要重定向。如果name所指向的文件是非目录的话,但是请求url最后一个字符是以/结尾的,此时需要将url重定向成一个以/开头 ,不以/*结尾的url

如果最后判定name所指向的文件是一个目录的话,那么就会去该目录下找到一个index.html的文件,将此文件的内容作为请求的响应内容。如果在目录下没有找到index.html文件的话,就会根据If-Modified-Since来判断客户端缓存的最后时间和服务器该文件修改的时间是否是有偏差的,如果客户端的缓存已经过期了,那么该请求的响应中就会列出该文件目录下的所有内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
	const indexPage = "/index.html"

	// redirect .../index.html to .../
	// can't use Redirect() because that would make the path absolute,
	// which would be a problem running under StripPrefix
	if strings.HasSuffix(r.URL.Path, indexPage) {
		localRedirect(w, r, "./")
		return
	}

	f, err := fs.Open(name)
	if err != nil {
		msg, code := toHTTPError(err)
		Error(w, msg, code)
		return
	}
	defer f.Close()

	d, err := f.Stat()
	if err != nil {
		msg, code := toHTTPError(err)
		Error(w, msg, code)
		return
	}

	if redirect {
		// redirect to canonical path: / at end of directory url
		// r.URL.Path always begins with /
		url := r.URL.Path
		if d.IsDir() {
			if url[len(url)-1] != '/' {
				localRedirect(w, r, path.Base(url)+"/")
				return
			}
		} else {
			if url[len(url)-1] == '/' {
				localRedirect(w, r, "../"+path.Base(url))
				return
			}
		}
	}

	// redirect if the directory name doesn't end in a slash
	if d.IsDir() {
		url := r.URL.Path
		if url[len(url)-1] != '/' {
			localRedirect(w, r, path.Base(url)+"/")
			return
		}
	}

	// use contents of index.html for directory, if present
	if d.IsDir() {
		index := strings.TrimSuffix(name, "/") + indexPage
		ff, err := fs.Open(index)
		if err == nil {
			defer ff.Close()
			dd, err := ff.Stat()
			if err == nil {
				name = index
				d = dd
				f = ff
			}
		}
	}

	// Still a directory? (we didn't find an index.html file)
	if d.IsDir() {
		if checkIfModifiedSince(w, r, d.ModTime()) == condFalse {
			writeNotModified(w)
			return
		}
		w.Header().Set("Last-Modified", d.ModTime().UTC().Format(TimeFormat))
		dirList(w, f)
		return
	}

	// serveContent will check modification time
	sizeFunc := func() (int64, error) { return d.Size(), nil }
	serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
}

serveContent函数里面将针对请求中是否开启range缓存以及range缓存的参数来向请求的响应中填充文件的内容。

SetCookie

1
2
3
4
5
func SetCookie(w ResponseWriter, cookie *Cookie) {
		if v := cookie.String(); v != "" {
			w.Header().Add("Set-Cookie", v)
		}
	}

SetCookie方法将会向http响应的头部写入Set-Cookie字段以及相应的值。

StatusText

StatusText这个函数将根据传递进来的status code去代码中查找预定义好的map里面查找对应的文字描述并返回。