Requests: Cabeçalho "Transfer-Encoding: chunked" definido mesmo se Content-Length for fornecido, o que faz com que o corpo não seja realmente fragmentado

Criado em 4 out. 2013  ·  52Comentários  ·  Fonte: psf/requests

Script de teste

import requests
import time

def f():
    yield b"lol"
    time.sleep(2)
    yield b"man"

requests.post('http://127.0.0.1:8801/', data=f(), headers={"Content-Length": 6})

Resultado atual

Recebido no servidor:

$ nc -p 8801 -l
POST / HTTP/1.1
Host: 127.0.0.1:8801
User-Agent: python-requests/2.0.0 CPython/3.3.1 Linux/3.11.0-031100rc4-generic
Accept: */*
Transfer-Encoding: chunked
Content-Length: 6
Accept-Encoding: gzip, deflate, compress

lolman

Resultado esperado

Não esperava "Transfer-Encoding: chunked" desde que forneci o Content-Length. Se as solicitações insistem em fazer a codificação de transferência em partes, ela deve desconsiderar o comprimento do conteúdo e realmente dividir o conteúdo (como faria se não houvesse o cabeçalho Content-Length fornecido).

Breaking API Change Bug

Comentários muito úteis

@timuralp Eu me oponho a adicionar um sinalizador para isso. É realmente inaceitável ter uma implementação HTTP / 1.1 que não consegue lidar com a codificação de transferência em partes em 2016. É um requisito de especificação há tanto tempo que a primeira especificação que o exigia é quase velha o suficiente para votar nos Estados Unidos da América: Eu não não acho que podemos manter a folga de entidades por não fazer isso.

Da minha perspectiva, o bug aqui continua sendo que podemos emitir incorretamente tanto Content-Length quanto Transfer-Encoding. Claro, minha perspectiva não é vinculativa. ;)

Todos 52 comentários

Eu concordo, devemos escolher uma dessas duas opções. =)

Então, comentando aqui como deveria ter feito> 6 horas atrás:

Se as solicitações insistem em fazer a codificação de transferência em partes, ela deve desconsiderar o comprimento do conteúdo e realmente dividir o conteúdo (como faria se não houvesse o cabeçalho Content-Length fornecido).

Você está insinuando que, neste caso, não dividimos os dados de fato. Você tem evidências reais disso? A saída por si só não é suficiente para demonstrar isso.

Além disso, executar seu exemplo acima em 2.0.0 não funciona: Eu recebo este traceback:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/api.py", line 88, in post
    return request('post', url, data=data, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/api.py", line 44, in request
    return session.request(method=method, url=url, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/sessions.py", line 357, in request
    resp = self.send(prep, **send_kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/sessions.py", line 460, in send
    r = adapter.send(request, **kwargs)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/adapters.py", line 319, in send
    timeout=timeout
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 541, in urlopen
    body=body, headers=headers)
  File "/Users/icordasc/virtualenv/vcr/lib/python2.7/site-packages/requests/packages/urllib3/connectionpool.py", line 366, in _make_request
    conn.request(method, url, **httplib_request_kw)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 955, in request
    self._send_request(method, url, body, headers)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 989, in _send_request
    self.endheaders(body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 951, in endheaders
    self._send_output(message_body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 815, in _send_output
    self.send(message_body)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py", line 787, in send
    self.sock.sendall(data)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
TypeError: must be string or buffer, not generator

Este não é mesmo um problema real, pelo que posso dizer. Não pelo menos no 2.0.0, que é a única versão atualmente suportada de solicitações.

Não esperava "Transfer-Encoding: chunked" desde que forneci o Content-Length

O que lhe deu a impressão de que seria esse o caso?

O que me deu a impressão foi o fato de que Requests não chunk (mesmo se definir o cabeçalho), uma vez que configurei Content-Length.

O exemplo funciona com Python 3.3.

Experimentando no Python 3.3, ele não explode, por outro lado, ele fragmenta o conteúdo. A fragmentação não depende da plataforma, então estou completamente confuso com isso.

POST / HTTP/1.1
Host: 127.0.0.1:8801
Content-Length: 6
User-Agent: python-requests/1.2.3 CPython/3.3.2 Darwin/12.5.0
Transfer-Encoding: chunked
Accept: */*
Accept-Encoding: gzip, deflate, compress

3
lol
3
man
0

Em segundo lugar, ainda gostaria de saber o que lhe deu a impressão de que definir o cabeçalho Content-Length impediria a codificação em partes dos dados.

@ sigmavirus24 Você não está usando Requests 2.0.0 na instalação do Python 3.3.

