Writing Tornado templates to work with nginx proxied location - python

I have a Tornado application that I want to host under a non-root location with nginx. So I have an nginx configuration that looks like
server {
listen 80;
server_name mysite.com;
location /myapp/ {
proxy_pass http://localhost:8888/;
}
}
I want the app to live at mysite.com/myapp/. With the current configuration all of the Handlers are routed to the correct url however all of the links used in the templates are wrong. For example
<a href='/'>Home</a>
links to mysite.com/ rather than mysite.com/myapp/. I'd also like the app to still work locally so I don't want /myapp/ hard-coded into the templates.
Is there a way to deal with this with either nginx or Tornado?
My solution thus far has been to add a convenience function to the template namespace
import tornado.ioloop
import tornado.web
import os
APPLICATION_ROOT = os.environ.get('MYAPP_ROOT')
class BaseHandler(tornado.web.RequestHandler):
def full_url(self, path, *args, **kwargs):
if path.startswith('/'):
path = path.lstrip('/')
return os.path.join(APPLICATION_ROOT, path)
else:
return path
def get_template_namespace(self):
"""Returns a dictionary to be used as the default template namespace.
May be overridden by subclasses to add or modify values.
The results of this method will be combined with additional
defaults in the `tornado.template` module and keyword arguments
to `render` or `render_string`.
"""
namespace = dict(
handler=self,
request=self.request,
current_user=self.current_user,
locale=self.locale,
_=self.locale.translate,
pgettext=self.locale.pgettext,
static_url=self.static_url,
xsrf_form_html=self.xsrf_form_html,
reverse_url=self.reverse_url,
full_url=self.full_url
)
namespace.update(self.ui)
return namespace
and set MYAPP_ROOT='/myapp/' as an environment variable and use
Home
in the templates.

Tornado doesn't have good support for applications that can be relocated to different paths like this - it's generally assumed that the path prefix is known and can be hard-coded. (The trailing slash in the nginx proxy_pass directive is significant - remove it and nginx passes the request through as-is to Tornado)
If you still want to use different paths depending on whether you're going through nginx or not, I would recommend a URL helper function as you have here. Other options include always using relative paths instead of absolute ones in your URLs (will probably require using /myapp/home and /home instead of /myapp/ and / for the home page), or having nginx rewrite your links as in this question

Related

flask templates works in local env but return error 404 under nginx

