Mục đích của ngăn xếp bối cảnh của Flask là gì?


157

Tôi đã sử dụng bối cảnh yêu cầu / ứng dụng trong một thời gian mà không hiểu đầy đủ về cách thức hoạt động hoặc lý do tại sao nó được thiết kế theo cách của nó. Mục đích của "ngăn xếp" khi nói đến yêu cầu hoặc bối cảnh ứng dụng là gì? Đây là hai ngăn xếp riêng biệt, hay chúng đều là một phần của một ngăn xếp? Là bối cảnh yêu cầu được đẩy lên một ngăn xếp, hay chính nó là một ngăn xếp? Tôi có thể đẩy / bật nhiều bối cảnh lên nhau không? Nếu vậy, tại sao tôi muốn làm điều đó?

Xin lỗi vì tất cả các câu hỏi, nhưng tôi vẫn bối rối sau khi đọc tài liệu về Bối cảnh yêu cầu và Bối cảnh ứng dụng.


5
kronosapiens.github.io/blog/2014/08/14/ Nhật IMO, bài đăng trên blog này cho tôi mô tả dễ hiểu nhất về bối cảnh bình.
nhiệm vụ.liao

Câu trả lời:


242

Nhiều ứng dụng

Bối cảnh ứng dụng (và mục đích của nó) thực sự khó hiểu cho đến khi bạn nhận ra rằng Flask có thể có nhiều ứng dụng. Hãy tưởng tượng tình huống mà bạn muốn có một trình thông dịch WSGI Python duy nhất chạy nhiều ứng dụng Flask. Chúng tôi không nói về Blueprints ở đây, chúng tôi đang nói về các ứng dụng Flask hoàn toàn khác nhau.

Bạn có thể thiết lập phần này tương tự như phần tài liệu Flask trong ví dụ "Xử lý ứng dụng" :

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

Lưu ý rằng có hai ứng dụng Flask hoàn toàn khác nhau đang được tạo ra "frontend" và "backend". Nói cách khác, hàm tạo Flask(...)ứng dụng đã được gọi hai lần, tạo hai thể hiện của ứng dụng Flask.

Bối cảnh

Khi bạn đang làm việc với Flask, bạn thường kết thúc việc sử dụng các biến toàn cục để truy cập các chức năng khác nhau. Ví dụ: bạn có thể có mã đọc ...

from flask import request

Sau đó, trong khi xem, bạn có thể sử dụng requestđể truy cập thông tin của yêu cầu hiện tại. Rõ ràng, requestkhông phải là một biến toàn cầu bình thường; trong thực tế, nó là một giá trị địa phương bối cảnh . Nói cách khác, có một số phép thuật đằng sau hậu trường nói rằng "khi tôi gọi request.path, hãy lấy paththuộc tính từ requestđối tượng của yêu cầu HIỆN TẠI". Hai yêu cầu khác nhau sẽ có kết quả khác nhau cho request.path.

Trong thực tế, ngay cả khi bạn chạy Flask với nhiều luồng, Flask vẫn đủ thông minh để giữ các đối tượng yêu cầu bị cô lập. Khi làm như vậy, có thể có hai luồng, mỗi luồng xử lý một yêu cầu khác nhau, đồng thời gọi request.pathvà nhận thông tin chính xác cho các yêu cầu tương ứng của chúng.

Đặt nó lại với nhau

Vì vậy, chúng ta đã thấy rằng Flask có thể xử lý nhiều ứng dụng trong cùng một trình thông dịch, và cũng vì cách mà Flask cho phép bạn sử dụng các quả cầu "ngữ cảnh cục bộ" nên phải có một số cơ chế để xác định yêu cầu "hiện tại" là gì ( để làm những việc nhưrequest.path ).

Đặt các ý tưởng này lại với nhau, cũng nên hiểu rằng Flask phải có một số cách để xác định ứng dụng "hiện tại" là gì!

Bạn cũng có thể có mã tương tự như sau:

from flask import url_for

Giống như requeství dụ của chúng ta , url_forhàm có logic phụ thuộc vào môi trường hiện tại. Tuy nhiên, trong trường hợp này, rõ ràng để thấy rằng logic phụ thuộc rất nhiều vào ứng dụng nào được coi là ứng dụng "hiện tại". Trong ví dụ frontend / backend được hiển thị ở trên, cả ứng dụng "frontend" và "backend" đều có thể có lộ trình "/ login" và vì vậyurl_for('/login') sẽ trả về một cái gì đó khác nhau tùy thuộc vào chế độ xem đang xử lý yêu cầu cho ứng dụng frontend hay backend.

