Using Python, I would like to create an API server with the following endpoints:
/metric - needs to return the number of times the API server has been called
/health - needs to return ok
Since you don't specify any framework ou give any initial code, here's a simple example using Flask on how it would be:
from flask import Flask
app = Flask(__name__)
count = 0
#app.route('/metric')
def metric():
global count
count += 1
return str(count)
#app.route('/healthz')
def health():
return "ok"
app.run()
To install Flask, run:
pip3 install flask
Run the python code and access on your browser http://127.0.0.1:5000/metric and http://127.0.0.1:5000/healthz
FastAPI is a great option. FastAPI is a "fast (high-performance), web framework for building APIs". It provides interactive API documentation (provided by Swagger UI) to visualize and interact with the API’s resources.
Working Example
You can access the interactive API autodocs at http://127.0.0.1:8000/docs. You can also access the API endpoints directly from the browser at, for instance, http://127.0.0.1:8000/metric.
import uvicorn
from fastapi import FastAPI
app = FastAPI()
hits = 0
#app.get("/metric")
async def metric():
global hits
hits+=1
return {"hits": hits}
#app.get("/health")
async def health():
return "ok"
if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=8000)
I have created fulfillment code for Dialogflow with Python Flask and deployed it as Azure Web App. Code is working fine with the url provided by Azure.
But if I use the same url in Dialog Flow's Fulfillment webhook, I get an error saying "Webhook call failed. Error: UNKNOWN."
Here's my simple Python Flask Code which is deployed in Azure as Web App.
from flask import Flask, request, make_response, jsonify
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
#app.route("/webhook")
def webhook():
return jsonify("Webhook Successfull")
if __name__ == "__main__":
app.run()
Webhook url in DialogFlow:
For your python code, there are two issues I think you met. First is that the route in the Flask just set to support the GET in default. So you need to set for the POST request manually. See the details here for the parameters:
By default a rule just listens for GET (and implicitly HEAD). Starting
with Flask 0.6, OPTIONS is implicitly added and handled by the
standard request handling.
Another is that you return the message via the function jsonify. It turns the JSON output into a Response object with the application/json mime-type, but you just give ita string. So the POST response will meet the conflict. This may be the problem you met.
You can change the code like this:
from flask import Flask, request, make_response, jsonify
app = Flask(__name__)
#app.route("/", methods=["GET", "POST"])
def hello():
return "Hello World!"
#app.route("/webhook", methods=["GET", "POST"])
def webhook():
return jsonify(response="Webhook Successfull")
if __name__ == "__main__":
app.run()
I am using python 2.7 and flask that returns a complete response to my local setup. Now the application is dockerized the and deployed in Google kubernetes container. This is a sample API of POST method which takes inputs as application/json, currently the internal function able to fetch the data in JSON format but its not return to the client end.
Python part:
from flask import Flask, render_template, request, jsonify
from flask_cors import CORS, cross_origin
import sys
from runmodel import run
reload(sys) # Reload is a hack
sys.setdefaultencoding('UTF8')
app = Flask(__name__, static_url_path='/static')
CORS(app)
#app.route("/modelrun", methods=['POST'])
def modelrun():
"""TO run the model and get data to populate"""
req_data = request.json
res = run(req_data) #another function return the data it will return json format
return jsonify(res)
My current problem is I am not getting the complete response, its return the ValueError: View function did not return a response// Werkzeug Debugger in the web browser.
Here are the logs and Traceback:
labels{
container.googleapis.com/stream: "stderr"
}
BrokenFilesystemWarning)
severity: "ERROR"
textPayload: " BrokenFilesystemWarning)
I created a simple REST API in Python using Flask. The code for that service is:
from flask import Flask, request, Response, jsonify
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class Ping(Resource):
def get(self):
resp = jsonify({"response": 'Pong!'})
resp.status_code = 200
return resp
api.add_resource(Ping, '/ping')
if __name__ == '__main__':
app.run()
I start the service with: $python minimal-rest.py
I curl the ping endpoint with: $ curl -i http://127.0.0.1:5000/ping and the results are exactly what I expect.
I then hit the same endpoint from a browser, and again the results are as expected.
If I issue another curl command, the console just hangs. However, if I then go refresh my browser, it responds, as well as the console that was running the hung curl.
Any ideas?
Try changing the Ping class:
class Ping(Resource):
def get(self):
resp = {"response": 'Pong!'}
return resp, 200
I have a website build using python3.4 and flask...I have generated my own self-signed certificate and I am currently testing my website through localhost.
I am using the python ssl module along with this flask extension: https://github.com/kennethreitz/flask-sslify
context = ('my-cert.pem', 'my-key.pem')
app = Flask(__name__)
sslify = SSLify(app)
...
if __name__ == '__main__':
app.debug = False
app.run(
host="127.0.0.1",
port=int("5000"),
ssl_context=context
)
This does not seem to be working however. I took a look in the sslify source code and this line does not seem to be working
def init_app(self, app):
"""Configures the configured Flask app to enforce SSL."""
app.before_request(self.redirect_to_ssl)
app.after_request(self.set_hsts_header)
Specifically the function call to redirect_to_ssl (I added my own print statement under the redirect_to_ssl function and my statement was never printed)
def redirect_to_ssl(self):
print("THIS IS WORKING")
"""Redirect incoming requests to HTTPS."""
Should we redirect?
criteria = [
request.is_secure,
current_app.debug,
request.headers.get('X-Forwarded-Proto', 'http') == 'https'
]
if not any(criteria) and not self.skip:
if request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 302
if self.permanent:
code = 301
r = redirect(url, code=code)
return r
I am pretty new to python. Any ideas?
To me, it appears you're making it more complicated than it needs to be. Here is the code I use in my views.py script to force user to HTTPS connections:
#app.before_request
def before_request():
if not request.is_secure:
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
According with the docs, after pip install Flask-SSLify you only need to insert the following code:
from flask import Flask
from flask_sslify import SSLify
app = Flask(__name__)
sslify = SSLify(app)
I have done it and it works very well. Am I missing something in the discussion ?
The Flask Security Guide recommends using Flask-Talisman.
$ pip install flask-talisman
Usage example:
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app)
It forces HTTPS by default (from the README):
force_https, default True, forces all non-debug connects to https.
Personally, I got some errors relating to CSP (Content Security Policy) which I disabled with:
Talisman(app, content_security_policy=None)
But use this at your own risk :)
Thanks to answer from Kelly Keller-Heikkila and comment by jaysqrd I ended up doing this in my Flask app:
from flask import request, redirect
...
#app.before_request
def before_request():
if app.env == "development":
return
if request.is_secure:
return
url = request.url.replace("http://", "https://", 1)
code = 301
return redirect(url, code=code)
I tried the flask_sslify solution suggested by Rodolfo Alvarez but ran into this issue and went with the above solution instead.
If the app is running in development mode or the request is already on https there's no need to redirect.
Here is a flask solution if you are on aws and behind a load balancer. Place it in your views.py
#app.before_request
def before_request():
scheme = request.headers.get('X-Forwarded-Proto')
if scheme and scheme == 'http' and request.url.startswith('http://'):
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
The standard solution is to wrap the request with an enforce_ssl decorator that after checking some flags in the app configuration (flags you can set depending on your debugging needs) modifies the request's url with request.url.
As it is written here.
You can modify the code to make it working with before_request as suggested by #kelly-keller-heikkila
I use a simple extra app that runs on port 80 and redirect people to https:
from flask import Flask,redirect
app = Flask(__name__)
#app.route('/')
def hello():
return redirect("https://example.com", code=302)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
An alternative to the other answers that I've been able to use with great success:
from http import HTTPStatus
from typing import Optional
from flask import Response, redirect, request, url_for
def https_redirect() -> Optional[Response]:
if request.scheme == 'http':
return redirect(url_for(request.endpoint,
_scheme='https',
_external=True),
HTTPStatus.PERMANENT_REDIRECT)
# ..
if app.env == 'production':
app.before_request(https_redirect)
# ..
On app engine flex, add:
from werkzeug.middleware.proxy_fix import ProxyFix
def create_app(config=None):
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app)
In addition to the solution of:
#app.before_request
def before_request():
if not request.is_secure:
url = request.url.replace('http://', 'https://', 1)
code = 301
return redirect(url, code=code)
Otherwise it'll cause infinite redirects since SSL is unwrapped behind the proxy but is noted in the headers.
I ran into the same solution running a Flask application in AWS Elastic Beanstalk behind a load balancer. The following AWS docs provided two steps to configure the environment for http redirects: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/configuring-https-httpredirect.html Following both steps fixed my issue.
One thing to note is that you'll have to create the .ebextenions folder at the root level of your application source bundle and add the config file to that .ebextensions folder. The readme here: https://github.com/awsdocs/elastic-beanstalk-samples explains this in a bit more detail.
For some reason it seems, requests from a Private AWS API Gateway with a VPC endpoint don't include the "X-Forwarded-Proto" header. This can break some of the other solutions (either it doesn't work or it continuously redirects to the same url). The following middleware forces https on most flask generated internal redirects:
class ForceHttpsRedirects:
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
environ["wsgi.url_scheme"] = "https"
return self.app(environ, start_response)
# Usage
app = flask.Flask(__name__)
app.wsgi_app = ForceHttpsRedirects(app.wsgi_app) # Add middleware to force all redirects to https
Use:
app.run(port="443")
All modern browsers automatically use HTTPS when the port is 443 or 8443.
I'm using cloud foundry python app which is behind a load balancer (like https://stackoverflow.com/users/5270172/kelly-keller-heikkila said) .
This resolution helped me by adding (_external and _Scheme to the url_for function). https://github.com/pallets/flask/issues/773
I had the same issue and mine is a brute-force solution, but it works.
Heroku in the past suggested flask_sslify, which is not maintained anymore. Nowadays the proper way in Flask should be flask-talisman, but I tried it and it has bad interactions with boostrap templates.
I tried the anulaibar solution but it did not always worked for me.
The following is what I came up with:
#app.before_request
def before_request():
# If the request is sicure it should already be https, so no need to redirect
if not request.is_secure:
currentUrl = request.url
if currentUrl.startswith('http://'):
# http://example.com -> https://example.com
# http://www.example.com -> https://www.example.com
redirectUrl = currentUrl.replace('http://', 'https://', 1)
elif currentUrl.startswith('www'):
# Here we redirect the case in which the user access the site without typing any http or https
# www.example.com -> https://www.example.com
redirectUrl = currentUrl.replace('www', 'https://www', 1)
else:
# I do not now when this may happen, just for safety
redirectUrl = 'https://www.example.com'
code = 301
return redirect(redirectUrl, code=code)
I have the domain registered in godaddy which is also redirecting to https://www.example.com.
In my case Flask app is sitting behind AWS API Gateway and solutions with #app.before_request were giving me permanent redirects.
The following simple solution finally worked:
#app.after_request
def adjust_response(response):
....
if response.location:
if app.env != "development":
response.location = response.location.replace("http://", "https://", 1)
return response