def __call__(self, callback, request): """ :X-RateLimit-Limit: <number of request>/<time unit> the current rate limit for this API key :X-RateLimit-Used: the number of method calls used on this API key this timespan :X-RateLimit-Remaining: the estimated number of remaining calls allowed by this API key this minute """ criteria = self.get_criteria(request) if criteria is None: return callback(request) used = self.get_rate_used(criteria) headers = { 'x-ratelimit-limit': '{0}/{1}s'.format(self._max, self._timespan), 'x-ratelimit-used': used, 'x-ratelimit-remaining': max(self._max - used, 0), } if used >= self._max: logger.warning('Rejecting request of %s, quota maxed', criteria) return HTTPResponse(429, headers, 'You exceeded your quota') return HTTPResponse(headers, callback(request))
def serialize(self, result): if self.method == 'HEAD': return None elif self.method == 'POST': if result is None: raise ValueError('create_resource method must return the id.') url = self.make_url(result) return HTTPResponse(201, {'Location': url}, None) elif self.method.startswith('getall'): if not isinstance(result, collections.Iterable): raise ValueError('get_all_resources returned a non-iterable object') pairs = list(result) if not all(len(pair) == 2 for pair in pairs): raise ValueError('get_all_resources must return a iterable of tuples') return dict( (self.make_url(id), self.manager.serialize(resource)) for id, resource in result) elif self.method == 'GET' or self.method == 'filter': if not isinstance(result, collections.Iterable): raise ValueError( 'list_resource returned a non-iterable object') return map(self.make_url, result) else: return result
def authorize(self, callback, request): """ Calls :meth:`authenticate` and the *callback* If :meth:`authenticate` returns a callable, it will call it with the result of the callback. When the *timed* option is enabled, the time spend in :meth:`authenticate` will be calculated and returned in the **x-auth-time** header. """ content = self.extract(request) if 'login' in content: request.environ['napixd.auth.username'] = content['login'] with Chrono() as chrono: check = self.authenticate(request, content) logger.debug('Authenticate took %s', chrono.total) if not check: raise HTTPError(403, 'Access Denied') resp = callback(request) if callable(check): resp = check(resp) if self._timed: return HTTPResponse({'x-auth-time': chrono.total}, resp) return resp
def test_cast_response_text(self): r = HTTPResponse({'Content-Type': 'text/yaml'}, '''base:\n '*':\n webserver''') resp = self.cast(r) self.assertEqual(resp.headers['content-length'], '26') self.assertEqual(resp.headers['content-type'], 'text/yaml') self.assertEqual(resp.body, ['''base:\n '*':\n webserver'''])
def test_3_args(self): body = mock.Mock() r = HTTPResponse(304, {'content_type': 'application/pdf'}, body) self.assertEqual(r.status, 304) self.assertEqual(r.headers['content-type'], 'application/pdf') self.assertEqual(r.body, body)
def test_0_args(self): r = HTTPResponse() self.assertEqual(r.status, 200) self.assertEqual(len(r.headers), 0) self.assertTrue(isinstance(r.headers, HeadersDict)) self.assertEqual(r.body, None)
def cast(self, request, response): """ Translates a response in a :class:`napixd.http.response.HTTPResponse` object. """ if isinstance(response, Response): return HTTPResponse(200, response.headers, response) elif isinstance(response, (HTTPError, HTTPResponse)): status = response.status body = response.body headers = response.headers else: status = 200 headers = HeadersDict() body = response if request.method == 'HEAD': body = None headers['Content-Length'] = 0 content_type = headers.get('Content-Type', '') content_length = headers.get('Content-Length', None) if isinstance(body, basestring): if not content_type: content_type = 'text/plain' if isinstance(body, unicode): content_type += '; charset=utf-8' body = body.encode('utf-8') elif hasattr(body, 'read'): body = file_wrapper(request.environ, body) elif body is not None: content_type = 'application/json' body = self._json_provider.dumps(body) else: content_type = '' body = [] if isinstance(body, str): content_length = len(body) body = [body] headers.setdefault('Content-Type', content_type) if content_length is not None: headers.setdefault('Content-Length', content_length) return HTTPResponse(status, headers, body)
def test_2_args(self): body = mock.Mock() r = HTTPResponse({'content_type': 'application/pdf'}, body) self.assertEqual(r.status, 200) self.assertEqual(r.headers['content-type'], 'application/pdf') self.assertTrue(isinstance(r.headers, HeadersDict)) self.assertEqual(r.body, body)
def test_1_arg(self): body = mock.Mock() r = HTTPResponse(body) self.assertEqual(r.status, 200) self.assertEqual(len(r.headers), 0) self.assertTrue(isinstance(r.headers, HeadersDict)) self.assertEqual(r.body, body)
def test_second_request(self): self.pipe.zcount.return_value = 1 resp = self.call() self.assertEqual(resp, HTTPResponse({ 'x-ratelimit-remaining': '1', 'x-ratelimit-limit': '2/60s', 'x-ratelimit-used': '1', }, self.cb.return_value)) self.criteria.assert_called_once_with(self.req)
def test_cast_response(self): r = HTTPResponse(302, {'Location': '/pim/pam/poum'}, u'See /pim/pam/poum') resp = self.cast(r) self.assertEqual(resp.status, 302) self.assertEqual(resp.headers['Location'], '/pim/pam/poum') self.assertEqual(resp.headers['content-type'], 'text/plain; charset=utf-8') self.assertEqual(resp.headers['content-length'], '17') self.assertEqual(resp.body, ['See /pim/pam/poum'])
def __call__(self, callback, request): with Chrono() as timing: proc = Greenlet.spawn(callback, request) resp = proc.get() return HTTPResponse( { 'x-total-time': timing.total, 'x-running-time': proc.get_running_time() }, resp)
def test_first_req(self): self.pipe.zcount.return_value = 0 resp = self.call() self.pipe.assert_has_calls([ mock.call.watch('rate_limit:123'), mock.call.zcount('rate_limit:123', 1140, 1200), mock.call.multi(), mock.call.zadd('rate_limit:123', 1200, 1200), mock.call.expireat('rate_limit:123', 1260), ]) self.assertEqual(resp, HTTPResponse({ 'x-ratelimit-remaining': '2', 'x-ratelimit-limit': '2/60s', 'x-ratelimit-used': '0', }, self.cb.return_value))
def serialize(self, result): """ Serialize the request. Also applies the *format* if asked in the parameters. """ if self.method == 'HEAD': return None if self.method == 'PUT': if result is not None and result != self.resource.id: self.path.pop() new_url = self.make_url(result) return HTTPError(205, None, Location=new_url) return HTTPError(204) if self.method != 'GET': return result if result is None: raise ValueError('resource cannot be None') format_ = self.context.parameters.get('format', None) if not format_: return self.default_formatter(result) try: formatter = self.manager.get_formatter(format_) except KeyError: message = 'Cannot render %s.' % format_ all_formats = self.service.collection.get_all_formats() if all_formats: message = '{0} Available formats {1}: {2} '.format( message, 'is' if len(all_formats) <= 1 else 'are', ', '.join(all_formats.keys())) return HTTPError(406, message) response = Response() result = formatter(self.resource, response) if result is None or result is response: return response else: return HTTPResponse(response.headers, result)
def test_status_line(self): r = HTTPResponse(204, {}, None) self.assertEqual(r.status_line, '204 No Content')
def test_override_status(self): r = HTTPResponse(200, {}, self.r) self.assertEqual(r.status, 200)
def test_with_response(self): resp = Response({'header2': 'value2'}) r = HTTPResponse({'header1': 'value1'}, resp) self.assertEqual(r.headers['header1'], 'value1') self.assertEqual(r.body, resp)
def test_status_line_unknown(self): r = HTTPResponse(198, {}, None) self.assertEqual(r.status_line, '198 Unknown')
def setUp(self): self.body = body = mock.Mock() self.r = HTTPResponse(201, {'header1': 'value1'}, body)
def test_with_respons_length(self): resp = Response() resp.write('1234') r = HTTPResponse(resp) self.assertEqual(r.headers['Content-Length'], '4')
def test_keep_status(self): r = HTTPResponse(self.r) self.assertEqual(r.status, 201)
def __call__(self, callback, request): with Chrono() as chrono: resp = callback(request) return HTTPResponse({self.header_name: chrono.total}, resp)
def test_keep_body(self): r = HTTPResponse(self.r) self.assertEqual(r.body, self.body)
def test_override_header(self): r = HTTPResponse({'header1': 'value2'}, self.r) self.assertEqual(r.headers['header1'], 'value2')
def test_merge_headers(self): r = HTTPResponse({'header2': 'value2'}, self.r) self.assertEqual(r.headers['header1'], 'value1') self.assertEqual(r.headers['header2'], 'value2')