Response content shorter than Content-Length - python

I'm doing a FastAPI app, with a function that authenticates to a CouchDB instance. In order to request Couchdb, I use the (yet unmaintained) library python-couchdb.
Here is the relevant portion of code that illustrates my issue:
from fastapi import FastAPI
from couchdb import http
resource = http.Resource(url, http.Session())
from couchdb import Unauthorized
from pydantic import BaseModel
app = FastAPI()
class RegisteredUser(BaseModel):
email: str
password: str
#app.post("/login")
async def log_user(user: RegisteredUser):
# some email format verifications here
# ...
try:
status, headers, _ = resource.post_json('_session', {
'name': user.email,
'password': user.password,
})
except Exception as e:
if isinstance(e, Unauthorized):
return 403
else:
return 500
# tests
print(headers)
The headers look like:
Cache-Control: must-revalidate
Content-Length: 54
Content-Type: application/json
Date: Sat, 08 Aug 2020 19:19:49 GMT
Server: CouchDB/3.1.0 (Erlang OTP/22)
Set-Cookie: AuthSession=am9zZWJvdmVAam9zZWJvdmUuY29tOjVGMkVGQUQ2Op-UUD22VvdxYzbMNp92e30Er_z0; Version=1; Expires=Sat, 08-Aug-2020 20:59:50 GMT; Max-Age=6000; Path=/; HttpOnly
At this point (if no error raised), I'd like to send back to the (browser) client the cookie that CouchDb provides. Something like:
...
# tests
print(headers)
if status == 200 and 'Set-Cookie' in headers:
return JSONResponse(content=True, headers=headers)
else:
return status
I'm not used to sessions-cookie and I'm not sure if I should send back the full headers or just the headers['Set-Cookie'] part
Whatever I do, I end up with the same error message
RuntimeError: Response content shorter than Content-Length
Would you mine to explain to me what the error is saying, and how I can solve my case? Ty!
Found this SO thread but no clue neither FastAPI middleware peeking into responses
Here is one solution:
if status == 200 and 'Set-Cookie' in headers:
return JSONResponse(content=True, headers={
'Set-Cookie' : headers['Set-Cookie']
})
else:
return status
Ty #HernánAlarcón

Related

Python HTTP Request Returns 404 or Bytes

