Append a new Http header using django request.META - django

i am using django-rest framework and i am able to get and set the custom headers using the below META information,
class log_middleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self,request):
# set thread local values
# This will execute before every request
correlation_id = request.META['HTTP_X_REQUEST_ID'] if 'HTTP_X_REQUEST_ID' in request.META else str(uuid.uuid4())
request.META['HTTP_X_REQUEST_ID'] = correlation_id
#logger.debug("Entered service")
response = self.get_response(request)
response['HTTP_X_REQUEST_ID'] = correlation_id
#logger.debug("Processed response")
return response
Now in my views.py i am able to get this header as request.META['HTTP_X_REQUEST_ID']. and it is available in the response header
But when I try to log the http header values in uwsgi using the below config,it has '-' empty value field. Because uwsgi has only actual request headers in %var.XXX variable and response headers goes to %headers and it shows only count and the actual values.
Issue: https://github.com/unbit/uwsgi/issues/1407
So Is there any way in django to append the data in actual request header instead of response header?
[uwsgi]
master = 1
memory-report = true
module = my_service.wsgi
http = 0.0.0.0:8080
max-requests = 50
processes = 16
log-format = { "ctime": "%(ctime)", "addr": "%(addr)", "method": "%(method)", "uri": "%(uri)", "correlation_id": "%(var.HTTP_X_REQUEST_ID)" }
But the same thing works if i set the header HTTP_X_REQUEST while sending the request itself from the rest client utils.

If you need middleware, you can use this:
middlewares.py:
def add_header_middleware(get_response):
def middleware(request):
request.META['hello'] = 'world'
response = get_response(request)
response['world'] = 'hello'
return response
return middleware
views.py:
#api_view(['GET'])
def sample_view(request):
return Response(request.META['hello'])
settings.py:
MIDDLEWARE = [
# ...
'your_app.middlewares.add_header_middleware'
]

Related

Django freezes when reading data from request.body

Let's say we have a simple Django view:
def my_view(request):
content = request.body
# some actions with content varible
response = HttpResponse('<h1>It work!</h1>')
And a simple api client, let's say based on the requests library, sending malformed Django view data:
headers = dict()
headers['Accept'] = '*/*'
headers['Content-Length'] = '13409'
headers['Content-Type'] = 'application/x-compressed'
headers['Expect'] = '100-continue'
headers['Host'] = '127.0.0.1:8000'
headers['User-Agent'] = 'Api client'
headers['content-encoding'] = 'gzip'
url = 'http://127.0.0.1:8000/api'
request_body = ''
r = requests.post(
url,
data=request_body,
headers=headers
)
As you can see, request_body contains an empty string, but the Content-Length header stores the value 13409. When such a request arrives, Django hangs on the line reading request.body. No exceptions occur. How to solve this problem? I cannot influence the client, so the only thing I can do is rewrite the Django view. Django version 3.2.15 is used.

Data in request.body can't be found by request.data - Django Rest Framework

I'm writing a django application. I am trying to call my django rest framework from outside, and expecting an answer.
I use requests to send some data to a function in the DRF like this:
j=[i.json() for i in AttachmentType.objects.annotate(text_len=Length('terms')).filter(text_len__gt=1)]
j = json.dumps(j)
url = settings.WEBSERVICE_URL + '/api/v1/inference'
headers = {
'Content-Disposition': f'attachment; filename={file_name}',
'callback': 'http://localhost',
'type':j,
'x-api-key': settings.WEBSERVICE_API_KEY
}
data = {
'type':j
}
files = {
'file':file
}
response = requests.post(
url,
headers=headers,
files=files,
json=data,
)
In the DRF, i use the request object to get the data.
class InferenceView(APIView):
"""
From a pdf file, extract infos and return it
"""
permission_classes = [HasAPIKey]
def post(self, request):
print("REQUEST FILE",request.FILES)
print("REQUEST DATA",request.data)
callback = request.headers.get('callback', None)
# check correctness of callback
msg, ok = check_callback(callback)
if not ok: # if not ok return bad request
return build_json_response(msg, 400)
# get zip file
zip_file = request.FILES.get('file', None)
parsed = json.loads(request.data.get('type', None).replace("'","\""))
The problem is that the data in the DRF are not received correctly. Whatever I send from the requests.post is not received.
I am sending a file and a JSON together. The file somehow is received, but other data are not.
If I try to do something like
request.data.update({"type":j})
in the DRF, the JSON is correctly added to the data, so it is not a problem with the JSON I'm trying to send itself.
Another thing, request.body shows that the JSON is somehow present in the body, but request.data can't find it.
I don't want to use request.body directly because I can't understand why it is present in the body but not visible with request.data.
In this line
response = requests.post(
url,
headers=headers,
files=files,
json=data,
)
replace json=data with data=data
like this:
response = requests.post(
url,
headers=headers,
files=files,
data=data,
)

