Python编程:HTTP Cookies

HTTP Cookies 是 Web 开发中用于维持状态的重要机制。

基本概念

定义:Cookie 是服务器发送到用户浏览器并保存在本地的小型数据片段,浏览器会在后续请求中自动携带这些数据。

主要用途

  • 会话管理(登录状态、购物车等)

  • 个性化设置(主题、语言等)

  • 用户行为追踪

Cookie 的工作原理

  1. 服务器设置:通过 Set-Cookie 响应头

    Set-Cookie: sessionid=38afes7a8; HttpOnly; Path=/
  2. 浏览器存储:浏览器按照规则存储 Cookie

  3. 自动发送:后续请求通过 Cookie 请求头自动发送

    Cookie: sessionid=38afes7a8; csrftoken=7hu8j3lo

Cookie 属性详解

属性说明示例
Name/ValueCookie 名称和值id=12345
Domain指定哪些域名可以接收.example.com
Path指定 URL 路径前缀/products
Expires过期时间(绝对时间)Wed, 21 Oct 2025 07:28:00 GMT
Max-Age存活秒数(相对时间)3600 (1小时)
Secure仅 HTTPS 连接发送Secure
HttpOnly禁止 JavaScript 访问HttpOnly
SameSite控制跨站发送SameSite=Lax

Cookie 类型

按生命周期分类:

  • 会话 Cookie:浏览器关闭后删除

  • 持久 Cookie:根据 Expires/Max-Age 保留

按用途分类:

  • 必要 Cookie:网站核心功能所需

  • 偏好 Cookie:存储用户设置

  • 统计 Cookie:分析用户行为

  • 营销 Cookie:追踪用户用于广告

安全相关

主要安全威胁:

  • 跨站脚本攻击 (XSS):通过 JS 窃取 Cookie

    • 防御:使用 HttpOnly 属性

  • 跨站请求伪造 (CSRF):利用已认证状态

    • 防御:使用 SameSite 属性 + CSRF Token

  • 会话劫持:窃取会话 ID

    • 防御:使用 Secure + 定期更换会话 ID