@ysangkok bom ponto. :-) Com 2.0 está definitivamente quebrado. Independentemente disso, minha opinião está definitivamente dentro do nosso tribunal, removendo o cabeçalho Content-Length .

Você provavelmente está certo @ sigmavirus24 , provavelmente deveríamos remover o cabeçalho Content-Length.

Hm, não estou mais totalmente convencido do meu próprio argumento. Procurei as discussões antigas sobre os próprios usuários que definem o cabeçalho Content-Length e devo estar me lembrando de conversas antigas do IRC.

Ainda sou da opinião de que os usuários não devem definir esses cabeçalhos sozinhos e que devemos removê-los, no entanto, acho que esse problema aponta para um problema muito mais importante, que é o comportamento muito diferente de solicitações em duas versões diferentes de Python.

Em 2.7 (como demonstrei), definir o cabeçalho Content-Length não altera a forma como as solicitações carregam os dados. No 3.3, no entanto, @ysangkok está correto ao definir que envia tudo assim que pode (ele ainda usa o gerador, mas não envia de uma forma realmente fragmentada).

Uma maneira fácil de corrigir isso é remover o cabeçalho ao usar um gerador (ou sempre fornecer um comportamento consistente), a falha disso é que este é um comportamento incompatível com versões anteriores.

A outra maneira fácil é quebrar a consistência da API, nem sempre usando a codificação de transferência em partes com um gerador. @Lukasa, isso definitivamente precisa de um pensamento mais profundo, já que não consigo encontrar as conversas antigas em que os usuários eram advertidos por

Para ser honesto, porém, eu nunca esperaria que definir um cabeçalho mudasse o comportamento de usar um gerador.

Esta certamente é uma situação extremamente complicada

Bleh. Eu vou ponderar.

Além disso, essa não é uma alteração significativa da API que eu hesitaria em fazer porque me pergunto quantas pessoas estão realmente contando com esse comportamento.

Eu me deparei com uma situação em que o comprimento de um gerador (por exemplo, um arquivo transmitido de outro servidor ou um disco realmente grande no arquivo) é conhecido. Por exemplo:

response = requests.get('http://example.com/mybig.iso', stream=True)
length = response.headers.get('Content-Length')

def exhaust(response):
    while True:
        out = response.raw.read(1024*1024)
        if not out:
            break
        yield out

response = requests.post('http://example.com/upload', data=exhaust(response), headers={'Content-Length': length})

Essa pode ser uma razão válida para manter o Content-Length por perto. A única solução agora (que eu saiba) é esgotar o gerador na memória e passar os bytes diretamente para dentro.

@bryanhelmig o servidor que você está enviando exige que você envie o cabeçalho Content-Length?

@bryanhelmig você viu os comentários na solicitação de pull vinculada?

De qualquer forma, não entendo por que Content-Transfer-Encoding não é apenas um sinalizador. Não há necessidade de deletar cabeçalhos (ou fazer qualquer outro tipo de manipulação), nunca foi uma questão de Content-Length estar certo ou errado, o problema real é que a presença de Content-Length semi-desativa Content-Transfer -Codificação, o que não faz sentido algum. Mas apenas fazer Requests ignorar Content-Length não resolve o problema real, que é, que Requests usa Content-Transfer-Encoding quando parece (parece que deveria ser ao ler de um gerador), mesmo que muitos web os servidores nem mesmo o suportam.

Ignorar o tamanho do conteúdo confundirá as pessoas que o fornecem. Se você (@ sigmavirus24) insiste em não transmitir, por que não lançar uma exceção? Como você disse, essa funcionalidade provavelmente não é usada amplamente.

Na solicitação pull, você disse "O caso de uso no problema é apenas um exemplo do comportamento. Não há justificativa para o motivo de você estar fazendo o que está fazendo.". Eu discordo, acho que o código original neste problema é um comportamento perfeitamente normal e, na verdade, acho que o streaming de dados POST é um grande caso de uso, e que é ridículo se alguém for forçado a usar Content-Transfer-Encoding ou recorrer a bibliotecas de nível inferior ao fazer streaming / usar geradores.

Então, para resumir: Content-Transfer-Encoding deve ser um sinalizador, combinações de parâmetros ilegais devem provocar exceções e sinalizadores fornecidos pelo usuário devem ser enviados quando possível. E, claro, não deveria ser possível semi-desabilitar Content-Transfer-Encoding.

Para para para.

Todos respirem.

