平时在工作的过程当中对net包里面的各种库有非常多的使用,本文将先对golang标准库中net/http包进行剖析,文末会向大家展示使用golang编写的简单网络服务的运行机制。基于golang1.8版本。

1
	func CanonicalMIMEHeaderKey(s string) string

函数功能描述

该函数将会按照mime的规范来格式化你传递进来的http header字符串。如你传入keep-alive,该函数就会返回一个Keep-Alive。具体的规范简单来说就是把开头的第一个字母以及如果连字符后面的第一个字母变成大写。

函数的内部会逐个字节的去检测你传递进来需要转换的http header字符串里面的字符是否合法。具体的检测规则如下:

  1. 检查某个字符的ascii码值是否处于ascii码表中可显示字符能表示的范围内
  2. 在net/textproto包下会有一个预定义的数组,它以ascii码值为索引,true为对应的元素值。在上一条检测成功的前提下,还会利用检查的字符作为数组索引去预定义的数组中取值的方式来再次校验这个字符是否真的是合法的ascii字符。

检测函数内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// validHeaderFieldByte reports whether b is a valid byte in a header
// field name. RFC 7230 says:
//   header-field   = field-name ":" OWS field-value OWS
//   field-name     = token
//   tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
//           "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
//   token = 1*tchar
func validHeaderFieldByte(b byte) bool {
	return int(b) < len(isTokenTable) && isTokenTable[b]
}

下面我们来看下这个函数的具体实现:

 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
func CanonicalMIMEHeaderKey(s string) string {
	// Quick check for canonical encoding.
	upper := true
	for i := 0; i < len(s); i++ {
		c := s[i]
		if !validHeaderFieldByte(c) {
			return s
		}
		if upper && 'a' <= c && c <= 'z' {
			return canonicalMIMEHeaderKey([]byte(s))
		}
		if !upper && 'A' <= c && c <= 'Z' {
			return canonicalMIMEHeaderKey([]byte(s))
		}
		upper = c == '-'
	}
	return s
}

// canonicalMIMEHeaderKey is like CanonicalMIMEHeaderKey but is
// allowed to mutate the provided byte slice before returning the
// string.
//
// For invalid inputs (if a contains spaces or non-token bytes), a
// is unchanged and a string copy is returned.
func canonicalMIMEHeaderKey(a []byte) string {
	// See if a looks like a header key. If not, return it unchanged.
	for _, c := range a {
		if validHeaderFieldByte(c) {
			continue
		}
		// Don't canonicalize.
		return string(a)
	}

	upper := true
	for i, c := range a {
		// Canonicalize: first letter upper case
		// and upper case after each dash.
		// (Host, User-Agent, If-Modified-Since).
		// MIME headers are ASCII only, so no Unicode issues.
		if upper && 'a' <= c && c <= 'z' {
			c -= toLower
		} else if !upper && 'A' <= c && c <= 'Z' {
			c += toLower
		}
		a[i] = c
		upper = c == '-' // for next time
	}
	// The compiler recognizes m[string(byteSlice)] as a special
	// case, so a copy of a's bytes into a new string does not
	// happen in this map lookup:
	if v := commonHeader[string(a)]; v != "" {
		return v
	}
	return string(a)
}

内层的canonicalMIMEHeaderKey函数在转换之前需要检查所有的字符是否符合规范,如果符合规范则开始转换。检测的过程当中,通过判断当前字符是否是连接符来决定是否应该转换字符为大写。在规范的http请求头中,第一个字符和连接线后第一个字符都应该是大写的。至于其余的字符,都应该是小写才对。第一个if分支用来将第一个小写字符和连接符后的小写字符变为大写,第二个if分支用来将除上述两个字符之外的字符变为小写。commonHeader检测我们转换后的字符串,如果是已知的,则直接返回我们预定义的,否则返回转换后的。

外层的CanonicalMIMEHeaderKey则决定是否要进行进一步的检测和转换,如果传递进来的字符串是符合规范的,那么外层的逻辑可保证不会进行多余的转换。外层的两个if逻辑和上面内层叙述的两个if逻辑是一样的。都是按照标准的http header来检测每一个字符来决定是否要进行下一步的转换。