Django-rest-framework: Adicione / valide etags facilmente.

Criado em 29 jun. 2011  ·  24Comentários  ·  Fonte: encode/django-rest-framework

Django tem um decorador etag / condition / last_modified incrível. Não funciona com as visualizações baseadas em classe drf, pois você não pode decorar 'get' com elas. Como get retorna um objeto que não é uma resposta http, não há como adicionar o cabeçalho etag à resposta.

Eu gostaria de ver uma maneira de fazer isso de dentro do drf. Estou pensando em algo como um método substituível em um recurso, ou uma visão (ou um mixin) que pode ser usado para gerar o etag.

A outra maneira de fazer isso no django é usar o middleware, mas ele não pode atalho para executar o corpo da visualização como o decorador pode.

Enhancement

Comentários muito úteis

Infelizmente, a implementação padrão e a documentação sobre a funcionalidade Etag em extensões drf são simplesmente erradas e perigosamente cheias de bugs. Ele muda a Etag se o _request_ muda, não se a _response_ muda. O que é exatamente o que você deseja para o cache do lado do servidor e exatamente o que você não deseja para um Etag.

Todos 24 comentários

Muito feliz por comentários sobre isso, no entanto.

Ok, então, inicialmente o que eu escrevi foi isso ....

Legal, sim, eu realmente adoraria ver isso.

Algumas idéias - você deve ser capaz de usar apenas View.add_header, ao invés de View._ETAG que você tem atualmente.
(E parece que .add_header provavelmente deve ser movido para a classe ResponseMixin.)

Em segundo lugar, gostaria de ver as decorações @condition , @etag e @last_modified podem ser praticamente um clone direto de https://github.com/django/django/blob/master/django/views/decorators /http.py , apenas substituindo alguns add_header e ErrorResponses

Mas estava examinando as coisas um pouco mais ...

E talvez este não seja o caminho certo a seguir ...

Na verdade, você pode retornar HttpResponses a partir de visualizações do framework REST, eles simplesmente não obtêm todo o material usual de negociação / serialização de conteúdo aplicado. Os decoradores @last_modified , @etag e @condition só retornam HttpResponses vazios, então isso não é realmente um problema.

Então, o que estou pensando é, se simplesmente adicionamos __setitem__ __getitem__ e has_header à classe Response, então acho que o @last_modified existente do Django, @etag decoradores @condition devem funcionar muito bem em uma visão de estrutura REST _tanto que_ a visão esteja usando o estilo return Response(status, data) vez do estilo return data .

Obviamente, seria útil documentarmos isso, mas pode fazer mais sentido do que ter que replicar algo que o Django já faz.

O que você acha?

Pode haver um problema com o uso de decoradores django: não tenho certeza se eles funcionarão com métodos, apenas funções básicas. O decorador que escrevi foi fortemente baseado em um que encontrei por meio do StackOverflow apenas para este caso.

Pode não ser o caso, caso em que esta solução parece superior.

Dito isso, tenho retornado o estilo de dados de retorno, pois é menos clichê, e geralmente estou retornando apenas objetos que desejo serializar como json. Podemos ser capazes de fazer funcionar das duas maneiras.

Outra opção pode ser um mixin que os adiciona à classe View.

Também me ocorreu que os decoradores django podem não fazer a coisa certa com respeito a solicitações condicionais PUT, POST e DELETE. Acabei de enviar um patch para o sinatra para corrigir esse problema.

Ignore a última parte: claramente não li o código corretamente.

Na verdade, há um ponto aí: esses decoradores podem não funcionar em ATMs em _métodos_, uma vez que têm o argumento 'self' adicional. Vou investigar isso e apresentar um tíquete para o Django, já que eles devem trabalhar com CBVs também ...

Ah, ok - vejo o @method_decorator agora ... https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating -class-based-views
Então eu acho que para que isso seja fechado, precisamos:

  1. Alguns ajustes na resposta
  2. Alguma documentação leve

Tenho certeza que olhei naquele documento, mas não vi aquele decorador!

Tentei usar o decorador django e o resultado inicial foi realmente estranho, com informações que deveriam pertencer apenas às visualizações que aparecem em funções etag não relacionadas.

Depois de algumas horas tentando torná-lo um pouco melhor, encontrei uma solução que resolveu meus problemas e parece geral o suficiente. Quais são seus pensamentos sobre isso:

https://bitbucket.org/vitormazzi/django-rest-framework/changeset/6f8de4500c6f

Esperamos dar um novo fôlego a esse problema, agora que estamos nos lançamentos 2.x.

Meus pensamentos aqui são em grande parte remendados de tentar adicionar a funcionalidade em um projeto e de ler este post, então definitivamente ainda está difícil.

Vejo duas áreas em que o DRF precisa considerar ETags - uso em visualizações e como obter a representação única da versão de uma instância.

Visualizações

PEGUE
As solicitações GET simplesmente precisam servir os objetos ETag no cabeçalho apropriado. Uma mudança de uma linha para RetrieveModelMixin pode facilmente adicionar isto:

def retrieve(self, request, *args, **kwargs):
    self.object = self.get_object()
    serializer = self.get_serializer(self.object)
    headers = {'ETag': self.object.etag}
    return Response(serializer.data, headers=headers)

COLOCAR, CORRIGIR, EXCLUIR
Uma verificação geral para atualizar verbos HTTP pode ser feita na visualização dispatch ou possivelmente puxada para outro método, pois será necessário verificar se as ETags estão ativadas (consulte a seção de opções abaixo):

    header_etag = request.META.get('HTTP_IF_MATCH')
    if header_etag is None:
        return Response({'error': 'IF_MATCH header is required'}, status=400)