@ysangkok Você pode fazer uploads de streaming sem geradores muito bem. Fornece Solicita um objeto semelhante a um arquivo no parâmetro de dados e isso funcionará. Sim, não é tão simples quanto usar um gerador, mas tudo bem porque ainda não é muito difícil.

Nesse ínterim, as Solicitações não devem sugerir que está fragmentando os dados, quando isso não ocorre. Estamos todos de acordo quanto a isso. A questão é o que devemos fazer _no seu caso específico_: a saber, fornecer um gerador e um Content-Length . Você e @ sigmavirus24 discordam legitimamente sobre este assunto, _o que está bem_. No entanto, podemos todos reconhecer que ambos os lados têm razões racionais para esperar sua posição?

@ysangkok Você disse que "Ignorar o comprimento do conteúdo confundirá as pessoas que o fornecem". @ sigmavirus24 afirma que ignorar a documentação muito clara quando fornecida com um gerador irá confundir as pessoas que fazem isso. Vocês dois estão certos.

(Como uma observação lateral, o fato de que muitos servidores não entendem Transfer-Encoding é apenas uma afirmação selvagem baseada em nenhuma evidência que eu tenha visto. Até que qualquer evidência seja fornecida, estou optando por ignorá-la. )

De uma forma ou de outra, teremos que escolher o que fazemos aqui. É possível que a decisão correta seja lançar uma exceção quando um gerador e Content-Length são fornecidos. Isso é viável. Isso nem torna o caso de @bryanhelmig pior, porque ele deveria apenas passar response.raw direto ao invés de embrulhar em um decorador ( para seu benefício, Bryan ).

Estou naturalmente inclinado a sentar em cima do muro aqui e lançar uma exceção YoureACrazyPerson , mas posso ver por que vocês dois acreditam no que acreditam. Em particular, tomar decisões com base em cabeçalhos fornecidos pelo usuário é chato e confuso, e devemos tentar não fazer isso. O que se segue, no entanto, são linhas rígidas:

  1. Controlar Transfer-Encoding não será uma bandeira. Nem agora, nem nunca. As solicitações não apresentam sinalizadores de minúsculos casos especiais como este.
  2. Não podemos fazer nada.
  3. As solicitações _não_ são obrigadas a oferecer suporte a todos os casos de uso. Eu ficarei feliz em jogar qualquer caso de uso sob um barramento se isso tornar a API melhor.

o servidor que você está enviando exige que você envie o cabeçalho Content-Length?

@ sigmavirus24 sim. :-( 411 para tentativas sem um comprimento de conteúdo. IMO bastante chato.

você viu os comentários na solicitação de pull vinculada?

@ysangkok eu fiz.

ele deve apenas passar a resposta. Desenhe direto em vez de embrulhar em um decorador

@Lukasa Essa foi minha edição original, na verdade, mas não tenho certeza do que isso nos

Obrigado por todas as respostas completas a todos. Na verdade, não há problema se os usuários em situações exóticas precisarem trocar para uma biblioteca diferente para uma entre 100 solicitações. A vida é muito mais fácil para os outros 99 casos.

Na verdade, não há problema se os usuários em situações exóticas precisarem trocar para uma biblioteca diferente para uma entre 100 solicitações.

Eu prefiro Solicitações _Make Easy Things Easy & Hard Things Possible_ :)

@ piotr-dobrogost Concordo, mas se tornar uma coisa difícil possível requer tornar uma coisa fácil ainda mais difícil, preferimos deixar a coisa fácil fácil. =)

Algumas coisas:

No que me diz respeito, este tem sido (e continuará a ser, até que cheguemos a uma decisão) comportamento indefinido

Como uma observação lateral, o fato de que muitos servidores não entendem Transfer-Encoding é apenas uma afirmação selvagem baseada em nenhuma evidência que eu tenha visto. Até que qualquer evidência seja fornecida, estou optando por ignorá-la.

Há uma diferença entre servidores que não entendem uma codificação de transferência em partes e servidores que não querem respeitá-la. Suspeito que o último seja o caso de @bryanhelmig . Como alternativa, o aplicativo pode ter sido escrito por alguém que não entende ou não sabe sobre Transfer-Encoding e, portanto, requer um Content-Length.

É possível que a decisão correta seja lançar uma exceção quando um gerador e Content-Length são fornecidos. Isso é viável.

