来自公众号:Gopher指北

缘起

做Web服务的时候,可能会有这样一个业务场景,获取一个HTTP申请的残缺URL。很巧,老许就碰到了这样的业务场景。面对如此简略的需要,CV大法基本没有展现能力的机会。啪啪啪,获取申请的残缺URL代码就进去了。

过后离验证只差一步,老许信念满满,很快,打脸来得很快就像龙卷风。。。

从图中能够晓得,req.URL中的SchemeHost均为空,所以r.URL.String()无奈失去残缺的申请连贯。这个后果让老许一阵冲动,万万没想到有一天我也有机会发现Go源码中可能脱漏的赋值。老许强行按耐住心中的冲动,筹备好好钻研一番,万一成为了Go的Contributor呢^^。最初发现官网实现没有问题,因而就有了明天这篇文章。

HTTP1.1中为什么无奈获取残缺的连贯

HTTP1.1的Server读取申请并构建Request.URL对象的逻辑在request.go文件的readRequest办法中,上面老许对其源码做一个简略剖析总结。

  1. 读取申请的第一行,HTTP申请的第一行又称为申请行。
// First line: GET /index.html HTTP/1.0var s stringif s, err = tp.ReadLine(); err != nil {    return nil, err}
  1. 将申请行的内容别离解析为req.Methodreq.RequestURIreq.Proto
var ok boolreq.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
  1. req.RequestURI解析为req.URL
rawurl := req.RequestURIif req.URL, err = url.ParseRequestURI(rawurl); err != nil {    return nil, err}
注:当申请办法是CONNECT时,上述流程略有变动

通过下面的流程咱们晓得req.URL的数据起源为req.RequestURI,而req.RequestURI到底是什么让咱们持续浏览后文。

申请资源

依据rfc7230中的定义, 申请行分为申请办法、申请资源和HTTP版本,别离对应上述的req.Methodreq.RequestURIreq.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.1Host: 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为例(其余模式的申请资源咱们在开发中简直不必思考),申请资源中自身就短少HostScheme信息,所以一行代码天然无奈获取申请的残缺URL。难道咱们就无奈获取到申请的残缺URL嘛?当然不是,咱们还能够通过以下两种计划失去残缺的URL。

计划一

  1. 通过req.Host失去Host相干信息。
  2. 如果req.TLS == nil则为HTTP申请,否则为HTTPS申请。
  3. 通过步骤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办法中。

  1. 如果是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}
  1. 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的思考就到这里。最初,衷心希望本文可能对各位读者有肯定的帮忙。

  1. 写本文时, 笔者所用go版本为: go1.15.2

参考:

https://tools.ietf.org/html/r...

https://tools.ietf.org/html/r...