2017-04-19 85 views
1

我一直在嘗試使用Django條件視圖處理功能。基本上我想拒絕一個實體的更新操作,如果它已經被另一個用戶修改過,並且這似乎與Django提供的@condition修飾器很好地工作。Django條件視圖處理裝飾器添加陳舊Etag

但是有一個問題是我在測試時注意到的,後來我檢查了Django的源代碼,發現我認爲可能是一個bug,但只是想在這裏先確認一下,然後向Django提交bug報告,固定。

裝飾器在新請求進入時調用,它首先根據傳入裝飾器的函數計算ETag和上次修改時間戳,然後將控制權交給get_conditional_response()函數。這裏將執行ETag和上次修改的驗證,如果它們與請求中提供的內容不匹配,請求將被拒絕。到現在爲止還挺好。

如果檢查通過,則允許請求並調用視圖來處理請求並生成響應。在處理請求時,如果這是一種不安全的方法,例如PUTPATCH,它會更新實體,這很可能會更改ETag和Last Modified值。

然而,我注意到,到PUTPATCH成功響應發送回用的ETag或最後修改時間戳計算之前實際進行了更新,而現在這些值是無效或失效。這對我來說似乎是錯誤的。在同一實體上執行新的GET,然後在響應中向用戶提供更新的ETag和Last Modified值。

難道你不認爲condition()修飾器應該檢查請求方法是否不安全,那麼它應該在視圖處理後重新計算ETag和Last Modified,然後將新值添加到響應中?

回答

2

我同意這裏有一個錯誤,雖然我認爲它與你描述的有點不同。

條件請求在RFC 7232中定義,但不幸的是,文檔並不是非常明確地指出應該在響應中使用條件標頭的時間。它does say

2.4。何時使用實體標籤和最後修改日期

在200(OK)響應GET或HEAD,源服務器...

這可能會導致一個假設使用頭在其他答覆中沒有定義。

但是,RFC 7231明確允許在對PUT的響應中使用ETags,以匹配新的表示(如同您的直覺一樣)。但是,請注意this caveat

原始服務器不能發送一個驗證報頭字段(第7.2節),如一個ETag或上次修改字段,在對PUT的成功響應,除非該請求的表示數據被保存而不適用於身體的任何轉換...

即,客戶端將使用的ETag的存在或不存在,以確定是否它的表示(它只是發送作爲身體PUT)是實際存儲的一個。 (有關此點的更多詳細信息,請參見this question。)

但是,Django的條件請求API不允許進行此區分。具體來說,用戶無法指示視圖是否保存了表示而沒有「應用於主體的轉換」。所以condition()修飾器無法知道是否需要添加ETag。

所以唯一要做的就是保守,而不是在這種情況下返回條件標題。隨意創建一張票(或者我可以做到)。

+0

感謝您的詳細解答。我同意,不發送任何ETag來響應PUT請求比發送過時/無效的ETag要好得多。 – Safi

+0

我想這取決於視圖的具體實現,例如在我的情況下,在成功處理PUT請求時,視圖將響應200 OK並且響應主體包含更新的資源表示。與此響應一起發送一個ETag將爲客戶端保存一個額外的GET請求,以獲取更新後的ETag以用於將來的條件請求。 然而,您的建議更加保守,並且與RFC更符合。我會創建一張票。 – Safi

+0

@Safi:您可以隨時在您的視圖中自行添加ETag標題。或者你可以編寫自己的裝飾器來包裝'condition()'並添加ETag。 –

0

創建一個自定義中間件來處理GET/HEAD請求中的etag。 以下代碼(Django 1.10)顯示瞭如何使用中間件創建和處理etag。

注意:不要在設置中啓用USE_ETAGS文件

from django.utils.cache import get_conditional_response, set_response_etag 
from django.utils.http import unquote_etag 


class ETag(object): 
    def __init__(self, get_response): 
     self.get_response = get_response 
     # One-time configuration and initialization. 

    def __call__(self, request): 
     # before view 

     response = self.get_response(request) 

     # after view 
     try: 
      if request.method in ('GET', 'HEAD'): 
       if not response.has_header('ETag'): 
        set_response_etag(response) 
       etag = response.get('ETag') 
       return get_conditional_response(
        request, 
        etag=unquote_etag(etag), 
        last_modified=None, 
        response=response, 
       ) 
     except Exception, e: 
      pass 

     return response 

我使用的Django 1.10。如果您使用的版本較低,則使用__call__方法中實施的邏輯覆蓋process_response(self, request, response)方法。並且不要忘記將其添加到設置文件中的MIDDLEWARE/MIDDLEWARE_CLASSES中

MIDDLEWARE = [ 
    'django.middleware.security.SecurityMiddleware', 
    'django.contrib.sessions.middleware.SessionMiddleware', 
    'django.middleware.common.CommonMiddleware', 
    'django.middleware.csrf.CsrfViewMiddleware', 


    # myapp contains middleware.py file and 
    # ETag class is implemented inside the middleware.py file 
    'myapp.middleware.Etag', 
] 
相關問題