As exceções podem ser muito extremas neste caso. Geralmente, não levantamos exceções para nada além de URLs inválidos. A maneira como processamos os parâmetros de dados e arquivos pode gerar exceções, mas não tratamos de casos especiais. Dito isso, falamos sobre como é uma ideia ruim quando os usuários especificam seus próprios cabeçalhos de comprimento de conteúdo e host (entre outros que provavelmente estou esquecendo). Como essas não são práticas tecnicamente inválidas, mas sim práticas contra as quais recomendamos, sugiro que, em vez disso, disparemos um aviso e façamos a coisa certa em certas situações bem documentadas.

  • Se recebermos um cabeçalho de Host, emitiremos um aviso, mas não o excluiremos.
  • Se recebermos um objeto cujo tamanho podemos determinar e o cabeçalho Content-Length for fornecido, devemos emitir um aviso de que fornecê-lo, nesse caso, eles não deveriam fazê-lo. Não tenho certeza se devemos substituir sua configuração, no entanto.
  • Se recebermos um gerador e um cabeçalho Content-Length, devemos emitir um aviso e remover o cabeçalho.

Usar avisos é uma maneira mais gentil de informar ao usuário o que ele está fazendo não é aconselhável. Também cobre o fato de que muitos de nossos usuários parecem não se importar em ler a documentação e, portanto, a biblioteca torna-se uma espécie de autodocumentada. Isso também nos dá a capacidade de mudar o comportamento no futuro. E, melhor ainda, se os usuários quiserem desabilitar os avisos, eles podem, porque o python fornece uma maneira de silenciar os avisos.

Há uma diferença entre servidores que não entendem uma codificação de transferência em partes e servidores que não querem respeitá-la. Suspeito que o último seja o caso de @bryanhelmig . Como alternativa, o aplicativo pode ter sido escrito por alguém que não entende ou não sabe sobre Transfer-Encoding e, portanto, requer um Content-Length.

Na verdade, acho que é muito provável que seja esse o caso na maioria dos meus exemplos. Nós (@zapier) tentamos fazer upload de arquivos para mais de uma dúzia de APIs diferentes e alguns deles que exigem o tempo limite de Content-Length (parece) com Transfer-Encoding em partes.

Como uma observação lateral, o fato de que muitos servidores não entendem Transfer-Encoding é apenas uma afirmação selvagem baseada em nenhuma evidência que eu tenha visto. Até que qualquer evidência seja fornecida, estou optando por ignorá-la.

Eu poderia montar um conjunto de testes de como vários serviços respondem a Content-Length / Transfer-Encoding, mas acho que mesmo que sejam APIs implementadas incorretamente, isso não deveria aconselhar o design de solicitações python. Mais fácil ainda, eu poderia apenas citar nomes com base na minha experiência de lutar contra isso na semana passada, mas, novamente, se eles são bugs de API / servidor, qual é o uso de tais informações para solicitações de python?

Em vez disso, sugiro que disparemos um aviso e, em seguida, façamos a coisa certa em certas situações bem documentadas.

Concorde com o comportamento padrão, mas às vezes a realidade supera a "coisa certa" (especialmente quando você não tem controle sobre um servidor potencialmente quebrado). Pode ser bom documentar uma técnica a ser substituída (mesmo que ela recomende que o usuário faça muito trabalho, como escrever um adaptador personalizado).

Só para esclarecer: por "a coisa certa", não quero dizer necessariamente a coisa RFC. (Caso seja isso que você acha que eu quis dizer)

Eu poderia montar um conjunto de testes de como vários serviços respondem a Content-Length / Transfer-Encoding, mas acho que mesmo que sejam APIs implementadas incorretamente, isso não deveria aconselhar o design de solicitações python.

Eu concordo que servidores com comportamento inadequado não devem influenciar nosso design.

Mais fácil ainda, eu poderia apenas citar nomes com base na minha experiência de lutar contra isso na semana passada, mas, novamente, se eles são bugs de API / servidor, qual é o uso de tais informações para solicitações de python?

Já sabemos o quão quebrada está a web. Independentemente disso, saber se devemos excluir o cabeçalho após avisar o usuário ou deixá-lo seria onde esses dados poderiam ser úteis.

Bleh. Isso me deixa triste.

Ok, acho que o plano de @ sigmavirus24 é o melhor aqui, pelo menos por enquanto.

Algumas coisas que surgiram estão me distraindo de conseguir alguns dados detalhados para vocês, mas aqui está um resumo do que eu vi:

O maior infrator que já vi são os endpoints de API que requerem upload de várias partes: eles geralmente precisam de um Content-Length ou surtam no Transfer-Encoding chunked (não tenho certeza de qual). Esse não é o caso em todos os lugares, mas aqui estão as partes que posso extrair de nossa fonte com um grep rápido:

  1. O Twitter faz várias partes e falha com codificação em partes / sem comprimento.
  2. O Podio faz multiparte e falha com codificação em partes / sem comprimento.
  3. O Facebook faz várias partes e funciona com codificação em partes / sem comprimento.
  4. O Salesforce faz várias partes e funciona com codificação em partes / sem comprimento.

