Root Path in Fast API behind ALB - python

I am deploying a fast API app behind AWS ALB, with listener rule path pattern /api/v1/ points towards fast API. My app looks like this
from typing import Union
import os
import mysql.connector
from fastapi import FastAPI
app = FastAPI()
#app.get("/")
def read_root():
print("Root path hit")
return {"App": "Fargate"}
#app.get("/api/v1/")
def read_apiv1():
print("Root path hit")
return {"App": "Path Fargate API v1"}
I deployed the app in ECS using docker and my docker run command is
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80", "--root-path", "/api/v1"]
Now when I hit my AWS ALB dns suffixed with /api/v1/ I see the endpoint /api/v1 which throws the response {"App": "Path Fargate API v1"}. However, based on the documentation from fast API it should load the api endpoint with /.
Can anyone help me why I am getting this unexpected behavior? Do I have to manually write /api/v1 before all of my endpoints?

Having a proxy with a stripped path prefix, in this case, means that you could declare a path at /app in your code, but then, you add a layer on top (the proxy) that would put your FastAPI application under a path like /api/v1.
In this case, the original path / would actually be served at /api/v1.
Even though all your code is written assuming there's just /.
And the proxy would be "stripping" the path prefix on the fly before transmitting the request to Uvicorn, keep your application convinced that it is serving at /, so that you don't have to update all your code to include the prefix /api/v1.
Reference: https://fastapi.tiangolo.com/advanced/behind-a-proxy/?h=root_#proxy-with-a-stripped-path-prefix
About root_path
Have in mind that the server (Uvicorn) won't use that root_path for anything else than passing it to the app.
But if you go with your browser to http://127.0.0.1:8000/app you will see the normal response:
{
"message": "Hello World",
"root_path": "/api/v1"
}
So, it won't expect to be accessed at http://127.0.0.1:8000/api/v1/app.
Uvicorn will expect the proxy to access Uvicorn at http://127.0.0.1:8000/app, and then it would be the proxy's responsibility to add the extra /api/v1 prefix on top.

This is how I added /api/v1 to all my routes:
from sys import prefix
from typing import Union
import os
from fastapi import FastAPI, APIRouter
app = FastAPI()
prefix_router = APIRouter(prefix="/api/v1")
#prefix_router.get("/")
def read_root():
print("Root path hit")
return {"App": "Fargate"}
#prefix_router.get("/something/")
def read_apiv1():
print("Root path hit")
return {"App": "Path Fargate API v1"}
app.include_router(prefix_router)
After this when I go to http://127.0.0.1/api/v1/something/ path it automatically redirects me to the second route.

Related

IIS to host python CherryPy API

I am looking for a solution to link IIS and Cherrypy.
I would like a specific explanation of doing this for Cherrypy as all others are for other applications like flask and django.
I can call the functions getHeight and getWidth by
using the call http://0.0.0.0:8080/getHeight
import cherrypy
import tileProvider
import time
class MyWebService(object):
provider = TileProvider('myPicture.JPEG')
#cherrypy.expose
def getHeight(self):
return str(MyWebService.provider.getHeight())
#cherrypy.expose
def getWidth(self):
return str(MyWebService.provider.getWidth())
if __name__ == '__main__':
IPv4 = socket.gethostbyname(socket.gethostname())
config = {'server.socket_host': IPv4,
'server.socket_port': 8080}
cherrypy.config.update(config)
cherrypy.quickstart(MyWebService())
So now how would create the same thing except hosted from IIS and not CherryPy's built in WebServer.
Does anybody have any useful pointers or links for me to follow?
to configure Cherrypy app in iis you could follow the below steps:
1.Run below command to install cherrypy
pip install cherrypy
2.install wfastcgi and enable it:
pip install wfastcgi
fastcgi-enable
3.enable iis cgi feature.
4.add site in iis with your cherrypy app path
5.select your site name then double click on the handler mapping feature of iis from the middle pane.
6.in handler mapping select add module mapping from the action pane.
executable path value:
C:\Python37-32\python.exe|C:\Python37-32\Lib\site-packages\wfastcgi.py
C:\Python37-32 is your python path.
7.Now go back and again select the server name and select fast CGI setting from the middle pane.
Double click it, then click the “…” for the Environment Variables collection to launch the EnvironmentVariables Collection Editor:
8.Set the PYTHONPATH variable(your cherrypy app folder path):
9.And the WSGI_HANDLER (my app is named app.py so the value is app.wsgiapp — if yours is named site.py it would be site.app.wsgiapp or similar):
You might have to restart the Server and the website after configuration changes.
Make sure you run the application pool identity with one of the admin user or if its running with App pool identity then make sure you provide full permission to the site folder which is C:\cherryapp and the python folder C:\Python37-32 or assign the iis_iusrs and iusr permission.
app.py:
import cherrypy
class Root:
#cherrypy.expose
def index(self):
return 'Hello CherryPy!'
#cherrypy.expose
def greet(self, name):
return 'Greetings, {0}'.format(name)
url_prefix = '/cherrypy'
cherrypy.config.update({'engine.autoreload.on': False})
cherrypy.server.unsubscribe()
cherrypy.engine.start()
wsgiapp = cherrypy.tree.mount(Root(), url_prefix)

