How to determine if client expects a JSON response - python

In my Werkzeug application I am intercepting all error responses and trying to respond with a JSON response if the client expects JSON or return the usual HTML page with 404 or 500:
def handle_error_response(self, environ, start_response, exc):
if ('application/json' in environ.get('CONTENT_TYPE', '')
and exc.get_response().content_type != 'application/json'):
start_response('%s %s' % (exc.code, exc.name),
(('Content-Type', 'application/json'), ))
return (json.dumps({"success": False, "error": exc.description}, ensure_ascii=False), )
# go the regular path
...
In this solution I am relying on Content-Type header containing string'application/json'.
However this doesn't look like a correct solution, because Wikipedia says:
Content-Type The MIME type of the body of the request (used with POST and PUT requests)
Is it a good strategy to check if 'text/html' is inside header Accept and then return HTML response otherwise return JSON response?
Any other more robust solutions?
When Chrome requests an HTML page header
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
is sent, when Ember makes an API request
Accept: application/json, text/javascript, */*; q=0.01
is sent.
Maybe X-Requested-With: XMLHttpRequest should be taken into account?

You should probably add AcceptMixin to your request object.
Once you do this, you can use the accept_mimetypes.accept_json, accept_mimetypes.accept_html and accept_mimetypes.accept_xhtml attributes on your request object. The response's default content type really depends only on what your application is; just try and imagine which would result in less confusion.

This works for us:
if ('text/html' not in environ.get('HTTP_ACCEPT', '')
and 'application/json' not in response.content_type):
# the user agent didn't explicitely request html, so we return json
... # make the JSON response
I.e. if the client expects html -- do not return json. Otherwise return
json response, if the response isn't already json.

Related

How to get Content-Type of URL using urllib3

I need to get the content-type of an internet resource not a local file. How can I get the MIME type from a resource behind an URL.
I have tried this :
http = urllib3.PoolManager()
response = http.request("GET",url)
print(response.headers)
How can I get the Content-Type, can be done using urllib3 and how or if not what is the other way?
You're almost there:
>>> response.headers['Content-Type']
'text/html; charset=UTF-8'
If the header is set correctly, you should be able to parse this to determine the MIME type.

OAuth1 Authentication of Rest API

I need to do a post request according to the scenario given below. I only have consumers_key and consumer_secret. I don't know from where to get all the other fields. and also do i need to send the Authorization request including keys in the header?
A request token for the application must be obtained using the consumer key from the above response.
Request
POST /public/v1/oauth1/request_token
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Authorization: OAuth
oauth_consumer_key="btgd2cg2bm3lbjqsfv150fj9q8",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1467792887",
oauth_nonce="9fd50a6f-40e0-41be-8809-34aa832b688e",
oauth_callback="oob",
oauth_signature="hcE6Q3boTytkHmM72xegCP1Y3W4%3D"
I am doing something like this
import requests
url="https://api.discovergy.com"
request_token_url=url+'/public/oauth1/request_token'
head={'Accept':'text/html, image/gif, image/jpeg, *;q=.2, '/';q=.2',
'Authorization':'OAuth'}
data= dict(oauth_consumer_key=client_key, oauth_signature_method='HMAC-SHA1', oauth_timestamp='1467792887', oauth_nonce='9fd50a6f-40e0-41be-8809-34aa832b688e', oauth_callback='oob', oauth_signature='hcE6Q3boTytkHmM72xegCP1Y3W4%3D')
r=request.post(url=request_token_url, data=data, headers=head)
print(r.content)
It gives 400 status..
Oauth1 has a different authorisation building module in python for Requests. See that documentation and the help files for examples of use.
The 400 RC you get is because you try to POST to that endpoint and the method is not supported. Most likely you can only GET from there.
The Discovergy API docs are at https://api.discovergy.com/docs/. I've setup a small PHP Discovergy API client at https://github.com/andig/discovergy. It demonstrates the entire process. For the /request_token POST (it is a POST) make sure to use the consumer key obtains from /consumer_token, not the credentials provided by Discovergy.
Update I've also noticed that according to your example you seem to pass the parameters as POST body. These need to be in the header as in the first code block you've shown.
Update 2 Also make sure to accept the right content types, at least application/json, maybe plus text/plain
 Recent versions does not have problem to request authentication using OAuth1 from request_oauthlib
from requests_oauthlib import OAuth1, OAuth1Session
OAUTH = OAuth1(API_KEY, API_KEY_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
HEADERS = {'Content-Type': 'application/json', 'Accept': '*/*'}
requests.method(url, data=json.dumps(payload), headers=HEADERS, auth=OAUTH)

How to sent a POST(request payload) with text/plain content type using Python-Requests?

The case is that I met a post request, the Content-Type is text/plain, the request data is not Form Data, but is Request Payload, I would like to know how can I post data correctly using python? especially in requests.
Request payload is a dict or text?
Try to send request without specifying headers like:
payload = {"callcount": 1,
"c0-scriptname": "newLoginDwr",
...
"xml": true}
requests.post(url, data=payload)

