#!/usr/bin/env python
# coding: utf-8

"""阻塞和非阻塞的 HTTP 客户端接口.

这个模块定义了一个被两种实现方式 ``simple_httpclient`` 和
``curl_httpclient`` 共享的通用接口 . 应用程序可以选择直接实例化相对应的实现类,
或使用本模块提供的 `AsyncHTTPClient` 类, 通过复写
`AsyncHTTPClient.configure` 方法来选择一种实现 .

默认的实现是 ``simple_httpclient``, 这可以能满足大多数用户的需要 . 然而, 一
些应用程序可能会因为以下原因想切换到 ``curl_httpclient`` :

* ``curl_httpclient`` 有一些 ``simple_httpclient`` 不具有的功能特性,
  包括对 HTTP 代理和使用指定网络接口能力的支持.

* ``curl_httpclient`` 更有可能与不完全符合 HTTP 规范的网站兼容, 或者与
  使用很少使用 HTTP 特性的网站兼容.

* ``curl_httpclient`` 更快.

* ``curl_httpclient`` 是 Tornado 2.0 之前的默认值.

注意, 如果你正在使用 ``curl_httpclient``, 强力建议你使用最新版本的
``libcurl`` 和 ``pycurl``.  当前 libcurl 能被支持的最小版本是
7.21.1, pycurl 能被支持的最小版本是 7.18.2. 强烈建议你所安装的 ``libcurl``
是和异步 DNS 解析器 (threaded 或 c-ares) 一起构建的,
否则你可能会遇到各种请求超时的问题 (更多信息请查看
http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUTMS
和 curl_httpclient.py 里面的注释).

为了选择 ``curl_httpclient``, 只需要在启动的时候调用
`AsyncHTTPClient.configure` ::

    AsyncHTTPClient.configure("tornado.curl_httpclient.CurlAsyncHTTPClient")
"""

from __future__ import absolute_import, division, print_function, with_statement

import functools
import time
import weakref

from tornado.concurrent import TracebackFuture
from tornado.escape import utf8, native_str
from tornado import httputil, stack_context
from tornado.ioloop import IOLoop
from tornado.util import Configurable


class HTTPClient(object):
    """一个阻塞的 HTTP 客户端.

    提供这个接口是为了方便使用和测试; 大多数运行于 IOLoop 的应用程序
    会使用 `AsyncHTTPClient` 来替代它.
    一般的用法就像这样 ::

        http_client = httpclient.HTTPClient()
        try:
            response = http_client.fetch("http://www.google.com/")
            print response.body
        except httpclient.HTTPError as e:
            # HTTPError is raised for non-200 responses; the response
            # can be found in e.response.
            print("Error: " + str(e))
        except Exception as e:
            # Other errors are possible, such as IOError.
            print("Error: " + str(e))
        http_client.close()
    """
    def __init__(self, async_client_class=None, **kwargs):
        self._io_loop = IOLoop(make_current=False)
        if async_client_class is None:
            async_client_class = AsyncHTTPClient
        self._async_client = async_client_class(self._io_loop, **kwargs)
        self._closed = False

    def __del__(self):
        self.close()

    def close(self):
        """关闭该 HTTPClient, 释放所有使用的资源."""
        if not self._closed:
            self._async_client.close()
            self._io_loop.close()
            self._closed = True

    def fetch(self, request, **kwargs):
        """执行一个请求, 返回一个 `HTTPResponse` 对象.

        该请求可以是一个 URL 字符串或是一个 `HTTPRequest` 对象.
        如果它是一个字符串, 我们会使用任意关键字参数构造一个
        `HTTPRequest` : ``HTTPRequest(request, **kwargs)``

        如果在 fetch 过程中发生错误, 我们将抛出一个 `HTTPError` 除非
        ``raise_error`` 关键字参数被设置为 False.
        """
        response = self._io_loop.run_sync(functools.partial(
            self._async_client.fetch, request, **kwargs))
        return response


