request an URL

    :arg string url: URL to be fetched.
    :arg string method: (optional) HTTP method, one of ``GET``, ``DELETE``,
                        ``HEAD``, ``OPTIONS``, ``PUT``, ``POST``, ``TRACE``,
                        ``PATCH``. ``GET`` is the default.
    :arg dict/string params: (optional) Dict or string to attach to url as
                                querystring.
    :arg dict headers: (optional) HTTP request headers.
    :arg float timeout: (optional) Timeout in seconds
    :arg files: (optional) Files to be sended
    :arg randua: (optional) If ``True`` or ``path string``, use a random
                    user-agent in headers, instead of
                    ``'urlfetch/' + __version__``
    :arg tuple auth: (optional) (username, password) for basic authentication
    :arg int length_limit: (optional) If ``None``, no limits on content length,
                        if the limit reached raised exception 'Content length
                        is more than ...'
    :arg dict proxies: (optional) HTTP proxy, like {'http': '127.0.0.1:8888',
                                                 'https': '127.0.0.1:563'}
    :arg bool trust_env: (optional) If ``True``, urlfetch will get infomations
                        from env, such as HTTP_PROXY, HTTPS_PROXY
    :arg int max_redirects: (integer, optional) Max redirects allowed within a
                            request. Default is 0, which means redirects are
                            not allowed.
    :arg tuple source_address: (optional) A tuple of (host, port) to
                               specify the source_address to bind to. This
                               argument is ignored if you're using Python prior
                               to 2.7/3.2.
    :returns: A :class:`~urlfetch.Response` object
    :raises: :class:`URLError`, :class:`UrlfetchException`,
             :class:`TooManyRedirects`,

