检测gzip编码以手动解压响应,但缺少’Content-Encoding’头部。

huangapple go评论115阅读模式
英文:

Detect gzip encoding to manually decompress response, but 'Content-Encoding' header missing

问题

我正在使用Go语言中的net/http库进行HTTP GET请求。在响应中,我得到了12个头部信息。但是当我通过Postman运行完全相同的查询时,我得到了16个头部信息。其中一个缺失的头部信息是Content-Encoding。我理解这可能是CORS问题。

但是由于我在请求中没有设置头部Accept-Encoding: gzip,但我仍然在响应中得到了gzip编码,Go传输并没有自动解压缩响应。因此,我需要能够手动检测编码,然后进行解压缩。但是,我无法检测到响应中是否缺少Content-Encoding头部信息。

以下是我尝试实现此功能的代码:

func calcDistanceAndDurationWithUberApi(originLat float64, originLon float64, destinationLat float64, destinationLon float64) (float64, float64, error) {
    endpoint := "https://api.uber.com/v1.2/estimates/price"
    parameters := fmt.Sprintf("?start_latitude=%v&start_longitude=%v&end_latitude=%v&end_longitude=%v", originLat, originLon, destinationLat, destinationLon)

    req, err := http.NewRequest("GET", endpoint+parameters, nil)
    if err != nil {
        return 0, 0, err
    }

    req.Header.Add("Authorization", "Token "+getUberApiKey())
    req.Header.Add("Accept-Language", "en_US")
    req.Header.Add("Content-Type", "application/json")

    httpClient := &http.Client{}
    resp, err := httpClient.Do(req)
    if err != nil {
        return 0, 0, err
    }
    if resp.StatusCode != 200 {
        return 0, 0, errors.NotFound("Response: %v", resp.StatusCode)
    }
    defer resp.Body.Close()

    pretty.Println("- REQUEST:")
    pretty.Println(req)

    // 检查服务器是否发送了gzip压缩的响应。如果是,则解压缩。
    var respReader io.ReadCloser
    switch resp.Header.Get("Content-Encoding") {
    case "gzip":
        fmt.Println("Content-Encoding is gzip")
        respReader, err = gzip.NewReader(resp.Body)
        defer respReader.Close()
    default:
        fmt.Println("Content-Encoding is Not gzip")
        respReader = resp.Body
    }

    pretty.Println("- RESPONSE HEADER:")
    pretty.Println(resp.Header)

    pretty.Println("- RESPONSE BODY:")
    pretty.Println(respReader)

    return 0, 0, nil
}

响应状态为200 OK。以下是输出(响应):

