共计 3933 个字符,预计需要花费 10 分钟才能阅读完成。
来自公众号:Gopher 指北
缘起
做 Web 服务的时候,可能会有这样一个业务场景,获取一个 HTTP 申请的残缺 URL。很巧,老许就碰到了这样的业务场景。面对如此简略的需要,CV 大法基本没有展现能力的机会。啪啪啪,获取申请的残缺 URL 代码就进去了。
过后离验证只差一步,老许信念满满,很快,打脸来得很快就像龙卷风。。。
从图中能够晓得,req.URL
中的 Scheme
和Host
均为空,所以 r.URL.String()
无奈失去残缺的申请连贯。这个后果让老许一阵冲动,万万没想到有一天我也有机会发现 Go 源码中可能脱漏的赋值。老许强行按耐住心中的冲动,筹备好好钻研一番,万一成为了 Go 的 Contributor 呢 ^ω^。最初发现官网实现没有问题,因而就有了明天这篇文章。
HTTP1.1 中为什么无奈获取残缺的连贯
HTTP1.1 的 Server 读取申请并构建 Request.URL
对象的逻辑在 request.go 文件的 readRequest
办法中,上面老许对其源码做一个简略剖析总结。
- 读取申请的第一行,HTTP 申请的第一行又称为申请行。
// First line: GET /index.html HTTP/1.0
var s string
if s, err = tp.ReadLine(); err != nil {return nil, err}
- 将申请行的内容别离解析为
req.Method
、req.RequestURI
和req.Proto
。
var ok bool
req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
- 将
req.RequestURI
解析为req.URL
。
rawurl := req.RequestURI
if req.URL, err = url.ParseRequestURI(rawurl); err != nil {return nil, err}
注:当申请办法是 CONNECT 时,上述流程略有变动
通过下面的流程咱们晓得 req.URL
的数据起源为 req.RequestURI
,而req.RequestURI
到底是什么让咱们持续浏览后文。
申请资源
依据 rfc7230 中的定义,申请行分为申请办法、申请资源和 HTTP 版本,别离对应上述的 req.Method
、req.RequestURI
和req.Proto
(request-target 在本文均被译作申请资源)。
对于申请办法有哪些想必不必老许在这儿科普了吧。至于罕用的 HTTP 版本无非就是 HTTP1.1 和 HTTP2。上面次要介绍申请资源的几种模式。
origin-form
这种模式是申请资源中最常见的模式,其格局定义如下。
origin-form = absolute-path ["?" query]
当间接向服务器发动申请时,除开 CONNECT 和 OPTIONS 申请,只容许发送 path 和 query 作为申请资源。如果申请链接的 path 为空,则必须发送 /
作为申请资源。申请链接中的 Host 信息以 Header 头的模式发送。
以 http://www.example.org/where?q=now
为例,申请行和 Host 申请头信息如下
GET /where?q=now HTTP/1.1
Host: www.example.org
absolute-form
这种模式目前仅在向代理发动申请时应用,其格局定义如下。
absolute-form = absolute-URI
依据 rfc7230 中的定义,目前 client 仅会向代理发送这种模式的申请资源,但为了未来某个 HTTP 版本可能会转换为这种模式的申请资源所以 server 须要反对这种模式的申请资源。这大略就是为什么 req.URL
中大部分字段值为空却依然将 URL 各局部定义残缺的起因。
一个 absolute-form
模式的申请行例子如下。
GET http://www.example.org/pub/WWW/TheProject.html HTTP/1.1
authority-form
authority-form
模式的申请资源仅用于 CONNECT
申请中,其格局定义如下。
authority-form = authority
发送 CONNECT
申请时,client 只能发送 URI 的 authority 局部(不蕴含 userinfo 和 @定界符)作为申请资源。这样讲比拟形象,咱们先来看看 http-URI
的定义。
通过下面这张图大略可能猜出来 authority
应该是指 Host 信息。Very Good!你没有猜错!
The origin server for an "http" URI is identified by the authority component, which includes a host identifier and optional TCP port.
下面是 rfc7230 对于 authority 的解释。老许依据本人的翻译,在这里单方面发表 authority
包含主机标识符和可选的端口信息。一个 authority-form
模式的申请行例子如下。
CONNECT www.example.com:80 HTTP/1.1
asterisk-form
asterisk-form
模式的申请资源仅实用于 OPTIONS
申请且只能为*
,其格局定义如下。
asterisk-form = "*"
一个 asterisk-form
模式的申请行例子如下。
OPTIONS * HTTP/1.1
对下面几种模式的申请资源有所理解后,咱们再次回到获取申请的残缺 URL 这一问题自身。以最罕用的 absolute-form
为例(其余模式的申请资源咱们在开发中简直不必思考),申请资源中自身就短少 Host
和Scheme
信息,所以一行代码天然无奈获取申请的残缺 URL。难道咱们就无奈获取到申请的残缺 URL 嘛?当然不是,咱们还能够通过以下两种计划失去残缺的 URL。
计划一:
- 通过
req.Host
失去 Host 相干信息。 - 如果
req.TLS == nil
则为 HTTP 申请,否则为 HTTPS 申请。 - 通过步骤 1、步骤 2 并联合申请行信息即可失去残缺的 URL。
计划二 :
在配置文件中配置好服务的 Host 信息,获取残缺申请时只须要读取配置文件并拼接 req.RequestURI
即可。事实上老许采纳的就是计划二,因为很多服务都在网关前面。当客户端应用 HTTPS 申请网关,网关以 HTTP 申请服务时应用 req.TLS == nil
判断就不合理了。
HTTP2 中为什么无奈获取残缺的连贯
须要留神的是在 HTTP2 中曾经没有申请行的概念了,取而代之的是申请伪标头,这一点老许在 Go 发动 HTTP2.0 申请流程剖析(后篇)——标头压缩这篇文章中提到过。
下图为一次 HTTP2 申请的局部 Header 信息。
从图中能够发现,HTTP1.1 中的申请行曾经没有了。依据 rfc7540 中的定义,申请的伪标头字段有 :method
、:scheme
、:authority
和:path
。
:method
和 :scheme
不须要老许多说,看英文单词的意思就能够了。
:authority
: 依据前文的解释,其值为主机标识符和可选的端口信息。另外须要留神的是 HTTP2 中没有 Host
申请头。
:path
: 如果是 OPTIONS
申请,则其值为*
。其余状况该值为申请 URI 的 path 和 query,如果 path 为空则其值为/
。
在对 HTTP2 申请的伪标头有了一个根本理解后,上面咱们来看一下 Request.URL
的赋值过程。HTTP2 的 Server 读取申请并构建 Request.URL
对象的逻辑在 h2_bundle.go 文件的 (*http2serverConn).newWriterAndRequestNoBody
办法中。
- 如果是
CONNECT
申请通过:authority
构建url_
,否则通过:path
构建url_
。
if rp.method == "CONNECT" {url_ = &url.URL{Host: rp.authority}
requestURI = rp.authority // mimic HTTP/1 server behavior
} else {
var err error
url_, err = url.ParseRequestURI(rp.path)
if err != nil {return nil, nil, http2streamError(st.id, http2ErrCodeProtocol)
}
requestURI = rp.path
}
- 将
url_
赋值给req.URL
。
req := &Request{
Method: rp.method,
URL: url_,
RemoteAddr: sc.remoteAddrStr,
Header: rp.header,
RequestURI: requestURI,
Proto: "HTTP/2.0",
ProtoMajor: 2,
ProtoMinor: 0,
TLS: tlsState,
Host: rp.authority,
Body: body,
Trailer: trailer,
}
因为 :path
标头的值也不蕴含 Host 信息,所以 HTTP2 的 server 也无奈通过 req.URL.String()
失去申请的残缺 URL。
在这里咱们反思一个问题。通过伪标头字段曾经可能失去残缺的 URL,为什么依然只读取 :path
和:authority
中的一个来赋值 req.URL
呢?
老许在这里猜想可能起因是心愿开发者无需关怀申请是 HTTP1.1 还是 HTTP2,防止不必要的 HTTP 版本判断。
对于获取申请残缺 URL 的思考就到这里。最初,衷心希望本文可能对各位读者有肯定的帮忙。
注:
- 写本文时,笔者所用 go 版本为: go1.15.2
参考:
https://tools.ietf.org/html/r…
https://tools.ietf.org/html/r…