My web app assigns a subdomain to users and optionally allows them to use a custom domain. This works except when the user visits their custom domain for a route without including a trailing slash.
GET requests to this url works as expected: http://user.example.com:5000/book/12345/
GET requests to this url works as expected: http://custom.com:5000/book/12345/
GET requests to this url attempt to redirect, but fail: http://custom.com:5000/book/12345
Flask ends up redirecting the browser to this url which, of course, doesn't work: http://<invalid>.example.com:5000/book/12345/
Is there a different way that I should handle custom domains? Here's a complete minimal example to reproduce this. I have set custom.com, example.com. and user.example.com to point to 127.0.0.1 in my /etc/hosts file in my development environment so that Flask receives the request.
from flask import Flask
app = Flask(__name__)
server = app.config['SERVER_NAME'] = 'example.com:5000'
#app.route('/', subdomain="<subdomain>")
#app.route('/')
def index(subdomain=None):
return ("index")
#app.route('/book/<book_id>/', subdomain="<subdomain>")
#app.route('/book/<book_id>/')
def posts(post_id, subdomain=None):
return (book_id)
if __name__ == '__main__':
app.run(host='example.com', debug=True)
I'm not sure that's possible. host matching and subdomain matching are mutually exclusive (look at host matching parameter).
I'd love to be wrong though.
One way around this issue that I can think of is to use something in front of Flask (say nginx) that points custom.com to custom.com._custom.example.com or something like that. In your code you could create a custom url_for function that would recognize this as a custom domain. I would ask on the Flask mailing list as they would be able to give you a solid answer.
Related
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 using flask and flask email for sending an email. When I work on I used the localhost as a base url. I deployed it on the server and email sent is still showing with localhost address.
For example
base_url = "http://localhost:0.0.0.6465"
url=base_url+'/login'
I sent an email(i am using flask-mail) with the url and I can login with the url.
With the same script when I deployed on the server I am getting with same localhost address.I need the server URL should be the base url.
To debug this I tried
url=request.base_url+'/login'
I am getting 404 error in the browser if I use this. I dont want to change the initializtion of base_url because I have to use both in the local as well as in the server.
How can I do that?
You can get the URL to the currently running app through request.host_url. But what you really want to to do to get an external URL to a specific part of your application, is to use url_for as you'd do when referencing your regular endpoints, but with the parameter _external=True:
Given that you have:
#app.route('/login')
def login():
....
You can generate an external URL by using:
from flask import (
url_for,
)
...
url = url_for('login', _external=True)
This will also take into account any proxies in front of your application if you need that, as long as you've used the ProxyFix middleware when setting up your app object.
Since this uses the same mechanism as Flask uses when generating URLs between different pages, it should behave just as you want - i.e. it'll work both on localhost and on the remote host.
I have an app that requires multiple domains pointing to the same app with different data being displayed, but I also have the same "admin" subdomain across all domains which also displays different data depending on the domain.
An example would be:
pinetree.com - displays information about pine trees
oaktree.com - displays information about oak trees
admin.pinetree.com - displays admin for managing pine trees
admin.oaktree.com - displays admin for managing oak trees
So far, I've found that you need to write the SERVER_NAME (domain name) in the Flask config in order to use subdomains with Flask, but since I have many different types of trees with unique domains, and new trees are added all the time, I don't see how I could use that functionality.
Also, I have seen that GAE flexible doesn't have multitenancy, which is what I had first thought would be the way to manage multiple domains on GAE.
Subdomain matching, explained in another answer, should be used if you have one base domain with several subdomains. It's more straightforward since Flask can infer more about the URLs it's matching.
However, if you have multiple base domains, you should use host matching instead. You must set host_matching=True on the app object, as well as setting static_host so the static route knows which host to to serve from. Unlike subdomains, you do not set SERVER_NAME. Then pass the host option to routes. This matches against the full domain, and so it requires writing out the full domain each time, rather than just the subdomain.
Unfortunately, matching the full host means matching the port as well. Under the dev server, the port will be 5000 by default, but in production the port may be 80, 443, or something else. You can write a small helper to set the port to 5000 when running in development mode (or whatever configuration logic you need for your deployment).
from flask.helpers import get_env
def p(host):
if get_env() == "development":
return host + ":5000"
return host
# p("example.com") -> "example.com:5000"
This example shows routing to any host of the form {tree}tree.com and admin.{tree}tree.com, with pinetree.com as the static host.
from flask import Flask
app = Flask(__name__, host_matching=True, static_host=p("pinetree.com"))
#app.route("/", host=p("<tree>tree.com"))
def index(tree):
return f"{tree} tree"
Blueprint does not accept a host option yet, so you'll need to specify the host for each route. You can simplify this a bit using partial.
from functools import partial
from flask import Blueprint
admin = Blueprint("admin", __name__)
admin_route = partial(admin.route, host=p("admin.<tree>tree.com"))
#admin_route("/")
def index(tree):
return f"admin for {tree} tree"
app.register_blueprint(admin)
Note that the host can take URL parameters just like the path in the route. It will be passed to views just like path parameters. This allows for dynamic hosts and subdomains. You can use #app.url_defaults and #app.url_value_preprocessor to extract this into g instead of writing it as an argument for each view.
from flask import g
#app.url_value_preprocessor
def extract_tree(endpoint, values):
g.tree = values.pop("tree")
#app.url_defaults
def inject_tree(endpoint, values):
values.setdefault("tree", g.tree)
#app.route("/")
def index()
return f"{g.tree} tree"
During development, add the hosts your hosts file (/etc/hosts on Unix so they route to localhost.
127.0.0.1 localhost pinetree.com admin.pinetree.com oaktree.com admin.oaktree.com
And run with:
export FLASK_DEBUG=1
flask run
I am creating a REST API using python flask. The API is ready and works on port number 8000 of my localhost. Now I intend to give this REST API a user friendly interface for which I decided to go with python - restplus. I thought of calling this service (running on 8000) internally from swagger application running on 5000
I was able to create the basic structure of the API (Swagger). The code for which looks like this:
import flask
from flask import Flask, request
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app)
#api.route('/HybridComparator/<string:url2>/<string:url1>')
class HybridComparator(Resource):
def get(self, url1, url2):
print(url1)
print(url2)
return url1 + ' ' + url2
if __name__ == '__main__':
app.run(debug=True)
The application as a whole runs seamlessly (with random strings as parameters) on port 5000. But when the URLs I pass are actual links, the application returns a response of 404 - Not found. Further to my investigation I realized the culprit being '/' embedded within the links I try to provide. Is there a way to handle URLs in particular?
Should I encode them before sending a request. (This will make my parameters look ugly). Is there something I am missing?
This is an entirely old question and I am sure you solved your problem by now.
But for new searchers, this may come in handy;
replace <string:url2>/<string:url1> with <path:url2>/<path:url1>
it seems that :
#api.route('/HybridComparator/<path:url2>/<path:url1>')
should fix it ,it fixes the 404 but i am getting only "http:/" part of the param
I have been successful in adding Heroku custom domains for my app. I'd like to know how to capture requests that begin with www and redirect them to the naked domain. For example, I have the following custom domains mapped to my Heroku app:
www.myapp.com
myapp.com
Requests for http://myapp.com and http://www.myapp.com are both successful, but the www stays on.
Objective
I want all requests for http://www.myapp.com to be redirected to http://myapp.com. This should also work for other paths, like http://www.myapp.com/some/foo/bar/path redirects to http://myapp.com/some/foo/bar/path. I want something like this: http://www.stef.io and see how the www. is gone from the address bar.
The instructions I've found on Google so far are about editing my .htaccess file, but I'm running on Heroku with a Python app on the Flask framework.
The recommended solution for this is DNS level redirection (see heroku help).
Alternatively, you could register a before_request function:
from urlparse import urlparse, urlunparse
#app.before_request
def redirect_www():
"""Redirect www requests to non-www."""
urlparts = urlparse(request.url)
if urlparts.netloc == 'www.stef.io':
urlparts_list = list(urlparts)
urlparts_list[1] = 'stef.io'
return redirect(urlunparse(urlparts_list), code=301)
This will redirect all www requests to non-www using a "HTTP 301 Moved Permanently" response.
I'm not used to Flask, but you could set up a route that matches /^www./ and then redirect it to the url without the "www".
You may want to check http://werkzeug.pocoo.org/docs/routing/#custom-converters or http://werkzeug.pocoo.org/docs/routing/#host-matching