Commits

Lynn Rees committed 524369f

- cleanup

Comments (0)

Files changed (24)

+Copyright (c) 2012 L. C. Rees.  All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+include LICENSE.txt
+include MANIFEST.in
+include README.rst
+recursive-include wire *.py
Empty file added.

unfinished/curl_client.py

-'''curl client wrapper'''
-
-from subprocess import check_output
-
-from wire.util import lazy
-from wire.http.util import Extract
-from wire.http.response import CoreResponse
-from wire.http.request import CoreRequest, CoreREST
-
-
-class _CurlBase(object):
-
-    @lazy
-    def _factory(self, url, **kw):
-        cmd = ['curl', '--include']
-        proxy = self._proxy
-        if proxy:
-            cmd.extend(
-                ['--proxy', '{0}:{1}'.format(proxy['server'], proxy['host'])]
-            )
-        auth = self._auth
-        if auth is not None:
-            cmd.extend(
-                ['--user', '{0}:{1}'.format(['username'], auth['password'])]
-            )
-        def func(urls=None, method='GET', body=None, headers=None, **kw):
-            cmd = cmd
-            for k, v in headers:
-                cmd.extend(['--header', '"{0}: {1}"'.format(k, v)])
-            for url in urls:
-                cmd.extend([])
-            cmd.extend(
-                ['--url', url, '--request', method, '--data-binary',
-                '"{0}"'.format(body)]
-            )
-            return check_output(cmd, **kw)
-        return func
-
-    @lazy
-    def _response(self):
-        return Response
-
-    def _request(self, **kw):
-        if self._randomua:
-            self.headers.user_agent = self.randomua
-        url = kw.get('url', self._url)
-        data = kw.get('data', self.body)
-        headers = kw.get('headers', dict(self.headers))
-        method = kw.get('method', 'GET')
-        return self._factory(
-            url=url, method=method, body=data, headers=headers, **kw
-        )
-
-
-class Request(CoreRequest, _CurlBase):
-
-    '''curl Request'''
-
-
-class RESTClient(CoreREST, _CurlBase):
-
-    '''curl REST client'''
-
-
-class Response(CoreResponse):
-
-    def __init__(self, new_response, **kw):
-        response = Extract(new_response)
-        self._original = response.rfile.read()
-        self._headers = response.headers
-        self._code = response['status']
-        self.url = response['content-location']
-        super(Response, self).__init__(**kw)

wire/http/errors.py

+# -*- coding: utf-8 -*-
+'''wire http errors'''
+
+
+class HTTPHeaderException(Exception):
+
+    '''Invalid HTTP header value'''

wire/http/header.py

+# -*- coding: utf-8 -*-
 '''wire HTTP header management'''
 
 import re
-import Cookie as _cook
-from keyword import iskeyword
-from functools import partial
-from Cookie import SimpleCookie, Morsel
+
 from stuf.utils import lazy_class
 
 
-class HTTPHeaderException(Exception):
-
-    '''Invalid HTTP header value'''
-
-
-class CoreMorsel(Morsel):
-
-    '''HTTP request specific morsel'''
-
-    def __getattr__(self, key):
-        try:
-            return object.__getattribute__(self, key)
-        except AttributeError:
-            try:
-                return self.__getitem__(key)
-            except KeyError:
-                raise AttributeError(key)
-
-    def __setattr__(self, k, v):
-        if k in CoreMorsel._reserved:
-            self.__setitem__(k, v)
-        else:
-            dict.__setattr__(self, k, v)
-
-    def __delattr__(self, key):
-        try:
-            dict.__delattr__(self, key)
-        except AttributeError:
-            try:
-                self.__delitem__(key)
-            except KeyError:
-                raise AttributeError(key)
-
-
-class CoreCookie(SimpleCookie):
-
-    def __init__(self, inpt=None):
-        super(CoreCookie, self).__init__()
-        if inpt:
-            self.load(input)
-
-    def __getattr__(self, key):
-        try:
-            return dict.__getattribute__(self, key)
-        except AttributeError:
-            try:
-                return self.__getitem__(key)
-            except KeyError:
-                try:
-                    return self.__getitem__(key.replace('_', '-'))
-                except KeyError:
-                    raise AttributeError(key)
-
-    def __setattr__(self, k, v):
-        try:
-            dict.__getattribute__(self, k, v)
-            object.__setattr__(self, k, v)
-        except TypeError:
-            rval, cval = self.value_encode(v)
-            self._set(k, rval, cval)
-
-    def __delattr__(self, key):
-        try:
-            dict.__delattr__(self, key)
-        except AttributeError:
-            try:
-                self.__delitem__(key)
-            except KeyError:
-                self.__delitem__(key.replace('_', '-'))
-                raise AttributeError(key)
-
-    def __setitem__(self, key, value):
-        rval, cval = self.value_encode(value)
-        self._set(key, rval, cval)
-
-    def __iter__(self):
-        return iter(list(
-            tuple(i.split(': ')) for i in str(self).splitlines())
-        )
-
-    def __str__(self):
-        return self.output
-
-    def _parse(self, cookie, patt=_cook._CookiePattern):
-        i = 0  # Our starting point
-        n = len(cookie)  # Length of string
-        m = None  # current morsel
-        unquote = _cook._unquote
-        reserved = CoreMorsel._reserved
-        while 0 <= i < n:
-            # Start looking for a cookie
-            match = patt.search(cookie, i)
-            if not match:
-                break  # No more cookies
-            k, v = match.group('key'), match.group('val').rstrip(', ')
-            i = match.end(0)
-            # Parse the key, value in case it's metainfo
-            if k[0] == '$':
-                if m:
-                    m._morsel[k[1:]] = v
-            elif k.lower() in reserved:
-                if m:
-                    m._morsel[k] = unquote(v)
-            else:
-                rval, cval = self.value_decode(v)
-                self._set(k, rval, cval)
-                m = self[k]
-
-    def _set(self, k, real_value, coded_value):
-        m = self.get(k, CoreMorsel())
-        m.set(k, real_value, coded_value)
-        dict.__setitem__(self, k, m)
-
-    def load(self, data):
-        if isinstance(data, str):
-            self._parse(data)
-        else:
-            for k, v in data.iteritems():
-                self.__setitem__(k, v)
-
-    def output(self):
-        return partial(super(CoreCookie, self).output, header='cookie:')
-
-
 class CoreHeaders(object):
 
     '''HTTP header manager'''
 
-    @staticmethod
-    def _checkname(name):
-        '''
-        Ensures string is legal Python name.
-
-        @param: name Name to check
-        '''
-        # Remove characters that are illegal in a Python name
-        name = name.lower().replace('-', '_').replace(
-            '.', '_'
-        ).translate(None, '()[]{}@,:.`=;+-*/%&|^><\'"#\\$?!~')
-        # Add _ if value is a Python keyword
-        return name + '_' if iskeyword(name) else name
-
     @lazy_class
     def _headattr(self):
         '''Python name -> header name mapping'''
         return dict((self._checkname(h), h) for h in self.REQ_HEADERS)
 