I'm trying to use a Python script to call the API detailed in the link below:
https://developer.wmata.com/docs/services/gtfs/operations/5cdc51ea7a6be320cab064fe?
When I use the code below, it always returns a 404 error:
import requests
import json
def _url(path):
return "http://api.wmata.com" + path
def pull_data():
return requests.get(_url("/gtfs/bus-gtfsrt-tripupdates.pb"), params=params)
def jprint(obj):
# create a formatted string of the Python JSON object
text = json.dumps(obj, sort_keys=True, indent=4)
print(text)
# authenticate with your api key
params = {
"apiKey": "mykey",
}
response = pull_data()
print(response)
jprint(response.json())
I have also tried using the python code provided in the link, but it returns meaningless response content as shown below. Any attempts to decode the content have been unsuccessful.
Request-Context: appId=cid-v1:2833aead-1a1f-4ffd-874e-ef3a5ceb1de8
Cache-Control: public, must-revalidate, max-age=5
Date: Thu, 11 Feb 2021 22:05:31 GMT
ETag: 0x8D8CED90CC8419C
Content-Length: 625753
Content-MD5: fspEFl7LJ8QbZPgf677WqQ==
Content-Type: application/octet-stream
Expires: Thu, 11 Feb 2021 22:05:37 GMT
Last-Modified: Thu, 11 Feb 2021 22:04:49 GMT
1.0�Ԗ��
1818410080�
181841008020210211*52!�Ԗ�"19032("�Ԗ�"18173(#�Ԗ�"7779($�Ֆ�"18174(%�Ֆ�"7909(&�Ֆ�"7986('�Ֆ�"8039((�Ֆ�"8130()�֖�"8276(+�֖�"8313(,�ז�"8403(-�ז�"8452(.�ז�"8520(/�ؖ�"8604(1�ؖ�"8676(
7070 �Ӗ�(����������
1814174080�
181417408020210211*P129�Ԗ�"2373(:�Ԗ�"2387(;�Ԗ�"17296(=�Ֆ�"17212(>�֖�"2444(?�֖�"2493(#�֖�"2607(A�֖�"14633(B�֖�"2784(C�ז�"2832(D�ז�"2843(E�ז�"2848(F�ז�"2875(G�ؖ�"2945(H�ؖ�"2987(I�ؖ�"21946(K�ٖ�"14636(L�ٖ�"3122(M�ٖ�"3227(N�ٖ�"3308(O�ٖ�"3411(P�ٖ�"3500(Q�ٖ�"3539(R�ٖ�"14637(S�ږ�"3685(T�ږ�"15195(U�ږ�"15196(V�ۖ�"4243(W�ۖ�"4443(X�ۖ�"4517(Y�ۖ�"4631([�ܖ�"11962(
8002 �Ӗ�(/�
1825989080�
182598908020210211*7Y�Ӗ�"2158(�Ԗ�"2215(�Ԗ�"2259(�Ԗ�"2292(�Ԗ�"2299(�Ֆ�"18701(�Ֆ�"2310( �Ֆ�"2245(!�Ֆ�"2174("�Ֆ�"1987(#�֖�"1937(%�֖�"1864(
3191 �Ӗ�(��
1819988080�
Any guidance or direction would be greatly appreciated!
Change as pull_data function as follows:
def pull_data():
return requests.get(_url("/gtfs/bus-gtfsrt-tripupdates.pb"), headers=headers)
Then rename params module global variable to headers .
headers = {"apiKey": "mykey"}
WMATA looks for a apiKey in the headers, not in the query params.
Update: I noticed they use api_key for some samples, and apiKey for another ones. For example see:
https://developer.wmata.com/docs/services/gtfs/operations/5cdc51ea7a6be320cab064fe
Update 2: Notice the content type in the response headers :
print(response.headers['content-type'])
# application/octet-stream
it is not a JSON. You can get contents as follows:
print(response.content)
Worked example:
import requests
API_URL = 'https://api.wmata.com'
def _prepare_url(path):
return f'{API_URL}/{path.lstrip("/")}'
def pull_data(**options):
url = _prepare_url('/gtfs/bus-gtfsrt-tripupdates.pb')
return requests.get(url, **options)
response = pull_data(headers={'api_key': 'secret'})
print(response.content)
Try changing your URL so that it is using https instead of http. The documentation that you have linked at Bus RT Trip Updates seems to indicate that https is required.
Change this:
def _url(path):
return "http://api.wmata.com" + path
to make it this:
def _url(path):
return "https://api.wmata.com" + path

flask json output look like different when Apache/uWSGI or Werkzeug will be used

Json output look like different when Apache/uWSGI and Werkzeug will be used.
Where's the trick?
see example:
Werkzeug:
curl -k -iL http://127.0.0.1:5000/test/
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 32
Content-Type: application/json
Server: Werkzeug/0.14.1 Python/3.6.6
Date: Tue, 30 Oct 2018 18:13:37 GMT
{
"data": "Hello, Api!"
}
The same code powered by Apache/uWSGI:
curl -k -iL https://flask.xxxxx.local/test/
HTTP/1.1 200 OK
Date: Tue, 30 Oct 2018 18:13:39 GMT
Server: Apache
Content-Type: application/json
Content-Length: 27
{"data":"Hello, Api!"}
i'am await for:
{
"data": "Hello, Api!"
}
The piece of code:
from flask import Flask, jsonify, abort, make_response, render_template, g
from flask_restful import Api, Resource, reqparse, fields, marshal
...
#app.route('/test/')
def get_resource():
headers = {'Content-Type': 'application/json'}
content = { 'data': 'Hello, Api'}
return make_response(jsonify(content),200,headers)
...
Flask==1.0.2
Flask-RESTful==0.3.6
uWSGI==2.0.17.1
Werkzeug==0.14.1
thx
The reason for the difference is the Flask config setting JSONIFY_PRETTYPRINT_REGULAR
This setting has default value False but will always be True when running in debug mode.
So when you run under uWsgi/Apache, the default setting of False is used, giving no indentation/newlines. When you run under the Werkzeug test server in debug mode, Flask sets the value to True.
To get indents and newlines under uwsgi, do the following in your wsgi script:
app = Flask(...)
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True
See documentation: http://flask.pocoo.org/docs/1.0/config/#JSONIFY_PRETTYPRINT_REGULAR
Also, you do not need the make_response() call. You can simply do:
#app.route('/test/')
def get_resource():
content = { 'data': 'Hello, Api'}
return jsonify(content)
... and Flask will set the correct content-type.

URLError doesn't return json body

I have built a REST interface. On '400 Bad Request' it returns a json body with specific information about the error.
(Pdb) error.code
400
Python correctly throws a URLError with these headers
(Pdb) print(error.headers)
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Sat, 20 Aug 2016 13:01:05 GMT
Connection: close
Content-Length: 236
There is a content of 236 char, but I cannot find a way to read the body.
I can see the extra information using DHC chrome plugin
{
"error_code": "00000001",
"error_message": "The json data is not in the correct json format.\r\nThe json data is not in the correct json format.\r\n'Execution Start Time' must not be empty.\r\n'Execution End Time' must not be empty.\r\n"
}
However, I cannot find a way in Python to read the body
Here are some of the things I have tried and what was returned.
(Pdb) len(error.read())
0
error.read().decode('utf-8', 'ignore')
''
(Pdb) error.readline()
b''
I found that this works the first time it is called, but does not work if called again.
error.read().decode('utf-8')

Enabling CORS in Python with Tastypie update (PUT)

There are several questions on SO related to this topic, but none that I have seen solve my issue.
I have an endpoint in my Django/Tastypie API that accepts a PUT in order to update the database. This works great when testing in localhost:8000, however, my production database is located in a different domain, so I need to enable CORS to get this PUT call to update the database.
I have found the tutorial here that gives an example of how to do this, however, when I execute the cURL command:
curl -X PUT --dump-header - -H "Content-Type: application/json" -H "Authorization: ApiKey api:MYAPIKEY" -d "{\"mykey\": \"my_value\", \"resource_uri\": \"/api/v1/mytable/362/\"}" my.domain.com/api/v1/mytable/362/
I am still receiving 401 UNAUTHORIZED for my calls (header dump below):
HTTP/1.1 401 UNAUTHORIZED
Date: Mon, 22 Sep 2014 16:08:34 GMT
Server: Apache/2.4.7 (Ubuntu)
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type,Authorization
X-Frame-Options: SAMEORIGIN
Content-Length: 0
Content-Type: text/html; charset=utf-8
My CoresResource superclass code:
class CorsResource(ModelResource):
""" adds CORS headers for cross-domain requests """
def patch_response(self, response):
allowed_headers = ['Content-Type', 'Authorization']
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Headers'] = ','.join(allowed_headers)
return response
def dispatch(self, *args, **kwargs):
""" calls super and patches resonse headers
or
catches ImmediateHttpResponse, patches headers and re-raises
"""
try:
response = super(CorsResource, self).dispatch(*args, **kwargs)
return self.patch_response(response)
except ImmediateHttpResponse, e:
response = self.patch_response(e.response)
# re-raise - we could return a response but then anthing wrapping
# this and expecting an exception would be confused
raise ImmediateHttpResponse(response)
def method_check(self, request, allowed=None):
""" Handle OPTIONS requests """
if request.method.upper() == 'OPTIONS':
if allowed is None:
allowed = []
allows = ','.join([s.upper() for s in allowed])
response = HttpResponse(allows)
response['Allow'] = allows
raise ImmediateHttpResponse(response=response)
return super(CorsResource, self).method_check(request, allowed)
My endpoint code:
class DataModelResource(CorsResource):
data = fields.OneToOneField(DataModelResource, "data", full=True)
class Meta:
allowed_methods = ['get', 'put']
authentication = ApiKeyAuthentication()
authorization = Authorization()
queryset = Data.objects.all()
resource_name = 'mytable'
Does anyone see any reason why making a PUT from a cross-domain should be failing with this code???
I'm at a complete loss here.

Abort a request after checking response headers

I have a script that requests a URL via urllib.request's urlopen and then gets it's info().
I don't want to proceed with the request after I've got these headers so I'm currently just leaving it as it is and forgetting about it, but this seems like I'm leaving the connection open and perhaps the server is sending more that just gets ignored.
How can I abort the request properly?
#!/usr/bin/python3
import urllib.request
response = urllib.request.urlopen('http://google.co.uk')
headers = dict(response.info())
print(headers)
# now finished with response, abort???
# ... more stuff
I think what you want is a HEAD request. Something like
>>> import httplib
>>> c = httplib.HTTPConnection("www.google.co.uk")
>>> c.request("HEAD", "/index.html")
>>> r = c.getresponse()
>>> r.getheaders()
[('x-xss-protection', '1; mode=block'), ('transfer-encoding', 'chunked'), ('set-cookie', 'PREF=ID=7867b0a5641d5f7b:FF=0:TM=1363882090:LM=1363882090:S=EXLl2JgBqzMKODcq; expires=Sat, 21-Mar-2015 16:08:10 GMT; path=/; domain=.google.co.uk, NID=67=qElAph6eqHyYKbh995ivP4B-21YRDRED4-uRXx0AvC3vLpv0SF1LkdsI2k6Hg1IhsatrVVqWf2slcMCaQsAZwZ89YfU0F1iPVBdt9PC2FItff31oRJ3gvhJVTQLa_RAt; expires=Fri, 20-Sep-2013 16:08:10 GMT; path=/; domain=.google.co.uk; HttpOnly'), ('expires', '-1'), ('server', 'gws'), ('cache-control', 'private, max-age=0'), ('date', 'Thu, 21 Mar 2013 16:08:10 GMT'), ('p3p', 'CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."'), ('content-type', 'text/html; charset=ISO-8859-1'), ('x-frame-options', 'SAMEORIGIN')]
>>>
From w3.org
The HEAD method is identical to GET except that the server MUST NOT
return a message-body in the response. The metainformation contained
in the HTTP headers in response to a HEAD request SHOULD be identical
to the information sent in response to a GET request. This method can
be used for obtaining metainformation about the entity implied by the
request without transferring the entity-body itself. This method is
often used for testing hypertext links for validity, accessibility,
and recent modification.
The response to a HEAD request MAY be cacheable in the sense that the
information contained in the response MAY be used to update a
previously cached entity from that resource. If the new field values
indicate that the cached entity differs from the current entity (as
would be indicated by a change in Content-Length, Content-MD5, ETag or
Last-Modified), then the cache MUST treat the cache entry as stale.

Categories

Resources