Proper URL routing configuration for Flask with proxy pass - flask

I'm struggling to find the proper way to setup my flask routes when moving my app to being hosted in a subdirectory. I've read a bunch of great answers on this subject, most notably this answer on adding a prefix to all routes. However, I think my situation is slightly different. I want to ONLY prefix the URLs I generate with url_for, not respond to those URLs. Is there a way to do this?
For example, assume my flask app is hosted at http://www.acme.com/tools/. The main acme.com domain is setup with a proxy pass to pass all requests from /tools/* to my app. So, my app only sees requests like /, /product_1/ even though the full URL is http://www.acme.com/tools/product_/, etc. So, my app.route rules can use / as the base route. The problem is I use url_for everywhere and I want the urls generated to be the full url like /tools/product_1, not /product_1. Is there a way to achieve this?
I've tried using blueprints but those will make the #app.route and url_for respond to /tools/xyz. Is there a more simple solution to this issue or is this a non-standard way to handle this issue?

I would take a look at this snippet: http://flask.pocoo.org/snippets/35/
I'm not sure I love the idea of modifying your WSGI environment as the only solution to this problem. You can always wrap url_for. (note that this won't work for _external=True)
def my_url_for(*args, **kwargs):
return '/tools' + url_for(*args, **kwargs)
Be sure to set your APPLICATION_ROOT to /tools so that cookies are only accessible from the app.

I found a different way to handle this without having to provide a wrapper for url_for. I'm using a custom routing rule to append the prefix. This means none of the app.route decorator calls have to be changed and all calls to url_for will automatically get the prefix added. Are there any caveats I'm missing by overriding the url_rule_class?
Add the following to your __init__.py after setting up your app.
from werkzeug.routing import Rule
class PrefixRule(Rule):
def build(self, *args, **kwargs):
domain_part, url = super(PrefixRule, self).build(*args, **kwargs)
return domain_part, u'%s%s' % (app.config['PREFIX'], url)
app.url_rule_class = PrefixRule

Related

Django drf-spectacular - Can you exclude specific paths?

We have a bunch of api's with different versions in urls.py, eg
api/v1
api/v2
api/v3
. We want to implement swagger with drf-spectacular, but we only want to expose api/v3 endpoints.
Is there a way to do this? I cannot make sense of the documentation.
Thanks
This works for me:
def preprocessing_filter_spec(endpoints):
filtered = []
for (path, path_regex, method, callback) in endpoints:
# Remove all but DRF API endpoints
if path.startswith("/api/"):
filtered.append((path, path_regex, method, callback))
return filtered
In settings:
"PREPROCESSING_HOOKS": ["common.openapi.preprocessing_filter_spec"],
Worked it out. Copied the custom preprocessing hook function, changed the pass statement to filter what I did not require in the endpoints, then mapped the location of the file and function in my spectacular settings for preprocessed hooks.

Dynamic Allowed Host in Django

I'm developing multitenant application using django. Every things works fine. But in the case of ALLOWED_HOST , I've little bit confusion, that how to manage dynamic domain name. I know i can use * for any numbers for domain, But i didn't wan't to use * in allowed host.
Here is my question is there any way to manage allowed host dynamically.
According to the Django doc,
Values in this list can be fully qualified names (e.g. 'www.example.com'), in which case they will be matched against the request’s Host header exactly (case-insensitive, not including port). A value beginning with a period can be used as a subdomain wildcard: '.example.com' will match example.com, www.example.com, and any other subdomain of example.com. A value of '*' will match anything; in this case you are responsible to provide your own validation of the Host header (perhaps in a middleware; if so this middleware must be listed first in MIDDLEWARE).
So, if you want to match a certain subdomain, you can use the subdomain wildcard as explained above. If you want a different validation, as the documentation says, the right way to do it would be to allow all hosts and write your own middleware.
If you don't know about middlewares, they are a simple mechanism to add functionality when certain events occur. For example, you can create a middleware function that executes whenever you get a request. This would be the right place for you to put your Host validation. You can get the host using the request object. Refer to the official docs on middleware if you want to know more.
A simple middleware class for your problem could look something like -
import requests
from django.http import HttpResponse
class HostValidationMiddleware(object):
def process_view(self, request, view_func, *args, **kwargs):
host = request.get_host()
is_host_valid = # Perform host validation
if is_host_valid:
# Django will continue as usual
return None
else:
response = HttpResponse
response.status_code = 403
return response
If you need more information, comment below and I will try to help.
The setting should be loaded once on startup - generally those settings don't change in a production server setup. You can allow multiple hosts, or allow multiple subdomains.
*.example.com would work, or you can pass a list of domains if I remember correctly.

HTTPS equivalent of Django's HttpResponse