Để trả lời câu hỏi của bạn ...

Mục đích của "ngăn xếp" khi nói đến yêu cầu hoặc bối cảnh ứng dụng là gì?

Từ các tài liệu bối cảnh yêu cầu:

Bởi vì bối cảnh yêu cầu được duy trì bên trong dưới dạng ngăn xếp, bạn có thể đẩy và bật nhiều lần. Điều này rất thuận tiện để thực hiện những thứ như chuyển hướng nội bộ.

Nói cách khác, mặc dù thông thường bạn sẽ có 0 hoặc 1 mục trên các yêu cầu "hiện tại" hoặc ứng dụng "hiện tại" này, có thể bạn có thể có nhiều hơn.

Ví dụ được đưa ra là nơi bạn sẽ yêu cầu trả về kết quả của "chuyển hướng nội bộ". Giả sử người dùng yêu cầu A, nhưng bạn muốn quay lại người dùng B. Trong hầu hết các trường hợp, bạn đưa ra chuyển hướng cho người dùng và trỏ người dùng đến tài nguyên B, nghĩa là người dùng sẽ chạy yêu cầu thứ hai để tìm nạp B. A Cách xử lý hơi khác một chút sẽ là thực hiện chuyển hướng nội bộ, điều đó có nghĩa là trong khi xử lý A, Flask sẽ đưa ra yêu cầu mới cho chính tài nguyên B và sử dụng kết quả của yêu cầu thứ hai này làm kết quả của yêu cầu ban đầu của người dùng.

Đây là hai ngăn xếp riêng biệt, hay chúng đều là một phần của một ngăn xếp?

Chúng là hai ngăn xếp riêng biệt . Tuy nhiên, đây là một chi tiết thực hiện. Điều quan trọng hơn không phải là có nhiều ngăn xếp, nhưng thực tế là bất cứ lúc nào bạn cũng có thể nhận được ứng dụng hoặc yêu cầu "hiện tại" (trên cùng của ngăn xếp).

Là bối cảnh yêu cầu được đẩy lên một ngăn xếp, hay chính nó là một ngăn xếp?

"Ngữ cảnh yêu cầu" là một mục của "ngăn xếp ngữ cảnh yêu cầu". Tương tự với "bối cảnh ứng dụng" và "ngăn xếp bối cảnh ứng dụng".

Tôi có thể đẩy / bật nhiều bối cảnh lên nhau không? Nếu vậy, tại sao tôi muốn làm điều đó?

Trong một ứng dụng Flask, bạn thường không làm điều này. Một ví dụ về nơi bạn có thể muốn là cho một chuyển hướng nội bộ (được mô tả ở trên). Tuy nhiên, ngay cả trong trường hợp đó, có lẽ bạn sẽ có Flask xử lý một yêu cầu mới, và vì vậy Flask sẽ thực hiện tất cả các thao tác đẩy / bật cho bạn.

Tuy nhiên, có một số trường hợp bạn muốn tự mình thao tác ngăn xếp.

Chạy mã ngoài yêu cầu

Một vấn đề điển hình mà mọi người gặp phải là họ sử dụng phần mở rộng Flask-SQLAlchemy để thiết lập cơ sở dữ liệu SQL và định nghĩa mô hình bằng cách sử dụng mã giống như những gì được hiển thị bên dưới ...

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

Sau đó, họ sử dụng các giá trị appdbtrong một tập lệnh nên được chạy từ trình bao. Ví dụ: tập lệnh "setup_tables.py" ...

from myapp import app, db

# Set up models
db.create_all()

Trong trường hợp này, tiện ích mở rộng Flask-SQLAlchemy biết về appứng dụng, nhưng trong quá trình create_all()đó, nó sẽ xuất hiện một lỗi phàn nàn về việc không có bối cảnh ứng dụng. Lỗi này là hợp lý; bạn không bao giờ nói với Flask ứng dụng nào nên xử lý khi chạy create_allphương thức.