-    def _items(self):
-        '''All the header fields and values'''
-        return list(self)
-
-    def _keys(self):
-        '''A list of all header field keys'''
-        return list(i[0] for i in self)
-
     def _parseint(self, value):
         '''
         Parses an integer from a header value.

wire/http/keys/__init__.py

-# -*- coding: utf-8 -*-
-'''http client keys'''

wire/http/keys/request.py

-# -*- coding: utf-8 -*-
-#@PydevCodeAnalysisIgnore
-#pylint: disable-msg=e0211,e0213
-'''http client keys'''
-
-from appspace.keys import AppspaceKey
-
-
-class KRequest(AppspaceKey):
-
-    '''HTTP client key'''
-
-    def __call__(*urls):
-        '''
-        load urls
-    
-        @param urls: Uniform Resource Locations (URLs)
-        '''
-
-    def get():
-        '''
-        *GET* HTTP method
-    
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
-        '''
-
-    def post():
-        '''
-        *POST* HTTP method
-    
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5
-        '''
-
-    def delete():
-        '''
-        *DELETE* HTTP method
-    
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7
-        '''
-
-    def put():
-        '''
-        *PUT* HTTP method
-    
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6
-        '''
-
-    def head():
-        '''
-        *HEAD* HTTP method
-    
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
-        '''
-
-    def options():
-        '''
-        *OPTIONS* HTTP method
-    
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
-        '''
-        
-    def authenticate(username, password):
-        '''
-        set authentication settings
-        
-        @param username: username credential
-        @param password: password credential
-        '''
-
-    def proxy(server, host):
-        '''
-        proxy settings
-        
-        @param server: proxy server
-        @param host: proxy host
-        '''
-        
-
-class KAPI(AppspaceKey):
-    
-    '''HTTP API key'''
-        
-    def api():
-        '''switch to HTTP API chain'''
-        
-
-class KRequestData(AppspaceKey):
-    
-    '''HTTP request data'''
-    
-    def file(io):
-        '''load file'''
-        
-    def params(**kw):
-        '''request parameters'''
-        
-        
-class KRequestHeaders(AppspaceKey):
-    
-    '''HTTP request key'''
-    
-    def header(key, value=None):
-        '''switch to HTTP request headers chain'''
-        
-    def headers(*args, **kw):
-        '''HTTP request header setter'''
-    
-    def cache_control(value=None):
-        '''
-        set *CACHE-CONTROL* HTTP header
-        
-        @param value HTTP header value
-        '''
-    
-    def connection(value=None):
-        '''
-        set *CONNECTION* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def date(value=None):
-        '''
-        *DATE* HTTP header
-        
-        @param value HTTP header value
-        '''
-    
-    def pragma(value=None):
-        '''
-        *PRAGMA* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def trailer(value=None):
-        '''
-        *TRAILER* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def via(value=None):
-        '''
-        *VIA* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def transfer_enconding(value=None):
-        '''
-        *TRANSFER-ENCODING* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def upgrade(value=None):
-        '''
-        *UPGRADE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def warning(value=None):
-        '''
-        *WARNING* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def allow(value=None):
-        '''
-        *ALLOW* HTTP header
-        
-        @param value HTTP header value
-        '''
-    
-    def content_encoding(value=None):
-        '''
-        *CONTENT-ENCODING* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def content_language(value=None):
-        '''
-        *CONTENT-LANGUAGE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def content_length(value=None):
-        '''
-        *CONTENT-LENGTH* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def content_location(value=None):
-        '''
-        *CONTENT-LOCATION* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def content_md5(value=None):
-        '''
-        take MD5 hash of data and set *CONTENT-MD5* HTTP header.
-
-        @param value: HTTP response data
-        '''
-        
-    def content_range(first, last, length='*'):
-        '''
-        set rangeset in HTTP header *CONTENT-RANGE*
-
-        @param first First byte position
-        @param last Last byte position
-        @param length Instance length
-        '''
-
-    def content_type(mime, charset=None):
-        '''
-        set the HTTP *CONTENT-TYPE* header.
-
-        @param mime Media type
-        @param charset Optional charset (default: None)
-        '''
-    
-    def expires(value=None):
-        '''
-        *EXPIRES* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def last_modified(value=None):
-        '''
-        *LAST-MODIFIED* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def cookie(morsel):
-        '''
-        *COOKIE* HTTP header
-        
-        @param value HTTP header value
-        '''
-    
-    def accept(value=None):
-        '''
-        *ACCEPT* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def accept_charset(value=None):
-        '''
-        *ACCEPT-CHARSET* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def accept_encoding(value=None):
-        '''
-        *ACCEPT-ENCODING* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def accept_language(value=None):
-        '''
-        *ACCEPT-LANGUAGE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def range(value=None):
-        '''
-        *RANGE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def authorization(value=None):
-        '''
-        *AUTHORIZATION* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def expect(value=None):
-        '''
-        *EXPECT* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def from_header(value=None):
-        '''
-        *FROM* HTTP header
-        
-        @param value HTTP header value
-        '''
-    
-    def host(value=None):
-        '''
-        *HOST* HTTP header
-        
-        @param value HTTP header value
-        '''
-    
-    def if_match(value=None):
-        '''
-        *IF-MATCH* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def if_modified_since(value=None):
-        '''
-        *IF-MODIFIED-SINCE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def if_none_match(value=None):
-        '''
-        *IF-NONE-MATCH* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def if_range(value=None):
-        '''if-range HTTP header'''
-        
-    def if_unmodified_since(value=None):
-        '''
-        *IF-UNMODIFIED-SINCE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def max_forwards(value=None):
-        '''*MAX-FORWARDS* HTTP header'''
-        
-    def te(value=None):
-        '''
-        *TE* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def proxy_authorization(value=None):
-        '''
-        *PROXY-AUTHORIZATION* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def referer(value=None):
-        '''
-        *REFERER* HTTP header
-        
-        @param value HTTP header value
-        '''
-
-    def user_agent(value=None):
-        '''
-        *USER-AGENT* HTTP header
-        
-        @param value HTTP header value
-        '''
-        
-    def rfc2822(key, value, charset=None):
-        '''
-        set header value with specific charset encoded following RFC 2822
-
-        @param key HTTP header key
-        @param value HTTP header value
-        @param charset HTTP charset (default: None)
-        '''

wire/http/keys/response.py

-# -*- coding: utf-8 -*-
-#@PydevCodeAnalysisIgnore
-#pylint: disable-msg=e0211,e0213
-'''http client response keys'''
-
-from appspace.keys import AppspaceKey
-
-
-class KResponse(AppspaceKey):
-    
-    '''HTTP client response key'''
-
-    def code():
-        '''
-        HTTP response code
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
-        '''
-
-    def good():
-        '''True if HTTP was successful enough'''
-
-    def fail():
-        '''True if HTTP request failed'''
-
-    def noop():
-        '''True if HTTP request was more or less harmless'''
-
-    def ok():
-        '''True if HTTP request was successful'''
-
-    def status():
-        '''status of HTTP response'''
-
-
-class KResponseCookie(AppspaceKey):
-    
-    '''HTTP response cookie key'''
-    
-    def cookie(*keys):
-        '''
-        HTTP cookie
-        
-        @param *keys: cookie keys
-        '''
-        
-    def morsel(*keys):
-        '''
-        switch to HTTP response cookies chain
-        
-        @param *keys: cookie keys
-        '''
-        
-        
-class KResponseData(AppspaceKey):
-    
-    '''HTTP response data'''
-    
-    def touched():
-        '''Verify MD5 digest of response data against "Content-MD5" digest'''
-        
-    def raw():
-        '''read original response content'''   
-    
-    def data():
-        '''response data'''
-
-
-class KResponseHeader(AppspaceKey):
-    
-    '''response headers key'''
-    
-    def header(*keys):
-        '''
-        fetch HTTP response headers
-        
-        @param key: header keys key
-        '''
-    
-    def cache_control():
-        '''*CACHE-CONTROL* HTTP header'''
-    
-    def connection():
-        '''*CONNECTION* HTTP header'''
-        
-    def date():
-        '''*DATE* HTTP header'''
-    
-    def pragma():
-        '''*PRAGMA* HTTP header'''
-        
-    def trailer():
-        '''*TRAILER* HTTP header'''
-        
-    def via():
-        '''*VIA* HTTP header'''
-        
-    def transfer_enconding():
-        '''*TRANSFER-ENCODING* HTTP header'''
-        
-    def upgrade():
-        '''*UPGRADE* HTTP header'''
-        
-    def warning():
-        '''*WARNING* HTTP header'''
-        
-    def allow():
-        '''*ALLOW* HTTP header'''
-    
-    def content_encoding():
-        '''*CONTENT-ENCODING* HTTP header'''
-        
-    def content_language():
-        '''*CONTENT-LANGUAGE* HTTP header'''
-        
-    def content_length():
-        '''*CONTENT-LENGTH* HTTP header'''
-        
-    def content_location():
-        '''*CONTENT-LOCATION* HTTP header'''
-        
-    def content_md5():
-        '''*CONTENT-MD5* HTTP header'''
-        
-    def content_range():
-        '''HTTP header *CONTENT-RANGE*'''
-
-    def content_type():
-        '''
-        HTTP *CONTENT-TYPE* header
-        
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
-        '''
-    
-    def expires():
-        '''*EXPIRES* HTTP header'''
-        
-    def last_modified():
-        '''*LAST-MODIFIED* HTTP header'''
-    
-    def accept_ranges():
-        '''*ACCEPT-RANGES* HTTP header'''
-        
-    def age():
-        '''*AGE* HTTP header'''
-        
-    def etag():
-        '''*ETAG* HTTP header''' 
-        
-    def location():
-        '''*LOCATION* HTTP header'''
-        
-    def proxy_authenticate():
-        '''*PROXY-AUTHENTICATE* HTTP header'''
-
-    def retry_after():
-        '''*RETRY-AFTER* HTTP header'''
-        
-    def server():
-        '''*SERVER* HTTP header'''
-        
-    def vary():
-        '''*VARY* HTTP header'''
-
-    def www_authenticate():
-        '''*WWW-AUTHETICATE* HTTP header'''

wire/http/request.py

-'''wire HTTP request core'''
-
-import re
-from random import choice
-from itertools import chain
-from datetime import datetime
-from functools import partial
-from collections import deque
-from email.header import Header
-
-from appspace.keys import appifies, imap
-from callchain.root.chain import callchain
-from stuf.utils import lazy_class, OrderedDict, exhaust, iterexcept
-
-from wire.util import missing
-
-from wire.http.util import httpdate
-from wire.http.header import CoreHeaders, HTTPHeaderException
-from wire.http.keys.request import KRequest, KRequestData, KRequestHeaders
-from callchain.root.linked import chainlink
-
-
-@appifies(KRequestHeaders)
-class Headers(CoreHeaders):
-
-    '''HTTP request header manager'''
-
-    def __init__(self, headers=()):
-        super(Headers, self).__init__()
-        # "best practice" sends HTTP headers in order: general, request, entity
-        self._general = OrderedDict()
-        self._entity = OrderedDict()
-        self._request = OrderedDict()
-        self._custom = OrderedDict()
-        # populate headers
-        self.headers(headers)
-        if self.G.randomua:
-            self.user_agent(self._randomua())
-
-    def _getheaders(self, key):
-        '''
-        Gets the list and tracker for a header's category.
-
-        @param: key Header key
-        '''
-        if key in self._GENERAL:
-            return self._general
-        elif key in self._REQUEST:
-            return self._request
-        elif key in self._ENTITY:
-            return self._entity
-        return self._custom
-
-    def _get(self, key):
-        key = key.lower().replace('_', '-')
-        headers = self._getheaders(key)
-        return headers.get(key)
-
-    def _set(self, key, value):
-        key = key.lower().replace('_', '-')
-        # validate header value
-        self._validate(key, value)
-        value = self._format(key, value)
-        headers = self._getheaders(key)
-        prev = headers.get(key)
-        if prev is not None:
-            # email.Header instance
-            try:
-                prev.append(value)
-            except AttributeError:
-                # join with any existing value if header allows multiple values
-                if key in self._COMMA_SEP:
-                    headers[key] = ', '.join([headers[key], value])
-                # replace value on headers allowing only single values
-                if key in self._SC_SEP:
-                    headers[key] = '; '.join([headers[key], value])
-                else:
-                    headers[key] = value
-        # add new value if not present
-        else:
-            headers[key] = value
-
-    def _delete(self, key):
-        key = key.lower().replace('_', '-')
-        headers = self._get(key)
-        del headers[key]
-
-    @lazy_class
-    def _badheaders(self):
-        '''bad header detecting regex from wsgiref'''
-        return re.compile(r'[\000-\037]')
-
-    def _format(self, key, value):
-        '''
-        Ensure proper formatting for a header value
-
-        @param key: Key associated with value
-        @param value: Value to format
-        '''
-        # Keep nulls out of the pipeline
-        if value is None or not value:
-            return value
-        # Join with any existing value if header allows multiple values
-        if key in self._DATES and isinstance(value, datetime):
-            value = httpdate(value)
-        return value
-
-    @lazy_class
-    def _langs(self):
-        '''language set.'''
-        from locale import locale_alias
-        return frozenset(i.replace('_', '-') for i in locale_alias)
-
-    @staticmethod
-    def _randomua():
-        return choice([
-        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) '
-        'Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)',
-        'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) '
-        'Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)',
-        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
-        'Gecko/20090718 Firefox/3.5.1',
-        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
-        'Gecko/20090718 Firefox/4.0.1',
-        'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 '
-        '(KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1',
-        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; '
-        'Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)',
-        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; '
-        'SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; '
-        '.NET CLR 3.0.30729)',
-        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; '
-        'Trident/4.0)',
-        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; '
-        '.NET CLR 2.0.50727; InfoPath.2)Mozilla/5.0 (Windows; U; MSIE 7.0; '
-        'Windows NT 6.0; en-US)',
-        'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)',
-        ])
-
-    @lazy_class
-    def _rules(self):
-        '''Validation rules for individual HTTP header fields'''
-        return {
-            # "Allow" values must be valid HTTP methods
-            'allow': (
-                lambda v: v.upper() not in self._METHODS,
-                '"Allow" value must be valid HTTP method'
-            ),
-            # "Content-Encoding" value must be zip, compress, or deflate
-            'content-encoding': (
-                lambda v: v.lower() not in self._ENCODINGS,
-                '"Content-Encoding" must be gzip, compress, deflate',
-            ),
-            # "Content-Language" values must be valid language codes
-            'content-language': (
-                lambda v: v.lower() not in self._langs,
-                'Unknown language code'
-            ),
-            # "Content-Length" value must be an integer
-            'content-length': (
-                lambda v: not v.isdigit(),
-                '"Content-Length" must be integer'
-            ),
-            # "Content-Range" value must be in the proper format
-            'content-range': (
-                lambda v: not self._iscontentrange(v),
-                'Invalid format for "Content-Range"'
-            ),
-        }
-
-    @classmethod
-    def _validate(self, key, value):
-        '''
-        Validate header value
-
-        @param key HTTP header name
-        @param value Putative header value
-        '''
-        err_msg = None
-        if not isinstance(value, basestring):
-            return True
-        # Don't allow request headers
-        if key in self._RESPONSE:
-            err_msg = '"%s" is HTTP response-only header' % key
-        # Don't allow unicode header names
-        if not isinstance(key, str):
-            err_msg = 'HTTP header name must be non-Unicode string'
-        # Don't allow unicode header values outside RFC2822
-        elif isinstance(value, unicode):
-            err_msg = 'use "set_rfc2822" to set Unicode HTTP header values'
-        # Don't allow "status" to be set in headers
-        elif key == 'status':
-            err_msg = 'HTTP status code cannot be set on HTTP request'
-        # Don't allow newline or colon in header name
-        elif '\n' in key or ':' in key:
-            err_msg = 'HTTP header names may not contain ":" or "\\n"'
-        # Do not allow invalid characters in header name
-        elif not self._ishttpname(key):
-            err_msg = 'Bad HTTP header name: %r' % key
-        # Do not allow dash or underscore at end of header name
-        elif key.endswith('-') or key.endswith('_'):
-            err_msg = 'HTTP header name may not end in "-" or "_"'
-        # No control characters in header name
-        elif isinstance(value, basestring) and self._badheaders.search(value):
-            err_msg = 'Bad character %r in HTTP header value %r' % (
-                self._badheaders.search(value).group(0), value
-            )
-        # Validate RFC 1123 compliance
-        elif key in self._DATES and not self._isrfc1123(value):
-            err_msg = 'Date is not RFC 1123 compliant'
-        # Validate individual header
-        else:
-            rule = self._rules.get(key)
-            if rule is not None and rule[0](value):
-                err_msg = rule[1]
-        if err_msg is not None:
-            raise HTTPHeaderException(err_msg)
-
-    def content_type(self, mime, charset=None):
-        '''
-        Utility method to set the "Content-Type" header.
-
-        @param mime Media type
-        @param charset Optional charset (default: None)
-        '''
-        if charset is None:
-            self['Content-Type'] = mime
-        else:
-            self['Content-Type'] = '{0}; charset={1}'.format(mime, charset)
-        return self
-
-    def end(self):
-        # Cast any header objects as strings (calling Header.decode)
-        return ((h[0].lower(), str(h[1])) for h in chain(
-            list((k, v) for k, v in self._general.iteritems()),
-            list((k, v) for k, v in self._request.iteritems()),
-            list((k, v) for k, v in self._entity.iteritems()),
-            list((k, v) for k, v in self._custom.iteritems()),
-            list(self.cookies),
-        ) if h is not None)
-
-    def md5(self, data):
-        '''
-        Take a MD5 hash of data and set the "Content-MD5" header.
-
-        @param data HTTP response data
-        '''
-        import md5
-        from base64 import b64encode
-        self['Content-MD5'] = b64encode(md5.new(data).digest())
-        return self
-
-    def range(self, first, last, length='*'):
-        '''
-        Set the rangeset in header "Content-Range"
-
-        @param first First byte position
-        @param last Last byte position
-        @param length Instance length
-        '''
-        if length == '*':
-            self._set('Content-Range', 'bytes %d-%d/*' % (first, last))
-        else:
-            self._set(
-                'Content-Range', 'bytes %d-%d/%d' % (first, last, length)
-            )
-        return self
-
-    def rfc2822(self, key, value, charset=None):
-        '''
-        Set a header value in with a specific charset encoded as RFC 2822.
-
-        @param key HTTP header key
-        @param value HTTP header value
-        @param charset HTTP charset (default: None)
-        '''
-        # Use an existing email.Header instance
-        existing = self._get(key)
-        if existing is not None:
-            try:
-                existing.append(value, charset)
-            except AttributeError:
-                pass
-        else:
-            # Override existing value if not Header instance or add new value
-            self._set(key, Header(value, charset))
-        return self
-
-    def header(self, key, value):
-        self._set(key, value)
-        return self
-
-    def headers(self, headers):
-        '''update header values'''
-        setitem = self.header
-        exhaust(imap(setitem, iterexcept(headers, IndexError)))
-        return self
-
-
-@appifies(KRequestData)
-class Data(chainlink):
-
-    def __init__(self, root):
-        super(Data, self).__init__(root)
-        self._data = ''
-
-    def files(self, files):
-        # files demand multipart as default otherwise go with urlencode
-        self._format = 'multipart'
-        self._files = files
-        return self
-
-    @property
-    def _thedata(self):
-        return self._data or self._original
-
-    def data(self, data):
-        # preserve any original data
-        self._original = data
-        if not self._data:
-            data = self._thedata
-            if data:
-                self._data = self._datafy(
-                    self._thedata, self.headers, self.format, **self._options
-                )
-        return self._data
-
-    def format(self):
-        return self._format or self._originalfmt
-
-    # private methods
-
-    def _datafy(self, data, headers, fmt, **kw):
-        if fmt in self._dumpers.apps:
-            files = kw.pop('files', self._files)
-            if files:
-                data = dict(form=data, files=files)
-            type_, data = self.dumps(data, fmt, **self._kwfy(kw)[-1])
-            headers.content_type = type_
-            headers.accept = type_
-        return data
-
-
-class Request(object):
-
-    def __init__(self, url):
-        self._data = ''
-        params = None
-        data = None
-        headers = None
-        cookies = None
-        files = None
-        auth = None
-        timeout = None
-        allow_redirects = False
-        proxies = None
-        hooks = None
-        return_response = True
-        config = None
-
-    def _request(self, **kw):
-        '''make HTTP request'''
-        kw.setdefault('url', self._url)
-        headers = self._headerfy(kw.pop('headers', self._headers), **kw)
-        kw['data'] = self._datafy(
-            kw.pop('data', self.data),
-            headers,
-            kw.pop('format', self.format),
-            **kw
-        )
-        kw['headers'] = headers
-        self._raw = self._request(**kw)
-        return self
-
-
-@appifies(KRequest)
-class http(callchain):
-
-    '''HTTP client call chain'''
-
-    def __init__(self, pattern=None, required=None, defaults=None, **kw):
-        '''
-        init
-
-        @param pattern: pattern configuration or appspace label (default: None)
-        @param required: required settings (default: None)
-        @param defaults: default settings (default: None)
-        '''
-        super(http, self).__init__(pattern, required, defaults, **kw)
-        # preserve any original data
-        self._original = ''
-        # url, if any
-        self._urls = deque()
-        # files demand multipart as default otherwise go with urlencode
-        self._format = kw.get('format', self.defaults.format)
-        # set data to something
-        self._data = ''
-        # files something
-        self._files = {}
-        # custom headers
-        self._headers = {}
-        # custom cookies
-        self._cookies = {}
-        # default timeout
-        self._timeout = kw.get('timeout', self.defaults.timeout)
-        # use random user agent
-        self._randomua = kw.pop('randomua', self.defaults.random_user_agent)
-        # authentication
-        self._auth = self.defaults.authentication
-        # proxy
-        self._proxy = self.defaults.proxy
-
-    def __call__(self, *urls, **kw):
-        '''
-        load urls
-
-        @param urls: Uniform Resource Locations (URLs)
-        '''
-        self.clear()
-        # urls, if any
-        self._urls = deque(urls)
-        # default timeout
-        self._timeout = kw.pop('timeout', self._timeout)
-        # use random user agent
-        self._randomua = kw.pop('randomua', self._randomua)
-        # authentication
-        self._auth = kw.pop('auth', {})
-        if self._auth:
-            # verify
-            if missing(self._auth, ('username', 'password')):
-                raise TypeError(
-                    u'authentication missing both "username" and "password"',
-                )
-                # proxy
-        self._proxy = kw.pop('proxy', {})
-        if self._proxy:
-            if missing(self._proxy, ('server', 'host')):
-                raise TypeError(u'proxy missing both "server" and "host"')
-        return self
-
-    def get(self):
-        '''
-        *GET* HTTP method
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
-        '''
-        return partial(self._request, method='GET')
-
-    def post(self):
-        '''
-        *POST* HTTP method
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5
-        '''
-        return partial(self._request, method='POST')
-
-    def delete(self):
-        '''
-        *DELETE* HTTP method
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7
-        '''
-        return partial(self._request, method='DELETE')
-
-    def put(self):
-        '''
-        *PUT* HTTP method
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6
-        '''
-        return partial(self._request, method='PUT')
-
-    def head(self):
-        '''
-        *HEAD* HTTP method
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
-        '''
-        return partial(self._request, method='HEAD')
-
-    def options(self):
-        '''
-        *OPTIONS* HTTP method
-
-        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
-        '''
-        return partial(self._request, method='OPTIONS')

wire/http/request/__init__.py

+# -*- coding: utf-8 -*-
+'''http requests'''

wire/http/request/apps.py

+# -*- coding: utf-8 -*-
+'''wire talk appconf'''
+
+from callchain.patterns import Pathways
+
+
+class talk(Pathways):
+    bencode = 'wire.talk.bencodew.Dumps'
+    bson = 'wire.talk.bsonw.Dumps'
+    csv = 'wire.talk.csvw.Dumps'
+    excel = 'wire.talk.excelw.Dumps'
+    html = 'wire.talk.htmlw.Dumps'
+    json = 'wire.talk.jsonw.Dumps'
+    marshal = 'wire.talk.marshalw.Dumps'
+    multipart = 'wire.talk.multipartw.Dumps'
+    pickle = 'wire.talk.picklew.Dumps'
+    protobuf = 'wire.talk.protobufw.Dumps'
+    thrift = 'wire.talk.thriftw.Dumps'
+    url = 'wire.talk.urlw.Dumps'
+    xml = 'wire.talk.xmlw.Dumps'
+    xmlrpc = 'wire.talk.xmlrpcw.Dumps'
+    yaml = 'wire.talk.yamlw.Dumps'

wire/http/request/client.py

+# -*- coding: utf-8 -*-
+'''wire HTTP request core'''
+
+import re
+import hashlib
+from random import choice
+from itertools import chain
+from base64 import b64encode
+from datetime import datetime
+from email.header import Header
+
+from appspace.keys import appifies, imap
+from callchain.root.chain import callchain
+from callchain.root.linked import chainlink
+from stuf.utils import OrderedDict, lazy_class, exhaust, iterexcept
+
+from wire.http.util import httpdate
+from wire.http.header import CoreHeaders
+from wire.http.errors import HTTPHeaderException
+from wire.http.keys.request import KRequest, KRequestData, KRequestHeaders
+
+
+@appifies(KRequestHeaders)
+class Headers(CoreHeaders):
+
+    '''HTTP request header manager'''
+
+    def __init__(self, headers=()):
+        super(Headers, self).__init__()
+        # "best practice" sends HTTP headers in order: general, request, entity
+        self._general = OrderedDict()
+        self._entity = OrderedDict()
+        self._request = OrderedDict()
+        self._custom = OrderedDict()
+        # populate headers
+        self.headers(headers)
+        if self.G.randomua:
+            self.user_agent(self._randomua())
+
+    def _getheaders(self, key):
+        '''
+        Gets the list and tracker for a header's category.
+
+        @param: key Header key
+        '''
+        if key in self._GENERAL:
+            return self._general
+        elif key in self._REQUEST:
+            return self._request
+        elif key in self._ENTITY:
+            return self._entity
+        return self._custom
+
+    def _get(self, key):
+        key = key.lower().replace('_', '-')
+        headers = self._getheaders(key)
+        return headers.get(key)
+
+    def _delete(self, key):
+        key = key.lower().replace('_', '-')
+        headers = self._get(key)
+        del headers[key]
+
+    @lazy_class
+    def _badheaders(self):
+        '''bad header detecting regex from wsgiref'''
+        return re.compile(r'[\000-\037]')
+
+    def _format(self, key, value):
+        '''
+        Ensure proper formatting for a header value
+
+        @param key: Key associated with value
+        @param value: Value to format
+        '''
+        # Keep nulls out of the pipeline
+        if value is None or not value:
+            return value
+        # Join with any existing value if header allows multiple values
+        if key in self._DATES and isinstance(value, datetime):
+            value = httpdate(value)
+        return value
+
+    @lazy_class
+    def _langs(self):
+        '''language set.'''
+        from locale import locale_alias
+        return frozenset(i.replace('_', '-') for i in locale_alias)
+
+    @staticmethod
+    def _randomua():
+        return choice([
+        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) '
+        'Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)',
+        'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) '
+        'Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)',
+        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
+        'Gecko/20090718 Firefox/3.5.1',
+        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
+        'Gecko/20090718 Firefox/4.0.1',
+        'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 '
+        '(KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; '
+        'Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; '
+        'SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; '
+        '.NET CLR 3.0.30729)',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; '
+        'Trident/4.0)',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; '
+        '.NET CLR 2.0.50727; InfoPath.2)Mozilla/5.0 (Windows; U; MSIE 7.0; '
+        'Windows NT 6.0; en-US)',
+        'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)',
+        ])
+
+    @lazy_class
+    def _rules(self):
+        '''Validation rules for individual HTTP header fields'''
+        return {
+            # "Allow" values must be valid HTTP methods
+            'allow': (
+                lambda v: v.upper() not in self._METHODS,
+                '"Allow" value must be valid HTTP method'
+            ),
+            # "Content-Encoding" value must be zip, compress, or deflate
+            'content-encoding': (
+                lambda v: v.lower() not in self._ENCODINGS,
+                '"Content-Encoding" must be gzip, compress, deflate',
+            ),
+            # "Content-Language" values must be valid language codes
+            'content-language': (
+                lambda v: v.lower() not in self._langs,
+                'Unknown language code'
+            ),
+            # "Content-Length" value must be an integer
+            'content-length': (
+                lambda v: not v.isdigit(),
+                '"Content-Length" must be integer'
+            ),
+            # "Content-Range" value must be in the proper format
+            'content-range': (
+                lambda v: not self._iscontentrange(v),
+                'Invalid format for "Content-Range"'
+            ),
+        }
+
+    @classmethod
+    def _validate(self, key, value):
+        '''
+        Validate header value
+
+        @param key HTTP header name
+        @param value Putative header value
+        '''
+        err_msg = None
+        if not isinstance(value, basestring):
+            return True
+        # Don't allow request headers
+        if key in self._RESPONSE:
+            err_msg = '"%s" is HTTP response-only header' % key
+        # Don't allow unicode header names
+        if not isinstance(key, str):
+            err_msg = 'HTTP header name must be non-Unicode string'
+        # Don't allow unicode header values outside RFC2822
+        elif isinstance(value, unicode):
+            err_msg = 'use "set_rfc2822" to set Unicode HTTP header values'
+        # Don't allow "status" to be set in headers
+        elif key == 'status':
+            err_msg = 'HTTP status code cannot be set on HTTP request'
+        # Don't allow newline or colon in header name
+        elif '\n' in key or ':' in key:
+            err_msg = 'HTTP header names may not contain ":" or "\\n"'
+        # Do not allow invalid characters in header name
+        elif not self._ishttpname(key):
+            err_msg = 'Bad HTTP header name: %r' % key
+        # Do not allow dash or underscore at end of header name
+        elif key.endswith('-') or key.endswith('_'):
+            err_msg = 'HTTP header name may not end in "-" or "_"'
+        # No control characters in header name
+        elif isinstance(value, basestring) and self._badheaders.search(value):
+            err_msg = 'Bad character %r in HTTP header value %r' % (
+                self._badheaders.search(value).group(0), value
+            )
+        # Validate RFC 1123 compliance
+        elif key in self._DATES and not self._isrfc1123(value):
+            err_msg = 'Date is not RFC 1123 compliant'
+        # Validate individual header
+        else:
+            rule = self._rules.get(key)
+            if rule is not None and rule[0](value):
+                err_msg = rule[1]
+        if err_msg is not None:
+            raise HTTPHeaderException(err_msg)
+
+    def end(self):
+        # Cast any header objects as strings (calling Header.decode)
+        return ((h[0].lower(), str(h[1])) for h in chain(
+            self._general.iteritems(),
+            self._request.iteritems(),
+            self._entity.iteritems(),
+            self._custom.iteritems(),
+            self.cookies,
+        ) if h is not None)
+
+    def rfc2822(self, key, value, charset=None):
+        '''
+        Set a header value in with a specific charset encoded as RFC 2822.
+
+        @param key HTTP header key
+        @param value HTTP header value
+        @param charset HTTP charset (default: None)
+        '''
+        # Use an existing email.Header instance
+        existing = self._get(key)
+        if existing is not None:
+            try:
+                existing.append(value, charset)
+            except AttributeError:
+                pass
+        else:
+            # Override existing value if not Header instance or add new value
+            self._set(key, Header(value, charset))
+        return self
+
+    def header(self, key, value):
+        '''
+        set HTTP request header
+
+        @param key: header key
+        @param value: header value
+        '''
+        key = key.lower().replace('_', '-')
+        # validate header value
+        self._validate(key, value)
+        value = self._format(key, value)
+        headers = self._getheaders(key)
+        prev = headers.get(key)
+        if prev is not None:
+            # email.Header instance
+            try:
+                prev.append(value)
+            except AttributeError:
+                # join with any existing value if header allows multiple values
+                if key in self._COMMA_SEP:
+                    headers[key] = ', '.join([headers[key], value])
+                # replace value on headers allowing only single values
+                if key in self._SC_SEP:
+                    headers[key] = '; '.join([headers[key], value])
+                else:
+                    headers[key] = value
+        # add new value if not present
+        else:
+            headers[key] = value
+        return self
+
+    def headers(self, headers):
+        '''
+        HTTP request headers setter
+
+        @param headers: HTTP headers
+        '''
+        header = self.header
+        exhaust(imap(header, iterexcept(headers, IndexError)))
+        return self
+
+
+@appifies(KRequestData)
+class Data(chainlink):
+
+    def __init__(self, root):
+        super(Data, self).__init__(root)
+        self._data = ''
+
+    def _md5(self, data):
+        '''
+        Take a MD5 hash of data and set the "Content-MD5" header.
+
+        @param data HTTP response data
+        '''
+        self['Content-MD5'] = b64encode(hashlib.md5.new(data).digest())
+        return self
+
+    @property
+    def _thedata(self):
+        return self._data or self._original
+
+    def _datafy(self, data, headers, fmt, **kw):
+        if fmt in self._dumpers.apps:
+            files = kw.pop('files', self._files)
+            if files:
+                data = dict(form=data, files=files)
+            type_, data = self.dumps(data, fmt, **self._kwfy(kw)[-1])
+            headers.content_type = type_
+            headers.accept = type_
+        return data
+
+    def content_type(self, mime, charset=None):
+        '''
+        set HTTP *Content-Type* header.
+
+        @param mime: MIME media type
+        @param charset: charset (default: None)
+        '''
+        if charset is None:
+            self['Content-Type'] = mime
+        else:
+            self['Content-Type'] = '{0}; charset={1}'.format(mime, charset)
+        return self
+
+    def content_range(self, first, last, length='*'):
+        '''
+        Set the rangeset in header "Content-Range"
+
+        @param first First byte position
+        @param last Last byte position
+        @param length Instance length
+        '''
+        if length == '*':
+            self._set('Content-Range', 'bytes %d-%d/*' % (first, last))
+        else:
+            self._set(
+                'Content-Range', 'bytes %d-%d/%d' % (first, last, length)
+            )
+        return self
+
+    def data(self, data):
+        # preserve any original data
+        self._original = data
+        if not self._data:
+            data = self._thedata
+            if data:
+                self._data = self._datafy(
+                    self._thedata, self.headers, self.format, **self._options
+                )
+        return self._data
+
+    def files(self, files):
+        # files demand multipart as default otherwise go with urlencode
+        self._format = 'multipart'
+        self._files = files
+        return self
+
+
+class Request(object):
+
+    def __init__(self, url, verify, allow_redirects, timeout, auth, proxy):
+        self.url = url
+        self._verify = verify
+        self._allow_redirects = allow_redirects
+        self._timeout = timeout
+        self._auth = auth
+        self._proxy = proxy
+        self._cookies = {}
+        self._data = None
+        self._file = None
+        self._headers = {}
+        self._params = None
+
+    @property
+    def args(self):
+        return dict(
+            verify=self._verify,
+            allow_redirects=self._allow_redirects,
+            timeout=self._timeout,
+            auth=self._auth,
+            proxy=self._proxy,
+            cookies=self._cookies,
+            data=self._data,
+            files=self._file,
+            headers=self._headers,
+            params=self._params,
+        )
+
+    def cookies(self, cookies):
+        self._cookies = cookies
+
+    def data(self, data):
+        self._data = data
+
+    def file(self, data):
+        self._file = data
+
+    def headers(self, headers):
+        self._headers.update(headers)
+
+    def params(self, params):
+        self._params = params
+
+
+@appifies(KRequest)
+class http(callchain):
+
+    '''HTTP client call chain'''
+
+    def __init__(self, pattern=None, required=None, defaults=None, **kw):
+        '''
+        init
+
+        @param pattern: pattern configuration or appspace label (default: None)
+        @param required: required settings (default: None)
+        @param defaults: default settings (default: None)
+        '''
+        super(http, self).__init__(pattern, required, defaults, **kw)
+        # files demand multipart as default otherwise go with urlencode
+        self._format = kw.get('format', self.defaults.format)
+        # allow redirects
+        self._allow_redirects = kw.get(
+            'allow_redirects', self.defaults.allow_redirects,
+        )
+        # cert verification
+        self._verify = kw.get('verify', self.defaults.verify)
+        # default timeout
+        self._timeout = kw.get('timeout', self.defaults.timeout)
+        # use random user agent
+        self._randomua = kw.pop('randomua', self.defaults.random_user_agent)
+        # authentication
+        self._auth = self.defaults.authentication
+        # proxy
+        self._proxy = self.defaults.proxy
+
+    def __call__(self, *urls, **config):
+        '''
+        load urls
+
+        @param *urls: Uniform Resource Locations (URLs)
+        '''
+        allow_redirects = self._allow_redirects
+        # verify certs
+        verify = self._verify
+        # default timeout
+        timeout = config.pop('timeout', self._timeout)
+        # authentication
+        auth = config.pop('auth', self._auth)
+        if auth:
+            self.authentication(*auth)
+        randomua = config.get('randomua', self._randomua)
+        # proxy
+        proxy = config.pop('proxy', self._proxy)
+        if proxy:
+            self.proxy(*proxy)
+        # urls, if any
+        return self._dcall(*(Request(
+            u, verify, allow_redirects, timeout, auth, proxy, randomua
+        ) for u in urls))
+
+    @property
+    def _requester(self):
+        return self.M.get(self.G.request, self.G.userspace)
+
+    def _pack(self, method):
+        '''
+        run HTTP `method`
+
+        @param method: HTTP method
+        '''
+        lchain = self.chain
+        requester = self._requester
+        pack = lambda x: lchain(requester, method, x.url, **x.args)
+        with self._sync as sync:
+            exhaust(pack, sync.interable)
+        self.commit()
+        return self
+
+    def get(self):
+        '''
+        *GET* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3
+        '''
+        return self._pack('GET')
+
+    def post(self):
+        '''
+        *POST* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5
+        '''
+        return self._pack('POST')
+
+    def delete(self):
+        '''
+        *DELETE* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.7
+        '''
+        return self._pack('DELETE')
+
+    def put(self):
+        '''
+        *PUT* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.6
+        '''
+        return self._pack('PUT')
+
+    def head(self):
+        '''
+        *HEAD* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
+        '''
+        return self._pack('HEAD')
+
+    def options(self):
+        '''
+        *OPTIONS* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
+        '''
+        return self._pack('OPTIONS')
+    
+    def trace(self):
+        '''
+        *TRACE* HTTP method
+
+        http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8
+        '''
+        return self._pack('TRACE')
+
+    def credentials(self, username, password):
+        '''
+        set authentication settings
+        
+        @param username: username credential
+        @param password: password credential
+        '''
+        self._auth = (username, password)
+        return self
+
+    def proxy(self, server, host):
+        '''
+        proxy settings
+        
+        @param server: proxy server
+        @param host: proxy host
+        '''
+        self._proxy = server, host
+        return self

wire/http/request/cookie.py

+# -*- coding: utf-8 -*-
+'''wire HTTP cookie management'''
+
+import Cookie as _cook
+from functools import partial
+from Cookie import SimpleCookie, Morsel
+
+
+class RequestCookie(SimpleCookie):
+
+    '''HTTP request specific cookie'''
+
+    def __init__(self, inpt=None):
+        super(RequestCookie, self).__init__()
+        if inpt:
+            self.load(input)
+
+    def __setitem__(self, key, value):
+        rval, cval = self.value_encode(value)
+        self._set(key, rval, cval)
+
+    def __iter__(self):
+        return iter(list(
+            tuple(i.split(': ')) for i in str(self).splitlines())
+        )
+
+    def __str__(self):
+        return self.dumps
+
+    def _set(self, k, real_value, coded_value):
+        m = self.get(k, Morsel())
+        m.set(k, real_value, coded_value)
+        dict.__setitem__(self, k, m)
+
+    def _parse(self, cookie, patt=_cook._CookiePattern):
+        i = 0  # Our starting point
+        n = len(cookie)  # Length of string
+        m = None  # current morsel
+        unquote = _cook._unquote
+        reserved = Morsel._reserved
+        while 0 <= i < n:
+            # Start looking for a cookie
+            match = patt.search(cookie, i)
+            if not match:
+                break  # No more cookies
+            k, v = match.group('key'), match.group('val').rstrip(', ')
+            i = match.end(0)
+            # Parse the key, value in case it's metainfo
+            if k[0] == '$':
+                if m:
+                    m._morsel[k[1:]] = v
+            elif k.lower() in reserved:
+                if m:
+                    m._morsel[k] = unquote(v)
+            else:
+                rval, cval = self.value_decode(v)
+                self._set(k, rval, cval)
+                m = self[k]
+
+    def loads(self, data):
+        if isinstance(data, str):
+            self._parse(data)
+        else:
+            for k, v in data.iteritems():
+                self.__setitem__(k, v)
+
+    def dumps(self):
+        return partial(super(RequestCookie, self).output, header='cookie:')

wire/http/request/header.py

+# -*- coding: utf-8 -*-
+'''wire HTTP request core'''
+
+import re
+from random import choice
+from itertools import chain
+from datetime import datetime
+from email.header import Header
+
+from appspace.keys import appifies, imap
+from stuf.utils import OrderedDict, lazy_class, exhaust, iterexcept
+
+from wire.http.util import httpdate
+from wire.http.header import CoreHeaders
+from wire.http.errors import HTTPHeaderException
+from wire.http.request.keys import KRequestHeaders
+
+
+@appifies(KRequestHeaders)
+class Headers(CoreHeaders):
+
+    '''HTTP request header manager'''
+
+    def __init__(self, headers=()):
+        super(Headers, self).__init__()
+        # "best practice" sends HTTP headers in order: general, request, entity
+        self._general = OrderedDict()
+        self._entity = OrderedDict()
+        self._request = OrderedDict()
+        self._custom = OrderedDict()
+        # populate headers
+        self.headers(headers)
+        if self.G.randomua:
+            self.user_agent(self._randomua())
+
+    def _getheaders(self, key):
+        '''
+        Gets the list and tracker for a header's category.
+
+        @param: key Header key
+        '''
+        if key in self._GENERAL:
+            return self._general
+        elif key in self._REQUEST:
+            return self._request
+        elif key in self._ENTITY:
+            return self._entity
+        return self._custom
+
+    def _get(self, key):
+        key = key.lower().replace('_', '-')
+        headers = self._getheaders(key)
+        return headers.get(key)
+
+    def _delete(self, key):
+        key = key.lower().replace('_', '-')
+        headers = self._get(key)
+        del headers[key]
+
+    @lazy_class
+    def _badheaders(self):
+        '''bad header detecting regex from wsgiref'''
+        return re.compile(r'[\000-\037]')
+
+    def _format(self, key, value):
+        '''
+        Ensure proper formatting for a header value
+
+        @param key: Key associated with value
+        @param value: Value to format
+        '''
+        # Keep nulls out of the pipeline
+        if value is None or not value:
+            return value
+        # Join with any existing value if header allows multiple values
+        if key in self._DATES and isinstance(value, datetime):
+            value = httpdate(value)
+        return value
+
+    @lazy_class
+    def _langs(self):
+        '''language set.'''
+        from locale import locale_alias
+        return frozenset(i.replace('_', '-') for i in locale_alias)
+
+    @staticmethod
+    def _randomua():
+        return choice([
+        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en; rv:1.9.1.3) '
+        'Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)',
+        'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US; rv:1.9.1.3) '
+        'Gecko/20090824 Firefox/3.5.3 (.NET CLR 3.5.30729)',
+        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
+        'Gecko/20090718 Firefox/3.5.1',
+        'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
+        'Gecko/20090718 Firefox/4.0.1',
+        'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/532.1 '
+        '(KHTML, like Gecko) Chrome/4.0.219.6 Safari/532.1',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; '
+        'Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; '
+        'SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.5.30729; '
+        '.NET CLR 3.0.30729)',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; '
+        'Trident/4.0)',
+        'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; SV1; '
+        '.NET CLR 2.0.50727; InfoPath.2)Mozilla/5.0 (Windows; U; MSIE 7.0; '
+        'Windows NT 6.0; en-US)',
+        'Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)',
+        ])
+
+    @lazy_class
+    def _rules(self):
+        '''Validation rules for individual HTTP header fields'''
+        return {
+            # "Allow" values must be valid HTTP methods
+            'allow': (
+                lambda v: v.upper() not in self._METHODS,
+                '"Allow" value must be valid HTTP method'
+            ),
+            # "Content-Encoding" value must be zip, compress, or deflate
+            'content-encoding': (
+                lambda v: v.lower() not in self._ENCODINGS,
+                '"Content-Encoding" must be gzip, compress, deflate',
+            ),
+            # "Content-Language" values must be valid language codes
+            'content-language': (
+                lambda v: v.lower() not in self._langs,
+                'Unknown language code'
+            ),
+            # "Content-Length" value must be an integer
+            'content-length': (
+                lambda v: not v.isdigit(),
+                '"Content-Length" must be integer'
+            ),
+            # "Content-Range" value must be in the proper format
+            'content-range': (
+                lambda v: not self._iscontentrange(v),
+                'Invalid format for "Content-Range"'
+            ),
+        }
+
+    @classmethod
+    def _validate(self, key, value):
+        '''
+        Validate header value
+
+        @param key HTTP header name
+        @param value Putative header value
+        '''
+        err_msg = None
+        if not isinstance(value, basestring):
+            return True
+        # Don't allow request headers
+        if key in self._RESPONSE:
+            err_msg = '"%s" is HTTP response-only header' % key
+        # Don't allow unicode header names
+        if not isinstance(key, str):
+            err_msg = 'HTTP header name must be non-Unicode string'
+        # Don't allow unicode header values outside RFC2822
+        elif isinstance(value, unicode):
+            err_msg = 'use "set_rfc2822" to set Unicode HTTP header values'
+        # Don't allow "status" to be set in headers
+        elif key == 'status':
+            err_msg = 'HTTP status code cannot be set on HTTP request'
+        # Don't allow newline or colon in header name
+        elif '\n' in key or ':' in key:
+            err_msg = 'HTTP header names may not contain ":" or "\\n"'
+        # Do not allow invalid characters in header name
+        elif not self._ishttpname(key):
+            err_msg = 'Bad HTTP header name: %r' % key
+        # Do not allow dash or underscore at end of header name
+        elif key.endswith('-') or key.endswith('_'):
+            err_msg = 'HTTP header name may not end in "-" or "_"'
+        # No control characters in header name
+        elif isinstance(value, basestring) and self._badheaders.search(value):
+            err_msg = 'Bad character %r in HTTP header value %r' % (
+                self._badheaders.search(value).group(0), value
+            )
+        # Validate RFC 1123 compliance
+        elif key in self._DATES and not self._isrfc1123(value):
+            err_msg = 'Date is not RFC 1123 compliant'
+        # Validate individual header
+        else:
+            rule = self._rules.get(key)
+            if rule is not None and rule[0](value):
+                err_msg = rule[1]
+        if err_msg is not None:
+            raise HTTPHeaderException(err_msg)
+
+    def end(self):
+        # Cast any header objects as strings (calling Header.decode)
+        return ((h[0].lower(), str(h[1])) for h in chain(
+            self._general.iteritems(),
+            self._request.iteritems(),
+            self._entity.iteritems(),
+            self._custom.iteritems(),