I 've read answer [https://stackoverflow.com/a/20895594/305883] but did not help for me.
I have a flask app that can serve a template in localhost or debug at endpoint /path/<int:id>/, but in production with nginx it will fail with error 404.
Flask project has default structure:
app.py
index.html
/templates/mytemplate.html
/static/..
mytemplate.html will load resources from /static/ folder, with jinja syntax.
EDITED
Goal: I want the application to serve:
root / will serve index.html
/path/ will open 'myTemplate.html' and populate it with variables
(jinja); if possible I want static assets included in template (e.g
js, css, images) to be served by nginx;
/api/ will serve api rest.
In my local environment I am using flask server, not ningx, and three end-points are working as expected.
In production, I use flask and ningx.
Root and /api/ edges work; /path/ , used for templating in flask, does not and return error 404.
For setting up nginx I followed the steps in this tutorial:
[https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-uwsgi-and-nginx-on-ubuntu-14-04]
The template in flask is served by:
#application.route('/path/<int:id>/')
def graph_template(id):
meta = {'og:image':'/url/image.jpg'}
try:
key = decode(id)[0]
except:
# todo replace with error 400
return jsonify({'error':'Something got wrong. ID not found'})
return render_template('mytemplate.html', meta=meta, id = id)
I am having difficulties in debugging and find the problem, making /path/ display the template.
nginx configuration
server {
listen 80;
# is this block to serve index on root only ?
# or will search for index files also in routes /path/ in ningx and/or flask?
root /var/www/mySite;
index index.html index.htm;
location /api {
# try_files $uri $uri/ /index.html;
include uwsgi_params;
uwsgi_pass unix:/var/www/mySite/myApp.sock;
# auth_basic "API Login";
# auth_basic_user_file /etc/nginx/pw_file;
# allow 127.0.0.1;
# allow XX.XX.XXX.XXX;
# deny all;
}
location /static {
alias /var/www/mySite/static;
}
}
I tried the following:
Included a proxy_pass:
location / {
proxy_pass http://127.0.0.1:8080;
}
result: cause error 502
Tried by changing port to :80
location / {
proxy_pass http://127.0.0.1:80;
}
result: error 404
Tried with uwsgi_pass socket at /path/ endpoint
location /path/ {
include uwsgi_params;
uwsgi_pass unix:/var/www/mySite/myApp.sock;
}
Result: This route does not exist http://example.com/path/
Which I don't understand because flask should serve the template here - at least not a misconfiguration error 502.
Answer at: [Python Flask server crashing on GET request to specific endpoint: shown two sockets for each endpoint - do I need to use such setup?
I m trying to document myself on what a socket is and using nginx documentation but sorry I m not that competent in it and I m moving a bit in darkness.
Could you help in debugging the problem?
YAII! I was able to find the answer myself:
adding this block in nginx worked out for serving templates from flask with nginx:
location /path/ {
include uwsgi_params;
uwsgi_pass unix:/var/www/mySite/myApp.sock;
}
where /path/ is the endpoint of the template.
I was receiving the message above because flask was actually handling the 404 error, due to I wrote my decorator to hit /path/<int:id> and handling 404 requests with:
#application.errorhandler(404)
def page_not_found(error):
return 'This route does not exist {}'.format(request.url), 404
However, I will be very grateful if someone could add some comments explaining how the routing between flask and nginx actually works, and a distinction between using a uwsgi_pass and proxy_pass (which I found in different tutorials), and the use of sockets (tutorial I followed).
As example, in this question [Python Flask server crashing on GET request to specific endpoint: the user use two sockets for each endpoint, and for me as beginner I thought it may have been the issue and it is not.
With this configuration, I understood flask will handle the .html template: does it handle also the resource in the template (js, css) or are they served by nginx (please see configuration above, all assets are in /static/ folder)
Would there be a better configuration to exploit ningx capabilities in serving flask templates ?
Comments most appreciated!
I assume your local env works without nginx, right? So, lets have a look:
In local environment, hitting /path/ (with no id) will return an error of url not found, which is ok. Instead in prod environment, nginx will search for /path/index.html - although I have not mentioned in flask decorator.
Have a look at line three of your nginx config. In there, you specified that nginx should look for index.html.
In local env, if I hit /path/id/ it works ok. In production env, it returns error 404, no such file or directory.
I assume, this is because you don't pass any traffic to your flask app. You need something along the lines of:
location / {
proxy_pass http://127.0.0.1:8080; #Change to match your environment
}
You're missing these (if not, please show your whole nginx config), so nginx will search for static files on your storage. However, these requests can't be served by static files, but instead your backend (aka your flask app) has to render and create these dynamically. For this to work, you have to send the traffic there.
I have not found an error due to failing with resources from /static/ folder (e.g. in permission); it looks like flask cannot load or be allowed to serve the templates.
Template file can be loaded by the browser at /mysite/templates/mytemplate.html Static folder permission is set on 403, but /static/js/file.js will be read.
Sorry, I don't get this. Could you elaborate?

Restricting access to private file downloads in Django

I have multiple FileFields in my django app, which can belong to different users.
I am looking for a good way to restrict access to files for user who aren't the owner of the file.
What is the best way to achieve this? Any ideas?
Unfortuanately #Mikko's solution cannot actually work on a production environment since django is not designed to serve files. In a production environment files need to be served by your HTTP server (e.g apache, nginx etc) and not by your application/django server (e.g uwsgi, gunicorn, mod_wsgi etc).
That's why restricting file acccess is not very easy: You need a way for your HTTP server to ask the application server if it is ok to serve a file to a specific user requesting it. As you can understand thiss requires modification to both your application and your http server.
The best solution to the above problem is django-sendfile (https://github.com/johnsensible/django-sendfile) which uses the X-SendFile mechanism to implement the above. I'm copying from the project's description:
This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.
To understand more about the senfile mechanism, please read this answer: Django - Understanding X-Sendfile
2018 Update: Please notice that django-sendfile does not seem to be maintained anymore; probably it should still be working however if you want a more modern package with similar functionality take a look at https://github.com/edoburu/django-private-storage as commenter #surfer190 proposes. Especially make sure that you implement the "Optimizing large file transfers" section; you actuallyu need this for all transfers not only for large files.
2021 Update: I'm returning to this answer to point out that although it hasn't been updated for like 4 years, the django-sendfile project still works great with the current Django version (3.2) and I'm actually using it for all my projects that require that particular functionality! There is also an actively-maintained fork now, django-sendfile2, which has improved Python 3 support and more extensive documentation.
If you need just moderate security, my approach would be the following:
1) When the user uploads the file, generate a hard to guess path for it. For example you can create a folder with a randomly generated name for each uploaded file in your /static folder. You can do this pretty simply using this sample code:
file_path = "/static/" + os.urandom(32).encode('hex') + "/" + file_name
In this way it will be very hard to guess where other users' files are stored.
2) In the database link the owner to the file. An example schema can be:
uploads(id, user_id, file_path)
3) Use a property for your FileFields in the model to restrict access to the file, in this way:
class YourModel(models.Model)
_secret_file = models.FileField()
def get_secret_file(self):
# check in db if the user owns the file
if True:
return self._secret_file
elif:
return None # or something meaningful depanding on your app
secret_file = property(get_secret_file)
This is best handled by the server, e.g. nginx secure link module (nginx must be compiled with --with-http_secure_link_module)
Example from the documentation:
location /some-url/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr some-secret";
if ($secure_link = "") {
return 403;
}
if ($secure_link = "0") {
return 410;
}
if ($secure_link = "1") {
// authorised...
}
}
The file would be accessed like:
/some-url/some-file?md5=_e4Nc3iduzkWRm01TBBNYw&expires=2147483647
(This would be both time-limited and bound to the user at that IP address).
Generating the token to pass to the user would use something like:
echo -n 'timestamp/some-url/some-file127.0.0.1 some-secret' | \
openssl md5 -binary | openssl base64 | tr +/ -_ | tr -d =
Generally, you do not route private files through normal static file serving directly through Apache, Nginx or whatever web server you are using. Instead write a custom Django view which handles the permission checking and then returns the file as streaming download.
Make sure files are in a special private folder folder and not exposed through Django's MEDIA_URL or STATIC_URL
Write a view which will
Check that the user has access to the file in your view logic
Open the file with Python's open()
Return HTTP response which gets the file's handle as the parameter http.HttpResponse(_file, content_type="text/plain")
For example see download() here.
For those who use Nginx as a webserver to serve the file, the 'X-Accel-Redirect' is a good choice.
At the first, request for access to the file comes to Django and after authentication and authorization, it redirects internally to Nginx with 'X-Accel-Redirect'. more about this header: X-Accel-Redirect
The request comes to Django and will be checked like below:
if user_has_right_permission
response = HttpResponse()
# Let nginx guess to correct file mime-type by setting
# below header empty. otherwise all the files served as
# plain text
response['Content-Type'] = ''
response['X-Accel-Redirect'] = path_to_file
return response
else:
raise PermissionDenied()
If the user has the right permission, it redirects to Nginx to serve the file.
The Nginx config is like this:
server {
listen 81;
listen [::]:81;
...
location /media/ {
internal; can be accessed only internally
alias /app/media/;
}
...
}
Note: The thing about the path_to_file is that it should be started with "/media/" to serve by Nginx (is clear though)