Bạn có thể tự hỏi tại sao cuối cùng bạn không cần with app.app_context()cuộc gọi này khi bạn chạy các chức năng tương tự trong chế độ xem của mình. Lý do là Flask đã xử lý việc quản lý bối cảnh ứng dụng cho bạn khi nó xử lý các yêu cầu web thực tế. Vấn đề thực sự chỉ xuất hiện bên ngoài các chức năng xem này (hoặc các cuộc gọi lại như vậy), chẳng hạn như khi sử dụng các mô hình của bạn trong tập lệnh một lần.

Nghị quyết là tự đẩy bối cảnh ứng dụng, có thể được thực hiện bằng cách thực hiện ...

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

Điều này sẽ đẩy một bối cảnh ứng dụng mới (sử dụng ứng dụng của app , hãy nhớ rằng có thể có nhiều hơn một ứng dụng).

Kiểm tra

Một trường hợp khác mà bạn muốn thao tác ngăn xếp là để thử nghiệm. Bạn có thể tạo một bài kiểm tra đơn vị xử lý một yêu cầu và bạn kiểm tra kết quả:

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty

3
Điều này vẫn còn khó hiểu với tôi! Tại sao không có một bối cảnh yêu cầu duy nhất và thay thế nó nếu bạn muốn thực hiện chuyển hướng nội bộ. Có vẻ như một thiết kế rõ ràng với tôi.
Maarten

@Maarten Nếu trong khi xử lý yêu cầu A bạn thực hiện yêu cầu B và yêu cầu B thay thế yêu cầu A trên ngăn xếp, việc xử lý yêu cầu A không thể kết thúc. Tuy nhiên, ngay cả khi bạn đã thực hiện chiến lược thay thế như bạn đề xuất và không có ngăn xếp (có nghĩa là chuyển hướng nội bộ sẽ khó khăn hơn), điều này thực sự không thay đổi thực tế rằng bối cảnh ứng dụng và yêu cầu được yêu cầu để cách ly xử lý các yêu cầu.
Đánh dấu Hildreth

Lời giải thích hay! Nhưng tôi vẫn hơi khó hiểu về: "Bối cảnh ứng dụng được tạo và hủy khi cần thiết. Nó không bao giờ di chuyển giữa các luồng và nó sẽ không được chia sẻ giữa các yêu cầu." Trong tài liệu của bình. Tại sao "bối cảnh ứng dụng" không tồn tại cùng với ứng dụng?
jayven

1
Một ví dụ về chuyển hướng nội bộ trong Flask sẽ hữu ích, làm cho nó không bật lên nhiều. Nếu không vì điều đó thì một request = Local()thiết kế đơn giản hơn có đủ cho global.py không? Có lẽ có những trường hợp sử dụng tôi không nghĩ đến.
QuadrupleA

Có ổn không khi đẩy bối cảnh ứng dụng vào bên trong phương thức xuất xưởng khi nhập lượt xem? Bởi vì các khung nhìn chứa các tuyến tham chiếu đến current_app nên tôi cần bối cảnh.
biến

48

Các câu trả lời trước đã đưa ra một cái nhìn tổng quan đẹp về những gì diễn ra trong nền của Flask trong một yêu cầu. Nếu bạn chưa đọc nó, tôi khuyên bạn nên trả lời @ MarkHildreth trước khi đọc nó. Nói tóm lại, một bối cảnh mới (luồng) được tạo cho mỗi yêu cầu http, đó là lý do tại sao cần có một Localtiện ích luồng cho phép các đối tượng như requestgđể có thể truy cập trên toàn cầu qua các chủ đề, trong khi duy trì bối cảnh cụ thể yêu cầu của họ. Hơn nữa, trong khi xử lý một yêu cầu http, Flask có thể mô phỏng các yêu cầu bổ sung từ bên trong, do đó cần phải lưu trữ bối cảnh tương ứng của chúng trên một ngăn xếp. Ngoài ra, Flask cho phép nhiều ứng dụng wsgi chạy cùng nhau trong một quy trình và nhiều hơn một ứng dụng có thể được gọi để hành động trong một yêu cầu (mỗi yêu cầu tạo ra một bối cảnh ứng dụng mới), do đó cần một ngăn xếp ngữ cảnh cho các ứng dụng. Đó là một bản tóm tắt những gì được đề cập trong các câu trả lời trước đó.

