def preprocess( self, method, url, body, basicauth ) : """Pre-process a request""" headers = {} # Cached Etag #if method in ('GET', 'HEAD') : # etag = self.cache.get( url, [None, {}] )[1].get( 'etag', None ) # headers.update({ 'If-None-Match' : etag }) if etag else None # JSON body if body and (not isinstance( body, basestring ) ) : json = JSON() try : body = json.encode( body ) except TypeError : pass headers.setdefault('Content-Type', 'application/json') # Content-length, Transfer-Encoding headers.setdefault( 'Content-Length', '0' ) if body is None else None headers.setdefault( 'Content-Length', str(len(body)) ) if isinstance( body, basestring ) else headers.update({ 'Transfer-Encoding' : 'chunked' }) # Basc-Authorization headers.update({ 'Authorization': basicauth }) if basicauth else None return headers, body
def request( self, method, url, body=None, headers=None, credentials=None, num_redirects=0, chunk_cb=None ) : """Handle a request :method :: HTTP method, GET, PUT, POST, DELETE, ALL :url :: CouchDB url :headers :: A dictionary of http headers, like `Content-type` and `Accept` ``Content-type`` The use of the Content-type on a request is highly recommended. When uploading attachments it should be the corresponding MIME type for the attachment or binary ``application/octet-stream`` ``Accept`` Again highly recommended. """ # Sanitize arguments method = method.upper() url = self.perm_redirects.get( url, url ) headers = headers or {} basicauth = self.basicauth( credentials ) if credentials else None retries = iter(self.retry_delays) json = JSON() if method not in self._allowed_methods : raise HTTPError( 'Method ``%s`` not allowed' % method ) # Process request headers.setdefault( 'Accept', 'application/json' ) headers['User-Agent'] = self.user_agent h, body = self.preprocess( method, url, body, basicauth ) headers.update(h) conn = self.obtain_connection(url) resp = self.try_request_with_retries( conn, retries, method, url, headers, body ) status = resp.status req = Dummy() req.conn, req.url, req.method, req.headers, req.body = \ conn, url, method, headers, body # Handle NOT_MODIFIED #rc = self.resp_not_modified(resp, req) #if rc != None : return rc #self.cache.pop( url, None ) # Handle redirects rc = self.resp_redirect(resp, req, num_redirects) if rc != None : return rc data = None streamed = False # Read the full response for empty responses so that the connection is # in good state for the next request conds = [ method == 'HEAD', resp.getheader('content-length') == '0', status < OK, status in (NO_CONTENT, NOT_MODIFIED) ] if any(conds) : resp.read() self.release_connection(url, conn) # Buffer small non-JSON response bodies elif int(resp.getheader('content-length', sys.maxint)) < CHUNK_SIZE: data = resp.read() self.release_connection(url, conn) # For large or chunked response bodies, do not buffer the full body, # and instead return a minimal file-like object else : close_cb = lambda: self.release_connection(url, conn) data = ResponseBody( resp, close_cb, chunk_cb=chunk_cb ) streamed = True # Handle errors if status >= BAD_REQUEST : # 400 ctype = resp.getheader('content-type') if data is not None and 'application/json' in ctype: data = json.decode( data ) error = data.get('error'), data.get('reason') elif method != 'HEAD': error = resp.read() self.release_connection(url, conn) else: error = '' cls = hterr_class.get( status, None ) if cls : raise cls(error) else : raise ServerError((status, error)) # Store cachable responses #if not streamed and method == 'GET' and 'etag' in resp.msg: # self.cache[url] = (status, resp.msg, data) # self.clean_cache() if len(self.cache) > CACHE_SIZE[1] else None if not streamed and data is not None: data = StringIO(data) return status, resp.msg, data