What is 'data' in python's urrlib2.Request?

I am new to Web requests . I saw a piece of code that does HTML PDF conversion like this :
headers = {'content-type': 'text/html', 'accept': 'application/pdf'}
urllib2.Request(url, data=html, headers=headers) # html is a string and it works fine
The url does the pdf conversion and it needs html as input .
Why is 'data' keyword argument so important ? Why can't be clubbed as just another param ?
I would have thought that urllib2.Request(url, params = {'data': html}) where data is just one of the key value pairs . And server does it processing accordingly .
Why do we need 'data' as something seperate from other parameters ?
Is it because we specify 'content-type' in the header and it bound to the data keyword as a convention ?
I am writing an API that makes everything is request like a keyword arguement , for a simple library purpose . So I would like to know when is data required and when is not as well . I do understand params but 'data' is that mandatory or only for post requests where you have a specific content-type to sent to server? What if I have multiple content types now ?
When the data attribute is provided, the request is sent as POST. It is not mandatory, it can be None, if it is none (or not provided) it is sent as GET. This is all described here: http://docs.python.org/2/library/urllib2.html#urllib2.Request
Does request also have the same convention ? I ask so because in
request we have request.get . So request.get(url, data=something)
would be converted to a POST ? And how is this data seen at the server
side any idea ?
request.get(url, data="test") would be sent as a GET request with "test" as the body of the request. This is the raw HTTP request:
GET /headers HTTP/1.1\r\nHost: httpbin.org\r\nContent-Length: 4\r\nAccept-Encoding: gzip, deflate, compress\r\nAccept: */*\r\nUser-Agent: python-requests/2.2.1 CPython/2.7.5 Windows/7\r\n\r\ntest
Formatted:
GET /headers HTTP/1.1
Host: httpbin.org
Content-Length: 4
Accept-Encoding: gzip, deflate, compress
Accept: */*
User-Agent: python-requests/2.2.1 CPython/2.7.5 Windows/7
test
The server will in most cases just ignore it.

Flask is not getting any POST data from a request

Within our server we've got this piece a code calling a function inside my APP like this:
data = urllib.urlencode( dict(api_key="a-key-goes-here") )
headers = {
"User-Agent" : "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36",
"Accept" : "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,text/png,*/*;q=0.5",
"Accept-Language" : "en-US,en;q=0.8",
"Accept-Charset" : "ISO-8859-1,utf-8",
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
}
request = urllib2.Request(url, data, headers)
response = urllib2.urlopen(request)
code = response.code
message = response.read()
response.close()
I know that this is not using url_for( neither other ways to call a url trough your APP but this has a reason. Our server is just testing that the call goes correctly but the url is expected to be outside our APP and so is the api key.
So, our server handling url looks like this:
#app.route('/loan_performer', methods=["GET", "POST"])
def loan_performer():
if 'api_key' in request.form and request.form['api_key'] == API_KEY:
ret = dict()
# rate1 return a random number between 3.000 and 4.000 and point1 will be 0
ret['rate_one'] = random.randint(3000, 4000)
ret['point_one'] = 0
# rate2 do it between 3.500 and 4.500, point2 being 0.5
ret['rate_two'] = random.randint(3500, 4500)
ret['point_two'] = 0.5
# rate3 between 4.000 and 5.000 with 1.0
ret['rate_three'] = random.randint(4000, 5000)
ret['point_three'] = 1.0
return json.dumps(ret), 200
else:
return u"Your API Key is invalid.", 403
Our error is as the title says:
We are constantly receiving the error "Bad request (GET and HEAD requests may not contain a request body)" Which is a 404 error handled by Passenger WSGI for Apache. But in other words, for a reason request.form is empty and it shouldn't.
Is there anything I'm doing wrong or any other way to POST data from Flask to outside?
If there is needed more info I'm willing to update, just call it.
Edit 1
I switched to use requests like this:
dat = dict( api_key="a-key-goes-here" )
request = requests.post(url, data=dat)
message = request.text
code = request.status_code
And it refuses to work. This is the logger I made in the handling url function:
(08/02/2014 07:07:20 AM) INFO This is message ImmutableMultiDict([]) ImmutableMultiDict([]) and this code 403
request.args - request.form - request.data , all of them are empty, either using urllib or requests.
Update on Edit 1
After removing "GET" from methods suggested by Martin konecny I got this response:
(08/02/2014 08:35:06 AM) INFO This is message <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
and this code 405
But in other words, for a reason request.form is empty and it shouldn't.
I don't think you can infer this conclusion. What appears to be happening is you have a GET request with a POST header.
From the Python docs:
data may be a string specifying additional data to send to the server, or None if no such data is needed. Currently HTTP requests are the only ones that use data; the HTTP request will be a POST instead of a GET when the data parameter is provided. data should be a buffer in the standard application/x-www-form-urlencoded format. The urllib.urlencode() function takes a mapping or sequence of 2-tuples and returns a string in this format.
You need to remove the header
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
when you are sending requests with an empty data structure, or just remove it alltogether (it will be added automatically by urllib when needed)

Categories