Mục tiêu của tôi bây giờ là bổ sung cho sự hiểu biết hiện tại của chúng tôi bằng cách giải thích cách Flask và Werkzeug làm những gì họ làm với những người dân địa phương này. Tôi đã đơn giản hóa mã để tăng cường sự hiểu biết về logic của nó, nhưng nếu bạn có được điều này, bạn sẽ có thể dễ dàng nắm bắt hầu hết những gì trong nguồn thực tế ( werkzeug.localflask.globals ).

Trước tiên hãy hiểu cách Werkzeug thực hiện chủ đề Người địa phương.

Địa phương

Khi một yêu cầu http đến, nó được xử lý trong ngữ cảnh của một luồng. Là một phương tiện thay thế để sinh ra một bối cảnh mới trong một yêu cầu http, Werkzeug cũng cho phép sử dụng greenlets (một loại "micro-thread" nhẹ hơn) thay vì các luồng thông thường. Nếu bạn chưa cài đặt greenlets, nó sẽ trở lại sử dụng các chủ đề thay thế. Mỗi luồng này (hoặc greenlets) có thể được nhận dạng bởi một id duy nhất mà bạn có thể truy xuất bằng get_ident()chức năng của mô-đun . Chức năng đó là điểm bắt đầu sự kỳ diệu đằng sau có request, current_app,url_for , g, và các đối tượng toàn cầu bối cảnh bị ràng buộc khác như vậy.

try:
    from greenlet import get_ident
except ImportError:
    from thread import get_ident

Bây giờ chúng ta có chức năng nhận dạng, chúng ta có thể biết chúng ta đang sử dụng chủ đề nào tại bất kỳ thời điểm nào và chúng ta có thể tạo ra cái được gọi là chủ đề Local , một đối tượng theo ngữ cảnh có thể được truy cập trên toàn cầu, nhưng khi bạn truy cập các thuộc tính của nó, chúng sẽ giải quyết giá trị của chúng chủ đề cụ thể đó. ví dụ

# globally
local = Local()

# ...

# on thread 1
local.first_name = 'John'

# ...

# on thread 2
local.first_name = 'Debbie'

Cả hai giá trị đều có mặt trên Localđối tượng có thể truy cập toàn cầu cùng một lúc, nhưng truy cậplocal.first_name trong ngữ cảnh của luồng 1 sẽ cung cấp cho bạn 'John', trong khi nó sẽ trả 'Debbie'về luồng 2.

Làm thế nào là có thể? Hãy xem xét một số mã (đơn giản hóa):

class Local(object)
    def __init__(self):
        self.storage = {}

    def __getattr__(self, name):
        context_id = get_ident() # we get the current thread's or greenlet's id
        contextual_storage = self.storage.setdefault(context_id, {})
        try:
            return contextual_storage[name]
        except KeyError:
            raise AttributeError(name)

    def __setattr__(self, name, value):
        context_id = get_ident()
        contextual_storage = self.storage.setdefault(context_id, {})
        contextual_storage[name] = value

    def __release_local__(self):
        context_id = get_ident()
        self.storage.pop(context_id, None)

local = Local()

Từ đoạn mã trên, chúng ta có thể thấy rằng phép thuật sôi sục get_ident() xác định greenlet hoặc luồng hiện tại. Bộ Locallưu trữ sau đó chỉ sử dụng như một khóa để lưu trữ bất kỳ dữ liệu theo ngữ cảnh nào cho luồng hiện tại.

Bạn có thể có nhiều Localđối tượng cho mỗi quá trình và request, g, current_appvà những người khác có thể chỉ đơn giản là đã được tạo ra như thế. Nhưng đó không phải là cách nó được thực hiện trong Flask, trong đó những thứ này không phải là kỹ thuật Local đối tượng , mà là LocalProxycác đối tượng chính xác hơn . Một là gì LocalProxy?

LocalProxy

LocalProxy là một đối tượng truy vấn a Localđể tìm một đối tượng quan tâm khác (tức là đối tượng mà nó ủy nhiệm). Hãy xem để hiểu:

class LocalProxy(object):
    def __init__(self, local, name):
        # `local` here is either an actual `Local` object, that can be used
        # to find the object of interest, here identified by `name`, or it's
        # a callable that can resolve to that proxied object
        self.local = local
        # `name` is an identifier that will be passed to the local to find the
        # object of interest.
        self.name = name

    def _get_current_object(self):
        # if `self.local` is truly a `Local` it means that it implements
        # the `__release_local__()` method which, as its name implies, is
        # normally used to release the local. We simply look for it here
        # to identify which is actually a Local and which is rather just
        # a callable:
        if hasattr(self.local, '__release_local__'):
            try:
                return getattr(self.local, self.name)
            except AttributeError:
                raise RuntimeError('no object bound to %s' % self.name)

        # if self.local is not actually a Local it must be a callable that 
        # would resolve to the object of interest.
        return self.local(self.name)

    # Now for the LocalProxy to perform its intended duties i.e. proxying 
    # to an underlying object located somewhere in a Local, we turn all magic
    # methods into proxies for the same methods in the object of interest.
    @property
    def __dict__(self):
        try:
            return self._get_current_object().__dict__
        except RuntimeError:
            raise AttributeError('__dict__')

    def __repr__(self):
        try:
            return repr(self._get_current_object())
        except RuntimeError:
            return '<%s unbound>' % self.__class__.__name__

    def __bool__(self):
        try:
            return bool(self._get_current_object())
        except RuntimeError:
            return False

    # ... etc etc ... 

    def __getattr__(self, name):
        if name == '__members__':
            return dir(self._get_current_object())
        return getattr(self._get_current_object(), name)

    def __setitem__(self, key, value):
        self._get_current_object()[key] = value

    def __delitem__(self, key):
        del self._get_current_object()[key]

    # ... and so on ...

    __setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
    __delattr__ = lambda x, n: delattr(x._get_current_object(), n)
    __str__ = lambda x: str(x._get_current_object())
    __lt__ = lambda x, o: x._get_current_object() < o
    __le__ = lambda x, o: x._get_current_object() <= o
    __eq__ = lambda x, o: x._get_current_object() == o

    # ... and so forth ...

Bây giờ để tạo proxy có thể truy cập toàn cầu, bạn sẽ làm

# this would happen some time near application start-up
local = Local()
request = LocalProxy(local, 'request')
g = LocalProxy(local, 'g')

và bây giờ sớm hơn trong quá trình yêu cầu, bạn sẽ lưu trữ một số đối tượng bên trong địa phương mà các proxy được tạo trước đó có thể truy cập, bất kể chúng ta đang sử dụng chủ đề nào

# this would happen early during processing of an http request
local.request = RequestContext(http_environment)
local.g = SomeGeneralPurposeContainer()

Ưu điểm của việc sử dụng LocalProxynhư các đối tượng có thể truy cập toàn cầu thay vì tự tạo ra chúng Localslà nó đơn giản hóa việc quản lý của họ. Bạn chỉ cần một Localđối tượng duy nhất để tạo ra nhiều proxy có thể truy cập trên toàn cầu. Khi kết thúc yêu cầu, trong khi dọn dẹp, bạn chỉ cần giải phóng cái đó Local(tức là bạn bật bối cảnh_id khỏi bộ lưu trữ của nó) và không bận tâm đến proxy, chúng vẫn có thể truy cập toàn cầu và vẫn trì hoãn Localđể tìm đối tượng của chúng quan tâm cho các yêu cầu http tiếp theo.

# this would happen some time near the end of request processing
release(local) # aka local.__release_local__()

Để đơn giản hóa việc tạo ra một LocalProxykhi chúng ta đã có Local, Werkzeug thực hiện Local.__call__()phương pháp ma thuật như sau:

class Local(object):
    # ... 
    # ... all same stuff as before go here ...
    # ... 

    def __call__(self, name):
        return LocalProxy(self, name)

# now you can do
local = Local()
request = local('request')
g = local('g')

