Source code for servermgr.nginx

# -*- mode: python; tab-width:8; py-indent-offset:4; indent-tabs-mode:nil -*-
#pylint: disable=C0301
#line length;

"""
Nginx process management.

Use this module to configure and manage a dedicated nginx server.
"""

import os
import subprocess
import urllib

import base

[docs]class _ConfigBuilder(object): """ Nginx configuration file builder. This mixin class provides the methods that can be used to add various types of URL mappings to an Nginx config file. """ CONFIG_TEMPLATE = """ daemon off; error_log %(logdir)s/error.log; pid %(rundir)s/nginx.pid; events { worker_connections 1024; use epoll; } http { access_log %(logdir)s/access.log; client_body_temp_path %(tmpdir)s 1 2; fastcgi_temp_path %(tmpdir)s/fastcgi; proxy_temp_path %(tmpdir)s/proxy; root %(root)s; server_name_in_redirect off; server { listen %(port)s; location /nginx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } %(config_blocks)s } } """ FILESYSTEM_BLOCK = """ location %(url_prefix)s { alias %(directory)s; autoindex on; } """ REDIRECT_BLOCK = """ location %(url_prefix)s { rewrite %(pattern)s %(rewrite)s permanent; } """ FCGI_BLOCK = """ location %(url_prefix)s { fastcgi_buffers 256 8k; fastcgi_max_temp_file_size 0; fastcgi_pass %(destination)s; fastcgi_param PATH_INFO $fastcgi_script_name; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param QUERY_STRING $query_string; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; fastcgi_param SERVER_NAME $server_name; fastcgi_param SERVER_PORT $server_port; fastcgi_param SERVER_PROTOCOL $server_protocol; fastcgi_pass_header Authorization; fastcgi_intercept_errors off; } """ HTTP_PROXY_BLOCK = """ location %(url_prefix)s { proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Server $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass %(destination)s; } """ @staticmethod def _generate_mapping(map_info): """Format the configuration section for a particular type of url mapping.""" block, info_dict = map_info config_data = block % info_dict return config_data def __init__(self, host, port, base_dir, http_root): self.host = host self.port = port self.base_dir = base_dir self.http_root = http_root self.mappings = [] def _write_config(self, file_handle): """Write the nginx configuration file.""" tmpl_vars = { "etc" : os.path.join(self.base_dir, "etc", "nginx"), "logdir" : os.path.join(self.base_dir, "logs", "nginx"), "rundir" : os.path.join(self.base_dir, "run"), "tmpdir" : os.path.join(self.base_dir, "tmp", "nginx"), "root" : self.http_root, "port" : self.port } for key in "etc", "logdir", "rundir", "tmpdir": dirname = tmpl_vars[key] if not os.path.exists(dirname): os.makedirs(dirname) config_blocks = [] for mapping in self.mappings: config_blocks.append(_ConfigBuilder._generate_mapping(mapping)) tmpl_vars["config_blocks"] = ''.join(config_blocks) config_data = [_ConfigBuilder.CONFIG_TEMPLATE % tmpl_vars] file_handle.write('\n'.join(config_data))
[docs] def add_filesystem_mapping(self, url_prefix, directory): """ Add a mapping from a URL prefix to a filesystem location. :param url_prefix: url prefix. :type url_prefix: string :param directory: directory name :type destination: string """ if not directory.endswith("/"): directory += "/" d__ = dict(url_prefix=url_prefix, directory=directory) self.mappings.append((_ConfigBuilder.FILESYSTEM_BLOCK, d__))
[docs] def add_redirect_mapping(self, url_prefix, pattern, rewrite): """ URLs that have the prefix *prefix* are then matched against the regular expression *pattern* and rewritten using *destination*, which may use matched subexpressions from *pattern*. See `Nginx Rewrite Module <http://wiki.nginx.org/NginxHttpRewriteModule#Directives>`_ for details. :param url_prefix: url prefix. :type url_prefix: string :param pattern: the re pattern to match against :type pattern: string, regular expression :param rewrite: the rewrite pattern, which may include matched patterns from *pattern*. :type rewrite: string """ d__ = dict(url_prefix=url_prefix, pattern=pattern, rewrite=rewrite) self.mappings.append((_ConfigBuilder.REDIRECT_BLOCK, d__))
[docs] def add_fastcgi_mapping(self, url_prefix, destination): """ Forward requests matching *url_prefix* to the fastcgi server at *destination*. :param url_prefix: url prefix. :type url_prefix: string :param destination: destination :type destination: string """ d__ = dict(url_prefix=url_prefix, destination=destination) self.mappings.append((_ConfigBuilder.FCGI_BLOCK, d__))
[docs] def add_http_mapping(self, url_prefix, destination): """ Act as a reverse proxy, sending URLs matching *url_prefix* to *destination*. :param url_prefix: url prefix. :type url_prefix: string :param destination: destination http server :type destination: string """ d__ = dict(url_prefix=url_prefix, destination=destination) self.mappings.append((_ConfigBuilder.HTTP_PROXY_BLOCK, d__))
[docs]class Manager(base.Manager, _ConfigBuilder): """ Nginx manager object. :param host: the interface to listen on. :type host: string :param port: the post to listen on. :type port: int :param base_dir: the base directory for logs and configuration files. :type base_dir: string """ __implements__ = base.ManagerInterface def __init__(self, host, port, base_dir, http_root="", *args, **kwargs): if not http_root: http_root = base_dir # super(Manager, self).__init__("Nginx", *args, **kwargs) base.Manager.__init__(self, "Nginx", *args, **kwargs) _ConfigBuilder.__init__(self, host, port, base_dir, http_root) self.process = None
[docs] def health(self): """ Check on the health of the Solr server. :raises: **base.WorkerError** if the server is not responding. """ try: urllib.urlopen("http://%s:%d/nginx_status" % (self.host, self.port)) except IOError, ex: raise base.WorkerError(ex)
[docs] def start(self, wait=True, timeout=10.0): """ Start the http server. :param wait: If true, call self.ready_wait() after starting the subprocess. :param timeout: When calling ready_wait(), use this timeout value. :type timeout: float, number of seconds :raises: **base.WorkerError** if the worker hasn't started before timeout elapses. """ config_file = os.path.join(self.base_dir, "etc", "nginx", "nginx.conf") if not os.path.exists(os.path.dirname(config_file)): os.makedirs(os.path.dirname(config_file)) file_handle = file(config_file, "w+") self._write_config(file_handle) file_handle.close() self.process = subprocess.Popen(["nginx", "-c", config_file], stdout=file("/dev/null"), stderr=subprocess.PIPE, close_fds=True) if wait: self.ready_wait(timeout=timeout)
[docs]def main(): """ You can use this script to start a copy of Nginx. Usage: nginx.py [--help] [-h HOST] [-p PORT] [-d DATADIR] [-f FSMAP] [-r REDIRECT] [-c FCGI] [-t HTTP] optional arguments: --help show this help message and exit -h HOST, --host=HOST listener hostname -p PORT, --port=PORT listener port -d DATADIR, --datadir=DATADIR data directory --fsmap FSMAP add a filesystem mapping --redirect REDIRECT add a redirect --fcgi FCGI add a fastcgi mapping --http HTTP add a http mapping --config print the generated config file and exit To specify a filesystem mapping, use the option: --fsmap=/some_url_prefix,/somedirectory To map a url prefix to a webapp using fcgi, use: --fcgi=/some_url_prefix,host:port To make a url prefix a reverse proxy for another http server, use: --http=/url_prefix,some_other_url To make a url redirect elsewhere: --redirect=/url_prefix,pattern,rewrite """ import argparse import signal import sys def interrupt(_unused_signum, _unused_frame): """Handle user stopping with control-C.""" print >> sys.stderr, "Keyboard interrupt, exiting." sys.exit() signal.signal(signal.SIGINT, interrupt) help_parser = argparse.ArgumentParser(add_help=False) help_parser.add_argument("--help", action='store_true') epilog = """ To specify a filesystem mapping, use the option: --fsmap=/some_url_prefix,/somedirectory To map a url prefix to a webapp using fcgi, use: --fcgi=/some_url_prefix,host:port To make a url prefix a reverse proxy for another http server, use: --http=/url_prefix,some_other_url To make a url redirect elsewhere: --redirect=/url_prefix,pattern,rewrite """ parser = argparse.ArgumentParser(parents=[help_parser], add_help=False) parser.add_argument("-h", "--host", action="store", help="listener hostname") parser.add_argument("-p", "--port", action="store", help="listener port") parser.add_argument("-d", "--datadir", action="store", help="directory for config file, log files") parser.add_argument("--fsmap", action="append", help="add a filesystem mapping") parser.add_argument("--redirect", action="append", help="add a redirect") parser.add_argument("--fcgi", action="append", help="add a fastcgi mapping") parser.add_argument("--http", action="append", help="add a http mapping") parser.add_argument("--config", action="store_true", help="print the generated config file and exit") options = parser.parse_args() if options.help: parser.print_help() print epilog return default_host = "localhost" default_port = 8080 default_datadir = "/var/tmp/nginx" host = options.host if options.host else default_host port = int(options.port) if options.port else default_port datadir = options.datadir if options.datadir else default_datadir webserver_mgr = Manager(host, port, datadir) if options.fsmap: for mapping in options.fsmap: try: url, filesystem = mapping.split(',') webserver_mgr.add_filesystem_mapping(url, filesystem) print >> sys.stderr, "mapping url prefix '%s' to directory '%s'" % (url, filesystem) except ValueError: print >> sys.stderr, "cannot process parameter '%s', skipping..." % mapping if options.redirect: for mapping in options.redirect: try: prefix, pattern, target = mapping.split(',') webserver_mgr.add_redirect_mapping(prefix, pattern, target) print >> sys.stderr, "with url prefix '%s', mapping pattern '%s' to target '%s'." % (prefix, pattern, target) except ValueError: print >> sys.stderr, "cannot process parameter '%s', skipping..." % mapping if options.http: for mapping in options.http: try: prefix, destination = mapping.split(',') if prefix[-1] != '/': prefix += "/" if destination[-1] != '/': destination += "/" webserver_mgr.add_http_mapping(prefix, destination) print >> sys.stderr, "reverse proxy url prefix '%s' to '%s'." % (prefix, destination) except ValueError: print >> sys.stderr, "cannot process parameter '%s', skipping..." % mapping if options.fcgi: for mapping in options.fcgi: try: prefix, destination = mapping.split(',') webserver_mgr.add_fastcgi_mapping(prefix, destination) print >> sys.stderr, "FCGI reverse proxy url prefix '%s' to '%s'." % (prefix, destination) except ValueError: print >> sys.stderr, "cannot process parameter '%s', skipping..." % mapping if options.config: print "Generated config file:" webserver_mgr._write_config(sys.stdout) #pylint: disable=W0212 return print >> sys.stderr, "starting nginx..." webserver_mgr.start() print >> sys.stderr, "nginx ready on %s:%d, data_dir is %s." % (host, port, datadir) webserver_mgr.wait()
if __name__ == "__main__": main()