Flask系列之源碼分析(一)

目錄:

  • 涉及知識點
  • Flask框架原理
  • 簡單示例
  • 路由系統原理源碼分析
  • 請求流程簡單源碼分析
  • 響應流程簡單源碼分析
  • session簡單源碼分析

 

涉及知識點

一、裝飾器html

閉包思想python

def wapper(func):
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner

"""
1. 當即執行wapper函數,並將下面裝飾的函數當作參數傳遞
2. 將wapper函數返回值獲取,在index賦值
    index = inner函數
"""
@wapper
def index():
    print('函數內容')

# 實際執行的 inner函數,inner函數內部調用原函數
index()
View Code

ps.@functools.wraps,以上咱們知道了python實現閉包,實際是index = inner(index)的封裝思想。但不可避免的是inner封裝後,會對封裝的函數隱藏一些信息。如:包裝異常,隱藏異常,打印日誌,統計函數使用時間等。@functools.wraps經過update_wrapper函數,用參數wrapped表示的函數對象(例如:square)的一些屬性(如:__name__、 __doc__)覆蓋參數wrapper表示的函數對象(例如:callf,這裏callf只是簡單地調用square函數,所以能夠說callf是 square的一個wrapper function)的這些相應屬性。數據庫

import functools
def wapper(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        return func(*args,**kwargs)
    return inner

@wapper
def index():
    print('函數內容')

@wapper
def order():
    print('函數內容')

print(index.__name__)
print(order.__name__)
View Code

二、面向對象封裝json

class Foo(object):
    def __init__(self,age,name):
        self.age = age
        self.name = name

class Bar(object):
    def __init__(self,counter):
        self.counter = counter
        self.obj = Foo('18','石鵬')

b1 = Bar(1)
print(b1.obj.name)
View Code

三、python對象什麼後面能夠加括號flask

- 函數
- 類
- 方法
- 對象
def f1():
    print('f1')

class F2(object):
    pass

class F3(object):
    def __init__(self):
        pass

    def ff3(self):
        print('ff3')

class F4(object):
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print('f4')


def func(arg):
    """
    因爲arg在函數中加括號,因此他只有4中表現形式:
        - 函數
        - 類
        - 方法
        - 對象
    :param arg:
    :return:
    """
    arg()

# 1. 函數,內部執行函數
func(f1)
# 2. 類,內部執行__init__方法
func(F2)

# 3. 方法,obj.ff3
obj1 = F3()
func(obj1.ff3)

# 4. 對象
obj2 = F4()
func(obj2)
View Code

四、call方法瀏覽器

class F4(object):
    def __init__(self):
        print('構造方法')

    def __call__(self, *args, **kwargs):
        print('f4')

    def run(self,str1):
        print("run:%s" % str1)

obj = F4()
obj()
obj.run('sssss')
View Code

五、函數和方法的區別cookie

在於調用時有沒有實例化對象,即跟某個對象關聯。session

from types import MethodType,FunctionType


class F3(object):
    def __init__(self):
        pass

    def ff3(self):
        print('ff3')

#
v1 = isinstance(F3.ff3,MethodType)  # 方法
v2 = isinstance(F3.ff3,FunctionType) # 函數
print(v1,v2) # False,True

obj = F3()
v1 = isinstance(obj.ff3,MethodType)  # 方法
v2 = isinstance(obj.ff3,FunctionType) # 函數
print(v1,v2) # True False
View Code

Flask框架原理

一、框架本質爲經過socket模塊實現工做流的請求和響應。閉包

經過socket創建實例,accept等待請求地址,並經過編寫路由系統來給予相應的響應。app

import socket

def main():
    # 建立老師
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 8000))
    sock.listen(5)

    while True:
        # 老師等待 用戶請求的到來
        connection, address = sock.accept()
        
        # 獲取發送的內容:吳亦凡是有沒有女友?
        buf = connection.recv(1024)

        # 根據請求URL的不一樣:
        # 回答:沒有
        connection.send(b"HTTP/1.1 200 OK\r\n\r\n")
        connection.send(b"No No No")

        # 關閉鏈接
        connection.close()


if __name__ == '__main__':
    main()

二、flask經過werkzeug模塊來幫助咱們完成socket性能。

"""
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    # 當請求打來以後,自動執行:hello()
    run_simple('localhost', 4000, hello)
"""


from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple

class Foo(object):
    def __call__(self, *args, **kwargs):
        return Response('Hello World!')

if __name__ == '__main__':
    # 當請求打來以後,自動執行:hello()
    obj = Foo()
    run_simple('localhost', 4000, obj)
View Code

三、flask快速入門

"""
pip install flask
pip3 install flask
"""

from flask import Flask
# 1. 實例化Flask對象
app = Flask('xxxx')

"""
1. 執行 app.route('/index')並獲取返回值 xx
2. 
    @xx
    def index():
        return 'Hello World'
3. 執行 index = xx(index)
本質: 
    {
        '/index': index
    }
"""
@app.route('/index')
def index():
    return 'Hello World'


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

簡單示例

一、實現簡單登錄

import functools
from flask import Flask,render_template,request,redirect,session

app = Flask('xxxx',template_folder="templates")
app.secret_key = 'as923lrjks9d8fwlkxlduf'


def auth(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        user_info = session.get('user_info')
        if not user_info:
            return redirect('/login')
        return func(*args,**kwargs)
    return inner


"""
{
    /order: inner函數, name: order
    /index: inner函數, name: index
}
"""

@app.route('/order',methods=['GET'])
@auth
def order():
    user_info = session.get('user_info')
    if not user_info:
        return redirect('/login')

    return render_template('index.html')


@app.route('/index',methods=['GET'])
@auth
def index():
    return render_template('index.html')


@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == "GET":
        return render_template('login.html')
    else:
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'alex' and pwd == '123':
            session['user_info'] = user
            return redirect('/index')
        # return render_template('login.html',msg = "用戶名或密碼錯誤",x =  123)
        return render_template('login.html',**{'msg':'用戶名或密碼錯誤'})

@app.route('/logout',methods=['GET'])
def logout():
    del session['user_info']
    return redirect('/login')

if __name__ == '__main__':
    app.run()
app.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登陸頁面</h1>
    <form method="post">
        <input type="text" name="user">
        <input type="password" name="pwd">
        <input type="submit" value="提交">{{msg}}
    </form>
</body>
</html>
templates/login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>歡迎進入系統</h1>
    <img src="/static/111.png" alt="">
</body>
</html>
templates/index.html

二、flask配置文件

import functools
from flask import Flask

# 配置:模板/靜態文件
app = Flask('xxxx',template_folder="templates")
# 配置:secret_key
app.secret_key = 'as923lrjks9d8fwlkxlduf'

# 導入配置文件
app.config.from_object('settings.TestingConfig')


@app.route('/index')
def index():
    return "index"



if __name__ == '__main__':
    app.run()
View Code
class BaseConfig(object):
    DEBUG = False
    SESSION_REFRESH_EACH_REQUEST = True

class ProConfig(BaseConfig):
    pass

class DevConfig(BaseConfig):
    DEBUG = True

class TestingConfig(BaseConfig):
    DEBUG = True
settings.py

ps.import importlib模塊,模塊支持傳遞字符串來導入模塊。咱們先來建立一些簡單模塊一遍演示。咱們在模塊裏提供了相同接口,經過打印它們自身名字來區分。可經過importlib.import_module(module_path)來動態導入。其等價於import module_path。

路由系統原理源碼分析

一、整體流程:

1.初始化Flask類,Rule類,Map類

2.調用app.route方法

3.route方法調用add_url_rule方法

4. add_url_rule方法rule = self.url_rule_class調用Rule方法,封裝url和試圖函數

5.add_url_rule方法調用url_map.add(Rule)對路由的rules進行添加[Rule('/index', 函數),]

6.map類存到self.url_map中,Rule存在url_rule_class中

import functools
from flask import Flask,views

# 配置:模板/靜態文件
app = Flask('xxxx',template_folder="templates")
"""
{
    '/index': index函數
}

1. decorator = app.route('/index')
2. 
    @decorator
    def index():
        return "index"
3. decorator(index)
"""

"""
Map() = [
    Rule(rule=/index/ endpoint=None  view_func=函數),
]
"""
@app.route('/index')
def index():
    return "index"

"""
Map() = [
    Rule(rule=/index endpoint=None  view_func=函數),
    Rule(rule=/order endpoint=None  view_func=order),
]
"""
def order():
    return 'Order'
app.add_url_rule('/order', None, order)


class TestView(views.View):
    methods = ['GET']
    def dispatch_request(self):
        return 'test!'

app.add_url_rule('/test', view_func=TestView.as_view(name='test'))  # name=endpoint
# app.add_url_rule('/test', view_func=view函數)  # name=endpoint


def auth(func):
    def inner(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result
    return inner

class X1View(views.MethodView):
    methods = ['GET','POST']
    decorators = [auth, ]

    def get(self):
        return 'x1.GET'

    def post(self):
        return 'x1.POST'


app.add_url_rule('/x1', view_func=X1View.as_view(name='x1'))  # name=endpoint


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

二、初始化Flask類,Rule類,Map類

#--------------------------------------
# Flask類
class Flask(_PackageBoundObject):
    url_rule_class = Rule
    def __init__(self, import_name, static_path=None, static_url_path=None,
                 static_folder='static', template_folder='templates',
                 instance_path=None, instance_relative_config=False,
                 root_path=None)
        self.url_map = Map()

#--------------------------------------
# Role類
@implements_to_string
class Rule(RuleFactory):
        def __init__(self, string, defaults=None, subdomain=None, methods=None,
                 build_only=False, endpoint=None, strict_slashes=None,
                 redirect_to=None, alias=False, host=None):
        if not string.startswith('/'):
            raise ValueError('urls must start with a leading slash')
        self.rule = string
        self.is_leaf = not string.endswith('/')

        self.map = None
        self.strict_slashes = strict_slashes
        self.subdomain = subdomain
        self.host = host
        self.defaults = defaults
        self.build_only = build_only
        self.alias = alias
        if methods is None:
            self.methods = None
        else:
            if isinstance(methods, str):
                raise TypeError('param `methods` should be `Iterable[str]`, not `str`')
            self.methods = set([x.upper() for x in methods])
            if 'HEAD' not in self.methods and 'GET' in self.methods:
                self.methods.add('HEAD')
        self.endpoint = endpoint
        self.redirect_to = redirect_to

        if defaults:
            self.arguments = set(map(str, defaults))
        else:
            self.arguments = set()
        self._trace = self._converters = self._regex = self._weights = None

#-------------------
#map類
class Map(object):
     default_converters = ImmutableDict(DEFAULT_CONVERTERS)

    def __init__(self, rules=None, default_subdomain='', charset='utf-8',
                 strict_slashes=True, redirect_defaults=True,
                 converters=None, sort_parameters=False, sort_key=None,
                 encoding_errors='replace', host_matching=False):
        self._rules = []
        self._rules_by_endpoint = {}
        self._remap = True
        self._remap_lock = Lock()

        self.default_subdomain = default_subdomain
        self.charset = charset
        self.encoding_errors = encoding_errors
        self.strict_slashes = strict_slashes
        self.redirect_defaults = redirect_defaults
        self.host_matching = host_matching

        self.converters = self.default_converters.copy()
        if converters:
            self.converters.update(converters)

        self.sort_parameters = sort_parameters
        self.sort_key = sort_key

        for rulefactory in rules or ():
            self.add(rulefactory)

三、調用app.route方法

def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

四、route方法調用add_url_rule方法

@setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
 if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
        if isinstance(methods, string_types):
            raise TypeError('Allowed methods have to be iterables of strings, '
                            'for example: @app.route(..., methods=["POST"])')
        methods = set(item.upper() for item in methods)

        # Methods that should always be added
        required_methods = set(getattr(view_func, 'required_methods', ()))

        # starting with Flask 0.8 the view_func object can disable and
        # force-enable the automatic options handling.
        provide_automatic_options = getattr(view_func,
            'provide_automatic_options', None)

        if provide_automatic_options is None:
            if 'OPTIONS' not in methods:
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            else:
                provide_automatic_options = False

        # Add the required methods now.
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)

五、 add_url_rule方法rule = self.url_rule_class調用Rule方法,封裝url和試圖函數

add_url_rule,代碼同4 ;經過url_rule_class = Rule實例化,代碼同2

六、add_url_rule方法調用url_map.add(Rule)對路由的rules進行添加[Rule('/index', 函數),]

add_url_rule代碼同4,調用url_map.add方法

    def add(self, rulefactory):
        """Add a new rule or factory to the map and bind it.  Requires that the
        rule is not bound to another map.

        :param rulefactory: a :class:`Rule` or :class:`RuleFactory`
        """
        for rule in rulefactory.get_rules(self):
            rule.bind(self)
            self._rules.append(rule)
            self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
        self._remap = True

7.map類存到self.url_map中,Rule存在url_rule_class中。

同代碼2.

請求流程簡單源碼分析

 一、綜述:

1.已生成路由後,由app.run執行run方法

2.run方法經過werkzeug模塊執行run_simple方法

3.werkzeug模塊會觸發__call__方法

4.__call__方法會觸發wsgi_app

5.ctx=request_context對象,觸發request_context對象

6.request_context對象__init__進行實例化

--request = app.request_class(environ)

7.flask初始化經過request實例化調用Request對象,經過request實例化調用Request對象

8.Request對象經過werkzeug模塊__init__進行實例化

9.存儲到ctx中

10.ctx.push()調用RequestContest.push方法

二、__call__方法會觸發wsgi_app

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

三、ctx=request_context對象,觸發request_context對象

def wsgi_app(self, environ, start_response):
    ctx = self.request_context(environ)
    ctx.push()

四、request_context對象__init__進行實例化

def request_context(self, environ):
    return RequestContext(self, environ)

--request = app.request_class(environ)

    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.flashes = None
        self.session = None

        # Request contexts can be pushed multiple times and interleaved with
        # other request contexts.  Now only if the last level is popped we
        # get rid of them.  Additionally if an application context is missing
        # one is created implicitly so for each level we add this information
        self._implicit_app_ctx_stack = []

        # indicator if the context was preserved.  Next time another context
        # is pushed the preserved context is popped.
        self.preserved = False

        # remembers the exception for pop if there is one in case the context
        # preservation kicks in.
        self._preserved_exc = None

        # Functions that should be executed after the request on the response
        # object.  These will be called before the regular "after_request"
        # functions.
        self._after_request_functions = []

        self.match_request()

五、flask初始化經過request實例化調用Request對象,經過request實例化調用Request對象

class Flask(_PackageBoundObject):
  request_class = Request

六、Request對象經過werkzeug模塊__init__進行實例化

class Request(RequestBase):
     #: The internal URL rule that matched the request.  This can be
    #: useful to inspect which methods are allowed for the URL from
    #: a before/after handler (``request.url_rule.methods``) etc.
    #:
    #: .. versionadded:: 0.6
    url_rule = None

    #: A dict of view arguments that matched the request.  If an exception
    #: happened when matching, this will be ``None``.
    view_args = None

    #: If matching the URL failed, this is the exception that will be
    #: raised / was raised as part of the request handling.  This is
    #: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
    #: something similar.
    routing_exception = None

    # Switched by the request context until 1.0 to opt in deprecated
    # module functionality.
    _is_old_module = False

七、.存儲到ctx中

同3代碼

八、ctx.push()調用RequestContest.push方法

同3代碼

flask的sesstion流程

一、綜述

1.RequestContext.push,調用app.open_sesstion

2.self.session調用app.open_session

3.經過session_interface變量調用到secureCookieSeeionInerface類的open_session

4.若是沒有,則session_class = SecureCookieSession,open_session通過loads加密返回self.session_class(),

5.將加密session返回到self.session

6.執行視圖函數

response = self.full_dispatch_request()
----調用try_trigger_before_first_request_functions
(before_first_request)
----調用preprocess_request(before_request)
----調用dispatch_request(執行試圖函數)
----調用finalize_request(@fater_request)

7.finalize_request

----response = self.process_response(response)

8.process_response

----執行@fater_request函數
----self.save_session(ctx.session, response)

9.經過self.save_session調用save_session並返回SecureCookieSessionInterface.save_session(self, session, response)

10.save_session保存session,報錯return null

 二、RequestContext.push,調用app.open_sesstion

def wsgi_app(self, environ, start_response):
    ctx = self.request_context(environ)
        ctx.push()
        error = None
        try:
            try:
                # 4 執行視圖函數
                response = self.full_dispatch_request()
            except Exception as e:
                # 異常處理試圖報錯,包含信號2345報錯執行,got_request_exception信號
                error = e
                response = self.handle_exception(e)
            except:
                error = sys.exc_info()[1]
                raise
            # 將處理的內容,返回給用戶瀏覽器
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None

            # 九、結束
            ctx.auto_pop(error)

經過self.session = self.app.open_session(self.request)調用flask.open_session方法

def open_session(self, request):
    return self.session_interface.open_session(self, request)

三、self.session調用app.open_session

class RequestContext(object):
  def push(self):
      self.session = self.app.open_session(self.request)
      if self.session is None:
          self.session = self.app.make_null_session()

四、經過session_interface變量調用到secureCookieSeeionInerface類的open_session

class Flask(_PackageBoundObject):
     session_interface = SecureCookieSessionInterface()

五、若是沒有,則session_class = SecureCookieSession,open_session通過loads加密返回self.session_class(),

class SecureCookieSessionInterface(SessionInterface):
    """The default session interface that stores sessions in signed cookies
    through the :mod:`itsdangerous` module.
    """
     #: the salt that should be applied on top of the secret key for the
    #: signing of cookie based sessions.
    salt = 'cookie-session'
    #: the hash function to use for the signature.  The default is sha1
    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 open_session(self, app, request):
  # sission,key值
  s = self.get_signing_serializer(app)
  if s is None:
  return None
  # 若是能從cookie拿到session的話
  val = request.cookies.get(app.session_cookie_name)
  if not val:
  return self.session_class() #若是沒有session,則返回一個空字典
  max_age = total_seconds(app.permanent_session_lifetime)
  try:
  data = s.loads(val, max_age=max_age) # 加密保存
  return self.session_class(data)
  except BadSignature:
  return self.session_class() # 返回session類

六、將加密session返回到self.session

 
 
class RequestContext(object):
  def push(self):
      self.session = self.app.open_session(self.request)
      if self.session is None:
          self.session = self.app.make_null_session()

七、執行視圖函數

response = self.full_dispatch_request()

同代碼2
----調用try_trigger_before_first_request_functions
(before_first_request)

    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        # 觸發只執行一次的裝飾器函數,@before_first_request
        self.try_trigger_before_first_request_functions()

----調用preprocess_request(before_request)

----調用dispatch_request(執行試圖函數)
----調用finalize_request(@fater_request)

def full_dispatch_request(self):
    try:
         # 執行特殊裝飾器:before_request裝飾的全部函數
         # 若是沒有返回值,rv=None;有返回值 「嘻嘻嘻」
         rv = self.preprocess_request()
         if rv is None:
                # 觸發執行視圖函數,使用session
             rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
        # 6 對返回值進行封裝,執行@fater_request裝飾器;session保存
    return self.finalize_request(rv)

八、finalize_request

----response = self.process_response(response)

 def finalize_request(self, rv, from_error_handler=False):
        ''' 建立返視圖返回'''
        response = self.make_response(rv)
        try:
            '''返回值'''
            response = self.process_response(response)
            # 執行信號request_finished
            request_finished.send(self, response=response)
        except Exception:
            if not from_error_handler:
                raise
            self.logger.exception('Request finalizing failed with an '
                                  'error while handling an error')
        return response

九、process_response

----執行@fater_request函數
----self.save_session(ctx.session, response)

def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprint
        funcs = ctx._after_request_functions
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        # 執行 after_request裝飾器
        for handler in funcs:
            response = handler(response)
        # 將內存中的session持久化到:數據庫、....
        if not self.session_interface.is_null_session(ctx.session):
            self.save_session(ctx.session, response)
        return response

十、經過self.save_session調用save_session並返回SecureCookieSessionInterface.save_session(self, session, response)

# Flask類
def
save_session(self, session, response): return self.session_interface.save_session(self, session, response)

十一、save_session保存session,報錯return null

SecureCookieSessionInterface類:
    def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app) # 域名
        path = self.get_cookie_path(app) # 路徑

        # Delete case.  If there is no session we bail early.
        # If the session was modified to be empty we remove the
        # whole cookie.
        if not session:
            if session.modified:
                response.delete_cookie(app.session_cookie_name,
                                       domain=domain, path=path)
            return

        # Modification case.  There are upsides and downsides to
        # emitting a set-cookie header each request.  The behavior
        # is controlled by the :meth:`should_set_cookie` method
        # which performs a quick check to figure out if the cookie
        # should be set or not.  This is controlled by the
        # SESSION_REFRESH_EACH_REQUEST config flag as well as
        # the permanent flag on the session itself.
        if not self.should_set_cookie(app, session):
            return

        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session)) # 加密
        response.set_cookie(app.session_cookie_name, val,    # 最後保存在cookie中
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)

 附錄

相關文章
相關標籤/搜索