Tuy nhiên, nếu bạn nhìn vào nguồn Flask (flask.globals) mà vẫn không phải là cách request, g, current_appsession được tạo ra. Khi chúng tôi đã thiết lập, Flask có thể sinh ra nhiều yêu cầu "giả mạo" (từ một yêu cầu http thực sự duy nhất) và trong quá trình này cũng đẩy nhiều bối cảnh ứng dụng. Đây không phải là trường hợp sử dụng phổ biến, nhưng đó là khả năng của khung. Vì các yêu cầu và ứng dụng "đồng thời" này vẫn bị giới hạn để chạy chỉ với một người có "tiêu điểm" bất cứ lúc nào, nên sử dụng một ngăn xếp cho bối cảnh tương ứng của họ là hợp lý. Bất cứ khi nào một yêu cầu mới được sinh ra hoặc một trong các ứng dụng được gọi, họ sẽ đẩy bối cảnh của họ lên đầu ngăn xếp tương ứng. Flask sử dụng LocalStackcác đối tượng cho mục đích này. Khi họ kết thúc việc kinh doanh của họ, họ bật bối cảnh ra khỏi ngăn xếp.

LocalStack

Đây là một giao LocalStackdiện (một lần nữa mã được đơn giản hóa để tạo điều kiện cho sự hiểu biết về logic của nó).

class LocalStack(object):

    def __init__(self):
        self.local = Local()

    def push(self, obj):
        """Pushes a new item to the stack"""
        rv = getattr(self.local, 'stack', None)
        if rv is None:
            self.local.stack = rv = []
        rv.append(obj)
        return rv

    def pop(self):
        """Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """
        stack = getattr(self.local, 'stack', None)
        if stack is None:
            return None
        elif len(stack) == 1:
            release_local(self.local) # this simply releases the local
            return stack[-1]
        else:
            return stack.pop()

    @property
    def top(self):
        """The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """
        try:
            return self.local.stack[-1]
        except (AttributeError, IndexError):
            return None

Lưu ý ở trên rằng a LocalStacklà một ngăn xếp được lưu trữ trong một địa phương, không phải là một nhóm các địa phương được lưu trữ trên một ngăn xếp. Điều này ngụ ý rằng mặc dù ngăn xếp có thể truy cập toàn cầu, đó là một ngăn xếp khác nhau trong mỗi luồng.

Flask không có nó request, current_app, g, và sessioncác đối tượng giải quyết trực tiếp đến một LocalStack, nó thay vì sử dụng LocalProxycác đối tượng bọc một chức năng tra cứu (thay vì một Localđối tượng) sẽ tìm ra đối tượng tiềm ẩn từ LocalStack:

_request_ctx_stack = LocalStack()
def _find_request():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.request
request = LocalProxy(_find_request)

def _find_session():
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of request context')
    return top.session
session = LocalProxy(_find_session)

_app_ctx_stack = LocalStack()
def _find_g():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.g
g = LocalProxy(_find_g)

def _find_app():
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError('working outside of application context')
    return top.app
current_app = LocalProxy(_find_app)

Tất cả những điều này được khai báo khi khởi động ứng dụng, nhưng thực tế không giải quyết bất cứ điều gì cho đến khi bối cảnh yêu cầu hoặc bối cảnh ứng dụng được đẩy đến ngăn xếp tương ứng của chúng.

Nếu bạn tò mò muốn xem bối cảnh thực sự được chèn vào ngăn xếp như thế nào (và sau đó bật ra), hãy xem flask.app.Flask.wsgi_app()đó là điểm vào của ứng dụng wsgi (tức là những gì máy chủ web gọi và chuyển môi trường http đến khi yêu cầu đến in), và làm theo sự sáng tạo của các RequestContextđối tượng suốt tiếp theo của nó push()vào _request_ctx_stack. Sau khi được đẩy lên trên cùng của ngăn xếp, nó có thể truy cập thông qua _request_ctx_stack.top. Đây là một số mã viết tắt để thể hiện dòng chảy:

Vì vậy, bạn bắt đầu một ứng dụng và cung cấp nó cho máy chủ WSGI ...

app = Flask(*config, **kwconfig)

# ...

Sau đó, một yêu cầu http xuất hiện và máy chủ WSGI gọi ứng dụng với các thông số thông thường ...

app(environ, start_response) # aka app.__call__(environ, start_response)

Đây là những gì xảy ra trong ứng dụng ...

def Flask(object):

    # ...

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

    def wsgi_app(self, environ, start_response):
        ctx = RequestContext(self, environ)
        ctx.push()
        try:
            # process the request here
            # raise error if any
            # return Response
        finally:
            ctx.pop()

    # ...

và đây là những gì xảy ra với RequestContext ...