SameSite 属性:

  • Strict:完全禁止跨站发送

  • Lax:允许安全方法(GET)的顶级导航

  • None:允许跨站发送(必须配合 Secure

JavaScript 操作

// 读取所有 Cookie
document.cookie // "id=123; theme=dark"

// 设置 Cookie(会追加而非覆盖)
document.cookie = "username=john; max-age=3600; path=/"

// 删除 Cookie(设置过期时间为过去)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT"

服务器端设置示例

Node.js (Express):

res.cookie('token', 'abc123', {
  maxAge: 24 * 60 * 60 * 1000, // 1天
  httpOnly: true,
  secure: true,
  sameSite: 'lax'
})

PHP:

setcookie("user", "John", [
  'expires' => time() + 3600,
  'path' => '/',
  'secure' => true,
  'httponly' => true,
  'samesite' => 'Lax'
]);

实践

  1. 敏感数据:不要存储在 Cookie 中,使用 Session

  2. 大小限制:单个 Cookie ≤ 4KB,每个域名 ≤ 50个(浏览器差异)

  3. 生产环境:始终设置 Secure 和 HttpOnly

  4. 会话管理:使用随机、不可预测的 Session ID

  5. 隐私合规:遵循 GDPR 等法规,提供 Cookie 使用说明

现代替代方案

  • Web StoragelocalStorage 和 sessionStorage

  • IndexedDB:客户端大规模数据存储

  • JWT (JSON Web Tokens):用于认证的无状态令牌

调试工具

  • 浏览器开发者工具 → Application → Cookies

  • document.cookie API

  • 网络抓包工具查看 HTTP 头

Cookie 仍然是 Web 开发中不可或缺的部分,正确理解和安全使用 Cookie 对于构建可靠的 Web 应用至关重要。

Requests中 Cookie实现

MockRequest 和 MockResponse 类

class MockRequest:
    """Wraps a `requests.Request` to mimic a `urllib2.Request`.

    The code in `http.cookiejar.CookieJar` expects this interface in order to correctly
    manage cookie policies, i.e., determine whether a cookie can be set, given the
    domains of the request and the cookie.

    The original request object is read-only. The client is responsible for collecting
    the new headers via `get_new_headers()` and interpreting them appropriately. You
    probably want `get_cookie_header`, defined below.
    """

    def __init__(self, request):
        self._r = request
        self._new_headers = {}
        self.type = urlparse(self._r.url).scheme

    def get_type(self):
        return self.type

    def get_host(self):
        return urlparse(self._r.url).netloc

    def get_origin_req_host(self):
        return self.get_host()

    def get_full_url(self):
        # Only return the response's URL if the user hadn't set the Host
        # header
        if not self._r.headers.get("Host"):
            return self._r.url
        # If they did set it, retrieve it and reconstruct the expected domain
        host = to_native_string(self._r.headers["Host"], encoding="utf-8")
        parsed = urlparse(self._r.url)
        # Reconstruct the URL as we expect it
        return urlunparse(
            [
                parsed.scheme,
                host,
                parsed.path,
                parsed.params,
                parsed.query,
                parsed.fragment,
            ]
        )

    def is_unverifiable(self):
        return True

    def has_header(self, name):
        return name in self._r.headers or name in self._new_headers

    def get_header(self, name, default=None):
        return self._r.headers.get(name, self._new_headers.get(name, default))

    def add_header(self, key, val):
        """cookiejar has no legitimate use for this method; add it back if you find one."""
        raise NotImplementedError(
            "Cookie headers should be added with add_unredirected_header()"
        )

    def add_unredirected_header(self, name, value):
        self._new_headers[name] = value

    def get_new_headers(self):
        return self._new_headers

    @property
    def unverifiable(self):
        return self.is_unverifiable()

    @property
    def origin_req_host(self):
        return self.get_origin_req_host()

    @property
    def host(self):
        return self.get_host()


class MockResponse:
    """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.

    ...what? Basically, expose the parsed HTTP headers from the server response
    the way `http.cookiejar` expects to see them.
    """

    def __init__(self, headers):
        """Make a MockResponse for `cookiejar` to read.

        :param headers: a httplib.HTTPMessage or analogous carrying the headers
        """
        self._headers = headers

    def info(self):
        return self._headers

    def getheaders(self, name):
        self._headers.getheaders(name)


def extract_cookies_to_jar(jar, request, response):
    """Extract the cookies from the response into a CookieJar.

    :param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar)
    :param request: our own requests.Request object
    :param response: urllib3.HTTPResponse object
    """
    if not (hasattr(response, "_original_response") and response._original_response):
        return
    # the _original_response field is the wrapped httplib.HTTPResponse object,
    req = MockRequest(request)
    # pull out the HTTPMessage with the headers and put it in the mock:
    res = MockResponse(response._original_response.msg)
    jar.extract_cookies(res, req)


def get_cookie_header(jar, request):
    """
    Produce an appropriate Cookie header string to be sent with `request`, or None.

    :rtype: str
    """
    r = MockRequest(request)
    jar.add_cookie_header(r)
    return r.get_new_headers().get("Cookie")


def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
    """Unsets a cookie by name, by default over all domains and paths.

    Wraps CookieJar.clear(), is O(n).
    """
    clearables = []
    for cookie in cookiejar:
        if cookie.name != name:
            continue
        if domain is not None and domain != cookie.domain:
            continue
        if path is not None and path != cookie.path:
            continue
        clearables.append((cookie.domain, cookie.path, cookie.name))

    for domain, path, name in clearables:
        cookiejar.clear(domain, path, name)

这两个类作为适配器,将 Requests 的请求/响应对象转换为 http.cookiejar 模块期望的接口格式:

  • MockRequest: 包装 Requests 的 Request 对象,模拟 urllib2.Request 接口

    • 提供获取 URL、主机名、头部信息等方法

    • 支持添加未重定向的 cookie 头部

  • MockResponse: 包装 HTTP 响应头部,模拟 urllib.addinfourl 接口

    • 提供获取响应头部信息的方法

1. MockRequest 类

作用:将 Requests 的 Request 对象适配成 http.cookiejar 模块需要的接口形式(模拟 urllib2.Request

核心方法

  • __init__:

    • 包装原始请求对象 request

    • 初始化空字典 _new_headers 用于存储新生成的 Cookie 头

    • 解析 URL 的协议类型(http/https)

  • URL 相关方法:

    get_host()       # 获取域名部分(如 "example.com:8080")
    get_full_url()   # 处理 Host 头覆盖的特殊情况
  • 头部操作:

    add_unredirected_header()  # 添加不会随重定向传递的头部(专用于 Cookie)
    get_new_headers()          # 获取新添加的头部(如生成的 Cookie 头)
  • 兼容性属性:

    @property
    def unverifiable(self):    # 标识请求是否可验证(始终返回 True)

关键设计

  • 只读访问原始请求头,通过 _new_headers 管理新增头

  • 自动处理 Host 头与 URL 不一致的情况

2. MockResponse 类

作用:将 HTTP 响应适配成 http.cookiejar 需要的接口(模拟 urllib.addinfourl

核心方法:

  • info(): 返回原始响应头(供 cookiejar 解析 Set-Cookie

  • getheaders(): 获取指定响应头(未实际使用)

设计意图

  • 极简包装器,仅暴露 http.cookiejar 所需的接口

3. 核心工具函数

extract_cookies_to_jar(jar, request, response)
功能:从响应中提取 Cookie 到 CookieJar
参数:
  - jar: CookieJar 实例
  - request: requests.Request 对象
  - response: urllib3.HTTPResponse 对象
流程:
  1. 检查是否存在原始响应对象(_original_response)
  2. 用 MockRequest/MockResponse 包装请求和响应
  3. 调用 jar.extract_cookies() 解析 Set-Cookie 头
get_cookie_header(jar, request)
功能:生成当前请求应发送的 Cookie 头字符串
流程:
  1. 用 MockRequest 包装请求
  2. 调用 jar.add_cookie_header() 让 CookieJar 计算匹配的 Cookie
  3. 返回生成的 Cookie 头(如 "name1=value1; name2=value2")
remove_cookie_by_name(cookiejar, name, domain, path)
功能:精确删除指定 Cookie
参数:
  - domain/path: 可选过滤条件
实现:
  1. 遍历 CookieJar 找出匹配的 Cookie
  2. 调用 clear() 方法批量删除
特点:
  - O(n) 时间复杂度
  - 支持按域名/路径精确删除

RequestsCookieJar 类

class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
    """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict
    interface.

    This is the CookieJar we create by default for requests and sessions that
    don't specify one, since some clients may expect response.cookies and
    session.cookies to support dict operations.

    Requests does not use the dict interface internally; it's just for
    compatibility with external client code. All requests code should work
    out of the box with externally provided instances of ``CookieJar``, e.g.
    ``LWPCookieJar`` and ``FileCookieJar``.

    Unlike a regular CookieJar, this class is pickleable.

    .. warning:: dictionary operations that are normally O(1) may be O(n).
    """

    def get(self, name, default=None, domain=None, path=None):
        """Dict-like get() that also supports optional domain and path args in
        order to resolve naming collisions from using one cookie jar over
        multiple domains.

        .. warning:: operation is O(n), not O(1).
        """
        try:
            return self._find_no_duplicates(name, domain, path)
        except KeyError:
            return default

    def set(self, name, value, **kwargs):
        """Dict-like set() that also supports optional domain and path args in
        order to resolve naming collisions from using one cookie jar over
        multiple domains.
        """
        # support client code that unsets cookies by assignment of a None value:
        if value is None:
            remove_cookie_by_name(
                self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
            )
            return

        if isinstance(value, Morsel):
            c = morsel_to_cookie(value)
        else:
            c = create_cookie(name, value, **kwargs)
        self.set_cookie(c)
        return c

    def iterkeys(self):
        """Dict-like iterkeys() that returns an iterator of names of cookies
        from the jar.

        .. seealso:: itervalues() and iteritems().
        """
        for cookie in iter(self):
            yield cookie.name

    def keys(self):
        """Dict-like keys() that returns a list of names of cookies from the
        jar.

        .. seealso:: values() and items().
        """
        return list(self.iterkeys())

    def itervalues(self):
        """Dict-like itervalues() that returns an iterator of values of cookies
        from the jar.

        .. seealso:: iterkeys() and iteritems().
        """
        for cookie in iter(self):
            yield cookie.value

    def values(self):
        """Dict-like values() that returns a list of values of cookies from the
        jar.

        .. seealso:: keys() and items().
        """
        return list(self.itervalues())

    def iteritems(self):
        """Dict-like iteritems() that returns an iterator of name-value tuples
        from the jar.

        .. seealso:: iterkeys() and itervalues().
        """
        for cookie in iter(self):
            yield cookie.name, cookie.value

    def items(self):
        """Dict-like items() that returns a list of name-value tuples from the
        jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
        vanilla python dict of key value pairs.

        .. seealso:: keys() and values().
        """
        return list(self.iteritems())

    def list_domains(self):
        """Utility method to list all the domains in the jar."""
        domains = []
        for cookie in iter(self):
            if cookie.domain not in domains:
                domains.append(cookie.domain)
        return domains

    def list_paths(self):
        """Utility method to list all the paths in the jar."""
        paths = []
        for cookie in iter(self):
            if cookie.path not in paths:
                paths.append(cookie.path)
        return paths

    def multiple_domains(self):
        """Returns True if there are multiple domains in the jar.
        Returns False otherwise.

        :rtype: bool
        """
        domains = []
        for cookie in iter(self):
            if cookie.domain is not None and cookie.domain in domains:
                return True
            domains.append(cookie.domain)
        return False  # there is only one domain in jar

    def get_dict(self, domain=None, path=None):
        """Takes as an argument an optional domain and path and returns a plain
        old Python dict of name-value pairs of cookies that meet the
        requirements.

        :rtype: dict
        """
        dictionary = {}
        for cookie in iter(self):
            if (domain is None or cookie.domain == domain) and (
                path is None or cookie.path == path
            ):
                dictionary[cookie.name] = cookie.value
        return dictionary

    def __contains__(self, name):
        try:
            return super().__contains__(name)
        except CookieConflictError:
            return True

    def __getitem__(self, name):
        """Dict-like __getitem__() for compatibility with client code. Throws
        exception if there are more than one cookie with name. In that case,
        use the more explicit get() method instead.

        .. warning:: operation is O(n), not O(1).
        """
        return self._find_no_duplicates(name)

    def __setitem__(self, name, value):
        """Dict-like __setitem__ for compatibility with client code. Throws
        exception if there is already a cookie of that name in the jar. In that
        case, use the more explicit set() method instead.
        """
        self.set(name, value)

    def __delitem__(self, name):
        """Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s
        ``remove_cookie_by_name()``.
        """
        remove_cookie_by_name(self, name)

    def set_cookie(self, cookie, *args, **kwargs):
        if (
            hasattr(cookie.value, "startswith")
            and cookie.value.startswith('"')
            and cookie.value.endswith('"')
        ):
            cookie.value = cookie.value.replace('\\"', "")
        return super().set_cookie(cookie, *args, **kwargs)

    def update(self, other):
        """Updates this jar with cookies from another CookieJar or dict-like"""
        if isinstance(other, cookielib.CookieJar):
            for cookie in other:
                self.set_cookie(copy.copy(cookie))
        else:
            super().update(other)

    def _find(self, name, domain=None, path=None):
        """Requests uses this method internally to get cookie values.

        If there are conflicting cookies, _find arbitrarily chooses one.
        See _find_no_duplicates if you want an exception thrown if there are
        conflicting cookies.

        :param name: a string containing name of cookie
        :param domain: (optional) string containing domain of cookie
        :param path: (optional) string containing path of cookie
        :return: cookie.value
        """
        for cookie in iter(self):
            if cookie.name == name:
                if domain is None or cookie.domain == domain:
                    if path is None or cookie.path == path:
                        return cookie.value

        raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")

    def _find_no_duplicates(self, name, domain=None, path=None):
        """Both ``__get_item__`` and ``get`` call this function: it's never
        used elsewhere in Requests.

        :param name: a string containing name of cookie
        :param domain: (optional) string containing domain of cookie
        :param path: (optional) string containing path of cookie
        :raises KeyError: if cookie is not found
        :raises CookieConflictError: if there are multiple cookies
            that match name and optionally domain and path
        :return: cookie.value
        """
        toReturn = None
        for cookie in iter(self):
            if cookie.name == name:
                if domain is None or cookie.domain == domain:
                    if path is None or cookie.path == path:
                        if toReturn is not None:
                            # if there are multiple cookies that meet passed in criteria
                            raise CookieConflictError(
                                f"There are multiple cookies with name, {name!r}"
                            )
                        # we will eventually return this as long as no cookie conflict
                        toReturn = cookie.value

        if toReturn:
            return toReturn
        raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")

    def __getstate__(self):
        """Unlike a normal CookieJar, this class is pickleable."""
        state = self.__dict__.copy()
        # remove the unpickleable RLock object
        state.pop("_cookies_lock")
        return state

    def __setstate__(self, state):
        """Unlike a normal CookieJar, this class is pickleable."""
        self.__dict__.update(state)
        if "_cookies_lock" not in self.__dict__:
            self._cookies_lock = threading.RLock()

    def copy(self):
        """Return a copy of this RequestsCookieJar."""
        new_cj = RequestsCookieJar()
        new_cj.set_policy(self.get_policy())
        new_cj.update(self)
        return new_cj

    def get_policy(self):
        """Return the CookiePolicy instance used."""
        return self._policy


def _copy_cookie_jar(jar):
    if jar is None:
        return None

    if hasattr(jar, "copy"):
        # We're dealing with an instance of RequestsCookieJar
        return jar.copy()
    # We're dealing with a generic CookieJar instance
    new_jar = copy.copy(jar)
    new_jar.clear()
    for cookie in jar:
        new_jar.set_cookie(copy.copy(cookie))
    return new_jar


def create_cookie(name, value, **kwargs):
    """Make a cookie from underspecified parameters.

    By default, the pair of `name` and `value` will be set for the domain ''
    and sent on every request (this is sometimes called a "supercookie").
    """
    result = {
        "version": 0,
        "name": name,
        "value": value,
        "port": None,
        "domain": "",
        "path": "/",
        "secure": False,
        "expires": None,
        "discard": True,
        "comment": None,
        "comment_url": None,
        "rest": {"HttpOnly": None},
        "rfc2109": False,
    }

    badargs = set(kwargs) - set(result)
    if badargs:
        raise TypeError(
            f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
        )

    result.update(kwargs)
    result["port_specified"] = bool(result["port"])
    result["domain_specified"] = bool(result["domain"])
    result["domain_initial_dot"] = result["domain"].startswith(".")
    result["path_specified"] = bool(result["path"])

    return cookielib.Cookie(**result)


def morsel_to_cookie(morsel):
    """Convert a Morsel object into a Cookie containing the one k/v pair."""

    expires = None
    if morsel["max-age"]:
        try:
            expires = int(time.time() + int(morsel["max-age"]))
        except ValueError:
            raise TypeError(f"max-age: {morsel['max-age']} must be integer")
    elif morsel["expires"]:
        time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
        expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
    return create_cookie(
        comment=morsel["comment"],
        comment_url=bool(morsel["comment"]),
        discard=False,
        domain=morsel["domain"],
        expires=expires,
        name=morsel.key,
        path=morsel["path"],
        port=None,
        rest={"HttpOnly": morsel["httponly"]},
        rfc2109=False,
        secure=bool(morsel["secure"]),
        value=morsel.value,
        version=morsel["version"] or 0,
    )


def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
    """Returns a CookieJar from a key/value dictionary.

    :param cookie_dict: Dict of key/values to insert into CookieJar.
    :param cookiejar: (optional) A cookiejar to add the cookies to.
    :param overwrite: (optional) If False, will not replace cookies
        already in the jar with new ones.
    :rtype: CookieJar
    """
    if cookiejar is None:
        cookiejar = RequestsCookieJar()

    if cookie_dict is not None:
        names_from_jar = [cookie.name for cookie in cookiejar]
        for name in cookie_dict:
            if overwrite or (name not in names_from_jar):
                cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))

    return cookiejar


def merge_cookies(cookiejar, cookies):
    """Add cookies to cookiejar and returns a merged CookieJar.

    :param cookiejar: CookieJar object to add the cookies to.
    :param cookies: Dictionary or CookieJar object to be added.
    :rtype: CookieJar
    """
    if not isinstance(cookiejar, cookielib.CookieJar):
        raise ValueError("You can only merge into CookieJar")

    if isinstance(cookies, dict):
        cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
    elif isinstance(cookies, cookielib.CookieJar):
        try:
            cookiejar.update(cookies)
        except AttributeError:
            for cookie_in_jar in cookies:
                cookiejar.set_cookie(cookie_in_jar)

    return cookiejar

核心架构设计

多重继承结构

class RequestsCookieJar(cookielib.CookieJar, MutableMapping)
  • 继承自 cookielib.CookieJar:获得标准库的 Cookie 处理能力

  • 实现 MutableMapping:提供字典式操作接口

设计哲学

  • 兼容性优先:既兼容标准库接口,又提供更友好的字典操作

  • 明确性能警告:所有字典操作都是 O(n) 而非 O(1)

  • 线程安全:内部使用 RLock 保证线程安全

核心功能

字典式接口实现

# 标准字典方法
__contains__、__getitem__、__setitem__、__delitem__
keys()、values()、items()
iterkeys()、itervalues()、iteritems()

增强型 Cookie 操作

get(name, default=None, domain=None, path=None)  # 支持域名/路径过滤
set(name, value, **kwargs)  # 支持完整 Cookie 属性设置
remove_cookie_by_name()  # 精确删除

特殊方法实现

__getstate__/__setstate__  # 支持 pickle 序列化
copy()  # 深度复制
update()  # 合并 CookieJar 或字典

关键算法

Cookie 查找实现

def _find(self, name, domain=None, path=None):
    # 线性扫描,返回第一个匹配的 Cookie
    # 可能产生不一致行为

def _find_no_duplicates(self, name, domain=None, path=None):
    # 严格查找,发现冲突时抛出 CookieConflictError
    # 被 __getitem__ 和 get() 调用

性能特点

  • 所有查找操作都是 O(n)

  • 使用 iter(self) 遍历内部存储

  • 域名/路径过滤增加比较开销

辅助函数分析

create_cookie()

# 创建 Cookie 对象的工厂函数
# 处理默认值:domain='', path='/', secure=False 等
# 验证参数有效性

morsel_to_cookie()

# 将标准库的 Morsel 对象转换为 Cookie 对象
# 特殊处理 expires/max-age 时间转换

cookiejar_from_dict()

# 从字典创建 CookieJar
# 支持 overwrite 参数控制是否覆盖现有 Cookie

merge_cookies()

# 合并两个 Cookie 源
# 智能处理字典和 CookieJar 两种输入

安全特性实现

Cookie 安全处理

def set_cookie(self, cookie, *args, **kwargs):
    # 自动去除 Cookie 值中的多余引号
    if cookie.value.startswith('"') and cookie.value.endswith('"'):
        cookie.value = cookie.value.replace('\\"', "")

属性强制

# 在 create_cookie() 中:
result["port_specified"] = bool(result["port"])
result["domain_specified"] = bool(result["domain"])
result["domain_initial_dot"] = result["domain"].startswith(".")

高级示例

精确控制 Cookie

jar = RequestsCookieJar()
jar.set('session', 'abc123', 
       domain='.example.com', 
       path='/admin',
       secure=True,
       httponly=True)

复杂查询

# 获取特定域名下的所有 Cookie
admin_cookies = jar.get_dict(domain='admin.example.com')

# 检查多域名情况
if jar.multiple_domains():
    print("跨域名 Cookie 存储")

合并操作

# 合并两个 CookieJar
merged = merge_cookies(jar1, jar2)

# 从字典更新
jar.update({'user': 'john', 'token': 'xyz456'})

设计亮点

  1. 灵活的接口设计

    • 同时满足高级用户和简单场景需求

    • 即支持精确控制,也支持简单字典操作

  2. 完善的错误处理

    • CookieConflictError 明确指示命名冲突

    • 严格的参数验证

  3. 实用的工具方法

    • list_domains()/list_paths() 方便调试

    • get_dict() 快速导出子集

  4. 良好的文档

    • 每个方法都有清晰的文档字符串

    • 明确警告性能特征

性能优化

虽然当前实现强调正确性而非性能,但在高频使用场景下可以考虑:

  1. 建立名称索引

    # 在 __setitem__ 中维护名称索引
    self._name_index = defaultdict(list)  # name -> [cookies]
  2. LRU 缓存

    # 对常见查询结果缓存
    @functools.lru_cache(maxsize=100)
    def get(self, name, domain=None, path=None):
  3. 批量操作

    def set_many(self, cookies_dict):
        # 批量设置 Cookie

与其他组件的关系

这个实现是 Requests 库能够优雅处理 HTTP 状态管理的基础,其设计平衡了灵活性、安全性和易用性,是 Python HTTP 客户端中 Cookie 处理的典范实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值