For some reason I am in need of a views.py that returns only some text. Normally, i'd use HttpResponse("text") for this. However, In this case I require the text to be send over https, to counter the inevitable mixed content warning.
What is the simplest way of sending pure text via django(1.7.11) over https?
Django in the relevant docs of httprequest.build_absolute_uri reads:
Mixing HTTP and HTTPS on the same site is discouraged, therefore
build_absolute_uri() will always generate an absolute URI with the
same scheme the current request has. If you need to redirect users to
HTTPS, it’s best to let your Web server redirect all HTTP traffic to
HTTPS.
The docs make clear that
the method of communication is entirely the responsibility of the server
as Daniel Roseman commented.
My prefered choice is to force https throughout a site, however it is possible to do it only for a certain page.
The above can be achieved by either:
Upgrading to a secure and supported release of Django where the use of SECURE_SSL_REDIRECT and SecurityMiddleware will redirect all traffic to SSL
Asking your host provider an advice on how could this be implemented in their servers
Using the apache config files.
Using .htaccess to redirect a single page.
There are also other -off the road- hackish solutions like a snippet which can be used with a decorator in urls.py to force https, or a custom middleware that redirects certain urls to https.
I've run into the mixed content problems as well. From my experience, you simply can't use the HttpResponse objects without running into trouble. I was never totally sure though and eventually found a way "around" it.
My solution for it was to use the JsonResponse object instead, to return JSON strings, kind of a work-around with the views returning something like:
mytext = 'stuff blablabla'
return JsonResponse({'response_text': mytext})
Which is super easy to parse, and OK with HTTPS.
Maybe not what you're looking for, but I hope it helps you find your way.

Service Worker caching resources with Flask Blueprints

I am using Flask as a web framework. I have defined an html resource within my service worker definition like below
var urlsToPrefetch = [
'webapp/templates/practice/foopage.html',
];
This works ok.
Using Flask Blueprints, foopage.html is rendered to the user when they visit https://example.com/practice/foopage. The Blueprint name is "practice".
The corresponding route is below:
#practice.route('/foopage', methods=['GET'])
def foopage():
return render_template('practice/foopage.html')
The problem is that within the service worker's fetch eventListener shows the event.request.url as https://example.com/practice/foopage
This is not found in the cache because I had to define the html file using its actual path.
Is there a way to map filenames to the routes used in the application? Or do I need to think about this differently?
You're confusing paths to templates, which is a server side thing, with urls that get routed to views. As far as the client can tell, the response from /foopage is the document, it doesn't matter how that document was generated on the server side.
Specify the url to the resource, /foopage, as the thing to prefetch.

Django as reverse proxy

My client-server application is mainly based on special purpose http server that communicates with client in an Ajax like fashion, ie. the client GUI is refreshed upon asynchronous http request/response cycles.
Evolvability of the special purpose http server is limited and as the application grows, more and more standard features are needed which are provided by Django for instance.
Hence, I would like to add a Django application as a facade/reverse-proxy in order to hide the non-standard special purpose server and be able to gain from Django. I would like to have the Django app as a gateway and not use http-redirect for security reasons and to hide complexity.
However, my concern is that tunneling the traffic through Django on the serer might spoil performance. Is this a valid concern?
Would there be an alternative solution to the problem?
Usually in production, you are hosting Django behind a web container like Apache httpd or nginx. These have modules designed for proxying requests (e.g. proxy_pass for a location in nginx). They give you some extras out of the box like caching if you need it. Compared with proxying through a Django application's request pipeline this may save you development time while delivering better performance. However, you sacrifice the power to completely manipulate the request or proxied response when you use a solution like this.
For local testing with ./manage.py runserver, I add a url pattern via urls.py in an if settings.DEBUG: ... section. Here's the view function code I use, which supports GET, PUT, and POST using the requests Python library: https://gist.github.com/JustinTArthur/5710254
I went ahead and built a simple prototype. It was relatively simple, I just had to set up a view that maps all URLs I want to redirect. The view function looks something like this:
def redirect(request):
url = "http://%s%s" % (server, request.path)
# add get parameters
if request.GET:
url += '?' + urlencode(request.GET)
# add headers of the incoming request
# see https://docs.djangoproject.com/en/1.7/ref/request-response/#django.http.HttpRequest.META for details about the request.META dict
def convert(s):
s = s.replace('HTTP_','',1)
s = s.replace('_','-')
return s
request_headers = dict((convert(k),v) for k,v in request.META.iteritems() if k.startswith('HTTP_'))
# add content-type and and content-length
request_headers['CONTENT-TYPE'] = request.META.get('CONTENT_TYPE', '')
request_headers['CONTENT-LENGTH'] = request.META.get('CONTENT_LENGTH', '')
# get original request payload
if request.method == "GET":
data = None
else:
data = request.raw_post_data
downstream_request = urllib2.Request(url, data, headers=request_headers)
page = urllib2.urlopen(downstream_request)
response = django.http.HttpResponse(page)
return response
So it is actually quite simple and the performance is good enough, in particular if the redirect goes to the loopback interface on the same host.