logging errors with flask - python

I'm trying to log an error in a decorator function using app.logger.error(''), but it just doesn't work. In addition I cant debug this well and I can only see the response from the http client:
(I'm using nginx+uwsgi+flask)
HTTP/1.1 502 Bad Gateway
Server: nginx
Date: Sun, 12 Aug 2012 15:45:09 GMT
Content-Type: text/html
Content-Length: 14
Connection: keep-alive
Everything works great with out the line: app.logger.error('panic !!!')
def mydecorator():
def decorator(f):
def wrapped_function(*args, **kwargs):
try:
ip = Mytable.query.filter_by(ip=request.remote_addr).first()
except:
app.logger.error('panic !!!')
else:
dootherthing()
resp = make_response(f(*args, **kwargs))
h = resp.headers
h['add-this-header'] = ":)"
return resp
return update_wrapper(wrapped_function, f)
return decorator
It seems that it is out of context or something.

in fact, the decorator wasnt able to detect the app instance out of context, i solve this using current_app:
1st. Import the method: from flask import current_app
2nd. append any app class to current_app: current_app.logger.error('panic !!!')
info # http://flask.pocoo.org/docs/api/#flask.current_app
"Points to the application handling the request. This is useful for
extensions that want to support multiple applications running side by
side. This is powered by the application context and not by the
request context, so you can change the value of this proxy by using
the app_context() method."

Is app defined anywhere in the script that you've posted?
Also, to help with debugging, when testing you should consider using the run() method with debug mode.
app.run(debug=True)

Related

uwsgi: Send http response and continue execution

From the uwsgi documentation:
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"]
Is it possible to respond to http request(close http connection) and continue execution flow(without any usage of threads/queues/external services etc)?
like this:
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
end_response(b"Hello World")
#HTTP connection is closed
#continue execution..
TL;DR - if you're using Django, you can skip to the end of the answer.
Yes, there is a way to do this. You can hook on to a .close() method of the object returned by your application callable (or alternatively, wsgi.file_wrapper returned through the env.
Check PEP333, specifically the server side spec part: https://peps.python.org/pep-0333/#the-server-gateway-side:
result = application(environ, start_response)
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()
As you can see, result.close() will be called at the very end, by which point the data is already sent.
This is, of course, just some reference code, and it does not deal with upstream and terminating the connection before continuing execution. But well-behaved wsgi servers, such as uwsgi and (presumably) gunicorn, do.
They will signal to the upstream that the request has finished sending by closing the socket (or whatever the upstream protocol requires), and then call .close().
If you are using Django, you are already set, because it has request_finished signal. Here's how it works, it hooks to .close() as described above:
https://github.com/django/django/blob/57c7220280db19dc9dda0910b90cf1ceac50c66f/django/http/response.py#L323
Unfortunately, there is no way to continue the code execution after you have returned the response. It would be much easier if you use multithreading but if not you can workaround it in Flask by adding an AJAX call to your HTML response which will send a POST request to one of the server extra route whose handler function will be the execution code you want after returning the response. Here's one of the possible approach using Flask:
myflaskapp.py
from flask import Flask, render_template_string
import time
app = Flask(__name__)
#app.route('/run', methods=['POST'])
def run():
# this is where you put your "continue execution..." code
# below code is used to test if it runs after HTTP connection close
time.sleep(8)
print('Do something')
return ''
#app.route('/')
def index():
return render_template_string('''
Hello World!
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>
$(function() {
$.ajax({
type: "POST",
url: "{{ url_for('run') }}"
});
})
</script>
''')
if __name__ == "__main__":
app.run(host='0.0.0.0')
You can run the server on port 9091 with the command:
uwsgi --http 127.0.0.1:9091 --wsgi-file myflaskapp.py --callable app
To test if it is working or not, you can go to the address localhost:9091. If everything works well, you should see that the page is loaded immediately while the terminal will only print out Do something after 8 seconds have passed, indicating the function run executes after the HTTP connection is closed.

Having trouble calling a get request using Falcon framework

I'm following Falcon tutorial for Python.
Everything worked fine until this part:
The response I'm getting when trying this command http localhost:8000/images is:
HTTP/1.1 500 Internal Server Error
Content-Length: 110
Content-Type: text/plain
Date: Sat, 01 Dec 2018 15:50:26 GMT
Server: waitress
Internal Server Error
The server encountered an unexpected internal server error
(generated by waitress)
I read it's a problem in the code but I can't find it, it's exactly as in the tutorial, app.py file:
import falcon
from images import Resource
api = application = falcon.API()
images = Resource()
api.add_route('/images', images)`
images.py:
import json
import falcon
class Resource(object):
def on_get(self, req, resp):
doc = {
'images': [
{
'href': '/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png'
}
]
}
# Create a JSON representation of the resource
resp.body = json.dumps(doc, ensure_ascii=False)
# The following line can be omitted because 200 is the default
# status returned by the framework, but it is included here to
# illustrate how this may be overridden as needed.
resp.status = falcon.HTTP_200
Also, I have an empty file named __init__.py and all the files are in the same folder, C:\look\look.
P.S.
I tried to add an HTTP requests scratch file (using PyCharm IDE) but there is no option to add that kind of a file (after I press Ctrl + Shift + Alt + Insert). I couldn't find how to fix this anywhere.
I see that the question is pretty old, but I found a solution. Just run the server with command:
waitress-serve --port=8000 --call look.app:get_app
since we get the app from calling the function get_app()

Flask-RESTplus CORS request not adding headers into the response

I have an issue trying to setup CORS for my REST API.
I'm currently using the Flask-Restplus package. Here's what my endpoints look like :
#api_ns.route('/some/endpoint')
#api_ns.response(code=400, description='Bad Request.')
class AEndpointResource(Resource):
#api_ns.param(**api_req_fields.POST_DOC)
#api_ns.expect(POST_REQUIRED_BODY)
#api_ns.marshal_with(code=201,
fields=my_api_models.MyEndpointResponse.get_serializer(),
description=my_api_models.MyEndpointResponse.description)
def post(self) -> Tuple[my_api_models.MyEndpointResponse, int]:
"""
The post body
"""
# Some logic here
return response, 200
If I code a small javascript snippet and I try to launch it in a browser, I will get an error because there's no CORS headers. I'm seeing the Flask-Restplus is already handling the OPTIONS request without me telling him anything. (This makes sense according to this link, mentioning that since Flask 0.6, the OPTIONS requests are handled automatically)
My problem is that even when I try to decorate my endpoint using :
from flask-restplus import cors # <--- Adding this import
...
class AnEndpointResource(Resource):
...
#my_other_decorators
...
#cors.crossdomain(origin='*') # <--- Adding this new decorator on my endpoint
def post(self) -> Tuple[my_api_models.MyEndpointResponse, int]:
...
Nothing changes and I still get the same result as before. I get an HTTP 200 from the OPTIONS request automatically handled as before, but I don't see my new headers (i.e. Access-Control-Allow-Origin) in the response.
Am I missing something ?
Using Flask-CORS, it works:
from flask import Flask, request
from flask_restplus import Resource, Api, fields
from flask_cors import CORS
# configuration
DEBUG = True
# instantiate the app
app = Flask(__name__)
api = Api(app)
app.config.from_object(__name__)
# enable CORS
CORS(app, resources={r'/*': {'origins': '*'}})
When doing local testing on my laptop and in my browser, I was able to solve the cors problem by adding the header in the response.
before: return state, 200
after: return state, 200, {'Access-Control-Allow-Origin': '*'}
I tested and the headers you are looking for are added to the response to the subsequent GET request. The decorator #cors.crossdomain has an option automatic_options which is set to be True by default. This means your OPTIONS request will still be handled automatically.
See this test to check how it should work.
The flask_restplus.cors module is not documented so not sure if you should use it.
I had a CORS problem as well and solved it this way:
from flask import Flask
from flask_restplus import Api
app = Flask('name')
api = Api(app)
// your api code here
#app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
return response
The problem is that flask-restplus only assigns CORS headers to the GET request. When the browser makes an OPTIONS request, this method isn't handled by a method on the Resource class. Then you get a flickering CORS header: one request it works, the next it doesn't, etc
When a 404 is raised, this also skips the CORS decorator.
A workaround for these bugs is using something like:
def exception_to_response(func):
#wraps(func)
def _exc_to_resp_decorator(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
return self.api.handle_error(e)
return _exc_to_resp_decorator
#api.route("/your-endpoint")
class SomeEndpoint(Resource):
#cors.crossdomain(origin='*')
def options(self):
# Make sure the OPTIONS request is also handled for CORS
# The "automatic_options" will handle this, no need to define a return here:
return
#api.expect(my_schema)
#api.response(200, "Success")
#cors.crossdomain(origin='*')
#exception_to_response
def get(self):
return {"json-fields": 123}, 200

POST to Flask from espduino timing out

I'm playing with an esp8266 providing WiFi to an Arduino with this library. I have it set up properly to POST to Pushover as well as requestbin, and between the debug output and the interface from these tools, I can verify that the request data is being POSTed properly, and the esp8266 / arduino unit's response status code properly shows 200.
I wanted to test the reliability of the setup, so I figured I'd turn to Flask. I put in a for loop to POST 100 requests from the espduino to a Flask app running at 0.0.0.0:5000. The POST contains a test string (trying to roughly gauge simple data integrity, make sure the string comes through uncorrupted) as well as the number of the loop that is being sent (e.g. 0 on the first loop ... 99 on the last). Flask keeps track of these requests and updates its output to show which requests came through properly and which did not.
The setup is working great except for one thing: the module is timing out after each request. Each POST appears to work, Flask updates the output appropriately, but the espduino takes its full 5 seconds (default timeout) for each request and says it got a response code of 0.
So to reiterate the situation: my espduino setup properly POSTs and gets a 200 response from requestb.in and pushover.net, but with my local Flask server POSTs and then times out.
WHYT:
Make sure all form data is read by Flask -> no difference
Serve with gunicorn instead of the built-in Flask server -> no difference
Change response content type explicitly to "text/html" -> no difference
Change response content type to "application/json" -> no difference
Add ; charset=utf-8 to content type -> no difference
Change Flask api endpoint from index / to /api -> no difference
Change port -> no difference
Verify Firewall is off (OS X 10.10.5 hosting the Flask app) -> no difference
nc -l 5000 and check the incoming post from the module (looks good)
Post from Postman and curl to the Flask app to inspect response (and compare with responses from Requestb.in / sites that work)
Removed special chars from the test_string
Change WSGIRequestHandler.protocol_version to HTTP/1.1 -> no difference
I've spent a couple days working on this and not made much progress. The biggest breakthrough I had today was that I can successfully post and get 200 responses if I put my gunicorn / Flask setup behind nginx, with no changes to the espduino code, so I'm sure there's something that Flask is or isn't doing (I was concerned it might be the espduino's processing of an IP address vs domain name, but I think this rules that out).
Summary of the setups I've tried:
POST to requestb.in -> POST works, 200 response, no timeout
POST to pushover.net -> POST works, 200 response, no timeout
POST to local Flask server -> POST works, no response, times out
GET to local Flask server -> no response, times out (have not tested if GETs data)
POST to local gunicorn serving Flask app -> POST works, no response, times out
POST to local nginx serving gunicorn / Flask -> POST works, 200 response, no timeout
POST to local gunicorn serving httpbin -> no response, times out
POST to local cherrypy server -> no response, times out
Current code:
from flask import Flask, request, make_response
from datetime import datetime
app = Flask(__name__)
ESPDUINO_IP = 'XXX.XXX.XXX.XXX'
INCOMING_TEST_STRING = 'This is my espduino test string!'
incoming_requests = []
with open('results.txt', 'w') as f:
f.write("Test run start: {}\n".format(datetime.now()))
#app.route('/api', methods=['GET', 'POST'])
def count_requests():
if request.method == 'POST':
form = request.form
incoming_ip = request.remote_addr
if incoming_ip == ESPDUINO_IP and form['test_string'] == INCOMING_TEST_STRING:
test_num = int(form['test_num'])
incoming_requests.append(test_num)
msg = "All is peachy!"
with open('results.txt', 'a') as f:
f.write("{:02d}: {}\n".format(test_num, datetime.now()))
else:
msg = "Hey, you're not the espduino!<br>"
msg += str(len(incoming_requests)) + " requests so far.<br>"
missing = set(range(100)) - set(incoming_requests)
msg += "Missing: {}<br>".format(', '.join(map(str, missing)) if missing else "None!")
msg += '<br>'.join(map(str, incoming_requests))
resp = make_response('{"this": "that"}')
resp.headers['Content-Type'] = "application/json"
return resp
# return "<html><body>{}</body></html>".format(msg)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
Here's what the POST from the espduino looks like:
$ nc -l 5000
POST /api HTTP/1.1
Host: XXX.XXX.XXX.XXX
Content-Length: 55
Connection: close
Content-Type: application/x-www-form-urlencoded
User-Agent: ESPDRUINO#tuanpm
test_string=This is my espduino test string!&test_num=0
As compared to curl -X POST -d 'test_string=This is my espduino test string!&test_num=0' localhost:5000/api:
$ nc -l 5000
POST /api HTTP/1.1
Host: localhost:5000
User-Agent: curl/7.43.0
Accept: */*
Content-Length: 55
Content-Type: application/x-www-form-urlencoded
test_string=This is my espduino test string!&test_num=0
Would love to hear any ideas on what may be going on. I'm wondering if this might be a WSGI problem?
Update Aug 31, 2015:
I still haven't figured this out, but it's definitely not a problem specific to Flask. As I noted above, I've also replicated the timeout with CherryPy, as well as with python3 -m http.server --bind 0.0.0.0 5000 (and changing the espduino code to GET /), as well as with ruby -run -e httpd. I still don't understand why nginx, requestbin, etc. serve it with no problems.
In response to #Miguel's comment about the HOST header not having the port, I'm working on forking and building the firmware to change this, but in the meantime I hard-coded the client host and port into a little HTTP server script with no luck.
from http.server import BaseHTTPRequestHandler, HTTPServer
class MyServer(BaseHTTPRequestHandler):
# protocol_version = 'HTTP/1.1'
# close_connection = True
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
self._set_headers()
self.wfile.write(b"<html><body><h1>hi!</h1></body></html>")
def do_HEAD(self):
self._set_headers()
def do_POST(self):
self.client_address = ("192.168.0.4", 5000)
self._set_headers()
self.wfile.write(b"<html><body><h1>POST!</h1></body></html>")
# import pdb; pdb.set_trace()
def run(server_class=HTTPServer, handler_class=MyServer, port=5000):
server_address = ('0.0.0.0', port)
httpd = server_class(server_address, handler_class)
print('Starting httpd...')
httpd.serve_forever()
if __name__ == "__main__":
run()
Looking through tcpdump to see if I can find any difference between the working (nginx) and nonworking network data. Haven't found anything so far, but I'm also new to the tool.
Update Sep 08, 2015
Still haven't figured this out, but it looks like the tcpdump is significantly different between the nginx and Python servers. Here is an example POST and response -- I have replaced the IPs with ESPDUINO_IP and OSX_IP for clarity, and cleaned up the surrounding ACK calls and such. I need to look into why the Python response is getting interrupted by that odd line -- I examined 10+ consecutive POST / Response pairs, and every one of the Python responses was interrupted like that (in exactly the same place), and none of the nginx responses were, so I wonder if that might be the problem. (Also, as you can see, during this round of testing I had changed the response body to text instead of JSON -- no change in results.)
nginx (works)
POST /api HTTP/1.1
Host: OSX_IP
Content-Length: 29
Connection: close
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: espduino#n8henrie
test_string=simple&test_num=0
09:16:04.079291 IP OSX_IP.commplex-main > ESPDUINO_IP.49146: Flags [P.], seq 1:183, ack 211, win 65535, length 182
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Mon, 31 Aug 2015 15:16:04 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 26
Connection: close
<html><body></body></html>
Flask (times out)
POST /api HTTP/1.1
Host: OSX_IP
Content-Length: 29
Connection: close
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: espduino#n8henrie
test_string=simple&test_num=3
09:00:19.424086 IP OSX_IP.commplex-main > ESPDUINO_IP.48931: Flags [P.], seq 1:18, ack 211, win 65535, length 17
HTTP/1.0 200 OK
09:00:36.382125 IP OSX_IP.commplex-main > ESPDUINO_IP.48931: Flags [FP.], seq 18:181, ack 211, win 65535, length 163
E....F#.#..,...e.......#...k..S.P.......Content-Type: text/html; charset=utf-8
Content-Length: 26
Server: Werkzeug/0.10.4 Python/3.4.3
Date: Mon, 31 Aug 2015 15:00:36 GMT
<html><body></body></html>
It looks to me like Python is breaking the response into two halves for some reason, e.g. one part of length 17 and another of length 163, as compared to nginx's single response of length 182.
Update Sep 10, 2015
Interestingly, everything works as expected if I run it through mitmproxy -- even directly to the Flask app without nginx or gunicorn. As soon as I remove mitmproxy, it's back to the timeouts as per above.
Still haven't fixed the problem, but I think I may have figured out what's causing it. Not a Flask problem after all.
Unfortunately this seems to be a bug with the esp_bridge library whose firmware espduino uses in the esp8266. Pardon the likely incorrect terminology, but from what I can tell for some reason it doesn't seem to be joining TCP packets. The servers that produce an HTTP response that is split into separate TCP packets (e.g. Flask) are failing, while tcpdump can verify that nginx and mitmproxy are joining the split TCP packets and returning a response in a single packet, which is why they are working.
https://github.com/tuanpmt/esp_bridge/issues/10
Update 20160128
I revisited this issue today and found a workaround. While the ideal solution would be to fix esp_bridge to reassemble multi-packet reponses, as long as the response is quite small one can force Flask to write the responses in a single packet.
from werkzeug.serving import WSGIRequestHandler
# Your Flask code here...
if __name__ == "__main__":
WSGIRequestHandler.wbufsize = -1
app.run()

Flask change the server header

I've made a simple flask application:
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
host:google.be
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 233
Server: Werkzeug/0.9.6 Python/2.7.6
Date: Mon, 08 Dec 2014 19:15:43 GMT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
Connection closed by foreign host.
One of the things I would like the change is the server header which at the moment is set as Werkzeug/0.9.6 Python/2.7.6 to something my own chosing. But I can't seem to find anything in the documentation on how to do this.
You can use Flask's make_response method to add or modify headers.
from flask import make_response
#app.route('/index')
def index():
resp = make_response("Hello, World!")
resp.headers['server'] = 'ASD'
return resp
#bcarroll's answer works but it will bypass other processes defined in original process_response method such as set session cookie.
To avoid the above:
class localFlask(Flask):
def process_response(self, response):
#Every response will be processed here first
response.headers['server'] = SERVER_NAME
super(localFlask, self).process_response(response)
return(response)
You can change the Server header for every response by overriding the Flask.process_response() method.
from flask import Flask
from flask import Response
SERVER_NAME = 'Custom Flask Web Server v0.1.0'
class localFlask(Flask):
def process_response(self, response):
#Every response will be processed here first
response.headers['server'] = SERVER_NAME
return(response)
app = localFlask(__name__)
#app.route('/')
def index():
return('<h2>INDEX</h2>')
#app.route('/test')
def test():
return('<h2>This is a test</h2>')
http://flask.pocoo.org/docs/0.12/api/#flask.Flask.process_response
Overriding Server header in code does not work if You use production server like gunicorn. The better way is to use proxy server behind gunicorn and there change Server header.
TL;DR - overwrite /python3.8/http/server.py send_response method. Comment the server header addition line.
Why?
Adding/Manipulating headers in flask (in any way that mentioned above) will fire the response with the configured headers from flask to the web server but the WSGI logic (which happens independently, after & before flask logic) will be the last one to modify those values if any.
In your case(Werkzeug) some headers are hard-coded in python http module which werkzeug depending on. The server header is one of them.
Easy way:
#app.after_request
def changeserver(response):
response.headers['server'] = SERVER_NAME
return response

Categories