- RESPONSE HEADER:
http.Header{
    "Content-Language":          {"en"},
    "Cache-Control":             {"max-age=0"},
    "X-Uber-App":                {"uberex-nonsandbox", "optimus"},
    "Strict-Transport-Security": {"max-age=604800", "max-age=2592000"},
    "X-Content-Type-Options":    {"nosniff"},
    "Date":                      {"Fri, 19 May 2017 07:52:17 GMT"},
    "Content-Geo-System":        {"wgs-84"},
    "Connection":                {"keep-alive"},
    "X-Frame-Options":           {"SAMEORIGIN"},
    "X-Xss-Protection":          {"1; mode=block"},
    "Server":                    {"nginx"},
    "Content-Type":              {"application/json"},
}
- RESPONSE BODY:
&http.gzipReader{
body: &http.bodyEOFSignal{
    body: &http.body{
        src: &internal.chunkedReader{
            r:  &bufio.Reader{
                buf: {0x48, 0x54, .......... }
英文:

I am using net/http library in 'Go' to make an HTTP GET request. In the response, i get 12 headers. But when i run the exact same query through postman, i get 16 headers. One of those missing is 'Content-Encoding'. I understand this must be a CORS issue.

But since i have not set the header Accept-Encoding: gzip in my request, and i am still getting the gzip encoding in response, the Go transport is not automatically decompressing the response for me. So, i need to be able to manually detect the encoding and then decompress it. But, i cannot detect if the 'Content-Encoding' header is missing in the response.

Here is my code where i try to do this:

func calcDistanceAndDurationWithUberApi(originLat float64, originLon float64, destinationLat float64, destinationLon float64) (float64, float64, error) {
endpoint := "https://api.uber.com/v1.2/estimates/price"
parameters := fmt.Sprintf("?start_latitude=%v&start_longitude=%v&end_latitude=%v&end_longitude=%v", originLat, originLon, destinationLat, destinationLon)
req, err := http.NewRequest("GET", endpoint + parameters, nil)
if err != nil {
return 0, 0, err
}
req.Header.Add("Authorization", "Token " + getUberApiKey())
req.Header.Add("Accept-Language", "en_US")
req.Header.Add("Content-Type", "application/json")
httpClient := &http.Client{}
resp, err := httpClient.Do(req)
if err != nil {
return 0, 0, err
}
if resp.StatusCode != 200 {
return 0, 0, errors.NotFound("Response: %v", resp.StatusCode)
}
defer resp.Body.Close()
pretty.Println("- REQUEST: ")
pretty.Println(req)
// Check if server sent gzipped response. Decompress if yes.
var respReader io.ReadCloser
switch resp.Header.Get("Content-Encoding") {
case "gzip":
fmt.Println("Content-Encoding is gzip")
respReader, err = gzip.NewReader(resp.Body)
defer respReader.Close()
default:
fmt.Println("Content-Encoding is Not gzip")
respReader = resp.Body
}
pretty.Println("- RESPONSE HEADER: ")
pretty.Println(resp.Header)
pretty.Println("- RESPONSE BODY: ")
pretty.Println(respReader)
return 0, 0, nil
}

The response status is '200 OK'. Here is the output (Response):

- RESPONSE HEADER: 
http.Header{
"Content-Language":          {"en"},
"Cache-Control":             {"max-age=0"},
"X-Uber-App":                {"uberex-nonsandbox", "optimus"},
"Strict-Transport-Security": {"max-age=604800", "max-age=2592000"},
"X-Content-Type-Options":    {"nosniff"},
"Date":                      {"Fri, 19 May 2017 07:52:17 GMT"},
"Content-Geo-System":        {"wgs-84"},
"Connection":                {"keep-alive"},
"X-Frame-Options":           {"SAMEORIGIN"},
"X-Xss-Protection":          {"1; mode=block"},
"Server":                    {"nginx"},
"Content-Type":              {"application/json"},
}
- RESPONSE BODY: 
&http.gzipReader{
body: &http.bodyEOFSignal{
body: &http.body{
src: &internal.chunkedReader{
r:  &bufio.Reader{
buf: {0x48, 0x54, .......... }

答案1

得分: 1

我屈服于Uber API的固执,添加了另一个请求头req.Header.Add("Accept-Encoding", "gzip")

现在我得到了响应头"Content-Encoding": "gzip",尽管我仍然得到了一个无法解读的响应体,但这超出了本问题的范围。

英文:

I gave in to the stubbornness of the uber api and added another request header, req.Header.Add("Accept-Encoding", "gzip").

Now i am getting the response header "Content-Encoding": "gzip", although i am still getting an undecipherable response body, but that's beyond the scope of this question.

答案2

得分: 1

如果您不禁用压缩1,也不使用Accept-Encoding: gzip手动请求压缩,那么我所说的"自动模式"将被使用。在自动模式下,Go会自动添加Accept-Encoding: gzip头部,然后如果服务器响应中包含Content-Encoding: gzip头部,Go会将响应体包装在一个Gzip读取器中,并且移除响应头部中的Content-Encoding和Content-Length字段2。我对这种做法持不同意见,因为实际上是在向最终用户提供关于真实响应内容的错误信息。与之相反,cURL会给出纯粹的响应,不管你做了什么:

PS C:\> curl -v --compressed https://github.com/manifest.json
< content-encoding: gzip
< content-length: 345

为了解决这个问题,我编写了一个http.Transport的包装器:

package mech

import (
   "compress/gzip"
   "io"
   "net/http"
   "strings"
)

type Transport struct { http.Transport }

func (t Transport) RoundTrip(req *http.Request) (*http.Response, error) {
   if !t.DisableCompression {
      req.Header.Set("Accept-Encoding", "gzip")
   }
   res, err := t.Transport.RoundTrip(req)
   if err != nil {
      return nil, err
   }
   if strings.EqualFold(res.Header.Get("Content-Encoding"), "gzip") {
      gz, err := gzip.NewReader(res.Body)
      if err != nil {
         return nil, err
      }
      res.Body = readCloser{gz, res.Body}
   }
   return res, nil
}

type readCloser struct {
   io.Reader
   io.Closer
}
  1. https://golang.org/pkg/net/http#Transport.DisableCompression
  2. https://github.com/golang/go/blob/go1.16.5/src/net/http/transport.go#L2186-L2192
英文:

If you don't disable compression 1, and you don't manually request compression with Accept-Encoding: gzip, then what I call "automatic mode" is used. With automatic mode, Go automatically adds Accept-Encoding: gzip, then if server responds Content-Encoding: gzip, Go wrap the response body in a Gzip reader, and removes the Content-Encoding and Content-Length response headers 2. I disagree with this practice, as the end user is essentially being lied to about what the true response was. Contrast this with cURL, which gives you the pure response, regardless of what you do:

PS C:\&gt; curl -v --compressed https://github.com/manifest.json
&lt; content-encoding: gzip
&lt; content-length: 345

To deal with this, I wrote a wrapper for http.Transport:

package mech

import (
   &quot;compress/gzip&quot;
   &quot;io&quot;
   &quot;net/http&quot;
   &quot;strings&quot;
)

type Transport struct { http.Transport }

func (t Transport) RoundTrip(req *http.Request) (*http.Response, error) {
   if !t.DisableCompression {
      req.Header.Set(&quot;Accept-Encoding&quot;, &quot;gzip&quot;)
   }
   res, err := t.Transport.RoundTrip(req)
   if err != nil {
      return nil, err
   }
   if strings.EqualFold(res.Header.Get(&quot;Content-Encoding&quot;), &quot;gzip&quot;) {
      gz, err := gzip.NewReader(res.Body)
      if err != nil {
         return nil, err
      }
      res.Body = readCloser{gz, res.Body}
   }
   return res, nil
}

type readCloser struct {
   io.Reader
   io.Closer
}
  1. https://golang.org/pkg/net/http#Transport.DisableCompression
  2. https://github.com/golang/go/blob/go1.16.5/src/net/http/transport.go#L2186-L2192

huangapple
  • 本文由 发表于 2017年5月19日 15:57:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/44064556.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定