I have setup a Flask app in a cPanel server with CloudLinux. I used the Setup Python App feature.
The app is at a folder /home/user/app . The app URL is https://www.example.com/app
cPanel uses Phusion Passenger to make the app work with Apache.
When I created the app, a folder /home/user/app/public was created and supposedly all images should go there.
The app is working well, but all URLs to images in the public folder are giving 404 error. I've tried different urls like https://www.example.com/app/image.jpg, and https://www.example.com/app/public/image.jpg . Always 404.
This is the first time I am using this Setup Python App feature.
Does anyone know which is the right URL to use, or what needs to be configured in order for images to be served?
Thanks!
EDIT:
The image url is created using url_for:
return url_for('general.send_data_file', filename=key)
The generated url looks correct
I added a route to serve the files:
#general_blueprint.route('/public/data_store/data/<filename>', methods=['GET'])
def send_data_file(filename):
return send_from_directory(app.config.get('DATA_FOLDER'), filename)
I get 404 instead of the file.
The generated file url is /app/public/data_store/data/test.jpg for example.
The app runs under /app, and all created routes work and don't use the /app when defining the route. I added an app.logger.info call inside the send_data_file, and it is being called.
app.config.get['DATA_FOLDER'] equals ./public/data_store/data
No error in log from send_from_directory .
For serving static files outside your main static folder within your app you need to change that in Flask config like this:
app = Flask(__name__, static_folder='/home/user/app/public')
Then, use url_for as relative path with static as first parameter in your HTML file:
<img src="{{ url_for('static', filename='images/image.svg') }}" alt="svg image">
Related
Background:
I've built and deployed an app with Google Cloud Firebase. At a high level, I have a Python Flask server running on Cloud Run, and I serve static JS files with Firebase hosting.
Issue:
Sometimes, I want to redirect the user, but I'm sending them to the Cloud Run service URL rather than my app domain.
EDIT: I'm NOT experiencing this in the JS on the browser, but ONLY in the Python on the server.
Python
If a user navigates to a page without being signed in, e.g. following a link, they are redirected to my login page. For example, if someone who is not signed in tries to look at someone else's profile, the following code redirects them to the authentication blueprint's login endpoint:
if not session.get('user'):
return redirect(url_for('authentication.login'))
I would expect them to be redirected to my-app-name.web.app/auth/login but instead they're routed to my-cloudrun-service-name-XXXXX-region.run.app/auth/login. While the pages loaded look the same, they're really not. The second one redirects to the Cloud Run service URL, which doesn't have my static files served by Firebase Hosting.
I'm not really sure, but I believe this happens because Flask is generating the URL using the request context. So the request from the browser hits Cloud Run Load Balancer, which directs the request to my Cloud Run instance, but that means the Flask app in my Cloud Run instance only sees the internal Google Cloud redirect, and it doesn't know my domain.
I've tried solving this by setting app.config['SEVER_NAME'] = my-app-name.web.app, but I just get the "Not Found" page on each request. Is SEVER_NAME the solution but I'm not implementing it correctly, or is there another way to fix the Flask url_for generation?
I've found what I deem to be an effective solution / workaround.
I set the app config to store my BASE_URL when the app is created:
app.config['BASE_URL'] = 'https://my-url.domain'
Then I can access this as application context during requests, even from blueprints:
#blueprint.route('my_route/')
def my_route():
if not session.get('user'):
return redirect(current_app.config['BASE_URL'] + url_for('authentication.login', 302)
This has two key benefits for me:
There's only one place, the app config, to update if I change the domain
url_for is still used, so there's no hardcoding in case blueprints or routes change
I'm trying to serve some simple service using flask and flask_restx (a forked project of flask-restplus, that would be eventually served on AWS.
When it is served, I want to generate swagger page for others to test it easily.
from flask import Flask
from flask_restx import Api
from my_service import service_namespace
app = Flask(__name__)
api = Api(app, version='1.0')
api.add_namespace(service_namespace)
if __name__ == '__main__':
app.run(debug=True)
When I test it locally (e.g. localhost:5000), it works just fine. Problem is, when it is hosted on AWS, because it has a specific domain (gets redirected?) (e.g. my-company.com/chris-service to a container), the document page is unable to find its required files like css and so:
What I've looked and tried
Python (Flask + Swagger) Flasgger throwing 404 error
flask python creating swagger document error
404 error in Flask
Also tried adding Blueprint (albeit without knowing exactly what it does):
app = Flask(__name__)
blueprint = Blueprint("api", __name__,
root_path="/chris-service",
# url_prefix="/chris-service", # doesn't work
)
api = Api(blueprint)
app.register_blueprint(blueprint)
...
And still no luck.
Update
So here's more information as per the comments (pseudo, but technically identical)
Access point for the swagger is my-company.com/chris (with or without http:// or https:// doesn't make difference)
When connecting to the above address, the request URL for the assets are my-company.com/swaggerui/swagger-ui.css
You can access the asset in my-company.com/chris/swaggerui/swagger-ui.css
So I my resolution (which didn't work) was to somehow change the root_path (not even sure if it's the correct wording), as shown in What I've looked and tried.
I've spent about a week to solve this but can't find a way.
Any help will be greatful :) Thanks
Swagger parameters defined at apidoc.py file. Default apidoc object also created in this file. So if you want to customize it you have change it before app and api initialization.
In your case url_prefix should be changed (I recommend to use environment variables to be able set url_prefix flexibly):
$ export URL_PREFIX='/chris'
from os import environ
from flask import Flask
from flask_restx import Api, apidoc
if (url_prefix := environ.get('URL_PREFIX', None)) is not None:
apidoc.apidoc.url_prefix = url_prefix
app = Flask(__name__)
api = Api(app)
...
if __name__ == '__main__':
app.run()
Always very frustrating when stuff is working locally but not when deployed to AWS. Reading this github issue, these 404 errors on swagger assets are probably caused by:
Missing javascript swagger packages
Probably not the case, since flask-restx does this for you. And running it locally should also not work in this case.
Missing gunicorn settings
Make sure that you are also setting gunicorn up correctly as well with
--forwarded-allow-ips if deploying with it (you should be). If you are in a kubernetes cluster you can set this to *
https://docs.gunicorn.org/en/stable/settings.html#forwarded-allow-ips
According to this post, you also have to explicitly set
settings.FLASK_SERVER_NAME to something like http://ec2-10-221-200-56.us-west-2.compute.amazonaws.com:5000
If that does not work, try to deploy a flask-restx example, that should definetely work. This rules out any errors on your end.
I am trying to deploy an app flask using flask-restx package and I am stuck on how to display properly the documentation on the prod server. It works well locally without any prefix, but on the server, the files url need to be prefixed by my-prefix and I am struggling to make the swaggerui files accessible from the url : url/my-prefix
Here is my code :
app = Flask(__name__)
api = Api(
app,
version='3.0',
title='My API',
description='My API',
doc="/my-prefix/docs/",
prefix="/my-prefix",
base_url="/my-prefix"
)
With this I get the swagger.json at the good url but not with the right reference in the code source of the documentation page. The swaggerui files are not well referenced in the code source oof the documentation page and not at the accessible from url/my-prefix/swaggerui/files.
So I have added this :
#apidoc.apidoc.add_app_template_global
def swagger_static(filename):
return f"/my-prefix/swaggerui/{filename}"
api = Api(
app,
version='3.0',
title='My API',
description='My API',
doc="/my-prefix/docs/",
prefix="/my-prefix",
base_url="/my-prefix"
)
#api.documentation
def custom_ui():
return render_template("swagger-ui.html", title=api.title, specs_url="/my-prefix/swagger.json")
With this, the swaggerui files and the swagger.json file are conveniently referenced in the code source of the documentation page (url/my-prefix/swaggerui/myfile and url/my-prefix/swagger.json) but the swaggerui files are not reachable from this url. Do you know what I should add to make it accessible at this url : url/my-prefix/swaggerui/myfile
I want to upload a js file in the root directory of my flask application such that I can access it on the url localhost:5000/file.js
I tried to put the js file on the same directory as app.py, but that didn't work.
It won't work like that, your app.py is running the server, so though it can access the js file, it would have to be on a route to shown. I would suggest you bring the js code to .html file in the tags and then return render_template on this #app.route('/') . This should display the html page whenever you visit 127.0.0.1:5000/ and run the JavaScript consequently.
My flask application is in my C: drive. The images I want to display on my flask server are on my D: drive because there are 160 GB of them, which my C: drive does not like to store. I was wondering how to access these files outside of the root of the flask application. Any help is appreciated!
SOLVED [Thanks to Shivendra Pratap Kushwaha]
I was able to access the other drive with the following test code:
HTML with Jinja2:
<img id="myimage9" src="{{ url_for('main.sendfiletest') }}" width="400" height="300" alt="TestLabel">
Python 3.7:
from flask import send_file
#main.route('/uploads/')
def sendfiletest():
test_url = "D:\myimage.png"
return send_file(test_url, mimetype='image/jpg')
Thanks for everyone for trying to help me out!
Add below code in your routes.py :
from flask import send_file
file = "D:\your\file\name"
return send_file(file, mimetype='image/jpg')
Do you want to expose images stored on your computer? Does that mean your computer is supposed to be the server of your flask app?
You could store your images on a AWS S3 bucket or Google Drive and store the links to these images in your flask app.