基于TCP套接字,通过Golang模拟HTTP请求(续)
使用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
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–