A razão pela qual isso está fresco em minha mente é que construímos um gerador de corpo multiparte personalizado que funciona com arquivos "preguiçosos" que também podem calcular o comprimento. Isso aconteceu para borbulhar muitos dos problemas que estamos falando aqui. No momento, estamos apenas trapaceando e fazendo getvalue() pelos endpoints de upload com falha. Provavelmente iremos revisitá-lo algum dia.

Os outros exemplos são mais difíceis de usar o grep, mas as empresas de arquivos (Box, Dropbox, SugarSync, etc ...) entendem e funcionam perfeitamente com codificação em partes, então não se preocupe.

Esperançosamente, isso ilumina um pouco mais nosso caso de uso no mundo real. Gostaria de poder obter mais informações para confirmar quais cabeçalhos geralmente causam quais erros (geralmente são tempos limite).

Isso deve ser mais fácil de testar agora que temos algumas APIs que podemos reproduzir, ou seja, o Twitter.

Tenho exatamente o mesmo caso de uso: enviar dados para um servidor que não oferece suporte à codificação em partes. O comprimento dos dados é conhecido com antecedência, mas não vem de um arquivo (de um gerador).
Eu esperava que definir um cabeçalho de comprimento de conteúdo desabilitasse o cálculo de comprimento em solicitações e também desabilitasse a codificação de transferência em partes.
A esta altura, consigo solucionar esse problema adicionando um atributo adicional 'len' ao meu objeto gerador, de modo que as solicitações utils.super_len () retornem algo de forma que a codificação fragmentada não seja escolhida pelas solicitações: isso é feio e brilhante.
Como um usuário Unix (e como @ piotr-dobrogost), espero que o programador saiba o que faz, e a biblioteca deva obedecer: fornecer um cabeçalho de comprimento de conteúdo é uma indicação clara de que a codificação em partes não deve ser usada. Mas isso está em conflito com sua frase acima: "Eu nunca esperaria que definir um cabeçalho mudasse o comportamento de usar um gerador". Bem, se está claramente documentado, não vejo o ponto. Não quebraria a API, não é?

@netheosgithub Eu diria que mudar esse comportamento constitui absolutamente uma mudança na API. Considere o estado atual da API, a partir da documentação :

As solicitações também oferecem suporte à codificação de transferência em partes para solicitações de entrada e saída. Para enviar uma solicitação codificada por fragmentos, basta fornecer um gerador (ou qualquer iterador sem comprimento) para seu corpo.

Observe que a documentação _não diz_ "Para enviar uma solicitação codificada em partes, basta fornecer um gerador e não definir o cabeçalho Content-Length." Isso torna isso uma mudança de API no meu livro. Não apenas qualquer alteração de API: uma péssima. A ideia de que definir um cabeçalho muda o corpo da solicitação me deixa profundamente desconfortável. Considere se alguém nos pediu uma solicitação semelhante em que se eles definirem o cabeçalho Content-Type: application/json , devemos codificar em JSON o parâmetro data vez de codificá-lo no formulário! Eu jogaria esse pedido em um piscar de olhos.

Acho que devemos tentar resolver a raiz do problema: por que as pessoas estão usando geradores nesta situação?

Atualmente, fornecer um gerador e especificar um comprimento de conteúdo é um erro (ele gera uma solicitação http inválida), portanto, esse caso de uso não deve ser usado por ninguém. É por isso que pensei que não iria quebrar os programas do seu usuário.
Por que geradores? Os dados nem sempre são fornecidos por um arquivo como um objeto. Por exemplo, eu gostaria de observar o progresso do upload, gerando dados por bloco, etc. (caso contrário, eu teria que substituir os métodos read ())

Se apenas o seu argumento 'solicitação de HTTP inválida' fosse válido. Eu gostaria de viver naquele mundo. No entanto, muitos e muitos servidores certamente aceitarão essa combinação.

O RFC 2616 fornece esta seção esclarecedora sobre a determinação do comprimento da mensagem:

O comprimento de transferência de uma mensagem é o comprimento do corpo da mensagem como ele aparece na mensagem; isto é, após a aplicação de quaisquer códigos de transferência. Quando um corpo de mensagem é incluído com uma mensagem, o comprimento de transferência desse corpo é determinado por um dos seguintes (em ordem de precedência):

  1. Qualquer mensagem de resposta que "NÃO DEVE" incluir um corpo de mensagem (como as respostas 1xx, 204 e 304 e qualquer resposta a uma solicitação HEAD) é sempre encerrada pela primeira linha vazia após os campos de cabeçalho, independentemente da entidade- campos de cabeçalho presentes na mensagem.
  2. Se um campo de cabeçalho Transfer-Encoding (Transfer-Encoding) estiver presente e tiver qualquer valor diferente de "identidade", o comprimento da transferência será definido pelo uso da codificação de transferência "fragmentada" (Transfer Codings), a menos que a mensagem seja encerrado ao fechar a conexão.
  3. Se um campo de cabeçalho Content-Length (Content-Length) estiver presente, seu valor decimal em OCTETs representará o comprimento da entidade e o comprimento da transferência. O campo de cabeçalho Content-Length NÃO DEVE ser enviado se esses dois comprimentos forem diferentes (ou seja, se um campo de cabeçalho Transfer-Encoding estiver presente). Se uma mensagem for recebida com um campo de cabeçalho Transfer-Encoding e um campo de cabeçalho Content-Length, o último DEVE ser ignorado.
    ...
    As mensagens NÃO DEVEM incluir um campo de cabeçalho Content-Length e uma codificação de transferência de não identidade. Se a mensagem incluir um código de transferência de não-identidade, o Content-Length DEVE ser ignorado.

Para esclarecer: estamos de acordo que Requests está fazendo a coisa errada. No entanto, _não_ estamos de acordo que definir um cabeçalho Content-Length deve desativar a fragmentação. Essa é a parte que está causando contenção.

Devo também salientar que qualquer servidor que falhe com a codificação em partes também viola o RFC 2616:

Todos os aplicativos HTTP / 1.1 DEVEM ser capazes de receber e decodificar a codificação de transferência "fragmentada" e DEVEM ignorar extensões de extensão de bloco que eles não entendem.

Esta não é uma tentativa de dizer que as solicitações não devem corrigir esse problema, é simplesmente apontar que todos os envolvidos estão violando o RFC 2616.

Acho que devemos tentar resolver a raiz do problema: por que as pessoas estão usando geradores nesta situação?

Um dos nossos casos de uso foi pegar um arquivo possivelmente muito grande com um tamanho conhecido de um serviço e enviá-lo para um segundo serviço, exceto que os dados binários eram simplesmente uma das partes de um POST de várias partes (com metadados JSON como o outro) que o segundo serviço necessário.

Portanto, envolvemos o construtor de várias partes em um gerador; um do qual poderíamos saber o tamanho. Parecia uma solução muito óbvia e útil, pelo menos até que esse recurso / bug de solicitação nos parasse (acredito que mantemos um fork agora, mas o truque do superlen pode ajudar a desfazer isso!).

Agora temos outros casos de uso de geradores com lentes, mas eu teria que desenterrá-los.

Bryan

Em 18 de fevereiro de 2014, às 12h45, Cory Benfield [email protected] escreveu:

Acho que devemos tentar resolver a raiz do problema: por que as pessoas estão usando geradores nesta situação?

@bryanhelmig Mm, sim. Acho que a solução correta existe de agora em diante para usar o codificador multipart de streaming do conjunto de ferramentas de solicitações .

No momento, porém, não está claro para mim por que há tanta resistência ao uso de objetos semelhantes a arquivos aqui.

Na edição # 1895, encontrei esse problema mesmo com um objeto semelhante a um arquivo. A solicitação era um PUT, e o objeto semelhante a um arquivo era sys.stdin, que na verdade é tão semelhante a um arquivo que acionou a codificação em partes como acontece com um arquivo normal. Fornecer um Content-Length (que eu aprendi por meios externos) nesta situação fez com que HTTPAdapter.send não fizesse chunking como o esperado, mas PreparedRequest.prepare_body ainda notou que o objeto semelhante a arquivo era iterável e adicionou um cabeçalho Transfer-Encoding qualquer maneira. O servidor (Amazon S3) engasgou com esse cabeçalho, embora a solicitação teria funcionado de outra forma.

@gholms Não. sys.stdin é tão _não_ como um arquivo que acionou a codificação em partes. Se você nos passar um objeto de arquivo, iremos transmiti-lo, não dividi-lo. O problema com sys.stdin é que ele não tem comprimento, o que se enquadra na descrição fornecida nos documentos conforme mencionado acima:

qualquer iterador sem comprimento

A expectativa geral é que os usuários nunca definirão explicitamente o cabeçalho Content-Length. Isso ocorre porque as Solicitações podem acabar bagunçando a forma como o corpo da solicitação é configurado.