Flask url_for generating http URL instead of https

I am using url_for to generate a redirect URL when a user has logged out:
return redirect(url_for('.index', _external=True))
However, when I changed the page to a https connection, the url_for still gives me http.
I would like to explicitly ask url_for to add https at the beginning of a URL.
Can you point me how to change it? I looked at Flask docs, without luck.
With Flask 0.10, there will be a much better solution available than wrapping url_for. If you look at https://github.com/mitsuhiko/flask/commit/b5069d07a24a3c3a54fb056aa6f4076a0e7088c7, a _scheme parameter has been added. Which means you can do the following:
url_for('secure_thingy',
_external=True,
_scheme='https',
viewarg1=1, ...)
_scheme sets the URL scheme, generating a URL like https://.. instead of http://. However, by default Flask only generates paths (without host or scheme), so you will need to include the _external=True to go from /secure_thingy to https://example.com/secure_thingy.
However, consider making your website HTTPS-only instead. It seems that you're trying to partially enforce HTTPS for only a few "secure" routes, but you can't ensure that your https-URL is not changed if the page linking to the secure page is not encrypted. This is similar to mixed content.
If you want to affect the URL scheme for all server-generated URLs (url_for and redirect), rather than having to set _scheme on every call, it seems that the "correct" answer is to use WSGI middleware, as in this snippet: http://flask.pocoo.org/snippets/35/
(This Flask bug seems to confirm that that is the preferred way.)
Basically, if your WSGI environment has environ['wsgi.url_scheme'] = 'https', then url_for will generate https: URLs.
I was getting http:// URLs from url_for because my server was deployed behind an Elastic Beanstalk load balancer, which communicates with the server in regular HTTP. My solution (specific to Elastic Beanstalk) was like this (simplified from the snippet linked above):
class ReverseProxied(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
scheme = environ.get('HTTP_X_FORWARDED_PROTO')
if scheme:
environ['wsgi.url_scheme'] = scheme
return self.app(environ, start_response)
app = Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)
The Elastic Beanstalk-specific part of that is HTTP_X_FORWARDED_PROTO. Other environments would have other ways of determining whether the external URL included https. If you just want to always use HTTPS, you could unconditionally set environ['wsgi.url_scheme'] = 'https'.
PREFERRED_URL_SCHEME is not the way to do this. It's ignored whenever a request is in progress.
I tried the accepted answer with an url_for arg but I found it easier to use the PREFERRED_URL_SCHEME config variable and set it to https with:
app.config.update(dict(
PREFERRED_URL_SCHEME = 'https'
))
since you don't have to add it to every url_for call.
If your are accessing your website through a reverse proxy like Nginx, then Flask correctly dectects the scheme being HTTP.
Browser -----HTTPS----> Reverse proxy -----HTTP----> Flask
The easiest solution is to configure your reverse proxy to set the X-Forwarded-Proto header. Flask will automatically detect this header and manage scheme accordingly. There is a more detailed explanation in the Flask documentation under the Proxy Setups section. For example, if you use Nginx, you will have to add the following line in your location block.
proxy_set_header X-Forwarded-Proto $scheme;
As other mentionned, if you can't change the configuration of your proxy, you can either use the werkzeug ProxyFix or build your own fix as described in the documentation:
http://flask.pocoo.org/docs/0.12/deploying/wsgi-standalone/#proxy-setups
Setting _scheme on every url_for() call is extremely tedious, and PREFERRED_URL_SCHEME doesn't seem to work. However, mucking with what the request's supposed scheme is at the WSGI level seems to successfully convince Flask to always construct HTTPS URLs:
def _force_https(app):
def wrapper(environ, start_response):
environ['wsgi.url_scheme'] = 'https'
return app(environ, start_response)
return wrapper
app = Flask(...)
app = _force_https(app)
For anyone ending up here recently there is an official uwsgi fixer for this:
https://stackoverflow.com/a/23504684/13777925
FWIW this still didn't work for me since the header wasn't being set correctly so I augmented the ReversedProxied middleware to prefer https if found thusly:
class ReverseProxied(object):
"""
Because we are reverse proxied from an aws load balancer
use environ/config to signal https
since flask ignores preferred_url_scheme in url_for calls
"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# if one of x_forwarded or preferred_url is https, prefer it.
forwarded_scheme = environ.get("HTTP_X_FORWARDED_PROTO", None)
preferred_scheme = app.config.get("PREFERRED_URL_SCHEME", None)
if "https" in [forwarded_scheme, preferred_scheme]:
environ["wsgi.url_scheme"] = "https"
return self.app(environ, start_response)
Called as:
app = flask.Flask(__name__)
app.wsgi_app = ReverseProxied(app.wsgi_app)
This way if you've set the environment var "PREFERRED_URL_SCHEME" explicitly or if the nginx/etc/proxy sets the X_FORWARDED_PROTO, it does the right thing.
I personally could not fix this problem with any of the answers here, but found that simply adding --cert=adhoc to the end of the flask run command, which makes the flask app run with https, solved the issue.
flask run --host=0.0.0.0 --cert=adhoc

How can I use an app-factory in Flask / WSGI servers and why might it be unsafe?

A question on app callables, WSGI servers and Flask circular imports
I am (possibly) confused. I want to safely create Flask / WSGI apps
from app-factories and still be able to use them in WSGI servers easily.
tl;dr
Can I safely avoid creating an app on import of init (as
recommended)and instead create it later (ie with a factory method)
How do I make that app work neatly with a WSGI server? Especially
when I am passing in the config and other settings not pulling them
from ENV
For example::
def make_app(configdict, appname):
app = Flask(appname)
app.config.update(configdict)
init_db(configdict)
set_app_in_global_namespace(app)
#importing now will allow from pkg import app
from mypackage import views
return app
I would like to use the above "factory", because I want to easily contorl config for testing etc.
I then presumably want to create a wsgi.py module that provides the app to a WSGI server.
So eventually things look a bit like this
init.py::
app = None
def make_app(configdict, appname):
flaskapp = Flask(appname)
flaskapp.config.update(configdict)
init_db(configdict)
global app
app = flaskapp
#importing now will allow from pkg import app
from mypackage import views
return flaskapp
wsgi.py::
from mypackage import app
app = make_app(configfromsomewhere, "myname")
uWSGI::
uwsgi --module=mypackage.wsgi:app
But still wsgi.py is NOT something I can call like wsgi.py --settings=x --host=10.0.0.1
So I don't really know how to pass the config in.
I am asking because while this seems ... OK ... it also is a bit messy.
Life was easier when everything was in the ENV.
And not only but also:
So what is unsafe about using an app-factory
The advice given here <http://flask.pocoo.org/docs/patterns/packages>_
is ::
1. the Flask application object creation has to be in the
__init__.py file. That way each module can import it safely and
the __name__ variable will resolve to the correct package.
2. all the view functions (the ones with a route() decorator on
top) have to be imported in the __init__.py file. Not the object
itself, but the module it is in. Import the view module after
the application object is created.
re: 2., clearly the route decorator expects certain abilities from
an instantiated app and cannot function without them. Thats fine.
re: 1., OK we need the name correct. But what is unsafe ? And
why? Is it unsafe to import and use the app if it is uninitialised?
Well it will break but thats not unsafe.
Is it the much vaunted thread-local? Possibly. But if I am plucking
app instances willy-nilly from random modules I should expect trouble.
Implications - we do not reference the app object from anything other than the views - essentially we keep our modularisation nice and tight, and pass around
dicts, error objects, or even WebObs.
http://flask.pocoo.org/docs/patterns/appdispatch
http://flask.pocoo.org/docs/deploying/#deployment
http://flask.pocoo.org/docs/patterns/packages/#larger-applications
http://flask.pocoo.org/docs/becomingbig
According to the Flask Documentation, an application factory is good because:
Testing. You can have instances of the application with different settings to test every case.
Multiple instances. Imagine you want to run different versions of the same application. Of course you could have multiple instances with different configs set up in your webserver, but if you use factories, you can have multiple instances of the same application running in the same application process which can be handy.
But, as is stated in the Other Testing Tricks section of the documentation, if you're using application factories the functions before_request() and after_request() will be not automatically called.
In the next paragraphs I will show how I've been using the application factory pattern with the uWSGI application server and nginx (I've only used those, but I can try to help you configure it with another server).
The Application Factory
So, let's say you have your application inside the folder yourapplication and inside it there's the __init__.py file:
import os
from flask import Flask
def create_app(cfg=None):
app = Flask(__name__)
load_config(app, cfg)
# import all route modules
# and register blueprints
return app
def load_config(app, cfg):
# Load a default configuration file
app.config.from_pyfile('config/default.cfg')
# If cfg is empty try to load config file from environment variable
if cfg is None and 'YOURAPPLICATION_CFG' in os.environ:
cfg = os.environ['YOURAPPLICATION_CFG']
if cfg is not None:
app.config.from_pyfile(cfg)
Now you need a file to create an instance of the app:
from yourapplication import create_app
app = create_app()
if __name__ == "__main__":
app.run()
In the code above I'm assuming there's an environment variable set with the path to the config file, but you could give the config path to the factory, like this:
app = create_app('config/prod.cfg')
Alternatively, you could have something like a dictionary with environments and corresponding config files:
CONFIG_FILES = {'development': 'config/development.cfg',
'test' : 'config/test.cfg',
'production' : 'config/production.cfg' }
In this case, the load_config function would look like this:
def load_config(app, env):
app.config.from_pyfile('config/default.cfg')
var = "YOURAPPLICATION_ENV"
if env is None and var in os.environ:
env = os.environ[var]
if env in CONFIG_FILES:
app.config.from_pyfile(CONFIG_FILES[env])
Nginx and uWSGI
Here's an example of a configuration file for nginx:
server {
listen 80;
server_name yourapplication.com;
access_log /var/www/yourapplication/logs/access.log;
error_log /var/www/yourapplication/logs/error.log;
location / {
try_files $uri #flask;
}
location #flask {
include uwsgi_params;
uwsgi_pass unix:/tmp/yourapplication.sock;
# /env is the virtualenv directory
uwsgi_param UWSGI_PYHOME /var/www/yourapplication/env;
# the path where the module run is located
uwsgi_param UWSGI_CHDIR /var/www/yourapplication;
# the name of the module to be called
uwsgi_param UWSGI_MODULE run;
# the variable declared in the run module, an instance of Flask
uwsgi_param UWSGI_CALLABLE app;
}
}
And the uWSGI configuration file looks like this:
[uwsgi]
plugins=python
vhost=true
socket=/tmp/yourapplication.sock
env = YOURAPPLICATION_ENV=production
logto = /var/www/yourapplication/logs/uwsgi.log
How to use before_request() and after_request()
The problem with those functions is that if your are calling them in other modules, those modules cannot be imported before the application has been instantiated. Again, the documentation has something to say about that:
The downside is that you cannot use the application object in the blueprints at import time. You can however use it from within a request. How do you get access to the application with the config? Use current_app:
from flask import current_app, Blueprint, render_template
admin = Blueprint('admin', __name__, url_prefix='/admin')
#admin.route('/')
def index():
return render_template(current_app.config['INDEX_TEMPLATE'])
Or you could consider creating an extension, then you could import the class without any existent instances of Flask, as the class extension would only use the Flask instance after being created.

Twisted Web behind Apache - How to correct links?

I am attempting to write a web application using the Twisted framework for python.
I want the application to work if run as a standalone server (ala twistd), or if Apache reverse proxies to it. E.g.
Apache https://example.com/twisted/ --> https://internal.example.com/
After doing some research, it seemed like I needed to use the vhost.VHostMonsterResource to make this work. So I set up apache with the following directive:
ProxyPass /twisted https://localhost:8090/twisted/https/127.0.0.1:443
Here is my basic SSL server:
from twisted.web import server, resource, static
from twisted.internet import reactor
from twisted.application import service, internet
from twisted.internet.ssl import SSL
from twisted.web import vhost
import sys
import os.path
from textwrap import dedent
PORT = 8090
KEY_PATH = "/home/waldbiec/projects/python/twisted"
PATH = "/home/waldbiec/projects/python/twisted/static_files"
class Index(resource.Resource):
def render_GET(self, request):
html = dedent("""\
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Index</title>
</head>
<body>
<h1>Index</h1>
<ul>
<li>Files</li>
</ul>
</body>
</html>
""")
return html
class ServerContextFactory:
def getContext(self):
"""
Create an SSL context.
Similar to twisted's echoserv_ssl example, except the private key
and certificate are in separate files.
"""
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.use_privatekey_file(os.path.join(KEY_PATH, 'serverkey.pem'))
ctx.use_certificate_file(os.path.join(KEY_PATH, 'servercert.pem'))
return ctx
class SSLService(internet.SSLServer):
def __init__(self):
root = resource.Resource()
root.putChild("", Index())
root.putChild("twisted", vhost.VHostMonsterResource())
root.putChild("files", static.File(PATH))
site = server.Site(root)
internet.SSLServer.__init__(self, PORT, site, ServerContextFactory())
application = service.Application("SSLServer")
ssl_service = SSLService()
ssl_service.setServiceParent(application)
It almost works-- but the "files" link on the index page does not behave how I want it to when using apache as a reverse proxy, because it is an absolute link.
My main question is, other than using a relative link, is there some way to compute what the full URL path of the link ought to be in such a way that the link still works in standalone server mode?
A second question would be, am I using VHostMonsterResource correctly? I did not find much documentation, and I pieced together my code from examples I found on the web.
This seems like too much work. Why use VHostMonsterResource at all? You may have very specific reasons for wanting some of this but....Most times:
Have apache handle the ssl. apache then passes off to your twisted app serving non SSL goodies back to apache. Documentation all over the net on the apache config stuff.
you can sill add another server on an ssl port if you really want to
Haven't tested but structure more like:
root = resource.Resource()
root.putChild("", Index())
root.putChild("files", static.File(PATH))
http = internet.TCPServer(8090, server.Site(root))
# change this port # to 443 if no apache
https= internet.SSLServer(8443, server.Site(root), ServerContextFactory())
application = service.Application("http_https_Server")
http.setServiceParent(application)
https.setServiceParent(application)
Dev tip:
During development, for the cost of a couple of extra lines you can add an ssl server so that you can ssh into the running web_server and inspect variables and other state. Way cool.
ssl = internet.TCPServer(8022, getManholeFactory(globals(), waldbiec ='some non-system waldbiec passwork'))
ssl.setServiceParent(application)
Configure the Twisted application so that it knows its own root location. It can use that information to generate URLs correctly.
So after digging into the vhost.VHostMonsterResource source, I determined I could create another resource that could let the reverse proxied URL prefix be specified by an additional marker in the Apache ProxyPass URL.
Firstly, I finally figured out that vhost.VHostMonsterResource is supposed to be a special URL in your back end web site that figures out the reverse proxy host and port from data encoded in the URL path. The URL path (sans scheme and net location) looks like:
/$PATH_TO_VHMONST_RES/$REV_PROXY_SCHEME/$REV_PROXY_NETLOC/real/url/components/
$PATH_TO_VHMONST : Path in the (internal) twisted site that corresponds to the VHostMonsterResource resource.
$REV_PROXY_SCHEME : http or https that is being used by the reverse proxy (Apache).
$REV_PROXY_NETLOC : The net location (host and port) or the reverse proxy (Apache).
So you can control the configuration from the reverse proxy by encoding this information in the URL. The result is that the twisted site will understand the HTTP request came from the reverse proxy.
However, if you are proxying a subtree of the external site as per my original example, this information is lost. So my solution was to create an additional resource that can decode the extra path information. The new proxy URL path becomes:
/$PATH_TO_MANGLE_RES/$REV_PROXY_PATH_PREFIX/$VHOSTMONST_MARKER/$REV_PROXY_SCHEME/$REV_PROXY_NETLOC/real/url/components/
$PATH_TO_MANGLE_RES : The path to the resource that decodes the reverse proxy path info.
$REV_PROXY_PATH_PREFIX : The subtree prefix of the reverse proxy.
$VHOSTMONST_MARKER : A path component (e.g. "vhost") that signals a VHostMonster Resource should be used to further decode the path.

Categories

Resources