class AsyncHTTPClient(Configurable):
    """一个非阻塞 HTTP 客户端.

    使用示例::

        def handle_request(response):
            if response.error:
                print "Error:", response.error
            else:
                print response.body

        http_client = AsyncHTTPClient()
        http_client.f
etch("http://www.google.com/", handle_request)

    这个类的构造器有几个比较神奇的考虑: 它实际创建了一个基于特定实现的子
    类的实例, 并且该实例被作为一种伪单例重用 (每一个 `.IOLoop` ).
    使用关键字参数 ``force_instance=True`` 可以用来限制这种单例行为.
    只有使用了 ``force_instance=True`` 时候, 才可以传递 ``io_loop`` 以外其他
    的参数给 `AsyncHTTPClient` 构造器.
    实现的子类以及它的构造器的参数可以通过静态方法 `configure()` 设置.

    所有 `AsyncHTTPClient` 实现都支持一个 ``defaults`` 关键字参数,
    可以被用来
设置默认 `HTTPRequest` 属性的值. 例如::

        AsyncHTTPClient.configure(
            None, defaults=dict(user_agent="MyUserAgent"))
        # or with force_instance:
        client = AsyncHTTPClient(force_instance=True,
            defaults=dict(user_agent="MyUserAgent"))

    .. versionchanged:: 4.1
       ``io_loop`` 参数被废弃.
    """
    @classmethod
    def configurable_base(cls):
        return AsyncHTTPClient

    @classmethod
    def configurable_default(cls):
        from tornado.simple_httpclient import SimpleAsyncHTTPClient
        return SimpleAsyncHTTPClient

    @classmethod
    def _async_clients(cls):
        attr_name = '_async_client_dict_' + cls.__name__
        if not hasattr(cls, attr_name):
            setattr(cls, attr_name, weakref.WeakKeyDictionary())
        return getattr(cls, attr_name)

    def __new__(cls, io_loop=None, force_instance=False, **kwargs):
        io_loop = io_loop or IOLoop.current()
        if force_instance:
            instance_cache = None
        else:
            instance_cache = cls._async_clients()
        if instance_cache is not None and io_loop in instance_cache:
            return instance_cache[io_loop]
        instance = super(AsyncHTTPClient, cls).__new__(cls, io_loop=io_loop,
                                                       **kwargs)
        # Make sure the instance knows which cache to remove itself from.
        # It can't simply call _async_clients() because we may be in
        # __new__(AsyncHTTPClient) but instance.__class__ may be
        # SimpleAsyncHTTPClient.
        instance._instance_cache = instance_cache
        if instance_cache is not None:
            instance_cache[instance.io_loop] = instance
        return instance

    def initialize(self, io_loop, defaults=None):
        self.io_loop = io_loop
        self.defaults = dict(HTTPRequest._DEFAULTS)
        if defaults is not None:
            self.defaults.update(defaults)
        self._closed = False

    def close(self):
        """销毁该 HTTP 客户端, 释放所有被使用的文件描述符.

        因为 `AsyncHTTPClient` 对象透明重用的方式, 该方法
        **在正常使用时并不需要** .
        ``close()`` 一般只有在 `.IOLoop` 也被关闭, 或在创建
        `AsyncHTTPClient` 的时候使用了 ``force_instance=True`` 参数才需要.

        在 `AsyncHTTPClient` 调用 ``close()`` 方法后, 其他方法就不能被调用
        了.

        """
        if self._closed:
            return
        self._closed = True
        if self._instance_cache is not None:
            if self._instance_cache.get(self.io_loop) is not self:
                raise RuntimeError("inconsistent AsyncHTTPClient cache")
            del self._instance_cache[self.io_loop]

    def fetch(self, request, callback=None, raise_error=True, **kwargs):
        """执行一个请求, 并且异步的返回 `HTTPResponse`.

        request 参数可以是一个 URL 字符串也可以是一个 `HTTPRequest` 对象.
        如果是一个字符串, 我们将使用全部的关键字参数一起构造一个
        `HTTPRequest` 对象: ``HTTPRequest(request, **kwargs)``

        这个方法返回一个结果为 `HTTPResponse` 的 `.Future` 对象.
        默认情况下, 如果该请求返回一个非 200 的响应码, 这个 ``Future``
        将会抛出一个 `HTTPError` 错误. 相反, 如果 ``raise_error`` 设置为
        False, 则无论响应码如何, 都将返回该 response (响应).

        如果给定了 ``callback`` , 它将被 `HTTPResponse` 调用.
        在回调接口中, `HTTPError` 不会自动抛出. 相反你必须检查该响应的
        ``error`` 属性或者调用它的 `~HTTPResponse.rethrow` 方法.
        """
        if self._closed:
            raise RuntimeError("fetch() called on closed AsyncHTTPClient")
        if not isinstance(request, HTTPRequest):
            request = HTTPRequest(url=request, **kwargs)
        # We may modify this (to add Host, Accept-Encoding, etc),
        # so make sure we don't modify the caller's object.  This is also
        # where normal dicts get converted to HTTPHeaders objects.
        request.headers = httputil.HTTPHeaders(request.headers)
        request = _RequestProxy(request, self.defaults)
        future = TracebackFuture()
        if callback is not None:
            callback = stack_context.wrap(callback)

            def handle_future(future):
                exc = future.exception()
                if isinstance(exc, HTTPError) and exc.response is not None:
                    response = exc.response
                elif exc is not None:
                    response = HTTPResponse(
                        request, 599, error=exc,
                        request_time=time.time() - request.start_time)
                else:
                    response = future.result()
                self.io_loop.add_callback(callback, response)
            fut