Finalmente, a ideia de que 'seria de se esperar' que fornecer um cabeçalho Content-Length desabilitará a fragmentação também não é verdadeira: eu esperaria que removêssemos o cabeçalho. =)

De qualquer forma, essa discussão já está longa demais. Quando fornecido com um iterador sem comprimento e um cabeçalho Content-Length fornecido pelo usuário, temos estas opções:

  1. Crie uma exceção.
  2. Limpe o cabeçalho Content-Length
  3. Não chunk.

Qualquer uma dessas três opções nos coloca em conformidade com a RFC. É claro que todos que levantam essa questão preferem (3). @ sigmavirus24?

Sempre achei que devíamos rebater o cabeceamento. Na minha experiência, o usuário pensa que sabe o que está fazendo, mas raramente sabe o suficiente para saber realmente o que está fazendo. Eu geralmente gosto de deixar o usuário dar um tiro no próprio pé e nós fizemos isso até agora e não nos rendeu muito. Na verdade, tivemos a oportunidade de disparar aquele cabeçalho antes e, especificamente, não fizemos isso. Isso levou exatamente a esse problema. Vamos ser lógicos sobre isso:

Nossa API e documentação sempre sustentaram que a passagem de um iterável sem nenhuma maneira de medir o comprimento significa que usaremos a codificação de transferência em partes. Pela especificação, neste caso, devemos ignorar o cabeçalho Content-Length porque alguns servidores não o obedecem. Deve ser ignorado. Não estamos fazendo nada de errado ao enviá-lo independentemente da codificação, o servidor está fazendo a coisa errada ao não ignorá-lo. Para se proteger contra servidores ruins como este, nós _devemos_ excluí-lo.

Se no 3.0.0 decidirmos mudar a API de modo que fornecer o cabeçalho não seja um pedaço diferente. No entanto, não estamos considerando o 3.0.0 e esse é o verdadeiro problema aqui. O que podemos fazer para resolver esse problema agora. O que podemos fazer é seguir as especificações.

Estou inclinado a concordar com você @ sigmavirus24. Vou ver se consigo alguns minutos com Kenneth em algum momento para conversar sobre isso com ele.

Estou no mesmo barco que @bryanhelmig e @netheosgithub , tenho um gerador onde sei de antemão qual o tamanho dos blocos combinados e tenho um servidor que não suporta uploads em blocos (um aplicativo WSGI, WSGI de acordo com meu a pesquisa não oferece suporte a codificação em partes). Os dados do gerador são muito grandes para caber na RAM, portanto, combinar os pedaços antes e passá-los para as solicitações está fora de questão.
Houve algum novo desenvolvimento em relação a este problema?

@jbaiter então o que você precisa é passar um objeto que se comporta como um arquivo com um __len__ definido. Tome, por exemplo, o MultipartEncoder do toolbelt . Você quer streaming, então, em geral, tudo que você precisa é algo assim, que tem um método read e uma maneira de determinar seu comprimento (de preferência implementando __len__ ). A API do seu objeto pode ser semelhante a:

class JBaiterStreamer(object):
    def __init__(self, size, iterator):
        self.size = size
        self.iterator = iterator

    def __len__(self):
        return self.size

    def read(self, *args):
        try:
            return next(self.iterator)
        except StopIteration:
            return b''

É verdade que não tentei ver se esse código funcionará, mas algo nesse sentido deve.

Obrigado @ sigmavirus24 : +1 :, Eu fiz exatamente isso, só estava me perguntando se agora havia uma maneira mais elegante de fazer isso

Talvez haja uma boa maneira de fornecer isso através do toolbelt, hein @Lukasa ?

@ sigmavirus24 Com certeza. =)

@ sigmavirus24 "Não estamos fazendo nada errado ao enviá-lo, independentemente da codificação, o servidor está fazendo a coisa errada ao não ignorá-lo." agora está errado:

RFC 7230: "Um remetente NÃO DEVE enviar um campo de cabeçalho Content-Length em qualquer mensagem que contenha um campo de cabeçalho Transfer-Encoding."

@ztane, este é um caso diferente daquele ao qual a questão do estouro de pilha está se referindo. No futuro, leia atentamente uma discussão antes de gerar notificações para todos os seus participantes.

@ sigmavirus24 que foi causado por "transfer-Encoding: chunked, content-length set => body not chunked." E o RFC 7230 proíbe a configuração de Content-Length com Transfer-Encoding .