Em seguida, uma verificação mais detalhada após recuperar o objeto para ver se a solicitação acha que está procurando o objeto certo:

    if self.object.etag != header_etag:
        return Response({'error': 'object has been updated since you last saw it'}, status=412)

Representação Única de uma Versão de Instância

Não acho que a geração real de ETag de um objeto deva ser um problema de DRF. Tenho testado usando o tempo de época do campo updated do meu objeto, mas pude ver facilmente que precisa ser mais complexo.

Proponho que o DRF procure obj.etag por padrão, mas é configurável usando o fluxo normal de CBV, por exemplo, get_etag() e etag_var = 'get_my_objects_etag' .

Também precisaremos fazer com que as ETags sejam recuperadas de objetos como uma string, já que estamos comparando com um cabeçalho e tentar interpretar o tipo seria, na melhor das hipóteses, doloroso.

Opções

  • Configuração global (como com serializadores, etc) para ativar ou desativar o uso de ETags.
  • Duas configurações no Views:

    • use_etags (ou algo semelhante) - um booleano

    • etag_var - string de um nome de função que podemos getattr no objeto em questão

@ghickman - gostaria de ver o comportamento para determinar ETags e LastModified semelhante ao das outras classes conectáveis. Ou seja, Tenha algo como:

class MyView(views.APIView):
    cache_lookup_classes = []

As assinaturas em cache devem lidar com ETags e LastModified, e há duas coisas diferentes que queremos fornecer:

  • Determine uma etag e / ou última modificação dada uma instância de objeto.
  • Determine preventivamente um etag e / ou última modificação de acordo com a solicitação recebida.

Haveria um BaseCacheLookup , com duas assinaturas de método que podem ser semelhantes a:

.object_etag_and_last_modified(self, view, obj)
.preemptive_etag_and_last_modified(self, view, request, *view_kwargs, **view_kwargs)

Dado um objeto retornar duas tuplas de (etag, last modified) , qualquer uma das quais pode simplesmente ser nenhuma.
Se a solicitação de entrada contiver um cabeçalho If-Modified-Since ou If-None-Match correspondente, uma resposta 304 Not Modified será retornada. Se a resposta de entrada contiver uma correspondência If-Match ou If-Unmodified-Since, então uma resposta 412 Precondition Failed será retornada.

Isso permitiria que um CacheLookupClass correspondesse à implementação que você descreveu, mas também a outras variantes.

Você também pode aplicar várias classes de pesquisa de cache, em diferentes granularidades modificadas pela última vez, por exemplo,
inclua GlobalLastModifiedLookup além de ObjectETagLookup . Isso permitiria que a visualização retornasse preventivamente antes de fazer qualquer chamada de banco de dados, se nenhuma gravação tivesse sido feita desde a cópia em cache. (Mesmo as políticas realmente básicas como essa podem fazer uma grande diferença se você estiver usando o cache do servidor com Varnish)

O lado de classe conectável deste parece razoável para você?

Não tinha pensado em LastModified porque não o estou usando em minha implementação atual, mas definitivamente faz sentido incluí-lo, dado seu propósito.

As classes conectáveis ​​parecem uma ótima ideia, especialmente se incluirmos as implementações LastModified e ETag como exemplos básicos. Eu gosto da ideia de que o cache GET seria super fácil de ativar com mudanças mínimas em um projeto.

Eu prefiro dividir a geração etag e last_modified em dois métodos (desses nomes) que, como você sugeriu, retornam None quando não implementados. Os back-ends do CacheLookup podem então escolher implementar um e / ou outro. Sempre poderíamos fornecer um método utilitário por conveniência ( cachable_obj_repr ou unique_obj_repr talvez?) Que combinasse os dois se você achasse que seria útil.

tl; dr sim, o lado da classe conectável parece razoável e deve oferecer muito mais flexibilidade. Estou feliz em começar a escrever o patch para isso.

Olá pessoal. Se você estiver interessado, implementei uma abordagem diferente para suporte etag em minha biblioteca de extensões http://chibisov.github.io/drf-extensions/docs/

@chibisov Neato. Nós realmente deveríamos terminar o # 1019, então temos algum lugar nos documentos para vincular a pacotes como este.

Fechar isto como # 1019 foi fechado e o pacote de @chibisov está listado.

Este foi descrito como 3.3 de propósito, pois eu gostaria que demos alguma direção formal sobre isso em algum ponto. Não estou superpreocupado se decidirmos deixar isso fechado, mas está no meu roteiro interno.

Infelizmente, a implementação padrão e a documentação sobre a funcionalidade Etag em extensões drf são simplesmente erradas e perigosamente cheias de bugs. Ele muda a Etag se o _request_ muda, não se a _response_ muda. O que é exatamente o que você deseja para o cache do lado do servidor e exatamente o que você não deseja para um Etag.

@mbox a melhor coisa seria abrir um problema sobre isso nas extensões drf ou se você acha que é um problema "central" do DRF aberto aqui. Observe que um teste reprovado seria um bom começo para examinarmos o problema.

@mbox @xordoquy
Acabei de enviar um PR para drf-extensions (https://github.com/chibisov/drf-extensions/pull/171) que permite o controle de simultaneidade otimista para manipular recursos por meio da API DRF. Ele está usando um hash semântico de todos os campos de objeto e incluí um aplicativo de teste para fins de demonstração. Ele foi testado contra DRF> = 3.3.1 e django> = 1.8 com Python 2.7, 3.4, 3.5.

obrigado

Apenas uma nota para os futuros leitores - criei um pequeno pacote para usar decoradores condicionais do Django junto com o DRF. Então, se você estiver interessado:
https://github.com/jozo/django-rest-framework-condition

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