Flash source code analysis - session implementation principle

1 cookie s and session s

http is stateless. For some scenarios, such as shopping websites, adding shopping carts and paying, you need to record the status of the previous page. If you choose goods in a shopping cart and are ready to pay on the payment page, at this time, all your commodity information is gone, which is not feasible. In order to maintain the session state between the client and the server, there are cookie s and sessions.
Loosely speaking, a cookie is some information stored in the browser, and a session is information stored in the server. After the session is stored, a sessionID will be generated. The browser sends the sessionID to the server through the cookie. The server judges the information stored in the sessionID and gives the information to the browser. If the browser does not have a sessionID, after accessing the server, The server will create a new sessionID for the browser.
The focus of this article is not to analyze sessions and cookie s, but to study how flash sessions are implemented.

2. Using session in flash

from flask import Flask, session


app = Flask(__name__)
#RuntimeError: The session is unavailable because no secret key was set.  
#Set the secret_key on the application to something unique and secret.
app.config['SECRET_KEY'] = 'secret key xxx'

@app.route('/set_session')
def set_session():
    session['key'] = 'value'
    return "set_cookie_success"


@app.route('/get_session')
def get_session():
    try:
        session_value = session['key']
    except KeyError:
        session_value = None
    if session_value:
        return session_value
    else:
        return 'session key not found'


if __name__ == '__main__':
    app.run()



Data can be shared between different requests through sessions. The above picture shows the process of writing and obtaining sessions

With a few questions:

  • Why can a session operate like a dictionary?
  • Why do I need a secret key to use session?
  • The developer tool will find that the session appears in the browser's cookie. Why?

3 analyze the implementation principle of flash internal session

"""
===============================step1==================================
from from flask import session start
"""
# session in globals.py is a global variable and a local proxy object
session: "SessionMixin" = LocalProxy(  # type: ignore
    partial(_lookup_req_object, "session")
)
"""
===============================step2==================================
Request just came in, ctx.py In the module class RequestContext:Defined in session
"""
class RequestContext:
    def __init__(
        self,
        app: "Flask",
        environ: dict,
        request: t.Optional["Request"] = None,
        session: t.Optional["SessionMixin"] = None,) # The session is initialized to None


"""
===============================step3==================================
stay RequestContext Object is push reach_request_ctx_stack Middle rear
"""
# The push method of RequestContext class is as follows:
	def push(self)
		...
				# When the request first came in, the session was None
        if self.session is None:
        		# When the code is executed, the session interface is defined here. It's very clever. As long as the session_interface class
        		# Execute open_ The session method is OK
           # Jump to the Flask class
           """
           ===================step3.1==================
           self.app yes Flask Class
           stay app.py In module Flask Class has the following code
           session_interface = SecureCookieSessionInterface()
           session_interface namely SecureCookieSessionInterface Class
           """
            session_interface = self.app.session_interface
           """
           ===================step3.2==================
           call SecureCookieSessionInterface Class open_session Method, will app Object and request Object is passed as a parameter
           """
            self.session = session_interface.open_session(self.app, self.request)

            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
		...



3.1 focus on the analysis of securecookeiesessioninterface class

class SecureCookieSessionInterface(SessionInterface):
    """By signature cookie Mode storage session,rely on itsdangerous modular"""
    #: salt value used to encrypt session
    salt = "cookie-session"
    #sha1 signature algorithm is used by default:
    digest_method = staticmethod(hashlib.sha1)
    #: the name of the itsdangerous supported key derivation.  The default is hmac.
    key_derivation = "hmac"
    #: A python serializer for the payload.  The default is a compact
    #: JSON derived serializer with support for some extra Python types
    #: such as datetime objects or tuples.
    serializer = session_json_serializer
    session_class = SecureCookieSession

    def get_signing_serializer(
        self, app: "Flask"
    ) -> t.Optional[URLSafeTimedSerializer]:
        if not app.secret_key:
            return None
        signer_kwargs = dict(
            key_derivation=self.key_derivation, digest_method=self.digest_method
        )
        return URLSafeTimedSerializer(
            app.secret_key,
            salt=self.salt,
            serializer=self.serializer,
            signer_kwargs=signer_kwargs,
        )

    def open_session(
        self, app: "Flask", request: "Request"
    ) -> t.Optional[SecureCookieSession]:
"""
===============================step4=============================
open_session The last thing to return is session_class,That is, when the request is pushed into the context, a session_class
 object
session_class = SecureCookieSession

"""
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        val = request.cookies.get(self.get_cookie_name(app))
        if not val:
            return self.session_class()
        max_age = int(app.permanent_session_lifetime.total_seconds())
        try:
            data = s.loads(val, max_age=max_age)
            return self.session_class(data)
        except BadSignature:
            return self.session_class()
"""
===============================step5=============================
see SecureCookieSession What is it
class SecureCookieSession(CallbackDict, SessionMixin):
class CallbackDict(UpdateDictMixin, dict):
class UpdateDictMixin(dict):
you 're right SecureCookieSession Inherited the dictionary, so session It has the function similar to dictionary
"""
    def save_session(
        self, app: "Flask", session: SessionMixin, response: "Response"
    ) -> None:
        name = self.get_cookie_name(app)
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        secure = self.get_cookie_secure(app)
        samesite = self.get_cookie_samesite(app)

        # If the session is modified to be empty, remove the cookie.
        # If the session is empty, return without setting the cookie.
        if not session:
            if session.modified:
                response.delete_cookie(
                    name, domain=domain, path=path, secure=secure, samesite=samesite
                )

            return

        # Add a "Vary: Cookie" header if the session was accessed at all.
        if session.accessed:
            response.vary.add("Cookie")

        if not self.should_set_cookie(app, session):
            return

        httponly = self.get_cookie_httponly(app)
        expires = self.get_expiration_time(app, session)
        """
        This sentence is the key session Serialize after conversion to field
        """
        val = self.get_signing_serializer(app).dumps(dict(session))  # type: ignore
        """
        Finally passed cookie Send to client
        """
        response.set_cookie(
            name,
            val,  # type: ignore
            expires=expires,
            httponly=httponly,
            domain=domain,
            path=path,
            secure=secure,
            samesite=samesite,
        )

3.2 after the request comes in, start preparing the response

        ctx = self.request_context(environ)
        error: t.Optional[BaseException] = None
        try:
            try:
                ctx.push()
                # Mainly look at this sentence
                response = self.full_dispatch_request()

"""
===============================step6=============================
response = self.full_dispatch_request()

"""
def full_dispatch_request(self) -> Response:
    return self.finalize_request(rv)

def finalize_request(
        self,
        rv: t.Union[ResponseReturnValue, HTTPException],
        from_error_handler: bool = False,
    ) -> Response:
        response = self.make_response(rv)
        try:
            response = self.process_response(response)

def process_response(self, response: Response) -> Response:
    if not self.session_interface.is_null_session(ctx.session):
        self.session_interface.save_session(self, ctx.session, response)

"""
===============================step7=============================
Back to save_session
"""

4 Summary

To sum up, session, like request, is a property of the request context object.
When the request comes in, the session opens up an object inherited from the dictionary to save information. In the process of processing the request, update the content of the session object, and finally send it to the browser by means of cookies. To be more popular, flash saves session information by encrypting cookies.
As long as the classes that meet the SessionInterface interface can be used for flash session processing. Although I can't fully understand all the details, the source code is really wonderful

Tags: Python Flask

Posted on Sun, 12 Sep 2021 03:16:18 -0400 by reckdan