Commits

Lynn Rees committed 3f4dcf4

- more

  • Participants
  • Parent commits c51b432
  • Branches pu

Comments (0)

Files changed (12)

wire/http/header.py

 
 from stuf.six import items
 from stuf.utils import lazy_class
-from appspace.utils import checkname
 
-from wire.http.httplet import httplet
+from callchain.mixin.reset import ResetLocalMixin
 
+from wire.support import n2b
 
-class CoreHeaders(httplet):
+n = n2b
 
-    '''HTTP header manager'''
+
+class CoreHeaders(ResetLocalMixin):
+
+    '''generic HTTP header manager'''
+
+    ###########################################################################
+    ## header processing definitions ##########################################
+    ###########################################################################
 
     @lazy_class
     def _COMMA_SEP(self):
-        '''May have values that split on the comma'''
-        return frozenset([
+        '''may have values that split on the comma'''
+        return self._freeze([
             'accept', 'accept-charset', 'accept-encoding', 'accept-language',
             'allow', 'cache-control', 'content-encoding', 'content-language',
             'if-match', 'if-none-match', 'pragma', 'vary', 'via',
         ])
 
+    _hCOMMA_SEP = _COMMA_SEP
+
+    @lazy_class
+    def _SC_SEP(self):
+        '''may have values that split on the semi-colon'''
+        return self._freeze([
+            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
+        ])
+
+    _hSC_SEP = _SC_SEP
+
     @lazy_class
     def _DATES(self):
-        '''Coverted to datetime fields'''
-        return frozenset([
+        '''covert to datetime fields'''
+        return self._freeze([
             'date', 'expires', 'if-modified-since',  'if-unmodified-since',
             'if-range', 'last-modified', 'retry-after',
         ])
 
+    _hSC_SEP = _DATES
+
     @lazy_class
     def _ENCODINGS(self):
-        '''"content-encoding" compatible values'''
-        return frozenset(['gzip', 'compress', 'deflate'])
+        '''*CONTENT-ENCODING* compatible values'''
+        return self._freeze(['gzip', 'compress', 'deflate'])
+
+    _hENCODINGS = _ENCODINGS
+
+    ###########################################################################
+    ## header family definitions ####EEEE######################################
+    ###########################################################################
 
     @lazy_class
     def _ENTITY(self):
         '''HTTP entity headers'''
-        return frozenset([
+        return self._freeze([
             'allow', 'content-encoding', 'content-language', 'content-length',
             'content-location', 'content-md5', 'content-range', 'content-type',
             'expires', 'last-modified', 'cookie',
         ])
 
+    _hENTITY = _ENTITY
+
     @lazy_class
     def _GENERAL(self):
-        '''General HTTP headers'''
-        return frozenset([
+        '''general HTTP headers'''
+        return self._freeze([
             'cache-control', 'connection', 'date', 'pragma', 'trailer', 'via',
             'transfer-encoding', 'upgrade', 'warning',
         ])
 
+    _hGENERAL = _GENERAL
+
     @lazy_class
     def _HOP_BY_HOP(self):
         '''hop by hop headers'''
-        return frozenset([
+        return self._freeze([
             'connection', 'keep-alive', 'proxy-authenticate', 'upgrade',
             'proxy-authorization', 'te', 'trailer', 'transfer-encoding',
         ])
 
+    _hHOP_BY_HOP = _HOP_BY_HOP
+
     @lazy_class
     def _HTTPIF(self):
         '''HTTP if tests'''
-        return frozenset([
+        return self._freeze([
             'if-match', 'if-none-match',  'if-modified-since', 'if-rangeset',
             'if-unmodified-since',
         ])
 
+    _hHTTPIF = _HTTPIF
+
+    ###########################################################################
+    ## request header definitions #############################################
+    ###########################################################################
+
     @lazy_class
-    def _METHODS(self):
-        '''HTTP methods'''
-        return frozenset([
-            'OPTIONS', 'GET', 'HEAD', 'POST', 'POST', 'DELETE', 'CONNECT',
+    def _REQUEST(self):
+        '''HTTP request only headers'''
+        return self._freeze([
+            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
+            'range', 'authorization', 'expect', 'from', 'host', 'if-match',
+            'if-modified-since', 'if-none-match', 'if-range', 'user-agent',
+            'if-unmodified-since', 'max-forwards', 'te', 'proxy-authorization',
+            'referer',
         ])
 
+    _hREQUEST = _REQUEST
+
+    @lazy_class
+    def _REQ_HEADERS(self):
+        '''combined HTTP request compatible headers'''
+        return self.GENERAL | self.REQUEST | self.ENTITY
+
+    _hREQ_HEADERS = _REQ_HEADERS
+
+    ###########################################################################
+    ## response header definitions ############################################
+    ###########################################################################
+
+    @lazy_class
+    def _RESPONSE(self):
+        '''HTTP response only headers'''
+        return self._freeze([
+            'accept-ranges', 'age', 'etag', 'location', 'proxy-authenticate',
+            'retry-after', 'server', 'set-cookie', 'vary', 'www-authenticate',
+        ])
+
+    _hRESPONSE = _RESPONSE
+
+    @lazy_class
+    def _RES_HEADERS(self):
+        '''combined HTTP response compatible headers'''
+        return self.GENERAL | self.RESPONSE | self.ENTITY
+
+    _hRES_HEADERS = _RES_HEADERS
+
+    ###########################################################################
+    ## header validators ######################################################
+    ###########################################################################
+
     @lazy_class
     def _PATTERNS(self):
         '''relevant HTTP validators'''
         )
         return dict((k, re.compile(v, re.U)) for k, v in items(_PATTERNS))
 
-    @lazy_class
-    def _REQUEST(self):
-        '''HTTP request headers'''
-        return frozenset([
-            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
-            'range', 'authorization', 'expect', 'from', 'host', 'if-match',
-            'if-modified-since', 'if-none-match', 'if-range', 'user-agent',
-            'if-unmodified-since', 'max-forwards', 'te', 'proxy-authorization',
-            'referer',
-        ])
+    _hPATTERNS = _PATTERNS
 
-    @lazy_class
-    def _REQ_HEADERS(self):
-        '''Combined HTTP request compatible headers'''
-        return self.GENERAL | self.REQUEST | self.ENTITY
+    @classmethod
+    def _regeval(cls, key, data):
+        '''Validates data by key'''
+        return True if cls.PATTERNS[key].search(data) else False
 
-    @lazy_class
-    def _RESPONSE(self):
-        '''HTTP response headers'''
-        return frozenset([
-            'accept-ranges', 'age', 'etag', 'location', 'proxy-authenticate',
-            'retry-after', 'server', 'set-cookie', 'vary', 'www-authenticate',
-        ])
-
-    @lazy_class
-    def _RES_HEADERS(self):
-        '''Combined HTTP response compatible headers'''
-        return self.GENERAL | self.RESPONSE | self.ENTITY
-
-    @lazy_class
-    def _SC_SEP(self):
-        '''May have values that are split on semi-colon'''
-        return frozenset([
-            'accept', 'accept-charset', 'accept-encoding', 'accept-language',
-        ])
+    _hregval = _regeval
 
     @classmethod
     def _iscontentrange(cls, data):
-        '''Validates a HTTP content range header'''
+        '''validates a HTTP content range header'''
         return cls._regeval('content_type', data)
 
+    _hiscontentrange = _iscontentrange
+
     @classmethod
     def _ishttpname(cls, data):
-        '''Validates data is a correct HTTP header name'''
+        '''validates data is a correct HTTP header name'''
         return cls._regeval('http_header_name', data)
 
+    _hishttpname = _ishttpname
+
     @classmethod
     def _isinteger(cls, data):
-        '''Validates data is an integer'''
+        '''validates data is an integer'''
         return cls._regeval('integer', data)
 
+    _hisinteger = _isinteger
+
     @classmethod
     def _isrfc1123(cls, data):
         '''RFC1123 compliance checker e.g. "Wed, 03 Oct 2007 23:59:24 GMT"'''
         return cls._regeval('rfc1123', data)
 
+    _hisrfc1123 = _isrfc1123
+
     @classmethod
     def _isurl(cls, data):
-        '''Validates data is a valid URL'''
+        '''validates data is a valid URL'''
         return cls._regeval('url', data)
 
-    @lazy_class
-    def _headattr(self):
-        '''Python name -> header name mapping'''
-        return dict((checkname(h), h) for h in self.REQ_HEADERS)
+    _hisurl = _isurl
+
+    ###########################################################################
+    ## header management ######################################################
+    ###########################################################################
 
     def _parseint(self, value):
         '''
-        Parses an integer from a header value.
+        parses an integer from a header value.
 
-        @param: value Integer value
+        @param value: integer value
         '''
         return int(value) if self.isinteger(value) else value
 
-    @classmethod
-    def _regeval(cls, key, data):
-        '''Validates data by key'''
-        return True if cls.PATTERNS[key].search(data) else False
+    _hparseint = _parseint
+
+    def _freeze(self, *args):
+        '''freeze header definition'''
+        return frozenset(n(a) for a in args)
+
+    _hfreeze = _freeze
+
+    ###########################################################################
+    ## header export ##########################################################
+    ###########################################################################
 
     def dumps(self):
-        return '\r\n'.join(': '.join([k, v]) for k, v in self)
+        return n('\r\n'.join(': '.join([k, v]) for k, v in self))
+
+    _hdumps = dumps

wire/http/httplet.py

 class httplet(chainlet):
 
     def __init__(self, root):
+        '''
+        init
+
+        @param root: root object
+        '''
         super(httplet, self).__init__(root)
-        self._headers = root._headers
         self._cookies = root._cookies
         self._data = root._data
+        self._headers = root._headers
+        self._params = root._params

wire/http/request/client.py

 # -*- coding: utf-8 -*-
 '''wire HTTP request core'''
 
+from functools import partial
+
 from appspace.keys import appifies
 from callchain.internal import inside
-from stuf.utils import OrderedDict, exhaust
+from stuf.utils import OrderedDict, exhaust, lazy_class
 from twoq.lazy.mixins import AutoResultMixin
 from callchain.assembly.chain import CallChainQ
 from callchain.mixin.reset import ResetTypeMixin
         # cookies
         self._cookies = None
 
+    def __getattr__(self, key):
+        return partial(
+            self._pack, key
+        ) if key.lower() in self._METHODS else self._fgetr(key)
+
+    _hgetter = __getattr__
+
     def __call__(self, *urls, **config):
         '''
         load urls
             u, verify, allow_redirects, timeout, auth, proxy, randomua
         ) for u in urls))
 
+    _hcall = __call__
+
+    ###########################################################################
+    ## http methods ###########################################################
+    ###########################################################################
+
+    @lazy_class
+    def _METHODS(self):
+        '''allowed HTTP methods'''
+        return self._freeze([
+            'OPTIONS', 'GET', 'HEAD', 'POST', 'POST', 'DELETE', 'CONNECT',
+        ])
+
+    _hMETHODS = _METHODS
+
+    @property
+    def _requester(self):
+        '''fetch http requester'''
+        return self.M.get(self.G.request, self.G.userspace)
+
+    _hrequester = _requester
+
+    def _pack(self, method):
+        '''
+        run HTTP `method`
+
+        @param method: HTTP method
+        '''
+        self.args(self._headers).invoke('headers').args(self._cookies).invoke(
+        'cookies').args(self._data).invoke('data')
+        pack = lambda x: self.chain(self._requester, method, x.url, **x.args)
+        with self._sync as sync:
+            exhaust(pack(i) for i in sync.interable)
+        self.commit()
+        return self
+
+    _hpack = _pack
+
     ###########################################################################
     ## usage management #######################################################
     ###########################################################################
         self._qclear()
         return self
 
+    _hclear = clear
+
+    def back(self, link):
+        '''
+        handle chainlet end
+
+        @param link: linked chain
+        '''
+        self._headers = link._headers
+        self._cookies = link._cookies
+        self._data = link._data
+        self._params = link._params
+        return self._rback(link)
+
+    _hback = back
+
     def auth(self, username, password):
         '''
         set authentication settings
         self._auth = (username, password)
         return self
 
+    _hauth = auth
+
     def proxy(self, server, host):
         '''
         set proxy settings
         self._proxy = (server, host)
         return self
 
-    ###########################################################################
-    ## http methods ###########################################################
-    ###########################################################################
-
-    @property
-    def _requester(self):
-        '''fetch http requester'''
-        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(i) for i in 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')
+    _hproxy = proxy

wire/http/request/cookie.py

 from stuf.utils import exhaustmap
 from twoq.support import isstring
 from appspace.keys import appifies
+# pylint: disable-msg=f0401
 from stuf.six.moves import http_cookies  # @UnresolvedImport
+# pylint: enable-msg=f0401
 
 from wire.http.httplet import httplet
 from wire.http.request.keys.header import KRequestCookie
         dict.__setitem__(self, k, m)
 
     def _parse(self, cookie, patt=http_cookies._CookiePattern):
-        i = 0  # Our starting point
-        n = len(cookie)  # Length of string
-        m = None  # current morsel
+        # our starting point
+        i = 0
+        # length of string
+        n = len(cookie)
+        # current morsel
+        m = None
         unquote = http_cookies._unquote
         reserved = http_cookies.Morsel._reserved
         value_decode = self.value_decode
         setter = self._set
         this = self
+        psearch = patt.search
         while 0 <= i < n:
             # Start looking for a cookie
-            match = patt.search(cookie, i)
+            match = psearch(cookie, i)
             if not match:
-                break  # No more cookies
+                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
+            # parse the key, value just in case it's metainfo
             if k[0] == '$':
                 if m:
                     m._morsel[k[1:]] = v
         else:
             exhaustmap(data, self.__setitem__)
 
-    def dumps(self):
-        return partial(self.output, header='cookie:')
-
     def set(self, key, field, value):
         m = self.get(key, http_cookies.Morsel())
         if key.lower() in m._reserved:
             m.set(field, real_value, coded_value)
         dict.__setitem__(self, key, m)
 
+    def dumps(self):
+        return partial(self.output, header='cookie:')
+
 
 @appifies(KRequestCookie)
 class RequestCookie(httplet):
 
-    '''request cookie manager'''
+    '''HTTP request cookie manager'''
 
     def __init__(self, root):
         '''
         exhaustmap(values, lambda x, y: setr(key, x, y))
         return self
 
-    def back(self):
-        '''revert to root chain'''
-        # synchronize all requests with cookies
-        self.root.args(self._cookies).invoke('cookies')
-        return self._rback()
+    _hcookie = cookie

wire/http/request/data.py

 '''wire HTTP data manager'''
 
 import hashlib
-from base64 import b64encode
 
 from appspace.keys import appifies
+from wire.support import base64_encode
 from callchain.root.linked import chainlink
 
 from wire.http.request.keys.data import KRequestData
         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 charset: charset (default: None)
         '''
         if charset is None:
-            self['Content-Type'] = mime
+            self._headers.set('Content-Type', mime)
         else:
-            self['Content-Type'] = '{0}; charset={1}'.format(mime, charset)
+            self._headers.set(
+                'Content-Type', '{0}; charset={1}'.format(mime, charset),
+            )
         return self
 
     def content_range(self, first, last, length='*'):
         '''
-        Set the rangeset in header "Content-Range"
+        set rangeset in HTTP header *Content-Range*
 
-        @param first First byte position
-        @param last Last byte position
-        @param length Instance length
+        @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))
+            self._headers.set('Content-Range', 'bytes %d-%d/*' % (first, last))
         else:
-            self._set(
+            self._headers.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 content_md5(self):
+        '''
+        take MD5 hash of data and set *CONTENT-MD5* HTTP header.
 
-    def files(self, files):
+        @param value: HTTP response data
+        '''
+        self._headers.set('Content-MD5', base64_encode(
+            hashlib.md5.new(self._data).digest()
+        ))
+        return self
+
+    def data(self, *data):
+        '''
+        `data` for urls (must equal number of previously passed urls)
+
+        param *data: data for request body
+        '''
+        self._data = data
+        return self
+
+    def files(self, *files):
+        '''
+        `files` for urls (must equal number of previously passed urls)
+
+        param *data: data for request body
+        '''
         # files demand multipart as default otherwise go with urlencode
-        self._format = 'multipart'
+        self._value = 'multipart'
         self._files = files
         return self
+
+    def params(self, **params):
+        '''
+        request parameters for all urls
+
+        param **params: parameters
+        '''
+        self._params = params
+        return self

wire/http/request/header.py

 # -*- coding: utf-8 -*-
-'''wire HTTP request core'''
+'''wire HTTP management'''
 
 import re
 from random import choice
+from itertools import chain
 from datetime import datetime
+from functools import partial
 from email.header import Header
-from itertools import chain, starmap
 
 from appspace.keys import appifies
+from appspace.utils import checkname
+from stuf.utils import OrderedDict, lazy_class
 from twoq.support import isstring, isunicode, items
-from stuf.utils import OrderedDict, lazy_class, exhaust
+
+from wire.support import n2b
 
 from wire.http.util import httpdate
 from wire.http.header import CoreHeaders
 from wire.http.errors import HTTPHeaderException
 from wire.http.request.keys.header import KRequestHeaders
 
+n = n2b
+
 
 @appifies(KRequestHeaders)
 class Headers(CoreHeaders):
 
     '''HTTP request header manager'''
 
-    def __init__(self, headers=()):
+    def __init__(self):
         super(Headers, self).__init__()
         # "best practice" sends HTTP headers in order: general, request, entity
         self._general = 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.
+    def __iter__(self):
+        # cast any header objects as `bytes` (calling `Header.decode`)
+        return ((h[0].lower(), n(h[1])) for h in chain(
+            items(self._general),
+            items(self._request),
+            items(self._entity),
+            items(self._custom),
+        ) if h is not None)
 
-        @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
+    ###########################################################################
+    ## header definitions #####################################################
+    ###########################################################################
 
     @lazy_class
     def _langs(self):
         '''language set.'''
         from locale import locale_alias
-        return frozenset(i.replace('_', '-') for i in locale_alias)
+        return frozenset(n(i.replace('_', '-')) for i in locale_alias)
 
     @staticmethod
     def _randomua():
+        '''random user agent'''
         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; '
+        n('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)'),
+        n('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)'),
+        n('Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
+        'Gecko/20090718 Firefox/3.5.1'),
+        n('Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.1) '
+        'Gecko/20090718 Firefox/4.0.1'),
+        n('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'),
+        n('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; '
+        'Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.2)'),
+        n('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 3.0.30729)'),
+        n('Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Win64; x64; '
+        'Trident/4.0)'),
+        n('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)',
+        'Windows NT 6.0; en-US)'),
+        n('Mozilla/4.0 (compatible; MSIE 6.1; Windows XP)'),
         ])
 
+    ###########################################################################
+    ## header validation ######################################################
+    ###########################################################################
+
+    @lazy_class
+    def _badheaders(self):
+        '''bad header detecting regex (from wsgiref)'''
+        return re.compile(r'[\000-\037]')
+
     @lazy_class
     def _rules(self):
         '''Validation rules for individual HTTP header fields'''
             ),
         }
 
-    def _header(self, headers):
-        '''
-        HTTP request headers setter
-
-        @param headers: HTTP headers
-        '''
-        header = self.header
-        exhaust(starmap(header, (i for i in headers)))
-        return self
-
     @classmethod
     def _validate(self, key, value):
         '''
-        Validate header value
+        validate header value
 
-        @param key HTTP header name
-        @param value Putative header value
+        @param key: HTTP header name
+        @param value: putative header value
         '''
         err_msg = None
         if not isstring(value):
         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):
+        if isunicode(key):
             err_msg = 'HTTP header name must be non-Unicode string'
         # Don't allow unicode header values outside RFC2822
         elif isunicode(value):
         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(
-            items(self._general),
-            items(self._request),
-            items(self._entity),
-            items(self._custom),
-            self.cookies,
-        ) if h is not None)
+    ###########################################################################
+    ## header retrieval #######################################################
+    ###########################################################################
+
+    def _getheaders(self, key):
+        '''
+        gets list 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):
+        '''
+        get HTTP request header value
+
+        @param key HTTP header key
+        '''
+        key = self._key(key)
+        return self._getheaders(key).get(key)
+
+    ###########################################################################
+    ## header manipulation ####################################################
+    ###########################################################################
+
+    def _key(self, key):
+        '''
+        Ensure proper formatting for header key
+
+        @param key: Key associated with value
+        '''
+        return n(key.lower().replace('_', '-'))
+
+    def _value(self, key, value):
+        '''
+        Ensure proper formatting for 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 n(value)
 
     def rfc2822(self, key, value, charset=None):
         '''
-        Set a header value in with a specific charset encoded as RFC 2822.
+        set header value with a specific charset encoded following 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)
+        existing = self.get(key)
         if existing is not None:
             try:
                 existing.append(value, charset)
                 pass
         else:
             # Override existing value if not Header instance or add new value
-            self._set(key, Header(value, charset))
-        return self
+            self.set(key, Header(value, charset))
 
-    def header(self, key, value):
+    def set(self, key, value):
         '''
         set HTTP request header
 
         @param key: header key
         @param value: header value
         '''
-        key = key.lower().replace('_', '-')
-        # validate header value
+        fmt = self._value
+        key = self._key(key)
+        value = fmt(key, 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 to assign as 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])
+                    headers[key] = fmt(', '.join([headers[key], value]))
                 # replace value on headers allowing only single values
-                if key in self._SC_SEP:
-                    headers[key] = '; '.join([headers[key], value])
+                elif key in self._SC_SEP:
+                    headers[key] = fmt('; '.join([headers[key], value]))
                 else:
                     headers[key] = value
         # add new value if not present
         else:
             headers[key] = value
+
+    def delete(self, key):
+        '''
+        delete HTTP request header value
+
+        @param key HTTP header key
+        '''
+        del self.get(key)[self._key(key)]
+
+
+@appifies(KRequestHeaders)
+class RequestHeaders(CoreHeaders):
+
+    '''request header chainlet'''
+
+    def __init__(self, root):
+        '''
+        init
+
+        @param root: root object
+        '''
+        super(RequestHeaders, self).__init__(root)
+        # initialize headers
+        self._headers = self._synchback(
+            '_headers', Headers() if self._headers is None else self._headers,
+        )
+
+    def __getattr__(self, key):
+        try:
+            return object.__getattribute__(self, key)
+        except AttributeError:
+            tkey = self._headattr.get(key)
+            # cast to native string to cast email.Header to native string type
+            if tkey is not None:
+                value = self._headers.get(tkey)
+                # don't make `None` a string
+                if value is not None:
+                    return partial(self.bheader, key)
+            raise AttributeError(key)
+
+    @lazy_class
+    def _headattr(self):
+        '''python name -> header name mapping'''
+        return dict((checkname(h), h) for h in self._headers.REQ_HEADERS)
+
+    def bheader(self, key, value):
+        '''
+        set HTTP request header with `bytes` (native string) value
+
+        @param key: header key
+        @param value: header value
+        '''
+        self._headers.set(key, value)
         return self
+
+    header = bheader
+
+    def uheader(self, key, value, charset=None):
+        '''
+        set HTTP request header with `unicode` value
+
+        @param key: header key
+        @param value: header value
+        '''
+        self._headers.rfc2822(key, value, charset)
+        return self

wire/http/request/keys/data.py

     
     '''HTTP request data key'''
     
+    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(value, charset=None):
+        '''
+        *CONTENT-TYPE* HTTP header.
+
+        @param value: media type
+        @param charset: optional charset (default: None)
+        '''
+    
     def content_md5():
         '''
         take MD5 hash of data and set *CONTENT-MD5* HTTP header.
 
         @param value: HTTP response data
         '''
+        
+    def data(*data):
+        '''
+        `data` for urls (must equal number of previously passed urls)
+        
+        param *data: data for request body
+        '''
     
-    def file(*files):
+    def files(*files):
         '''
         `files` for urls (must equal number of previously passed urls)
         
         
         param **params: parameters
         '''
-        
-    def data(*data):
-        '''
-        `data` for urls (must equal number of previously passed urls)
-        
-        param *data: data for request body
-        '''

wire/http/request/keys/header.py

         
         @param value: HTTP header value
         '''
-
+        
+    def content_length(value):
+        '''
+        *CONTENT-LENGTH* HTTP header
+        
+        @param value: HTTP header value
+        '''
+        
     def content_encoding(value):
         '''
         *CONTENT-ENCODING* HTTP header
         
         @param value HTTP header value
         '''
-        
-    def content_length(value):
-        '''
-        *CONTENT-LENGTH* HTTP header
-        
-        @param value: HTTP header value
-        '''
-        
+
     def content_location(value):
         '''
         *CONTENT-LOCATION* HTTP header
         
         @param value: HTTP header value
         '''
-        
-    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(value, charset=None):
-        '''
-        *CONTENT-TYPE* HTTP header.
-
-        @param value: media type
-        @param charset: optional charset (default: None)
-        '''
     
     def date(value):
         '''
         @param value: HTTP header value
         '''
     
-    def header(key, value):
+    def uheader(key, value):
         '''
         HTTP request header setter
         
         @param value: HTTP header value
         '''
         
-    def headers(headers):
+    def bheader(key, value):
         '''
-        HTTP request headers setter
+        HTTP request header setter
         
-        @param headers: HTTP headers
+        @param key: HTTP header key
+        @param value: HTTP header value
+        '''
+
+    def header(key, value):
+        '''
+        HTTP request header setter
+        
+        @param key: HTTP header key
+        @param value: HTTP header value
         '''
 
     def host(value):
         @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)
-        '''
-
     def te(value):
         '''
         *TE* HTTP header

wire/http/response/header.py

         '''
         # Keep nulls out of the pipeline
         if isstring(value):
-        # Request HTTP variables to cast to integers
+            
+            # HTTP variables to cast to integers
             if key in set(['accept-ranges', 'age', 'content-length']):
                 value = cls._parseint(value)
             elif key in cls.COMMA_SEP:
                 # Split comma-separatable values
                 newval = value.replace(', ', ',').replace(' ,', ',').split(',')
-                if len(newval) > 1: value = newval
+                if len(newval) > 1:
+                    value = newval
             # Reformat HTTP dates as datetime objects
             elif key in cls.DATES:
                 value = datehttp(value)

wire/http/util.py

 import time
 from datetime import datetime
 
-from six.moves import formatdate, parsedate_tz  # @UnresolvedImport
-from six.moves.BaseHTTPServer import BaseHTTPRequestHandler  # @UnresolvedImport @IgnorePep8
+from wire.support import parsedate_tz, _formatdate, brh
 
 
 def datehttp(value):
     if isinstance(date, datetime):
         # Put datetime object in UNIX time
         date = time.mktime(date.timetuple()) + 1e-6 * date.microsecond
-    return formatdate(date)
+    return _formatdate(date)
 
 
 def message(code):
 
     @param code: HTTP response (integer)
     '''
-    return BaseHTTPRequestHandler.responses[code][1]
+    return brh[code][1]
 
 
 def statuscode(code):
 
     @param code: HTTP response code (integer)
     '''
-    return '{0} {1}'.format(code, BaseHTTPRequestHandler.responses[code][0])
+    return '{0} {1}'.format(code, brh[code][0])
+# -*- coding: utf-8 -*-
+# pylint: disable-msg=f0401,w0613,w0631,w0622,w0122,w0611,e0611
+
+from twoq.support import isstring
+from stuf.six import PY3, native, texts
+
+if PY3:
+    from email.utils import formatdate, parsedate_tz
+
+    def _formatdate(timeval=None):
+        return formatdate(timeval, usegmt=True)
+
+    def n2b(n, encoding='ISO-8859-1'):
+        '''
+        the given native string as a byte string in the given encoding
+        '''
+        # In Python 3, the native string type is unicode
+        return n.encode(encoding)
+
+    def n2u(n, encoding='ISO-8859-1'):
+        '''
+        the given native string as a unicode string with the given encoding
+        '''
+        # In Python 3, the native string type is unicode
+        return n
+
+    def ton(n, encoding='ISO-8859-1'):
+        '''
+        the given string as a native string in the given encoding
+        '''
+        # In Python 3, the native string type is unicode
+        if isinstance(n, bytes):
+            return n.decode(encoding)
+        return n
+else:
+    import re
+    from rfc822 import formatdate as _formatdate, parsedate_tz  # @UnusedImport
+
+    def n2b(n, encoding='ISO-8859-1'):
+        '''the given native string as a byte string in the given encoding'''
+        # In Python 2, the native string type is bytes. Assume it's already
+        # in the given encoding, which for ISO-8859-1 is almost always what
+        # was intended.
+        return n
+
+    def n2u(n, encoding='ISO-8859-1'):
+        '''
+        the given native string as a unicode string with the given encoding
+        '''
+        # In Python 2, the native string type is bytes.
+        # First, check for the special encoding 'escape'. The test suite uses
+        # this
+        # to signal that it wants to pass a string with embedded \unnnn
+        # escapes, but without having to prefix it with u'' for Python 2, but
+        # no prefix for Python 3.
+        if encoding == 'escape':
+            return unicode(re.sub(
+                r'\\u([0-9a-zA-Z]{4})',
+               lambda m: unichr(int(m.group(1), 16)),
+               n.decode('ISO-8859-1')
+            ))
+        # assume it's already in the given encoding, which for ISO-8859-1 is
+        # almost always what was intended.
+        return n.decode(encoding)
+
+    def ton(n, encoding='ISO-8859-1'):
+        '''return the given string as a native string in the given encoding'''
+        # in Python 2, the native string type is bytes.
+        if isinstance(n, unicode):
+            return n.encode(encoding)
+        return n
+
+from six.moves import BaseHTTPServer, range  # @UnresolvedImport @UnusedImport
+brh = BaseHTTPServer.BaseHTTPRequestHandler.responses
+
+
+try:
+    # Python 3.1+
+    from base64 import (
+        decodebytes as _base64decode, encodebytes as _base64encode)
+except ImportError:
+    # Python 3.0-
+    from base64 import (
+        decodestring as _base64decode, encodestring as _base64encode)
+
+
+def base64_encode(n, encoding='ISO-8859-1'):
+    '''native string base64-encoded (as a native string)'''
+    b = n.encode(encoding) if native is texts else n
+    return _base64encode(b.decode(encoding) if isstring(b) else b)
+
+
+def base64_decode(n, encoding='ISO-8859-1'):
+    '''native string base64-decoded (as a native string)'''
+    b = _base64decode(n.encode(encoding) if isstring(n) else n)
+    return b.decode(encoding) if native is texts else b
 
 import mimetypes
 
-from six.moves import range  # @UnresolvedImport
+from wire.support import range
 
 
 def content_type(filename):