Deploying Python Falcon on Apache2 with mod_wsgi - python

Scenario: I'm using Python Falcon framework for my REST API service. The application is initially run by Gunicorn.
Right now when I call https://example.com:8000/, I get a proper response that the API logic dictates.
Goal: I want to deploy it on Apache2 along with mod_wsgi on my personal development server.
The API would still be running under Gunicorn, and Apache should interact with Gunicorn whenever a request is made on that end-point.
Directory structure:
| - abc_service
| - abc_service
| - __init__.py
| - app.py
| - wsgi.py
| - .abc_venv
The source code for app.py looks something like this:
import falcon
from .abc import ABC
api = application = falcon.API() # pylint: disable=invalid-name
# Creating a unique GET resource from the ABC class
test_handler_resource = ABC() # pylint: disable=invalid-name
# Mapping the HTTP GET endpoint with the unique resource
api.add_route('/abc', test_handler_resource)
# Mapping the HTTP POST endpoint with the unique resource
api.add_route('/abc/filters', test_handler_resource)
In my wsgi.py, I have the following contents:
from abc_service import application
The configuration for Apache in /etc/apache2/sites-available/000-default.conf is as follows:
# WSGI
WSGIDaemonProcess python-path=/home/uname/abc-service/src/abc_service/.abc_venv/lib/python3.5/site-packages
WSGIScriptAlias /abc /home/uname/abc-service/src/abc_service/wsgi.py
ProxyPass /abc http://localhost:8000/abc
ProxyPassReverse /abc http://localhost:8000/abc
<Location /abc>
AuthType None
Require all granted
# Always set these headers.
Header always set Access-Control-Allow-Origin "https://example.com"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token"
</Location>
I can't find any helpful resource on the web regarding the same on the web that uses this combination of Falcon, Gunicorn, mod_wsgi, and Apache. So, I can't figure out what I'm doing wrong.
I appreciate your time and help. Thanks!

So, a solution that worked for me was to add the WSGIPythonHome path to the Apache configuration.
WSGIPythonHome /var/local/abc-service/src/abc_service/.venv
The final Apache configuration looks like this:
# At the top of the Apache Config file
WSGIPythonHome /var/local/abc-service/src/abc_service/.venv
...
...
...
# WSGI
WSGIDaemonProcess python-path=/home/uname/abc-service/src/abc_service/.abc_venv/lib/python3.5/site-packages
WSGIScriptAlias /abc /home/uname/abc-service/src/abc_service/wsgi.py
ProxyPass /abc http://localhost:8000/abc
ProxyPassReverse /abc http://localhost:8000/abc
<Location /abc>
AuthType None
Require all granted
# Always set these headers.
Header always set Access-Control-Allow-Origin "https://example.com"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Max-Age "1000"
Header always set Access-Control-Allow-Headers "x-requested-with, Content-Type, origin, authorization, accept, client-security-token"
</Location>

Related

How to get apache to serve static files on Flask webapp