def request(url, method="GET", params=None, data=None, headers={},
            timeout=None, files={}, randua=False, auth=None, length_limit=None,
            proxies=None, trust_env=True, max_redirects=0,
            source_address=None, **kwargs):
    """request an URL

    :arg string url: URL to be fetched.
    :arg string method: (optional) HTTP method, one of ``GET``, ``DELETE``,
                        ``HEAD``, ``OPTIONS``, ``PUT``, ``POST``, ``TRACE``,
                        ``PATCH``. ``GET`` is the default.
    :arg dict/string params: (optional) Dict or string to attach to url as
                                querystring.
    :arg dict headers: (optional) HTTP request headers.
    :arg float timeout: (optional) Timeout in seconds
    :arg files: (optional) Files to be sended
    :arg randua: (optional) If ``True`` or ``path string``, use a random
                    user-agent in headers, instead of
                    ``'urlfetch/' + __version__``
    :arg tuple auth: (optional) (username, password) for basic authentication
    :arg int length_limit: (optional) If ``None``, no limits on content length,
                        if the limit reached raised exception 'Content length
                        is more than ...'
    :arg dict proxies: (optional) HTTP proxy, like {'http': '127.0.0.1:8888',
                                                 'https': '127.0.0.1:563'}
    :arg bool trust_env: (optional) If ``True``, urlfetch will get infomations
                        from env, such as HTTP_PROXY, HTTPS_PROXY
    :arg int max_redirects: (integer, optional) Max redirects allowed within a
                            request. Default is 0, which means redirects are
                            not allowed.
    :arg tuple source_address: (optional) A tuple of (host, port) to
                               specify the source_address to bind to. This
                               argument is ignored if you're using Python prior
                               to 2.7/3.2.
    :returns: A :class:`~urlfetch.Response` object
    :raises: :class:`URLError`, :class:`UrlfetchException`,
             :class:`TooManyRedirects`,
    """
    def make_connection(conn_type, host, port, timeout, source_address):
        """Return HTTP or HTTPS connection."""
        if support_source_address:
            kwargs = {'timeout': timeout, 'source_address': source_address}
        else:
            kwargs = {'timeout': timeout}
            if source_address is not None:
                raise UrlfetchException('source_address requires'
                                        'Python 2.7/3.2 or newer versions')
        if conn_type == 'http':
            conn = HTTPConnection(host, port, **kwargs)
        elif conn_type == 'https':
            conn = HTTPSConnection(host, port, **kwargs)
        else:
            raise URLError('Unknown Connection Type: %s' % conn_type)
        return conn

    via_proxy = False

    method = method.upper()
    if method not in ALLOWED_METHODS:
        raise UrlfetchException("Method should be one of " +
                                ", ".join(ALLOWED_METHODS))
    if params:
        if isinstance(params, dict):
            url = url_concat(url, params)
        elif isinstance(params, basestring):
            if url[-1] not in ('?', '&'):
                url += '&' if ('?' in url) else '?'
            url += params

    parsed_url = parse_url(url)

    reqheaders = {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate, compress, identity, *',
        'User-Agent': random_useragent(randua),
        'Host': parsed_url['http_host']
    }

    # Proxy support
    scheme = parsed_url['scheme']
    if proxies is None and trust_env:
        proxies = PROXIES

    ignore_hosts = PROXY_IGNORE_HOSTS
    if trust_env:
        no_proxy = os.getenv('no_proxy') or os.getenv('NO_PROXY')
        if no_proxy:
            ignore_hosts = no_proxy.split(",")

    proxy = proxies.get(scheme)
    if proxy and not any(match_no_proxy(parsed_url['host'], host) for host in ignore_hosts):
        via_proxy = True
        if '://' not in proxy:
            proxy = '%s://%s' % (scheme, proxy)
        parsed_proxy = parse_url(proxy)
        # Proxy-Authorization
        if parsed_proxy['username'] and parsed_proxy['password']:
            proxyauth = '%s:%s' % (parsed_proxy['username'],
                                   parsed_proxy['password'])
            proxyauth = base64.b64encode(proxyauth.encode('utf-8'))
            reqheaders['Proxy-Authorization'] = 'Basic ' + \
                                                proxyauth.decode('utf-8')
        conn = make_connection(scheme, parsed_proxy['host'],
                               parsed_proxy['port'], timeout, source_address)
    else:
        conn = make_connection(scheme, parsed_url['host'], parsed_url['port'],
                               timeout, source_address)

    if not auth and parsed_url['username'] and parsed_url['password']:
        auth = (parsed_url['username'], parsed_url['password'])
    if auth:
        if isinstance(auth, (list, tuple)):
            auth = '%s:%s' % tuple(auth)
        auth = base64.b64encode(auth.encode('utf-8'))
        reqheaders['Authorization'] = 'Basic ' + auth.decode('utf-8')

    if files:
        content_type, data = encode_multipart(data, files)
        reqheaders['Content-Type'] = content_type
    elif isinstance(data, dict):
        data = urlencode(data, 1)

    if isinstance(data, basestring) and not files:
        # httplib will set 'Content-Length', also you can set it by yourself
        reqheaders["Content-Type"] = "application/x-www-form-urlencoded"
        # what if the method is GET, HEAD or DELETE
        # just do not make so much decisions for users

    reqheaders.update(headers)

    start_time = time.time()
    try:
        request_url = url if via_proxy else parsed_url['uri']
        conn.request(method, request_url, data, reqheaders)
        resp = conn.getresponse()
    except socket.timeout as e:
        raise Timeout(e)
    except Exception as e:
        raise UrlfetchException(e)

    end_time = time.time()
    total_time = end_time - start_time
    history = []
    response = Response.from_httplib(resp, reqheaders=reqheaders,
                                     length_limit=length_limit,
                                     history=history[:], url=url,
                                     total_time=total_time,
                                     start_time=start_time)

    while (response.status in (301, 302, 303, 307) and
           'location' in response.headers and max_redirects):
        response.body, response.close(), history.append(response)

        if len(history) > max_redirects:
            raise TooManyRedirects('max_redirects exceeded')

        method = method if response.status == 307 else 'GET'
        location = response.headers['location']
        if location[:2] == '//':
            url = parsed_url['scheme'] + ':' + location
        else:
            url = urlparse.urljoin(url, location)
        parsed_url = parse_url(url)

        reqheaders['Host'] = parsed_url['http_host']
        reqheaders['Referer'] = response.url

        # Proxy
        scheme = parsed_url['scheme']
        proxy = proxies.get(scheme)
        if proxy and parsed_url['host'] not in PROXY_IGNORE_HOSTS:
            via_proxy = True
            if '://' not in proxy:
                proxy = '%s://%s' % (parsed_url['scheme'], proxy)
            parsed_proxy = parse_url(proxy)
            # Proxy-Authorization
            if parsed_proxy['username'] and parsed_proxy['password']:
                proxyauth = '%s:%s' % (parsed_proxy['username'],
                                       parsed_proxy['username'])
                proxyauth = base64.b64encode(proxyauth.encode('utf-8'))
                reqheaders['Proxy-Authorization'] = 'Basic ' + \
                                                    proxyauth.decode('utf-8')
            conn = make_connection(scheme, parsed_proxy['host'],
                                   parsed_proxy['port'], timeout,
                                   source_address)
        else:
            via_proxy = False
            reqheaders.pop('Proxy-Authorization', None)
            conn = make_connection(scheme, parsed_url['host'],
                                   parsed_url['port'], timeout, source_address)

        try:
            request_url = url if via_proxy else parsed_url['uri']
            conn.request(method, request_url, data, reqheaders)
            resp = conn.getresponse()
        except socket.timeout as e:
            raise Timeout(e)
        except Exception as e:
            raise UrlfetchException(e)

        response = Response.from_httplib(resp, reqheaders=reqheaders,
                                         length_limit=length_limit,
                                         history=history[:], url=url,
                                         total_time=total_time,
                                         start_time=start_time)

    return response