@ztane, obrigado por continuar a não ler esta discussão. Este bug em particular é sobre alguém configurando um cabeçalho Content-Length manualmente e esperando que as solicitações não usem um upload em partes quando fornecem um gerador (que sempre dispara um upload em partes com solicitações). O link stackoverflow que você forneceu e sobre o que está falando é um bug diferente que está sendo tratado em outro lugar. Você está confundindo esta discussão com detalhes irrelevantes porque permitimos que os usuários atiram no próprio pé. Por exemplo, permitimos que os usuários especifiquem um cabeçalho de comprimento de conteúdo incorreto ou um cabeçalho de host inválido, ou basicamente o que quiserem. Nós não policiamos tudo nem o faremos. Concentre sua conversa no problema _correto_ no futuro.

Eu tropecei nisso ao usar boto3 para fazer um PUT de um stream contra AWS S3 (https://github.com/boto/botocore/issues/911). No meu caso, o tamanho do stream é conhecido - é um objeto de outro provedor. Ao usar assinaturas S3 versão 2, "Transfer-Encoding: chunked" não é suportado e S3 retorna o erro 501. A documentação do boto3 parece sugerir que definir o Content-Length contornaria esse problema, pois afirmam o seguinte:

ContentLength (integer) -- Size of the body in bytes. This parameter is useful when
the size of the body cannot be determined automatically. [1]

Embora o boto3 deva fazer duas coisas - corrigir sua documentação e garantir que "Transfer-Encoding: chunked" não esteja definido - para o consumidor indireto de solicitações, esse comportamento é difícil de depurar. Se o cabeçalho Content-Length for ignorado, o lançamento de uma exceção tornaria pelo menos óbvio o que está acontecendo. Como está, foi ofuscado pelo fato de que S3 retorna apenas um erro 501 com uma explicação de que um dos cabeçalhos não é compatível (mas não especifica o cabeçalho).

A solução alternativa sugerida para embrulhar e fornecer o comprimento funciona, mas parece feia. Expandir a API para permitir a alternância da codificação em partes (enquanto mantém o comportamento atual como padrão) seria uma maneira palatável de avançar (em oposição a usar o cabeçalho Content-Length como sinalizador)?

[1] http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.put_object

@timuralp Eu me oponho a adicionar um sinalizador para isso. É realmente inaceitável ter uma implementação HTTP / 1.1 que não consegue lidar com a codificação de transferência em partes em 2016. É um requisito de especificação há tanto tempo que a primeira especificação que o exigia é quase velha o suficiente para votar nos Estados Unidos da América: Eu não não acho que podemos manter a folga de entidades por não fazer isso.

Da minha perspectiva, o bug aqui continua sendo que podemos emitir incorretamente tanto Content-Length quanto Transfer-Encoding. Claro, minha perspectiva não é vinculativa. ;)

Além disso, se boto3 deseja substituir forçosamente o cabeçalho Content-Length, eles devem utilizar o fluxo de solicitação preparado para que possam remover o cabeçalho Transfer-Encoding .

@Lukasa @ sigmavirus24 justo o suficiente - obrigado pela resposta rápida. Vou continuar tentando corrigir o problema de boto nesse projeto.

A maneira que consegui contornar essa incapacidade de controlar se algo é fragmentado é controlar se meu POST usa a parte "dados" ou "arquivos" da chamada do método. Parece estar funcionando bem.

    req = Request('POST',url='http://apiendpointurl',
        headers=headers,
        data=fs)
    prepped = req.prepare()

    if 'Transfer-Encoding' in prepped.headers and prepped.headers['Transfer-Encoding'] == 'chunked':
        res=postAsFiles(headers, fs)
    else:
        res=postAsData(headers,fs)

onde a única diferença entre postAsFiles e postAsData é esta:

def postAsData(headers, fs):
    return requests.post(
        url='http://apiendpointurl',
        headers=headers,
        data=fs)

def postAsFiles(headers, fs):
    return requests.post(
        url='http://apiendpointurl',
        headers=headers,
        files=fs)

Com # 3897, Requests 3.0.0 irá gerar uma exceção neste caso, impedindo o envio de ambos os cabeçalhos.

Os usuários que desejam enviar seu próprio cabeçalho Content-Length poderão modificar os cabeçalhos usando o fluxo PreparedRequests . Observe que Content-Length ainda não funcionará para dados passados ​​como um gerador. Se o usuário PRECISA especificar o comprimento do conteúdo, os geradores precisarão ser consumidos e passados ​​em algo com uma representação de string / arquivo. Na maioria dos casos, esses cabeçalhos devem ser simplesmente deixados sozinhos para tratamento automático por solicitações.

Esta página foi útil?
0 / 5 - 0 avaliações