I am trying to implement a type of custom authentication by using aiohttp something like the example in this link but I also need request body. Here is an example for requests:
class CustomAuth(AuthBase):
def __init__(self, secretkey):
self.secretkey = secretkey
def get_hash(self, request):
if request.body:
data = request.body.decode('utf-8')
else:
data = "{}"
signature = hmac.new(
str.encode(self.secretkey),
msg=str.encode(data),
digestmod=hashlib.sha256
).hexdigest().upper()
return signature
def __call__(self, request):
request.headers["CUSTOM-AUTH"] = self.get_hash(request)
return request
I've looked into tracing and BasicAuth but they are useless in my situation. On on_request_start request body is not ready, on on_request_chunk_sent headers have already been sent. A solution like BasicAuth don't have access the request data at all.
Do you have any idea?
Thanks in advance.
I use APIClient() for my tests.
I use Token auth, so I need to use THIS
If we dive into the source code we'll see next:
# rest_framework/test.py
class APIClient(APIRequestFactory, DjangoClient):
def __init__(self, enforce_csrf_checks=False, **defaults):
super().__init__(**defaults)
self.handler = ForceAuthClientHandler(enforce_csrf_checks)
self._credentials = {}
def credentials(self, **kwargs):
"""
Sets headers that will be used on every outgoing request.
"""
self._credentials = kwarg
Also I use APIClient() as a pytest fixture in my code:
#pytest.fixture(scope="function")
def _api():
"""API factory for anonymous and auth requests"""
def __api(token=None, field=None):
api_client = APIClient()
headers = {}
if token:
headers["HTTP_AUTHORIZATION"] = f"Token {token}"
if field:
headers["X-CUSTOM-HEADER"] = field
api_client.credentials(**headers)
return api_client
return __api
But if we create TestMiddleware to look for headers, we see next:
lass TestMiddleware:
def __init__(self, get_response):
self._get_response = get_response
def __call__(self, request):
header = request.headers.get("X-CUSTOM-HEADER") # None
header = request.META.get("X-CUSTOM-HEADER") # Works fine!
...
# Response processing
response = self._get_response(request)
return response
The question is: Have we any way to have access to the X-CUSTOM-HEADER with APIClient() ?
Also if I use Postman it obviously works fine with request.headers.get()
The kwargs passed to the credentials() method ends up feeding directly into the constructor for a WSGIRequest; this means the kwargs it accepts aren't HTTP headers, but WSGI environment variables. And HTTP headers passed as WSGI env vars are always prefixed with HTTP_ — e.g. the Authorization header is configured with HTTP_AUTHORIZATION. Also, underscores are used in place of dashes.
To have your X-Custom-Header header come out the other side in request.headers (not request.META, which is a copy of the WSGI env vars), pass HTTP_X_CUSTOM_HEADER instead of X-CUSTOM-HEADER.
I have a class called Client which uses the requests module to interact with a service. It has methods like:
def get_resource(self, url, headers):
response = requests.get(url, headers, auth=self.auth)
return response
Now I want to call some methods before and after each call to the requests module. Something like:
def get_resource(self, url, headers):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
I'm having trouble finding a way to do this without having to rewrite all Client methods. The most straightforward way is to change the calls to the request module to calls to self and add the calls to the methods there.
def get_resource(self, url, headers):
response = self.__get(url, headers, auth=self.auth)
return response
def __get(self, headers, auth):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
But this requires me to change all the call sites and duplicate functionality of the request module. I've tried to use decorators to add these method calls to the request module functions, but got stuck with how to pass in self to the decorator.
I'm sure there's an elegant way to do this in Python.
You can use monkey patching.
read this : Python: Monkeypatching a method of an object
import requests
def get(self, url, params=None, **kwargs):
self.add_request_header(self.headers)
response = requests.get(url, self.headers, auth=self.auth)
self.process_response_headers()
setattr(requests.Session, 'get', requests.Session.get)
s = requests.Session()
I think in this case decorators will not be as good as it sounds and OOP is a better approach to your problem. You could use a base class Client:
class Client(object):
def __init__(self, auth):
self.auth = auth
def add_request_header(self, headers):
pass
def process_response_headers(self):
pass
def get_resource(self, url, headers):
self.add_request_header(headers)
response = requests.get(url, headers, auth=self.auth)
self.process_response_headers()
return response
And create another subclasses with other implementations of add_request_header and/or process_response_headers so later you just need to instantiate the class that better suites your case
I want to make multiple internal REST API call from my Django TemplateView, using requests library. Now I want to pass the session too from template view to api call. What is the recommended way to do that, keeping performance in mind.
Right now, I'm extracting cookie from the current request object in template view, and passing that to requests.get() or requests.post() call. But problem with that is, I would have to pass request object to my API Client, which I don't want.
This the current wrapper I'm using to route my requests:
def wrap_internal_api_call(request, requests_api, uri, data=None, params=None, cookies=None, is_json=False, files=None):
headers = {'referer': request.META.get('HTTP_REFERER')}
logger.debug('Request API: %s calling URL: %s', requests_api, uri)
logger.debug('Referer header sent with requests: %s', headers['referer'])
if cookies:
csrf_token = cookies.get('csrftoken', None)
else:
csrf_token = request.COOKIES.get('csrftoken', None)
if csrf_token:
headers['X-CSRFToken'] = csrf_token
if data:
if is_json:
return requests_api(uri, json=data, params=params, cookies=cookies if cookies else request.COOKIES, headers=headers)
elif not files:
return requests_api(uri, data=data, params=params, cookies=cookies if cookies else request.COOKIES, headers=headers)
else:
return requests_api(uri, data=data, files=files, params=params, cookies=cookies if cookies else request.COOKIES,
headers=headers)
else:
return requests_api(uri, params=params, cookies=cookies if cookies else request.COOKIES, headers=headers)
Basically I want to get rid of that request parameter (1st param), because then to call it I've to keep passing request object from TemplateViews to internal services. Also, how can I keep persistent connection across multiple calls?
REST vs Invoking the view directly
While it's possible for a web app to make a REST API call to itself. That's not what REST is designed for. Consider the following from: https://docs.djangoproject.com/ja/1.9/topics/http/middleware/
As you can see a django request/response cycle has quite a bit of overhead. Add to this the overhead of webserver and wsgi container. At the client side you have the overhead associated with the requests library, but hang on a sec, the client also happens to be the same web app so it become s part of the web app's overhead too. And there is the problem of peristence (which I will come to shortly).
Last but not least, if you have a DNS round robin setup your request may actually go out on the wire before coming back to the same server. There is a better way, to invoke the view directly.
To invoke another view without the rest API call is really easy
other_app.other_view(request, **kwargs)
This has been discussed a few times here at links such as Django Call Class based view from another class based view and Can I call a view from within another view? so I will not elaborate.
Persistent requests
Persistent http requests (talking about python requests rather than django.http.request.HttpRequest) are managed through session objects (again not to be confused with django sessions). Avoiding confusion is really difficult:
The Session object allows you to persist certain parameters across
requests. It also persists cookies across all requests made from the
Session instance, and will use urllib3's connection pooling. So if
you're making several requests to the same host, the underlying TCP
connection will be reused, which can result in a significant
performance increase
Different hits to your django view will probably be from different users so you don't want to same cookie reused for the internal REST call. The other problem is that the python session object cannot be persisted between two different hit to the django view. Sockets cannot generally be serialized, a requirement for chucking them into memcached or redis.
If you still want to persist with internal REST
I think #julian 's answer shows how to avoid passing the django request instance as a parameter.
If you want to avoid passing the request to wrap_internal_api_call, all you need to do is do a bit more work on the end of the TemplateView where you call the api wrapper. Note that your original wrapper is doing a lot of cookies if cookies else request.COOKIES. You can factor that out to the calling site. Rewrite your api wrapper as follows:
def wrap_internal_api_call(referer, requests_api, uri, data=None, params=None, cookies, is_json=False, files=None):
headers = {'referer': referer}
logger.debug('Request API: %s calling URL: %s', requests_api, uri)
logger.debug('Referer header sent with requests: %s', referer)
csrf_token = cookies.get('csrftoken', None)
if csrf_token:
headers['X-CSRFToken'] = csrf_token
if data:
if is_json:
return requests_api(uri, json=data, params=params, cookies=cookies, headers=headers)
elif not files:
return requests_api(uri, data=data, params=params, cookies=cookies, headers=headers)
else:
return requests_api(uri, data=data, files=files, params=params, cookies=cookies, headers=headers)
else:
return requests_api(uri, params=params, cookies=cookies, headers=headers)
Now, at the place of invocation, instead of
wrap_internal_api_call(request, requests_api, uri, data, params, cookies, is_json, files)
do:
cookies_param = cookies or request.COOKIES
referer_param = request.META.get['HTTP_REFERER']
wrap_internal_api_call(referer_param, requests_api, uri, data, params, cookies_param, is_json, files)
Now you are not passing the request object to the wrapper anymore. This saves a little bit of time because you don't test cookies over and over, but otherwise it doesn't make a difference for performance. In fact, you could achieve the same slight performance gain just by doing the cookies or request.COOKIES once inside the api wrapper.
Networking is always the tightest bottleneck in any application. So if these internal APIs are on the same machine as your TemplateView, your best bet for performance is to avoid doing an API call.
Basically I want to get rid of that request parameter (1st param), because then to call it I've to keep passing request object from TemplateViews to internal services.
To pass function args without explicitly passing them into function calls you can use decorators to wrap your functions and automatically inject your arguments. Using this with a global variable and some django middleware for registering the request before it gets to your view will solve your problem. See below for an abstracted and simplified version of what I mean.
request_decorators.py
REQUEST = None
def request_extractor(func):
def extractor(cls, request, *args, **kwargs):
global REQUEST
REQUEST = request # this part registers request arg to global
return func(cls, request, *args, **kwargs)
return extractor
def request_injector(func):
def injector(*args, **kwargs):
global REQUEST
request = REQUEST
if len(args) > 0 and callable(args[0]): # to make it work with class methods
return func(args[0], request, args[1:], **kwargs) # class method
return func(request, *args, **kwargs) # function
return injector
extract_request_middleware.py
See the django docs for info on setting up middleware
from request_decorators import request_extractor
class ExtractRequest:
#request_extractor
def process_request(self, request):
return None
internal_function.py
from request_decorators import request_injector
#request_injector
def internal_function(request):
return request
your_view.py
from internal_function import internal_function
def view_with_request(request):
return internal_function() # here we don't need to pass in the request arg.
def run_test():
request = "a request!"
ExtractRequest().process_request(request)
response = view_with_request(request)
return response
if __name__ == '__main__':
assert run_test() == "a request!"
When Falcon(-Framework) could not find a route for a specific request, 404 is returned. How can I override this default handler? I want to extend the handler with a custom response.
The default handler when no resource matches is the path_not_found responder:
But as you can see in the _get_responder method of falcon API, it can't be override without some monkey patching.
As far as I can see, there are two different ways to use a custom handler:
Subclass the API class, and overwrite the _get_responder method so it calls your custom handler
Use a default route that matches any route if none of the application ones are matched. You probably prefer to use a sink instead of a route, so you capture any HTTP method (GET, POST...) with the same function.
I would recommend the second option, as it looks much neater.
Your code would look like:
import falcon
class HomeResource:
def on_get(self, req, resp):
resp.body = 'Hello world'
def handle_404(req, resp):
resp.status = falcon.HTTP_404
resp.body = 'Not found'
application = falcon.API()
application.add_route('/', HomeResource())
# any other route should be placed before the handle_404 one
application.add_sink(handle_404, '')
There is a better solution here.
def custom_response_handler(req, resp, ex, params):
resp.status = falcon.HTTP_404
resp.text = "custom text response"
app = falcon.App()
app.add_error_handler(HTTPRouteNotFound, custom_response_handler)