本系列通过解析fasthttp(v1.16.0)源码学习HTTP/1.1

第一行

WEB服务接收HTTP请求要做的第一件事,就是解析请求行(Request Line)。它的格式如下:

1
request-line   = method SP request-target SP HTTP-version CRLF

对应到fasthttp中的代码是:

 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
func (h *RequestHeader) parseFirstLine(buf []byte) (int, error) {
	bNext := buf
	var b []byte
	var err error
	for len(b) == 0 {
		if b, bNext, err = nextLine(bNext); err != nil {
			return 0, err
		}
	}

	// parse method
	n := bytes.IndexByte(b, ' ')
	if n <= 0 {
		return 0, fmt.Errorf("cannot find http request method in %q", buf)
	}
	h.method = append(h.method[:0], b[:n]...)
	b = b[n+1:]

	// parse requestURI
	n = bytes.LastIndexByte(b, ' ')
	if n < 0 {
		h.noHTTP11 = true
		n = len(b)
	} else if n == 0 {
		return 0, fmt.Errorf("requestURI cannot be empty in %q", buf)
	} else if !bytes.Equal(b[n+1:], strHTTP11) {
		h.noHTTP11 = true
	}
	h.requestURI = append(h.requestURI[:0], b[:n]...)

	return len(buf) - len(bNext), nil
}

我们可以看到fasthttp先调用nextLine方法,根据分隔符(\r\n)获取出第一行的所有字节数据b

再根据b中第一个空格字节的位置解析出method,存入请求头对象中h.method = append(h.method[:0], b[:n]...)

再根据b中最后一个空格的位置,解析出request-targetHTTP-version

其中HTTP-versionh.noHTTP11保存一个bool值,不存在HTTP版本数据或者它不等于HTTP/1.1时,这个字段都为false

request-target也是直接保存到请求头对象中h.requestURI = append(h.requestURI[:0], b[:n]...)

最后返回第一行的字节数,done!

请求方法

The method token indicates the request method to be performed on the target resource. The request method is case-sensitive.

我们注意到RFC中有提到请求method是大小写敏感的(The request method is case-sensitive)。

而且根据上面的解析过程,我们可以看出fasthttp获取到method时,直接保存在请求头中,并没有做任何检查。所以当请求端发送了不正确(比如用了小写)的method值时,服务端可以拒绝这次请求(400 Bad Request)。

所有的请求method可以在RFC7231中查看,之后也会细讲。

请求目标

客户端发送HTTP请求消息时,其中包含从目标URI派生的请求目标。请求目标有四种不同的格式,具体取决于请求的方法以及请求是否针对代理:

1
2
3
4
  request-target = origin-form
                 / absolute-form
                 / authority-form
                 / asterisk-form

之后的文章会针对请求目标做详细的分析,这里不再赘述。

总结

本系列的第一篇文章就这样结束了。我们了解了请求行的结构,并剖析了fasthttp是如何从数据流中解析这第一行请求数据。

敬请期待之后的系列文章! 👋