I'm getting a 500 internal error while trying to get Apache to serve my static files.
The application will be locally hosted (not www facing). There will be no DNS to resolve a 'www.domain.com' name. I want to be able to access the application by entering the IP address of the server when I'm on that network.
This is my httpd.conf file (I'm on RHEL):
<Directory /var/www/testapp>
Order allow,deny
Allow from all
</Directory>
WSGIScriptAlias / /var/www/testapp/service.wsgi
If I change the WSGIScriptAlias to WGSIScriptAlias /test /var/www/testapp/service.wsgi then I can view my static files when I type in the IP, but I still can't access the service.py script from [IP]/test.
In any case, I want to be able to service all GET/POST requests with the service.py script so I want my alias to start at /, not some other place.
All my static files are in /var/www/html (Apache was automatically displaying these files before I messed with the httpd.conf, now I'm just getting a 500).
This is my service.wsgi:
import sys
sys.path.insert(0, '/var/www/testapp')
from service import app as application
This is my service.py:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello(environ, start_response):
status = '200 OK'
output = "Hello"
response_headers = [('Content-type', 'text/plain'), ('Content-length', str(len(output)))]
start_response(status, response_headers)
return output
if __name__=='__main__'
app.run()
Do I need keep my .wsgi files in the /var/www/html directory as well? Or can they go in a different folder? I can see that there might be some conflict between the message I am sending to the server ('Hello') and the static files that are already in the /var/www/html/ directory. That's why I tried setting the alias to /test but that didn't work either.
I just want my Flask application to service GET/POST requests and want apache to serve all the static files.
Fixing the 500 errors
You are currently getting 500 errors because your handler is a basic WSGI handler, but Flask handlers are not WSGI handlers (Flask / Werkzeug abstracts all that for you). Change your handler to:
#app.route("/")
def hello():
return "Hello"
and the 500 errors should go away.
Serving static files with Apache
The following techniques can be used when your application is serving the root of the domain (/), depending on whether you are using WSGIScriptAlias or AddHandler.
When using WSGIScriptAlias
When using the WSGIScriptAlias to mount a WSGI application at / you can use an Apache Alias directive to ensure that certain sub-routes are not handled by WSGIScriptAlias (this is further documented in mod_wsgi's wiki as well):
Alias "/static/" "/path/to/app/static/"
<Directory "/path/to/app/static/">
Order allow,deny
Allow from all
</Directory>
If you also want to support blueprint static folders as well you'll also need to use the AliasMatch directive:
AliasMatch "(?i)^/([^/]+)/static/(.*)$" "/path/to/app/blueprints-root/$1/static/$2"
<Directory ~ "/path/to/app/blueprints-root/[^/]+/static/.*">
Order allow,deny
Allow from all
</Directory>
See also: The Directory directive.
When using AddHandler
As Graham Dumpleton has pointed out in the comments, you can use mod_rewrite to pass requests off to Python if and only if a file does not exist in DocumentRoot. Quoting from the linked docs:
When using the AddHandler directive, with WSGI applications identified by the extension of the script file, the only way to make the WSGI application appear as the root of the server is to perform on the fly rewriting of the URL internal to Apache using mod_rewrite. The required rules for mod_rewrite to ensure that a WSGI application, implemented by the script file 'site.wsgi' in the root directory of the virtual host, appears as being mounted on the root of the virtual host would be:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /site.wsgi/$1 [QSA,PT,L]
Do note however that when the WSGI application is executed for a request the 'SCRIPT_NAME' variable indicating what the mount point of the application was will be '/site.wsgi'. This will mean that when a WSGI application constructs an absolute URL based on 'SCRIPT_NAME', it will include 'site.wsgi' in the URL rather than it being hidden. As this would probably be undesirable, many web frameworks provide an option to override what the value for the mount point is. If such a configuration option isn't available, it is just as easy to adjust the value of 'SCRIPT_NAME' in the 'site.wsgi' script file itself.
from your.app import app # Your Flask app
import posixpath
def application(environ, start_response):
# Wrapper to set SCRIPT_NAME to actual mount point.
environ['SCRIPT_NAME'] = posixpath.dirname(environ['SCRIPT_NAME'])
if environ['SCRIPT_NAME'] == '/':
environ['SCRIPT_NAME'] = ''
return app(environ, start_response)
This wrapper will ensure that 'site.wsgi' never appears in the URL as long as it wasn't included in the first place and that access was always via the root of the web site instead.

Hosting a bottlepy app in apache (mod-wsgi)

I have developed a Python web application using bottlepy. It takes in 7 input parameters and return a JSON string.
#route('/aggregation')
def service():
poi_data = request.GET.get('poi', default=None)
crime_data = request.GET.get('crime', default=None)
walkshed_collection = request.GET.get('walkshed_collection', default=None)
walkshed_union = request.GET.get('walkshed_union', default=None)
start_point = request.GET.get('start_point', default=None)
transit_data = request.GET.get('transit', default=None)
distance_decay_function = request.GET.get('distance_decay_function', default=None).lower()
walking_time_period = request.GET.get('walking_time_period', default=None)
if start_point and poi_data and crime_data and walkshed_collection and walkshed_union and transit_data and distance_decay_function and walking_time_period is not None:
return aggregation(start_point, poi_data, transit_data, crime_data, walkshed_collection, walkshed_union, distance_decay_function, walking_time_period)
run(host='0.0.0.0', port=9364, debug=True)
It works fine with both HTTP GET and POST when I run the application using the bottlepy web server using terminal, like python aggregation.py.
But when I managed to host it in Apache using mod-wsgi, it did not work; the Apache logged "URI Too Long" when using HTTP GET and "caught SIGTERM, shutting down" when using HTTP POST. Actually, some of the inputs are too long JSON strings like poi_data, crime_data, walkshed_collection_walkshed_union, and transit_data. For example, the length of KVP request is around 200KB.
Here is the Apache configuration:
Listen *:9364
<VirtualHost *:9364>
ServerName 127.0.0.1
WSGIDaemonProcess aggregation user=www-data group=www-data processes=1 threads=5
WSGIScriptAlias /aggregation /var/www/aggregation/adapter.wsgi
<Directory /var/www/aggregation>
WSGIProcessGroup aggregation
WSGIApplicationGroup %{GLOBAL}
Order deny,allow
Allow from all
</Directory>
</VirtualHost>
I should mention that I have developed couple of Python web applications that work fine with both bottle web server and Apache mod-wsgi. But they take in small input parameters. So I assume that the problem might be related to the size of request. Do you guys have any idea how I can fix the problem? Any suggestion would be much appreciated.
Thanks,
Ebrahim
Duplicate of:
https://groups.google.com/d/msg/modwsgi/8HJhaOleYwA/e29CtXY4_8UJ
Answer provided in the mod_wsgi mailing list.

Twisted web server behind apache - no resource found

I am running twisted.web.server on localhost at port 8001 and apache2 with mod_proxy.
Apache is set to proxy according to the following config
http://localhost/jarvis ----> http://localhost:8001/
The httpd config for this rule is
ProxyPass /jarvis http://localhost:8001/
ProxyPassReverse /jarvis http://localhost:8001/
The twisted app's code fragment for server config is as follows:
if __name__ == '__main__':
root = Resource()
root.putChild("clientauth", boshProtocol())
logging.basicConfig()
factory = Site(root)
reactor.listenTCP(8001, factory)
reactor.run()
When I go to
http://localhost:8001/clientauth
it runs as expected.
However when I use
http://localhost/jarvis/clientauth
It give the error - "No such child resource."
As i understand - the request is correctly proxied to the twisted web server. But why is the child resource not identified?
You are missing a RewriteRule. I haven't tested it, but the fix for your problem is more or less like this:
RewriteRule ^/jarvis/(.*) /$1
Be sure to have mod_rewrite enabled.
Here is a link I usually use for reference: http://httpd.apache.org/docs/2.0/misc/rewriteguide.html
Good luck!

Django: only blank page

I have a server with Apache and I would like to start website written in Django. I user mod_wsgi. Now I have it prepared. But the respond of a server is empty. And in error log, there is nothing. Do you know what is the reason why?
If some file could help (*.wsgi, settings.py) I will append it.
Prochazky.wsgi
import os
import sys
import site
os.environ['PYTHON_EGG_CACHE'] = '/home/prochazky/venv/.python-eggs'
site.addsitedir('/home/prochazky/venv/lib/python2.6/site-packages')
os.environ['DJANGO_SETTINGS_MODULE'] = 'Prochazky.settings'
sys.path.append('/home/prochazky/')
sys.path.append('/home/prochazky/Prochazky/')
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
Apache vhost:
<VirtualHost *:80>
WSGIScriptAlias / /home/prochazky/Prochazky/Prochazky.wsgi
ServerName testing.prochazky.net
DocumentRoot /home/prochazky
ErrorLog /home/prochazky/wsgi.log
</VirtualHost>
Trying getting a hello world program working first and not Django. Watch:
http://code.google.com/p/modwsgi/wiki/WhereToGetHelp?tm=6#Conference_Presentations
and read:
http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide
At a guess though, are you perhaps loading mod_python into the same Apache. An incompatible mod_python will cause exactly that symptom with merely a segmentation fault message in main Apache error log.
UPDATE 1
Are you loading mod_php into same Apache? It can sometimes have conflicting shared library requirements and will cause a crash. If you are also loading it, disable it from Apache configuration.
Also try setting:
WSGIApplicationGroup %{GLOBAL}
This will force use of main interpreter which will avoid problems with third party extensions that aren't written properly to work in sub interpreters.
You really though need to look more closely at the main Apache error log, not the virtual host one. Run a 'tail -f' on it when you make a request and check for sure you are seeing messages there, specifically a segmentation fault or similar message. Such a message about process crashing and causing empty page will not show in virtual host error log.
Is it possible the template file your root url/view is empty or evaluates to empty?
For instance, if you have a template like so:
{% extends "base.html" %}
{% block content %}blah blah{% endblock %}
and base.html doesn't use a block "content", then the content block from your template won't be used and your template will evaluate to empty despite having content.
This is from my setup (names altered to protect the innocent guilty).
<VirtualHost *:80>
ServerName site.domain.com
ServerAdmin webmaster#domain.com
WSGIScriptAlias / /home/user/site/django.wsgi
<Directory /home/user/site/>
Options FollowSymLinks
AllowOverride None
Order allow,deny
allow from all
</Directory>
... etc etc etc.
I think you need the <directory> to allow the server to access the .wsgi.
I'm really not an apache guru though so don't take this example as perfect. (I think all that is needed is the order Allow, deny and allow from all)
a good site to check out: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango

Django keeps changing URL from http://localhost/ to http://127.0.0.1:8080/

As the title described, Django keeps changing my URL from /localhost/ to /127.0.0.1:8080/ which keeps messing up my serving static files by Nginx. Any ideas why its doing this? Thanks!
/**EDIT**/
Here is the Nginx configuration:
server {
listen 80; ## listen for ipv4
listen [::]:80 default ipv6only=on; ## listen for ipv6
server_name localhost;
access_log /var/log/nginx/localhost.access.log;
location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|html|htm)$
{
root /srv/www/testing;
}
location / {
proxy_pass http://127.0.0.1:8080/;
proxy_redirect off;
}
location /doc {
root /usr/share;
autoindex on;
allow 127.0.0.1;
deny all;
}
location /images {
root /usr/share;
autoindex on;
}
Here is Apache config file:
<VirtualHost *:8080>
ServerName testing
DocumentRoot /srv/www/testing
<Directory /srv/www/testing>
Order allow,deny
Allow from all
</Directory>
WSGIScriptAlias / /srv/www/testing/apache/django.wsgi
</VirtualHost>
If you're using VirtualHost, you need to set USE_X_FORWARDED_HOST = True in your settings.py
Here's the reference: Django Doc for Settings.py
USE_X_FORWARDED_HOST New in Django 1.3.1: Please, see the release
notes
Default: False
A boolean that specifies whether to use the X-Forwarded-Host header in
preference to the Host header. This should only be enabled if a proxy
which sets this header is in use.
Here's some example code:
import os, socket
PROJECT_DIR = os.path.dirname(__file__)
on_production_server = True if socket.gethostname() == 'your.productionserver.com' else False
DEBUG = True if not on_production_server else False
TEMPLATE_DEBUG = DEBUG
USE_X_FORWARDED_HOST = True if not on_production_server else False
edit2:
http://wiki.nginx.org/HttpProxyModule#proxy_redirect
http://wiki.nginx.org/HttpProxyModule#proxy_pass
What I think is happening is when you use your httpresponseredirect, the HTTP_HOST header is giving it the 127.0.0.1:8080, because of your proxy_pass setting.
Django's HttpResponseRedirect seems to strip off my subdomain?
Django has some methods it always
applies to a response. One of these is
django.utils.http.fix_location_header.
This ensures that a redirection
response always contains an absolute
URI (as required by HTTP spec).
Had the same problem (django redirects got to the browser with the ":8080" appended). After further searching, I found some nginx info. The following fixed it...
In your nginx config, replace...
proxy_redirect off;
with....
proxy_redirect http://127.0.0.1:8080/ http://127.0.0.1/;
Remember to restart your nginx daemon. This causes nginx to strip the 8080 on packets flowing from apache back to the browser. For example a redirect from django via apache, http://127.0.0.1:8080/my/test/file.html will become http://127.0.0.1/my/test/file.html after nginx sends it back to the client.
Now you won't have to modify your django code.

Categories