29 September 2014
使用Golang语言,了解HTTP协议通信过程。

HTTP报文的格式问题

接着昨天的写。昨天的文章有个不确定的地方,就是建立TCP连接之后,向服务器发送的数据,包括命令、头和主体的格式。这三个部分是如何分割的,我是参考了POSTMAN预览的格式和telnet发送的格式猜测的,原认为行直接是通过\n进行区分的,而头和主题是两个\n进行区分。这样发送也是能够正常解析的,今天去读了一下《HTTP权威指南》和Golangnet/http包,具体了解了下到底是如何区分的。

《HTTP权威指南》第三章3.2节,报文的组成部分当中提到:

每行都以一个由两个字符组成的行终止序列作为结束,其中包括一个回车符\r和一个换行符\n,这个终止序列可以写作CRLF。尽管HTTP规范中说明应该用CRLF来表示终止,但稳健的应用程序也应该接受单个换行符\n作为行的终止。

这也就解释了我昨天留下的疑问。简单的说,我那样猜测是能够发送成功,原因是人家服务器牛逼。而标准的写法是两个CRLF,而不是\n。HTTP的写入是在net/http/request.go第365行的Write函数中。可以看到,每一次写入都是以\r\n结束。这里是写入命令和写入头中的User-Agent

// Header lines
fmt.Fprintf(w, "Host: %s\r\n", host)

// Use the defaultUserAgent unless the Header contains one, which
// may be blank to not send the header.
userAgent := defaultUserAgent
if req.Header != nil {
	if ua := req.Header["User-Agent"]; len(ua) > 0 {
		userAgent = ua[0]
	}
}
if userAgent != "" {
	fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
}

HTTP的超时问题

通过阅读Golang源码,再结合昨天的理解,明白了谢大的github.com/astaxie/beego/httplib包,设置请求超时为啥是两个参数。

func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest

IMG-THUMBNAIL

HTTP协议首先要和服务器建立TCP连接,接着再向服务器发送HTTP报文。这里涉及到两个和服务器端的步骤,也就需要两次超时判断。一次是判断TCP连接建立是否超时,另一次是需要判断HTTP报文发送和响应是否超时。

发送超时在net/http/client.go第278行的doFollowingRedirects函数中进行。

if c.Timeout > 0 {
	type canceler interface {
		CancelRequest(*Request)
	}
	tr, ok := c.transport().(canceler)
	if !ok {
		return nil, fmt.Errorf("net/http: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", c.transport())
	}
	timer = time.AfterFunc(c.Timeout, func() {
		reqmu.Lock()
		defer reqmu.Unlock()
		tr.CancelRequest(req)
	})
}

TCP建立连接超时判断是在net/http/transport.go的495行dialConn函数中处理。

if d := t.TLSHandshakeTimeout; d != 0 {
	timer = time.AfterFunc(d, func() {
		errc <- tlsHandshakeTimeoutError{}
	})
}

对于超时的判断都是基于time包的AfterFunc函数,它会在指定时间后调用函数。如果超过指定时间还没有收到响应或者建立连接,就取消这次请求。


参考文献
  • 【1】《HTTP权威指南》
  • 【2】《计算机网络 - 谢希仁》

原文链接:基于TCP套接字,通过Golang模拟HTTP请求(续),转载请注明来源!

EOF