Previously, I asked How to get data received in Flask request because request.data was empty. The answer explained that request.data is the raw post body, but will be empty if form data is parsed. How can I get the raw post body unconditionally?
@app.route('/', methods=['POST'])
def parse_request():
data = request.data # empty in some cases
# always need raw data here, not parsed form data
Use request.get_data() to get the raw data, regardless of content type. The data is cached and you can subsequently access request.data, request.json, request.form at will.
If you access request.data first, it will call get_data with an argument to parse form data first. If the request has a form content type (multipart/form-data, application/x-www-form-urlencoded, or application/x-url-encoded) then the raw data will be consumed. request.data and request.json will appear empty in this case.
request.stream is the stream of raw data passed to the application by the WSGI server. No parsing is done when reading it, although you usually want request.get_data() instead.
data = request.stream.read()
The stream will be empty if it was previously read by request.data or another attribute.
from io importBytesIOclassWSGICopyBody(object):def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
length = int(environ.get('CONTENT_LENGTH')or0)
body = environ['wsgi.input'].read(length)
environ['body_copy']= body
# replace the stream since it was exhausted by read()
environ['wsgi.input']=BytesIO(body)return self.application(environ, start_response)
app.wsgi_app =WSGICopyBody(app.wsgi_app)
I created a WSGI middleware that stores the raw body from the environ['wsgi.input'] stream. I saved the value in the WSGI environ so I could access it from request.environ['body_copy'] within my app.
This isn’t necessary in Werkzeug or Flask, as request.get_data() will get the raw data regardless of content type, but with better handling of HTTP and WSGI behavior.
This reads the entire body into memory, which will be an issue if for example a large file is posted. This won’t read anything if the Content-Length header is missing, so it won’t handle streaming requests.
from io import BytesIO
class WSGICopyBody(object):
def __init__(self, application):
self.application = application
def __call__(self, environ, start_response):
length = int(environ.get('CONTENT_LENGTH') or 0)
body = environ['wsgi.input'].read(length)
environ['body_copy'] = body
# replace the stream since it was exhausted by read()
environ['wsgi.input'] = BytesIO(body)
return self.application(environ, start_response)
app.wsgi_app = WSGICopyBody(app.wsgi_app)
request.data will be empty if request.headers["Content-Type"] is recognized as form data, which will be parsed into request.form. To get the raw data regardless of content type, use request.get_data().
request.data calls request.get_data(parse_form_data=True), which results in the different behavior for form data.
I’m making a website where users can log on and download files, using the Flask micro-framework (based on Werkzeug) which uses Python (2.6 in my case).
I need to get the IP address of users when they log on (for logging purposes).
Does anyone know how to do this? Surely there is a way to do it with Python?
Proxies can make this a little tricky, make sure to check out ProxyFix (Flask docs) if you are using one. Take a look at request.environ in your particular environment. With nginx I will sometimes do something like this:
from flask import request
request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
When proxies, such as nginx, forward addresses, they typically include the original IP somewhere in the request headers.
UpdateSee the flask-security implementation. Again, review the documentation about ProxyFix before implementing. Your solution may vary based on your particular environment.
#!flask/bin/python
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route('/', methods=['GET'])
def get_tasks():
if request.environ.get('HTTP_X_FORWARDED_FOR') is None:
return jsonify({'ip': request.environ['REMOTE_ADDR']}), 200
else:
return jsonify({'ip': request.environ['HTTP_X_FORWARDED_FOR']}), 200
if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0', port=8000)
My Request and Response:
curl -X GET http://test.api
{
"ip": "Client Ip......"
}
回答 5
以下代码始终提供客户端的公共IP(而不是代理后面的私有IP)。
from flask import requestif request.environ.get('HTTP_X_FORWARDED_FOR')isNone:print(request.environ['REMOTE_ADDR'])else:print(request.environ['HTTP_X_FORWARDED_FOR'])# if behind a proxy
The below code always gives the public IP of the client (and not a private IP behind a proxy).
from flask import request
if request.environ.get('HTTP_X_FORWARDED_FOR') is None:
print(request.environ['REMOTE_ADDR'])
else:
print(request.environ['HTTP_X_FORWARDED_FOR']) # if behind a proxy
if'X-Forwarded-For'in request.headers:
proxy_data = request.headers['X-Forwarded-For']
ip_list = proxy_data.split(',')
user_ip = ip_list[0]# first address in list is User IPelse:
user_ip = request.remote_addr # For local development
If you use Nginx behind other balancer, for instance AWS Application Balancer, HTTP_X_FORWARDED_FOR returns list of addresses. It can be fixed like that:
if 'X-Forwarded-For' in request.headers:
proxy_data = request.headers['X-Forwarded-For']
ip_list = proxy_data.split(',')
user_ip = ip_list[0] # first address in list is User IP
else:
user_ip = request.remote_addr # For local development
I’m not sure if this is Flask specific, but when I run an app in dev mode (http://localhost:5000), I cannot access it from other machines on the network (with http://[dev-host-ip]:5000). With Rails in dev mode, for example, it works fine. I couldn’t find any docs regarding the Flask dev server configuration. Any idea what should be configured to enable this?
While this is possible, you should not use the Flask dev server in production. The Flask dev server is not designed to be particularly secure, stable, or efficient. See the docs on deploying for correct solutions.
Add a parameter to your app.run(). By default it runs on localhost, change it to app.run(host= '0.0.0.0') to run on your machines IP address.
Documented on the Flask site under “Externally Visible Server” on the Quickstart page:
Externally Visible Server
If you run the server you will notice that the server is only
available from your own computer, not from any other in the network.
This is the default because in debugging mode a user of the
application can execute arbitrary Python code on your computer. If you
have debug disabled or trust the users on your network, you can make
the server publicly available.
Just change the call of the run() method to look like this:
app.run(host='0.0.0.0')
This tells your operating system to listen on a public IP.
回答 1
如果使用flask可执行文件启动服务器,则可以使用flask run --host=0.0.0.0更改默认值,从127.0.0.1并将其打开到非本地连接。其他答案描述的config和app.run方法可能是更好的做法,但这也很方便。
If you use the flask executable to start your server, you can use flask run --host=0.0.0.0 to change the default from 127.0.0.1 and open it up to non local connections. The config and app.run methods that the other answers describe are probably better practice but this can be handy as well.
Externally Visible Server If you run the server you will notice that
the server is only accessible from your own computer, not from any
other in the network. This is the default because in debugging mode a
user of the application can execute arbitrary Python code on your
computer.
If you have the debugger disabled or trust the users on your network,
you can make the server publicly available simply by adding
–host=0.0.0.0 to the command line:
flask run –host=0.0.0.0 This tells your operating system to listen on
all public IPs.
I personally battled a lot to get my app accessible to other devices(laptops and mobile phones) through a local-server. I tried the 0.0.0.0 method, but no luck. Then I tried changing the port, but it just didn’t work. So, after trying a bunch of different combinations, I arrived to this one, and it solved my problem of deploying my app on a local-server.
Steps
Get the local IPv4 address of your computer.
This can be done by typing ipconfig on Windows and ifconfig on linux
and Mac.
Please note: The above step is to be performed on the machine you are serving the app on, and on not the machine on which you are accessing it. Also note, that the IPv4 address might change if you disconnect and reconnect to the network.
Now, simply run the flask app with the acquired IPv4 address.
flask run -h 192.168.X.X
E.g. In my case (see the image), I ran it as:
flask run -h 192.168.1.100
On my mobile device
Optional Stuff
If you are performing this procedure on Windows, and using Power Shell as the CLI, and you still aren’t able to access the website, try a CTRL + C command in the shell that’s running the app. Power Shell get frozen up sometimes and it needs a pinch to revive. Doing this might even terminate the server, but it sometimes does the trick.
That’s it. Give a thumbs up if you found this helpful.😉
Some more optional stuff
I have created a short Powershell script that will get you your IP address whenever you need one:
$env:getIp = ipconfig
if ($env:getIp -match '(IPv4[\sa-zA-Z.]+:\s[0-9.]+)') {
if ($matches[1] -match '([^a-z\s][\d]+[.\d]+)'){
$ipv4 = $matches[1]
}
}
echo $ipv4
Save it to a file with .ps1 extenstion (for PowerShell), and run it on before starting your app. You can save it in your project folder and run it as:
If your cool app has it’s configuration loaded from an external file, like in the following example, then don’t forget to update the corresponding config file with HOST=”0.0.0.0″
If you’re having troubles accessing your Flask server, deployed using PyCharm, take the following into account:
PyCharm doesn’t run your main .py file directly, so any code in if __name__ == '__main__': won’t be executed, and any changes (like app.run(host='0.0.0.0', port=5000)) won’t take effect.
Instead, you should configure the Flask server using Run Configurations, in particular, placing --host 0.0.0.0 --port 5000 into Additional options field.
I had the same problem, I use PyCharm as an editor and when I created the project, PyCharm created a Flask Server. What I did was create a server with Python in the following way;
basically what I did was create a new server but flask if not python
This answer is not solely related with flask, but should be applicable for all cannot connect service from another host issue.
use netstat -ano | grep <port> to see if the address is 0.0.0.0 or ::. If it is 127.0.0.1 then it is only for the local requests.
use tcpdump to see if any packet is missing. If it shows obvious imbalance, check routing rules by iptables.
Today I run my flask app as usual, but I noticed it cannot connect from other server. Then I run netstat -ano | grep <port>, and the local address is :: or 0.0.0.0 (I tried both, and I know 127.0.0.1 only allows connection from the local host). Then I used telnet host port, the result is like connect to .... This is very odd. Then I thought I would better check it with tcpdump -i any port <port> -w w.pcap. And I noticed it is all like this:
Then by checking iptables --list OUTPUT section, I could see several rules:
these rules forbid output tcp vital packets in handshaking. By deleting them, the problem is gone.
go to project path
set FLASK_APP=ABC.py
SET FLASK_ENV=development
flask run -h [yourIP] -p 8080
you will following o/p on CMD:-
* Serving Flask app “expirement.py” (lazy loading)
* Environment: development
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 199-519-700
* Running on http://[yourIP]:8080/ (Press CTRL+C to quit)
回答 12
您还可以通过环境变量设置主机(将其暴露在面向IP地址的网络上)和端口。
$ export FLASK_APP=app.py
$ export FLASK_ENV=development
$ export FLASK_RUN_PORT=8000
$ export FLASK_RUN_HOST=0.0.0.0
$ flask run
*ServingFlask app "app.py"(lazy loading)*Environment: development
*Debug mode: on
*Running on https://0.0.0.0:5000/(Press CTRL+C to quit)*Restartingwith stat
*Debuggeris active!*Debugger PIN:329-665-000
The docs describe the attributes available on the request. In most common cases request.data will be empty because it’s used as a fallback:
request.data Contains the incoming request data as string in case it came with a mimetype Flask does not handle.
request.args: the key/value pairs in the URL query string
request.form: the key/value pairs in the body, from a HTML post form, or JavaScript request that isn’t JSON encoded
request.files: the files in the body, which Flask keeps separate from form. HTML forms must use enctype=multipart/form-data or files will not be uploaded.
request.values: combined args and form, preferring args if keys overlap
To get the raw data, use request.data. This only works if it couldn’t be parsed as form data, otherwise it will be empty and request.form will have the parsed data.
If you post JSON with content type application/json, use request.get_json() to get it in Flask. If the content type is not correct, None is returned. If the data is not JSON, an error is raised.
@app.route("/something", methods=["POST"])
def do_something():
data = request.get_json()
To get the raw post body regardless of the content type, use request.get_data(). If you use request.data, it calls request.get_data(parse_form_data=True), which will populate the request.formMultiDict and leave data empty.
@app.route('/process_data', methods=['POST'])def process_data():
req_data = request.get_json(force=True)
language = req_data['language']return'The language value is: {}'.format(language)
To get JSON posted without the application/json content type, use request.get_json(force=True).
@app.route('/process_data', methods=['POST'])
def process_data():
req_data = request.get_json(force=True)
language = req_data['language']
return 'The language value is: {}'.format(language)
Here’s an example of posting form data to add a user to a database. Check request.method == "POST" to check if the form was submitted. Use keys from request.form to get the form data. Render an HTML template with a <form> otherwise. The fields in the form should have name attributes that match the keys in request.form.
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/user/add", methods=["GET", "POST"])
def add_user():
if request.method == "POST":
user = User(
username=request.form["username"],
email=request.form["email"],
)
db.session.add(user)
db.session.commit()
return redirect(url_for("index"))
return render_template("add_user.html")
If the content type is recognized as form data, request.data will parse that into request.form and return an empty string.
To get the raw data regardless of content type, call request.get_data(). request.data calls get_data(parse_form_data=True), while the default is False if you call it directly.
If the body is recognized as form data, it will be in request.form. If it’s JSON, it will be in request.get_json(). Otherwise the raw data will be in request.data. If you’re not sure how data will be submitted, you can use an or chain to get the first one with data.
def get_request_data():
return (
request.args
or request.form
or request.get_json(force=True, silent=True)
or request.data
)
request.args contains args parsed from the query string, regardless of what was in the body, so you would remove that from get_request_data() if both it and a body should data at the same time.