Badass Resumes Julep Configuration
When I first switch Badass Resumes to julep and nginx from apache, I expected to spend a few days tweaking the configuration and then I would document the process in a blog post (like this one). Ultimately though, I ended up forgetting about it for several months, during which time it has chugged along with no maintenance whatsoever.
So, I present to you, many months late, the julep and nginx setup that is currently running badass resumes.
The Julep Configuration
The julep configuration is pretty straightforward. The site had previously been running on mod_wsgi, so I just used the existing wsgi file to configure the app. I made the scoreboard available to check on it periodically, though I haven’t ended up using it too much.
from julep import config
from julep import scoreboard
from julep import preload
from wsgiref import simple_server
import logging
logging.basicConfig(level=logging.DEBUG,filename="/var/log/julep/julep.log")
badass_resumes = config.NetworkServer(
address = '127.0.0.1',
port = 5557,
wsgi_file = "/var/www/badassresumes/resumes.wsgi",
access_log = "/var/log/julep/resumes.log",
error_log = "/var/log/julep/resumes.err.log",
preload = [preload.preload_django,]
)
scoreboard_server = config.NetworkServer(
address = '127.0.0.1',
port = 8687,
app = scoreboard.ScoreboardWSGIApplication([
badass_resumes,
])
)
The WSGI File
When I switched from mod_wsgi to julep, I didn’t actually need to change the wsgi file. It is a standard django configuration.
<span style="font-family: Consolas, Monaco, 'Courier New', Courier, monospace; line-height: 18px; font-size: 12px; white-space: pre;">import os,sys</span>
<pre>sys.path.append("/var/www/badassresumes/")
os.environ['DJANGO_SETTINGS_MODULE'] = 'badassresumes.settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
The nginx Configuration
The nginx configuration is also pretty simple. The julep instance serving on port 5557 is used as the upstream server that handles the actual requests. Exceptions are made for the media and admin media folders. The final location for handling internal requests is used to support the X-Accel-Redirect header (so that the resumes can be served by nginx rather than being handled by the julep processes).
upstream badass-resumes {
server localhost:5557;
}
server {
listen 80;
server_name badassresumes.com badass-resumes.com;
access_log /var/log/nginx/resumes.access.log;
location / {
proxy_pass http://badass-resumes;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /robots.txt {
alias /var/www/badassresumes/shared/robots.txt;
}
location /admin/media {
alias /var/lib/python-support/python2.5/django/contrib/admin/media;
}
location /media {
alias /var/www/badassresumes/current/files/media;
}
location /var/www/badassresumes/badassresumes/generated/ {
root /;
internal;
}
}
server {
listen 443;
server_name badassresumes.com badass-resumes.com;
access_log /var/log/nginx/resumes-ssl.access.log;
ssl on;
ssl_certificate /etc/nginx/ssl/resumes.crt;
ssl_certificate_key /etc/nginx/ssl/resumes.key;
location / {
proxy_pass http://badass-resumes;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /robots.txt {
alias /var/www/badassresumes/shared/robots.txt;
}
location /admin/media {
alias /var/lib/python-support/python2.5/django/contrib/admin/media;
}
location /media {
alias /var/www/badassresumes/current/files/media;
}
location /var/www/badassresumes/badassresumes/generated/ {
root /;
internal;
}
}
what the hell is julep?
A couple of weeks ago, Zellyn Hunter, a coworker of mine, pointed me towards a description of unicorn, a backend web server for running rails/rack applications. As a django person, he was interested in seeing if the same basic preforking model could be implemented in a WSGI server to achieve the same results.
After drinking a bunch and watching hackers, he managed to convince me that this was a good idea.
So, the next day, I started working on re-implementing unicorn in python. After several name changes, the proof of concept that I built has evolved into julep. I’m happy to say that I’ve managed to put it into production on one of my own sites, badass resumes.
If you’re interested in trying out julep, feel free to check out the google code page. So far, it has been tested on python 2.5 and 2.6, but could potentially run on 2.4 if the wsgiref library is installed. In the coming days, I’ll work on documenting it and putting up more information about the deployment details of badassresumes. I’m also planning a series of benchmarks to see how well it does scale.
As with any free software project, you’re thoughts and ideas are welcome.
Djangocon X-Sendfile Lightning Talk
Here’s the source code and slides from my Djangocon lightning talk about the X-Sendfile header.
from django.http import HttpResponse,Http404
from django.core.servers.basehttp import FileWrapper
from django.conf import settings
import mimetypes
import os
def basic_sendfile(fname,download_name=None):
if not os.path.exists(fname):
raise Http404
wrapper = FileWrapper(open(fname,"r"))
content_type = mimetypes.guess_type(fname)[0]
response = HttpResponse(wrapper, content_type=content_type)
response['Content-Length'] = os.path.getsize(fname)
if download_name:
response['Content-Disposition'] = "attachment; filename=%s"%download_name
return response
def x_sendfile(fname,download_name=None):
if not os.path.exists(fname):
raise Http404
content_type = mimetypes.guess_type(fname)[0]
response = HttpResponse('', content_type=content_type)
response['Content-Length'] = os.path.getsize(fname)
response['X-Sendfile'] = fname
if download_name:
response['Content-Disposition'] = "attachment; filename=%s"%download_name
return response
if getattr(settings,'SENDFILE',False) == 'x_sendfile':
sendfile = x_sendfile
else:
sendfile = basic_sendfile
Here are the slides: X-Sendfile Slides
Here’s a little snippet that you can stick into your .bashrc file. It creates a hash of the hostname and uses that to color the ps1 variable, so that hosts with similar names look different when you are shelled into them.
function colorps1() {
word=`hostname --short`
hash=`echo "$word" | md5sum`
fullstr=""
for l in `echo $word | sed 's/\(...\)/\1\n/g'`; do
control=""
endcontrol='\[33[00m\]'
case "${hash:0:1}" in
0)
control='\[33[1;30m\]'
;;
1)
control='\[33[0;31m\]'
;;
2)
control='\[33[0;32m\]'
;;
3)
control='\[33[0;33m\]'
;;
4)
control='\[33[0;34m\]'
;;
5)
control='\[33[0;35m\]'
;;
6)
control='\[33[0;36m\]'
;;
7)
control='\[33[0;37m\]'
;;
8)
control='\[33[1;30m\]'
;;
9)
control='\[33[1;31m\]'
;;
a)
control='\[33[1;32m\]'
;;
b)
control='\[33[1;33m\]'
;;
c)
control='\[33[1;34m\]'
;;
d)
control='\[33[1;35m\]'
;;
e)
control='\[33[1;36m\]'
;;
f)
control='\[33[1;37m\]'
;;
esac
hash=${hash:1}
fullstr="$fullstr$control$l"
done
fullstr="\[33[1;31m\]\\u@$fullstr$endcontrol \[33[1;34m\]\W \$ \[33[00m\]"
export PS1=$fullstr
}
colorps1