Install Flask,Gunicorn & Nginx
A common way to run python codes from a web service it to use some existing Python web framework and web host.
The layers are as follows:
web server (where nginx sits)
|
WSGI server1------WSGI server2------WSGI server... (where Gunicorn sits)
| |
WSGI framework1 WSGI framework2 (where Flask sits)
| |
python codes python codes
Firstly, install a few environment packages. The following was tested on Ubuntu. (use pip3 so it sticks to python 3.x otherwise pip will work)
sudo apt-get install python3-dev python3-pip
sudo pip3 install flask --proxy http://1.1.1.1:8080 #if proxy required
sudo pip3 install gunicorn --proxy http://1.1.1.1:8080
to support odbc connection it also needs:
sudo apt-get install unixodbc-dev
sudo pip3 install pyodbc
------------------------------------
test Flask individually
crate a sample script
#sample.py#
from flask import Flask, Response
app_name = Flask(__name__)
@app_name.route("/test")
def test():
return Response("helloworld"), 200
if __name__ == '__main__'
app_name.run(host='0.0.0.0')
run:
Python sample.py
and the flask app will listen on http://0.0.0.0:5000/test by default. The if __name__ == __main__ checks if
the script is run directly by python as an entry point.
go web browser, type in http://0.0.0.0:5000 (replace 0.0.0.0 as the actual ip) and it will display "helloworld"
------------------------------------
2 ways to deploy the flask app on Gunicorn, as flask is only a framework and not supposed to process web request
1. deploy Flask app as a module
save the same sample code, but dont need the app.run():
#sample.py#
from flask import Flask, Response
app_name = Flask(__name__)
@app_name.route("/test")
def test():
return Response("helloworld"), 200
go to the directory and run:
gunicorn -b 0.0.0.0:8000 sample:app_name
here 'sample' is the module(file) name and 'app_name' is the Flask instance
In this way gunicorn manages the web requests and use the flask app as a backend logic.
2. deploy as a package
create a folder flask_folder
under the folder create sample.py same as above
under the folder create __init__.py as an empty file, so the folder is recoginized as a package
go to the parent of the folder and run:
gunicorn -b 0.0.0.0:8000 flask_folder.sample:app_name
pretty much same as above but the flask app is put in a package
3. Register the gunicorn service as a system service so it automatically starts
note: this is based on red hat, ubuntu would have a slightly different setting
Firstly create a service definition at:
/etc/systemd/system/helloworld.service
The minimum of the definition is as follows.
[unit]
Description=helloworld
[Service]
WorkingDirectory=/home/user
ExecStart=/bin/gunicorn -b 0.0.0.0:8080 sample:app_name
[Install]
WantedBy=multi-user.target
A more complete version is:
[Unit]
Description=helloworld
After=network.target
[Service]
User=xxx
Group=yyy
RemainAfterExit=yes
RuntimeDirectory=helloworld
WorkingDirectory=/home/user
ExecStart=/usr/bin/gunicorn -b 0.0.0.0:8080 sample:app_name
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=True
[Install]
WantedBy=multi-user.target
To enable the service to run as root
sudo systemctl enable myfirst
Restart the server and the gunicorn service should be running.
Add Nginx as a proxy
usually gunicorn service is surfaced as an internal wsgi service. It needs to stay behind a reverse proxy (strongly recommend nginx) to provide service to external users
sudo apt-get install nginx
the configuration file is usually at:
sudo vi /etc/nginx/nginx.conf
to turn on the reverse proxy on http, add the following section to the http{} block in the conf file. The example below listen at port 8081, so the request url is http://url:8081/... which maps to http://127.0.0.1:8080/...
server {
listen 8081;
server_name _;
location /score {
proxy_pass http://127.0.0.1:8080;
}
}
To turn on the proxy on https
Firstly create self signed key and certificate to support ssl. Save the key and certificate a folder, eg. /etc/cert/nginx
sudo openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/cert/nginx/nginx-selfsigned.key -out /etc/cert/nginx/nginx -selfsigned.crt
Add a server section to the conf file for https service. this is a minimum. The request url is https://url/... which maps to http://127.0.0.1:8080/...
server {
listen 443 ssl;
server_name _;
ssl_certificate "/etc/cert/nginx/nginx-selfsigned.crt";
ssl_certificate_key "/etc/cert/nginx/nginx-selfsigned.key";
location / {
proxy_pass http://127.0.0.1:8080;
}
}
The following is a more complete version
server {
listen 443 ssl;
server_name _;
root /var/www/html;
ssl_certificate "/etc/cert/nginx/nginx-selfsigned.crt";
ssl_certificate_key "/etc/cert/nginx/nginx-selfsigned.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://127.0.0.1:8080;
}
error_page 404 /404.html;
location = /40x.html {
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
Modify the firewall setting to allow traffic coming in and out
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=public --add-service=https
sudo firewall-cmd --reload
If other ports, e.g. 8081 need to be opened:
sudo firewall-cmd --zone=public --add-port=8081/tcp --permanent
Note, because it's a self-signed certificate, when open the service from the browser, it would say it's a unsafe website and display all sort of alerts, simply skip it and proceed to the site
However, if calling the service from a program, the httprequest needs to accepts the certificate.
The following C# script (the bold line) is an example to accept the self signed certificate:
HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create("https://url/showmethemoney");
httpWebRequest.ContentType = "text/xml; encoding='utf-8'";
httpWebRequest.Method = "POST";
httpWebRequest.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true; //accepts the certificate anyway
//send out the request
String xml = ".... you request message ...";
StreamWriter streamWriter = new StreamWriter(httpWebRequest.GetRequestStream());
streamWriter.Write(xml);
streamWriter.Flush();
streamWriter.Close();
//receive the response
HttpWebResponse httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
StreamReader streamReader = new StreamReader(httpResponse.GetResponseStream());
String result = streamReader.ReadToEnd();
Console.WriteLine(result);
#############url rewrite in nginx#########################
Sometimes people need to rewrite the request url.
For example, people want to direct http://1.1.1.1/superservice/showmethemoney to http://1.1.1.2:8080/showmethemoney
and direct http://1.1.1.1/ironservice/showmethemoney to http://1.1.1.3:8080/showmethemoney
So the base url superservice / ironservice determines which gunicorn instance the request is forwarded to, and it needs to strip the base url before passing it to gunicorn.
A very simply way is to define the location as follows:
location /superservice {
proxy_pass http://1.1.1.1:8080/;
}
The location /superservice will match the incoming url
When it come to proxy_pass, the additional / at the end of the url strips the matched prefix /superserice and pass the remainder on.
A more complex way is to use REWRITE
location /superservice{
rewrite /superservice/(.*) /$1 break;
proxy_pass http://1.1.1.1:8080;
}
The rewrite statement has a syntax of "rewrite regex replacement flag"
The reges "/superservice/(.*)" capture url starts with /superservice, and match the rest of the url as a group (.*)
The $1 refers to the first matching group which is the suburl, so it simply replaces the whole url with the suburl (i..e striping the superservice)
The flag "break" means stop processing the original url.