El cliente HTTP go está agregando un campo de codificación de transferencia "fragmentado" a la solicitud de mi cliente. Desafortunadamente, esto no es compatible con el servicio al que me estoy conectando y vuelve con un error.

¿Hay alguna manera de desactivar esto?

Este es mi código de solicitud:

// DoHTTPRequest Do a full HTTP Client Request, with timeout
func DoHTTPRequest(method, url string, body io.Reader, headers map[string]string, timeout time.Duration) (*http.Response, error) {

    // Create the request
    req, err := http.NewRequest(method, url, body)
    if err != nil {
        return nil, err
    }

    // Add headers
    for k, v := range headers {
        req.Header.Set(k, v)
    }

    client := &http.Client{
        Timeout: timeout,
    }

    return client.Do(req)
}

Básicamente, me gustaría que se cayera este encabezado. Este cliente está hablando con S3, que es bastante sensible a los encabezados que se envían.

Recibo este error:

A header you provided implies functionality that is not implemented
go
1
hookenz 14 nov. 2017 a las 02:35

2 respuestas

La mejor respuesta

TransferEncoding es un campo directamente en la estructura de solicitud. Si lo establece explícitamente, no se anulará.

req.TransferEncoding = []string{"identity"}

https://golang.org/pkg/net/http/#Request

2
ctcherry 14 nov. 2017 a las 00:13

Configurar el encabezado Transfer Encoding de esta manera hace que no se use fragmentado:

req.TransferEncoding = []string{"identity"}

Sin embargo, las fuentes del cliente http dan la razón por la que se eligió fragmentado en mi caso. Específicamente, estaba usando "PUT" como método y no tenía especificada la longitud del contenido. Así que todo lo que necesitaba era establecer req.ContentLength.

Sin embargo, puede ver que mi función contenedora DoHTTPRequest no sabe qué tan grande debe configurarla. Y había asumido que configurar el encabezado lo haría funcionar originalmente. Bueno, no funciona configurando el encabezado. Y puede ver por qué en las fuentes que determinan si utilizar codificación fragmentada.

// shouldSendChunkedRequestBody reports whether we should try to send a
// chunked request body to the server. In particular, the case we really
// want to prevent is sending a GET or other typically-bodyless request to a
// server with a chunked body when the body has zero bytes, since GETs with
// bodies (while acceptable according to specs), even zero-byte chunked
// bodies, are approximately never seen in the wild and confuse most
// servers. See Issue 18257, as one example.
//
// The only reason we'd send such a request is if the user set the Body to a
// non-nil value (say, ioutil.NopCloser(bytes.NewReader(nil))) and didn't
// set ContentLength, or NewRequest set it to -1 (unknown), so then we assume
// there's bytes to send.
//
// This code tries to read a byte from the Request.Body in such cases to see
// whether the body actually has content (super rare) or is actually just
// a non-nil content-less ReadCloser (the more common case). In that more
// common case, we act as if their Body were nil instead, and don't send
// a body.
func (t *transferWriter) shouldSendChunkedRequestBody() bool {
    // Note that t.ContentLength is the corrected content length
    // from rr.outgoingLength, so 0 actually means zero, not unknown.
    if t.ContentLength >= 0 || t.Body == nil { // redundant checks; caller did them
        return false
    }
    if requestMethodUsuallyLacksBody(t.Method) {
        // Only probe the Request.Body for GET/HEAD/DELETE/etc
        // requests, because it's only those types of requests
        // that confuse servers.
        t.probeRequestBody() // adjusts t.Body, t.ContentLength
        return t.Body != nil
    }
    // For all other request types (PUT, POST, PATCH, or anything
    // made-up we've never heard of), assume it's normal and the server
    // can deal with a chunked request body. Maybe we'll adjust this
    // later.
    return true
}

Entonces mi solución es simplemente:

// DoHTTPRequest Do a full HTTP Client Request, with timeout
func DoHTTPRequest(method, url string, body io.Reader, headers map[string]string, timeout time.Duration) (*http.Response, error) {

    // Create the request
    req, err := http.NewRequest(method, url, body)
    if err != nil {
        return nil, err
    }

    // Add headers
    for k, v := range headers {
        req.Header.Set(k, v)
        // Set the Content Length correctly if specified.
        if strings.EqualFold(k, "Content-Length") {
            length, err := strconv.Atoi(v)
            if err != nil {
               return nil, fmt.Errorf("Bad Content-Length header")
            }
            req.ContentLength = int64(length)
        }
    }

    client := &http.Client{
        Timeout:   timeout,
        Transport: &loghttp.Transport{},
    }

    return client.Do(req)
}

Lo que satisface a S3 en cuanto a tener una longitud de contenido correcta. No necesitaba configurar TransferEncoding a la identidad.

2
Matt 14 nov. 2017 a las 00:54