<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Web框架的請求上下文

    VSole2022-04-21 16:54:48

    背景

    最近在研究web框架時,對"請求上下文"這個基礎概念有了更多的了解,因此記錄一下,包括以下內容:

    • "請求上下文"是什么?

    • web框架(flask和gin)實現"請求上下文"的區別?

    • "線程私有數據"是什么?

    學習過程

    "請求上下文"是什么?

    根據 Go語言動手寫Web框架 - Gee第二天 上下文Context[1] 和 Context:請求控制器,讓每個請求都在掌控之中[2] 兩篇文章,可以知道從"框架開發者"的角度看,"請求上下文"包括:

    * 請求對象:包括請求方法、路徑、請求頭等內容
    * 響應對象:可以用來返回http響應
    * 工具函數:可以用來更方便地操作"請求對象"和"響應對象"
    

    那么web框架怎么讓"框架的使用者"拿到"請求上下文"呢?

    "框架的使用者怎么"拿到"請求上下文"?

    flask框架中請求上下文是一個全局變量,而gin框架中請求上下文被當作參數傳遞。

    根據flask文檔[3]知道request對象包含有請求信息,可以如下獲取

    from flask import request
    
    @app.route('/login', methods=['POST', 'GET'])
    def login():
        ...
        if request.method == 'POST':
            if valid_login(request.form['username'],
                           request.form['password'])
    

    根據gin文檔[4]知道gin.Context實例c中包含有請求信息,可以如下獲取

    router := gin.Default()
    
      router.GET("/welcome", func(c *gin.Context) {
       firstname := c.DefaultQuery("firstname", "Guest")
       lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
    
       c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
      })
    

    從上面的使用方法可以看出來,flask和gin框架實現"請求上下文"有一些區別:

    * gin框架中"框架使用者"需要把"請求上下文"當作參數,顯示地傳遞
    * flask框架中"框架使用者"只需要request這個全局變量,就能獲得"請求上下文"
    

    于是就有兩個問題:

    * flask的request是個全局變量,那"基于多線程實現"的服務端同時收到多個請求時,request怎么能代表當前線程處理的請求呢?
    * flask似乎對"框架使用者"來說更方便,畢竟不需要多傳遞一個參數。那為什么gin框架不也這么設計呢?
    

    第一個問題其實涉及到"線程私有數據"的概念

    線程私有數據是什么?

    舉個例子,下面代碼中新線程看不到主線程的mydata變量,因為mydata是"主線程"和"新線程"的私有數據"

    import threading
    from threading import local
    
    mydata = local()
    mydata.number = 42
    
    
    def f():
        if getattr(mydata, "number", None) is not None:
            print(mydata.number)    # 這里會打印42嗎?
    
    
    thread = threading.Thread(target=f)
    thread.start()
    thread.join()
    

    threading.local是怎么實現的?

    源碼[5]中可以看到localdict是實際存放數據的對象,每個線程對應一個localdict。

    線程在讀寫"線程私有數據"時,會找到自己的localdict。

    class _localimpl:
      ...
    
      def get_dict(self):
          """Return the dict for the current thread. Raises KeyError if none
          defined."""
          thread = current_thread()
          return self.dicts[id(thread)][1]    # id(thread)是當前線程對象內存地址,每個線程應該是唯一的
    
      def create_dict(self):
          """Create a new dict for the current thread, and return it."""
          localdict = {}
          key = self.key
          thread = current_thread()
          idt = id(thread)    # id(thread)是當前線程對象內存地址,每個線程應該是唯一的
          ...
          self.dicts[idt] = wrthread, localdict
          return localdict
    
      from threading import current_thread, RLock
    

    那flask框架是用了threading.local嗎?

    flask框架用了threading.local嗎?

    先說結論:flask的request對象不是基于"threading.local",而是"contextvars.ContextVar",后者可以實現"協程私有數據"

    下面代碼運行結果中,task1函數不會打印hello,可以看出來ContextVar是實現"協程私有數據"。

    from greenlet import greenlet
    from contextvars import ContextVar
    from greenlet import getcurrent as get_ident
    
    var = ContextVar("var")
    var.set("hello")
    
    
    def p(s):
        print(s, get_ident())
    
        try:
            print(var.get())
        except LookupError:
            pass
    
    
    def task1():
        p("task1")    # 不會打印hello
        # gr2.switch()
    
    
    # 測試ContextVar能否支持"協程私有數據"
    p("main")
    gr1 = greenlet(task1)
    gr1.switch()
    
    # 測試ContextVar能否支持"線程私有數據",結論是支持
    # import threading
    # p("main")
    # thread = threading.Thread(target=task1)
    # thread.start()
    # thread.join()
    

    flask/globals.py[6]中可以看到request是werkzeug庫的Local類型。

    _request_ctx_stack = LocalStack()
    ...
    request: "Request" = LocalProxy(partial(_lookup_req_object, "request"))  # type: ignore
    

    而從werkzeug/local.py源碼[7]可以看出來werkzeug庫的Local是基于contextvars.ContextVar實現的

    class Local:
      ...
    
      def __init__(self) -> None:
          object.__setattr__(self, "_storage", ContextVar("local_storage")) 
    

    所以,flask并沒有用threading.local,而是werkzeug庫的Local類型。也因此在"多線程"或者"多協程"環境下,flask的request全局變量能夠代表到當前線程或者協程處理的請求。

    總結

    web框架讓"框架使用者"拿到"請求對象"有兩種方式,包括"參數傳遞"、"全局變量"。

    實現"全局變量"這種方式時,因為web服務可能是在多線程或者多協程的環境,所以需要每個線程或者協程使用"全局變量"時互不干擾,就涉及到"線程私有數據"的概念。

    SpringWeb中在使用"RequestContextHolder.getRequestAttributes()靜態方法"獲取請求時,也是類似的業務邏輯。

    參考

    [1]Go語言動手寫Web框架 - Gee第二天 上下Context:

    https://geektutu.com/post/gee-day2.html

    [2]Context:請求控制器,讓每個請求都在掌控之中:

    https://time.geekbang.org/column/article/418283

    [3]flask文檔: 

    https://flask.palletsprojects.com/en/2.1.x/quickstart/#accessing-request-data

    [4]gin文檔: 

    https://pkg.go.dev/github.com/gin-gonic/gin#section-readme

    [5]源碼: 

    https://github.com/python/cpython/blob/main/Lib/_threading_local.py

    [6]flask/globals.py: 

    https://github.com/pallets/flask/blob/main/src/flask/globals.py

    [7]werkzeug/local.py源碼: 

    https://github.com/pallets/werkzeug/blob/main/src/werkzeug/local.py

    [8]flask 源碼解析:上下文:

    https://cizixs.com/2017/01/13/flask-insight-context/

    flask上下文
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Web框架的請求上下文
    2022-04-21 16:54:48
    最近在研究web框架時,對"請求上下文"這個基礎概念有了更多的了解,因此記錄一下,包括以下內容: "請求上下文"是什么? web框架(flask和gin)實現"請求上下文"的區別? "線程私有數據"是什么? 學習過程 "請求上下文"是什么? 根據 Go語言動手寫Web框架 - Gee第二天 上下文Context[1] 和 Context:請求控制器,讓每個請求都在掌控之中[2] 兩篇文章
    再看尋找Python SSTI攻擊載荷的過程。獲取基本類,此外,在引入了Flask/Jinja的相關模塊后還可以通過
    跨語言移植一直是技術領域內難以解決的問題,需要解決語言之間的約束,好在先前我們成功使用 Go 實現了 IIOP 協議通信,有了前車之鑒,所以這次我們將繼續使用跨語言方式實現 Flask Session 偽造。本文以 Apache Superset 權限繞過漏洞(CVE-2023-27524) 為例講述我們是如何在 Go 中實現 Flask 框架的 Session 驗證、生成功能的。
    Commander是一款功能強大的命令與控制C2服務器框架,在該工具的幫助下,廣大紅隊和藍隊研究人員可以輕松部署自己的C2組件。
    借助SecureX,您可以通過無縫集成SecureX威脅響應和您現有的安全技術來加速威脅搜尋和事件響應。無論是內置,預打包或自定義的集成,您都可以靈活地將您的工具組合在一起。如果您有Cisco Stealthwatch,Firepower,A...
    sql注入已經出世很多年了,對于sql注入的概念和原理很多人應該是相當清楚了,SSTI也是注入類的漏洞,其成因其實是可以類比于sql注入的。BladeBlade 是 Laravel 提供的一個既簡單又強大的模板引擎。它不是面向最終用戶的,而是一個Java類庫,是一款程序員可以嵌入他們所開發產品的組件。
    shad0w原理分析 part 1
    2021-10-18 16:17:10
    shad0w原理分析!
    雖然市面上關于SSTI的題大都出在python上,但是這種攻擊方式請不要認為只存在于 Python 中,凡是使用模板的地方都可能會出現 SSTI 的問題,SSTI 不屬于任何一種語言。
    loguru 是一個 Python 簡易且強大的第三方日志記錄庫,該庫旨在通過添加一系列有用的功能來解決標準記錄器的注意事項,從而減少 Python 日志記錄的痛苦。
    惡意軟件會利用用戶的信任進行傳播,VirusTotal 利用海量數據總結了四種在惡意軟件中常見的信任濫用方式。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类