class RequestContext(object):

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.session = self.app.open_session(self.request)
        if self.session is None:
            self.session = self.app.make_null_session()
        self.flashes = None

    def push(self):
        _request_ctx_stack.push(self)

    def pop(self):
        _request_ctx_stack.pop()

Giả sử một yêu cầu đã khởi tạo xong, việc tìm kiếm request.pathtừ một trong các chức năng xem của bạn sẽ diễn ra như sau:

  • bắt đầu từ LocalProxyđối tượng truy cập toàn cầu request.
  • để tìm đối tượng quan tâm cơ bản của nó (đối tượng mà nó ủy quyền), nó gọi hàm tra cứu của nó _find_request()(chức năng mà nó đã đăng ký làself.local ).
  • Hàm đó truy vấn LocalStackđối tượng_request_ctx_stack cho bối cảnh trên cùng trên ngăn xếp.
  • để tìm bối cảnh trên cùng, LocalStacktrước tiên , đối tượng truy vấn Localthuộc tính bên trong của nó ( self.local) chostack tính được lưu trữ trước đó ở đó.
  • từ stack nó có được bối cảnh hàng đầu
  • top.request do đó được giải quyết như là đối tượng quan tâm cơ bản.
  • từ đối tượng đó, chúng ta có được paththuộc tính

Vì vậy, chúng ta đã thấy làm thế nào Local, LocalProxyLocalStacklàm việc, bây giờ nghĩ về một ý nghĩa và sắc thái trong việc truy xuất pathtừ:

  • một requestđối tượng sẽ là một đối tượng đơn giản có thể truy cập toàn cầu.
  • một requestđối tượng sẽ là một địa phương.
  • một requestđối tượng được lưu trữ như một thuộc tính của một địa phương.
  • một request đối tượng là proxy cho một đối tượng được lưu trữ cục bộ.
  • một request đối tượng được lưu trữ trên một ngăn xếp, lần lượt được lưu trữ trong một cục bộ.
  • một requestđối tượng là proxy cho một đối tượng trên ngăn xếp được lưu trữ trong một cục bộ. <- đây là những gì Flask làm.

4
Tuyệt vời, tôi đã nghiên cứu mã trong jar / continals.py và werkzeug / local.py và điều này giúp làm rõ sự hiểu biết của tôi về nó. Ý thức cá nhân của tôi cho tôi biết đây là một cách thiết kế quá phức tạp, nhưng tôi thừa nhận tôi không hiểu tất cả các trường hợp sử dụng mà nó dự định. "Chuyển hướng nội bộ" là lời biện minh duy nhất tôi đã thấy trong các mô tả ở trên và việc "chuyển hướng nội bộ" của Google không xuất hiện nhiều nên tôi vẫn hơi thua lỗ. Một trong những điều tôi thích về bình là nó thường không phải là một loại súp đối tượng java có đầy đủ AbstractProviderContextBaseFactories, v.v.
QuadrupleA

1
@QuadrupleA Một khi bạn hiểu làm thế nào những Local, LocalStackLocalProxycông việc, tôi đề nghị xem xét lại những điều của doc: flask.pocoo.org/docs/0.11/appcontext , flask.pocoo.org/docs/0.11/extensiondev , và flask.pocoo .org / docs / 0.11 / reqcontext . Nắm bắt mới của bạn có thể cho phép bạn nhìn thấy chúng với một ánh sáng mới và có thể cung cấp cái nhìn sâu sắc hơn.
Michael Ekoka

Đọc qua các liên kết đó - chúng chủ yếu có ý nghĩa nhưng thiết kế vẫn khiến tôi thấy quá phức tạp và có thể quá thông minh vì lợi ích của chính nó. Nhưng tôi không phải là một fan hâm mộ lớn của OOP nói chung và các công cụ kiểm soát luồng ngầm định (ghi đè __call __ (), __getattr __ (), gửi sự kiện động so với các lệnh gọi hàm đơn giản, gói mọi thứ trong các bộ truy cập thuộc tính thay vì chỉ sử dụng một thuộc tính thông thường, v.v. .) vì vậy có lẽ đó chỉ là một sự khác biệt trong triết học. Không phải là một học viên TDD, mà rất nhiều máy móc bổ sung này dường như được dự định hỗ trợ.
QuadrupleA