PyTest: How to add X-BULK-OPERATION with HTTP_AUTHORIZATION for session credentials (headers)

I'm writing test for Django Rest Framework with PyTest. There was a problem with headers X-BULK-OPERATION because I can't add X-BULK-OPERATION with HTTP_AUTHORIZATION on header.
my code
#pytest.fixture(scope="module")
def session(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
user = User.objects.get(email=TEST_EMAIL)
user.set_password(TEST_EMAIL)
user.save(update_fields=["password"])
client = APIClient()
url = reverse("login_sys:login")
response = client.post(url, {
"email": TEST_EMAIL,
"password": TEST_EMAIL
})
client.credentials(**{"X-CSRFToken": response.cookies['csrftoken'].value})
yield client
client.logout()
#pytest.fixture(scope='module')
def choose_crm(session):
crm = Crm.objects.get()
url = reverse("crm:set-jwt", kwargs={"pk": crm.id})
response = session.get(url)
session.credentials(HTTP_AUTHORIZATION=response.data.get("token", None),
**{"X-BULK-OPERATION": True})
yield session
session.logout()
error
{'detail': "Header 'X-BULK-OPERATION' should be provided for bulk operation."}
Debug:print request, as you can see I have Bulk-Operation in header but still gives the error no BULK-OPERATION in header
'HTTP_AUTHORIZATION': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJjcm1fdXNlciI6MSwiY3JtX2lkIjoxLCJncm91cF9pZCI6MSwiZGVhbF9wZXJtIjp7ImNyZWF0ZSI6MSwiZGlzcGxheSI6MSwiZWRpdCI6MSwiZGVsZXRlIjoxLCJleHBvcnQiOjF9LCJjb250YWN0X3Blcm0iOnsiY3JlYXRlIjoxLCJkaXNwbGF5IjoxLCJlZGl0IjoxLCJkZWxldGUiOjEsImV4cG9ydCI6MX0sImxlYWRfcGVybSI6eyJjcmVhdGUiOjEsImRpc3BsYXkiOjEsImVkaXQiOjEsImRlbGV0ZSI6MSwiZXhwb3J0IjoxfSwidGFza19wZXJtIjp7ImNyZWF0ZSI6MSwiZGlzcGxheSI6MSwiZWRpdCI6MSwiZGVsZXRlIjoxLCJleHBvcnQiOjF9fQ.Wrd89a8jpRnNu1XhNsOKFAkNQR6v_Y_X0nKySocMtLM', 'HTTP_X-BULK-OPERATION': True}

django concurrency request two url, each request header is wrong to each url

concurrency to request django app with two url, each url request header I give one id, like urla id=1, urlb id=2, but when i get from django view from request header, I get urla's id from request header is 2 ...so confused...post body is ok
client code
#coding=utf-8
import threadpool
import requests
HOST = 'http://127.0.0.1:8000'
urls = ['/health', '/x2']
headers = {'Content-Type': 'application/json', 'HOST': 'xx.com'}
pool = threadpool.ThreadPool(5)
def request_api(n):
url = urls[n%2]
print('---------------------start request url {}'.format(url))
headers.update(url=url)
body = dict(
url=url
)
r = requests.post(url=HOST + url, headers=headers, json=body)
print(r.status_code)
reqs = threadpool.makeRequests(request_api, range(5))
[pool.putRequest(req) for req in reqs]
pool.wait()
print('over')
server code
# coding=utf-8
from rest_framework.views import APIView
from django.http.response import HttpResponse
class ApiHealthCheck(APIView):
def post(self, request, request_id='', **kwargs):
print('api_health_check: {},{},{}'.format(request.path, request.META.get('HTTP_URL'), request.data))
return HttpResponse("ok")
class ApiHealthCheck2(APIView):
def post(self, request, request_id='', **kwargs):
print('api_health_check2: {},{},{}'.format(request.path, request.META.get('HTTP_URL'), request.data))
return HttpResponse("ok")
server print
api_health_check: /health,/health,{u'url': u'/health'}
api_health_check2: /x2,/health,{u'url': u'/x2'}
api_health_check: /health,/health,{u'url': u'/health'}
api_health_check2: /x2,/x2,{u'url': u'/x2'}
api_health_check: /health,/health,{u'url': u'/health'}
attention one of them have wrong,path not match META's content,body is ok
I am sorry, I found the problem, not django problem, it is my fault.
headers = {'Content-Type': 'application/json', 'HOST': 'xx.com'}
headers is a global variable, concurrent request can concurrent modify this header, so request django, django get wrong header

Prevent Django from blocking while proxying an HTTP request

I'm working on a Django site that allows connecting to devices in restricted networks through a cloud service. The devices connect to a cloud server through a VPN or SSH tunnel and clients connect to a virtual host via HTTP. The Django part is required for managing complex organization-role-access-user relationships.
Currently I'm doing access control in a custom Django middleware module that parses HTTP_HOST, does authentication, gets the page and forwards it to the original requester. The problem is that while a request is going on, Django is not handling any other requests. Celery does not solve the problem because this isn't really a background task. Clients are served through a single address and port, making firewall rules unsuitable for this task.
The relevant code is below:
class NodeProxyMiddleware:
def process_request(self, request, *args, **kwargs):
if not 'HTTP_HOST' in request.META:
return None
hardware_id = match_hwid.match(request.META["HTTP_HOST"])
if not hardware_id:
return None
kwargs["hardware_id"] = hardware_id.group("hwid")
if not authenticate(request, *args, **kwargs):
return HttpResponseForbidden("No access")
return proxy_request(request, *args, **kwargs)
#csrf_exempt
def proxy_request(request, *args, **kwargs):
# Get the port of target Node
hardware_id = kwargs.get("hardware_id", "")
try:
port = Node.objects.filter(hardware_id=hardware_id)[0].port
except IndexError: # Node with given hwid was not found
raise Http404
# We have to convert request.META back to original form manually
headers = convert_headers(request) # HTTP_FOO_BAR to Foo-Bar
headers["connection"] = "close"
connection = httplib2.Http(timeout=5)
url = "http://127.0.0.1:%d%s" % (port, request.META['PATH_INFO'])
method = request.method
# GET -- url ?d=a&t=a has to be urlencoded
if method == "GET":
data = None
if request.GET:
url += "?" + request.GET.urlencode()
# POST -- body has to be urlencoded
elif method == "POST":
data = request.POST.urlencode()
headers["content-type"] = "application/x-www-form-urlencoded"
try:
response, content = connection.request(
url, method, data, headers=headers)
except Exception as e:
print e
return HttpResponse(content=e, status=503)
django_response = HttpResponse(
content=content,
status=int(response["status"]),
mimetype=response["content-type"],
)
# Strip hop-by-hop headers -- See RFC2616 semantically transparent
# proxying. Also, WSGI forbids passing such headers back to it.
hop_by_hop_headers = [
"connection",
"keep-alive",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailers",
"transfer-encoding",
"upgrade",
]
for key, value in response.iteritems():
if key.lower() in hop_by_hop_headers:
continue
django_response[key] = value
return django_response
Is it possible to do this kind of proxying at all in Django by tweaking the code above or other settings? The software stack I'm running on is Nginx + uWSGI + Django 1.6. The uWSGI configuration is:
[uwsgi]
chdir = /home/foo/production/
file = /home/foo/production/wsgi.py
home = /home/foo/virtualenv
master = true
processes = 8
socket = /var/nginx/foo.socket
chmod-socket = 666
vacuum = true
daemonize = /home/foo/production/uwsgi.log