Azure use python flask framework for function app

I saw that Azure now supports Python (preview) in the function apps. I have a existing Flask app and was wondering if it's possible to deploy that one as a function app without major changes?
I have read through the Azure tutorials that uses Python in function apps (https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python), however not with the flask framework...
Has anyone any experience with it?
I tried different ways to integrate Azure Functions for Python with Flask framework. Finally, I did it success in my HttpTrigger function named TryFlask via app.test_client().
Here is my sample code, as below.
import logging
import azure.functions as func
from flask import Flask, request
app = Flask(__name__)
#app.route('/')
def hello_world():
return 'Hello World!'
#app.route('/hi')
def hi():
return 'Hi World!'
#app.route('/hello')
#app.route('/hello/<name>', methods=['POST', 'GET'])
def hello(name=None):
return name != None and 'Hello, '+name or 'Hello, '+request.args.get('name')
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
uri=req.params['uri']
with app.test_client() as c:
doAction = {
"GET": c.get(uri).data,
"POST": c.post(uri).data
}
resp = doAction.get(req.method).decode()
return func.HttpResponse(resp, mimetype='text/html')
For testing on local and Azure, to access the urls /, '/hi' and /hello via the url http(s)://<localhost:7071 or azurefunchost>/api/TryFlask with query string ?uri=/, ?uri=/hi and ?uri=/hello/peter-pan in browser, and to do the POST method for the same url above with query string ?uri=/hello/peter-pan, these are all work. Please see the results as the figures locally below, the same on cloud.
Note: In my solution, the url must have to be http(s)://<localhost:7071 or azurefunchost>/<routePrefix defined in host.json, default is api>/<function name>?uri=<uri defined in app.route, like / or /hi or /hello, even /hello/peter-pan?name=peter>.
Flask app is just an WSGI application. WSGI is a rather simple interface (see http://ivory.idyll.org/articles/wsgi-intro/what-is-wsgi.html. So instead of using test_client() as middleware to connect to the Azure function environment, a proper wsgi wrapper implementation should be used, which calls the app=Flask() object.
There is a nice Azure Python wsgi wrapper implementation "azf-wsgi" available in https://github.com/vtbassmatt/azf-wsgi.
In order to use the azf-wsgi wrapper with Flask, I found it useful to use a middleware to rewrite the URL:s from /api/app to / so when developing, I don't need to know where my Flask app gets mounted.
Additional benefit is that my main.py is just a normal Flask application, which I can run locally without using Azure functions environment (way faster).
My HttpTriggerApp/__init__.py of Azure function is attached. The myFlaskApp-folder is located under the HttpTriggerApp. Remember to use rlative import in the http-trigger as well as main.py (from . import myHelperFooBar).
For host.json and function.json, follow the azf-wsgi instructions.
import logging
import azure.functions as func
# note that the package is "azf-wsgi" but the import is "azf_wsgi"
from azf_wsgi import AzureFunctionsWsgi
# Import the Flask wsgi app (note relative import from the folder under the httpTrigger-folder.
from .myFlaskAppFolder.main import app
# rewrite URL:s to Azure function mount point (you can configure this in host.json and function.json)
from werkzeug.middleware.dispatcher import DispatcherMiddleware
app.config["APPLICATION_ROOT"] = "/api/app" # Flask app configuration so it knows correct endpoint urls
application = DispatcherMiddleware(None, {
'/api/app': app,
})
# Wrap the Flask app as WSGI application
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
return AzureFunctionsWsgi(application).main(req, context)
Flask can be used now out of the box with Python Azure Functions -> see https://github.com/Azure/azure-functions-python-library/pull/45

Unable to render create-react-app boilerplate views from Flask backend

I'm having a hard time integrating create-react-app single page application to my flask backend. I want to be able to make a fetch/axios call from my front end like so: axios.get('/getResults') and fetch('/getResults'). Some things I have tried but not limited to is specifying the Flask port as 3000 which is the same used by create-react-app. Also, used the proxy configuration feature on the "package.json" file of create-react-app but to no avail. I suspect my folder structure and Flask code implementation may likely be causing this. Below is my folder structure and "app.py" code. Any help I could get will be appreciated. I can provide additional information if necessary. Thanks
Project -build(contains static folder, index.html...Other meta files)-node_modules-public-srcapp.pypackage.jsonrequirements.txt
app.py:
from flask import Flask, Response, request, jsonify, make_response, send_from_directory,render_template
app = Flask(__name__, static_path='/build/static/')
app.debug=True
#app.route('/')
def root():
print('Inside root function')
return app.send_static_file('index.html')
#app.route('/getResults', methods=["GET"])
def results():
print('Inside getResults path')
return app.send_static_file('index.html')
#app.route('/postData', methods=["POST"])
def data_results():
print('Inside postData path')
data = request.get_json
return jsonify(data)
#app.route('/<path:path>')
def send_js(path):
print("inside send_js fxn")
return send_from_directory('./build/static',path)
if __name__ == "__main__":
print("inside main host call")
app.run(host='0.0.0.0', port=3000)
Errors I get when I run "python app.py" are:
On the terminal: Inside root function
127.0.0.1 - - [12/Jun/2017 09:42:24] "GET / HTTP/1.1" 404 -
On the browser:Not Found - The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
I was having the exact same issue and I was able to solve it by appeasing Flask with symlinks.
Keep the templates and static directory paths at their defaults and in the directory with your Flask main file, add these symlinks
ln -s build/ templates
ln -s build/static static
In case you were curious, this was my specific problem, which just involved a few more nested directories but was in essence the same:
Running NPM Build from Flask
You can then use Nurzhan's root configuration:
#app.route('/')
def root():
print('Inside root function')
return render_template('index.html')
But you only require your app declaration to be: app = Flask(__name__)
The only thing that doesn't work for me is the favicon, and I will update this answer once I figure that out.
In development mode, you need to configure your create-react-app package.json to forward "ajax" request to the flask server.
Here is what my package.json looks like:
{
"name": "socialite",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8080",
"devDependencies": {
"react-scripts": "1.0.10"
},
"dependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
See the proxy field? That's where the magic happens, replace its value with the flask server address. That way, you can take advantage of CRA hot reloading feature. This is documented at in create-react-app as "Proxying API Requests in Development"
Then do run your application, you go at localhost:3000 or whatever port yarn opens for you. And when you do an API call in javascript over the rainbow to the server for instance: fetch('/api/model/') or something nodejs' server will forward to the flask app. I think the nodejs server does look at the content-type field of the ajax request to know whether it should forward the request to the backend server or not.
I recommend you prefix all your backend routes with something like /api/v1/ or something so the nginx configuration is neat and easy to write.
I think you have a number of misunderstandings.
The create-react-app runs on its own server on port 3000 and if you try to run your flask app on the same port on the same machine it will complain that port 3000 is already in use. So from this we move to another question - the structure of your application.
Will it be a separate reactjs based client on the frontend and api based on flask in the backend which will be 2 separate applications communicating with each other over HTTP? In this case the frontend and backend will usually run on separate servers.
Or it will one flask application which will use reactjs in its template pages?
You can fix your current problem with not finding URL by changing to this in your code:
#app.route('/')
def root():
print('Inside root function')
return render_template('index.html')
And this:
template_dir = os.path.abspath('build/templates')
app = Flask(__name__, static_path='/build/static/',
template_folder=template_dir)
Since your templates folder is in the build directory.

Combining resources in Python with Flask

I' trying to combine two independent Flask apps like the example below:
from geventwebsocket import WebSocketServer, Resource
...
server = WebSocketServer(('', 8080), Resource({
'/': frontend,
'/one': flask_app_one,
'/two': flask_app_two}))
server.serve_forever()
Inside each Flask app I declare the full path, isn't that suppose to be relative path, inside flask_app_one:
from flask import Flask
app = Flask(__name__)
#app.route('/one/ping')
def ping():
return 'hello\n'
Why I should specify in #app.route('/one/ping') instead of just #app.route('/ping') since all traffic to /one will be forwarded to the corresponding app?
Let me know if you need any additional info I kept my example clean
Thank you
Finally I have managed to do it with the so called Application Dispatching and the resources found in this page:
http://flask.pocoo.org/docs/0.10/patterns/appdispatch/#app-dispatch
Thanks

How to see if a Flask app is being run on localhost?

I want my Flask app to have different behaviors when it is being run on localhost and when it is being hosted online. How can I detect from a flask app when it is on localhost and when it is deployed?
You'll want to look at the configuration handling section of the docs, most specifically, the part on dev / production. To summarize here, what you want to do is:
Load a base configuration which you keep in source control with sensible defaults for things which need to have some value. Anything which needs a value should have the value set to what makes sense for production not for development.
Load an additional configuration from a path discovered via an environment variable that provides environment-specific settings (e. g. the database URL).
An example in code:
from __future__ import absolute_imports
from flask import Flask
import .config # This is our default configuration
app = Flask(__name__)
# First, set the default configuration
app.config.from_object(config)
# Then, load the environment-specific information
app.config.from_envvar("MYAPP_CONFIG_PATH")
# Setup routes and then ...
if __name__ == "__main__":
app.run()
See also: The docs for Flask.config
Here is one way of doing it. The key is comparing the current root url flask.request.url_root against a known url value you want to match.
Excerpt taken from github repo https://github.com/nueverest/vue_flask
from flask import Flask, request
def is_production():
""" Determines if app is running on the production server or not.
Get Current URI.
Extract root location.
Compare root location against developer server value 127.0.0.1:5000.
:return: (bool) True if code is running on the production server, and False otherwise.
"""
root_url = request.url_root
developer_url = 'http://127.0.0.1:5000/'
return root_url != developer_url

Categories