1
Cảm ơn đã chia sẻ điều này, đánh giá cao. Luồng là điểm yếu với các ngôn ngữ như python - bạn kết thúc với các mẫu như trên bị rò rỉ vào các khung ứng dụng và cũng không thực sự mở rộng quy mô. Java là một ví dụ khác trong một tình huống tương tự. chủ đề, semaphors, vv Khó khăn để có được quyền, hoặc duy trì. Đây là nơi các ngôn ngữ như Erlang / Elixir (sử dụng BEAM) hoặc phương pháp tiếp cận vòng lặp sự kiện (ví dụ: nginx vs apache, v.v.) thường cung cấp một cách tiếp cận mạnh mẽ hơn, có thể mở rộng và ít phức tạp hơn.
arcseldon

13

Bổ sung ít @Mark Hildreth .

Ngăn xếp bối cảnh trông giống như {thread.get_ident(): []}, nơi []được gọi là "stack" vì chỉ sử dụng các hoạt động append( push) pop[-1]( __getitem__(-1)). Vì vậy, stack stack sẽ giữ dữ liệu thực tế cho luồng hoặc luồng greenlet.

current_app, g, request, sessionVà vv là LocalProxyđối tượng mà chỉ overrided phương pháp đặc biệt __getattr__, __getitem__, __call__, __eq__và vv và giá trị trả về từ ngữ cảnh chồng đầu ( [-1]) theo tên đối số ( current_app, requeství dụ). LocalProxycần thiết để nhập đối tượng này một lần và chúng sẽ không bỏ lỡ thực tế. Vì vậy, tốt hơn là chỉ nhập bất cứ requestnơi nào bạn đang ở trong mã thay vì chơi với việc gửi đối số yêu cầu xuống cho bạn các hàm và phương thức. Bạn có thể dễ dàng viết các tiện ích mở rộng của riêng mình với nó, nhưng đừng quên rằng việc sử dụng phù phiếm có thể khiến mã khó hiểu hơn.

Dành thời gian để hiểu https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/local.py .

Vậy làm thế nào dân cư cả hai ngăn xếp? Theo yêu cầu Flask:

  1. tạo request_contextbởi môi trường (init map_adapter, khớp đường dẫn)
  2. nhập hoặc đẩy yêu cầu này:
    1. xóa trước request_context
    2. tạo app_contextnếu nó bị mất và được đẩy vào ngăn xếp bối cảnh ứng dụng
    3. yêu cầu này được đẩy để yêu cầu ngăn xếp bối cảnh
    4. phiên init nếu nó bỏ lỡ
  3. yêu cầu công văn
  4. xóa yêu cầu và bật nó khỏi ngăn xếp

2

Hãy lấy một ví dụ, giả sử bạn muốn đặt một giao diện người dùng (sử dụng cấu trúc bình của Local và LocalProxy).

Xác định một lớp người dùng:

class User(object):
    def __init__(self):
        self.userid = None

định nghĩa một hàm để truy xuất lại đối tượng người dùng bên trong luồng hiện tại hoặc greenlet

def get_user(_local):
    try:
        # get user object in current thread or greenlet
        return _local.user
    except AttributeError:
        # if user object is not set in current thread ,set empty user object 
       _local.user = User()
    return _local.user

Bây giờ hãy xác định LocalProxy

usercontext = LocalProxy(partial(get_user, Local()))

Bây giờ để có được userid của người dùng trong luồng hiện tại usercontext.userid

giải trình :

1.Local có một danh tính nhận dạng và objet, danh tính là threadid hoặc greenlet id, trong ví dụ này _local.user = User () là eqivalent cho _local .___ Storage __ [id của chủ đề hiện tại] ["user"] = User ()

  1. Đại biểu LocalProxy hoạt động để bọc đối tượng Local hoặc bạn có thể cung cấp một hàm trả về đối tượng đích. Trong ví dụ trên, hàm get_user cung cấp đối tượng người dùng hiện tại cho LocalProxy và khi bạn hỏi userid của người dùng hiện tại bởi usercontext.userid, hàm __getattr__ của LocalProxy trước tiên gọi get_user để lấy đối tượng Người dùng (người dùng) và sau đó gọi getattr (user, "userid"). để đặt userid trên User (trong luồng hiện tại hoặc greenlet) bạn chỉ cần làm: usercontext.userid = "user_123"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.