├── .gitignore ├── LICENSE ├── MANIFEST.in ├── NOTICE ├── README.rst ├── THANKS ├── bin └── hroute ├── hroute ├── __init__.py ├── app.py ├── config.py ├── lookup.py ├── proxy.py ├── rewrite.py └── util.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.swp 3 | *.pyc 4 | *#* 5 | build 6 | dist 7 | setuptools-* 8 | .svn/* 9 | .DS_Store 10 | *.so 11 | hroute.egg-info 12 | nohup.out 13 | .coverage 14 | doc/.sass-cache 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2011 (c) Benoît Chesneau 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .gitignore 2 | include LICENSE 3 | include NOTICE 4 | include README.rst 5 | include THANKS 6 | include requirements.txt 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | hroute 2 | 2011 (c) Benoît Chesneau 3 | 4 | hroute is released under the MIT license. See the LICENSE 5 | file for the complete license. 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | hroute 2 | ------ 3 | 4 | simple HTTP proxy based on `tproxy `_. 5 | 6 | Features 7 | ++++++++ 8 | 9 | - location rewriting 10 | - links rewriting to handle proxy / paths 11 | - simple configuration 12 | - vhosts support 13 | - logging (coming) 14 | - authentification (coming) 15 | 16 | 17 | Requirements 18 | ------------ 19 | 20 | - `Python `_ 2.6 and sup (Python 3 not suppported yet) 21 | - `gevent `_ >= 0.13.4 22 | - `setproctitle `_ >= 1.1.2 23 | - `tproxy `_ >= 0.5.2 24 | - `http-parser `_ >= 0.3.3 25 | - `lxml `_ 26 | 27 | Install 28 | ------- 29 | 30 | :: 31 | 32 | $ pip install -r https://github.com/downloads/benoitc/hroute/requirements.txt 33 | $ pip install hroute 34 | 35 | 36 | Usage 37 | ----- 38 | 39 | Create a configuration file named **route** in /var/spool/hroute 40 | (default path) or any folder you want, for example in /tmp, put the 41 | following configuration:: 42 | 43 | { 44 | "all": { 45 | "routes": { 46 | "/": { 47 | "remote": "benoitc.io:80", 48 | "rewrite_response": true 49 | }, 50 | "/local": { 51 | "remote": "127.0.0.1:8000" 52 | }, 53 | "/google": { 54 | "remote": "google.com:80" 55 | }, 56 | "/gunicorn": { 57 | "remote": "gunicorn.org:80", 58 | "rewrite_response": true 59 | }, 60 | "/googles": { 61 | "remote": "encrypted.google.com:443", 62 | "ssl": true, 63 | "rewrite_response": true 64 | } 65 | } 66 | } 67 | } 68 | 69 | 70 | then launch hroute:: 71 | 72 | $ hroute -s /tmp -w 3 73 | 74 | and go on `http://127.0.0.1:5000/gunicorn 75 | `_. You should see the gunicorn.org 76 | website. 77 | 78 | 79 | More features soon. 80 | -------------------------------------------------------------------------------- /THANKS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benoitc/hroute/5b22ce9c15e671619839d2eecdbe32d2b4cc77fe/THANKS -------------------------------------------------------------------------------- /bin/hroute: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # This file is part of tproxy released under the MIT license. 5 | # See the NOTICE for more information. 6 | 7 | from hroute.app import run 8 | 9 | if __name__ == "__main__": 10 | run() 11 | -------------------------------------------------------------------------------- /hroute/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | version_info = (0, 2, 0) 7 | __version__ = ".".join(map(str, version_info)) 8 | -------------------------------------------------------------------------------- /hroute/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | import os 6 | import sys 7 | 8 | from tproxy.app import Application, Script 9 | 10 | from .config import RouteConfig 11 | 12 | 13 | class HrouteApp(Application): 14 | 15 | def __init__(self): 16 | self.logger = None 17 | self.cfg = RouteConfig("%prog [OPTIONS] script_path") 18 | self.script = None 19 | 20 | def load_config(self): 21 | # parse console args 22 | parser = self.cfg.parser() 23 | opts, args = parser.parse_args() 24 | 25 | # Load conf 26 | try: 27 | for k, v in opts.__dict__.items(): 28 | if v is None: 29 | continue 30 | self.cfg.set(k.lower(), v) 31 | except Exception, e: 32 | sys.stderr.write("config error: %s\n" % str(e)) 33 | os._exit(1) 34 | 35 | spooldir = self.cfg.spooldir 36 | if not spooldir and not os.path.exists('/var/spool/hroute'): 37 | sys.stderr.write("spool directory '/var/spool/hroute'" 38 | "doesn't exist\n") 39 | os._exit(1) 40 | 41 | # setup script 42 | script_uri = "hroute.proxy:Route" 43 | self.cfg.default_name = "hroute" 44 | self.script = Script(script_uri, cfg=self.cfg) 45 | sys.path.insert(0, os.getcwd()) 46 | 47 | 48 | def run(): 49 | return HrouteApp().run() 50 | 51 | -------------------------------------------------------------------------------- /hroute/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | import os 6 | import re 7 | 8 | from tproxy import config 9 | from tproxy.util import parse_address 10 | 11 | try: 12 | import simplejson as json 13 | except ImportError: 14 | import json 15 | 16 | from .util import base_uri, get_host 17 | 18 | ROUTE_RE = re.compile("^([^(].*)\(.*\)$") 19 | 20 | 21 | def validate_path(value): 22 | if value is not None and not os.path.exists(value): 23 | raise IOError("spool directory %s doesn't exist" % value) 24 | return value 25 | 26 | class SpoolDir(config.Setting): 27 | name = "spooldir" 28 | section = "Spool Directory" 29 | cli = ["-s", "--spool"] 30 | meta = "STRING" 31 | validator = validate_path 32 | default = None 33 | desc = """\ 34 | The path to a hroute sppol dir. 35 | 36 | Used to set hroute rules and users. 37 | """ 38 | 39 | class Hostname(config.Setting): 40 | name = "host" 41 | section = "Spool Directory" 42 | cli = ["--host"] 43 | meta = "STRING" 44 | validator = config.validate_string 45 | default = None 46 | desc = """\ 47 | default hostname used in rewriting rules. 48 | """ 49 | 50 | class RouteConfig(config.Config): 51 | 52 | def __init__(self, usage=None): 53 | super(RouteConfig, self).__init__(usage=usage) 54 | 55 | self._spooldir = None 56 | self.routes = {} 57 | self.hosts = [] 58 | self.rmtime = None 59 | 60 | def is_listen_ssl(self): 61 | return self.ssl_keyfile is not None 62 | 63 | def get_spooldir(self): 64 | if self._spooldir: 65 | return self._spooldir 66 | 67 | spooldir = self.spooldir 68 | if not spooldir: 69 | if not os.path.exists('/var/spool/hroute'): 70 | raise IOError("spool directory '/var/spool/hroute'" 71 | "doesn't exist\n") 72 | spooldir = '/var/spool/hroute' 73 | self._spooldir = spooldir 74 | return self._spooldir 75 | 76 | def load_routes(self): 77 | """ load route from configuration file """ 78 | fname = os.path.join(self.get_spooldir(), 'routes') 79 | if not os.path.exists(fname): 80 | return 81 | 82 | # do we need to relad routes ? 83 | mtime = os.stat(fname).st_mtime 84 | if self.rmtime == mtime: 85 | return 86 | self.rmtime = mtime 87 | 88 | # build rules 89 | with open(fname, 'r') as f: 90 | routes_conf = json.load(f) 91 | for name, conf in routes_conf.items(): 92 | host = conf.get('host', '(.*)') 93 | routes = conf.get('routes', {}) 94 | self.hosts.append((re.compile(host), name)) 95 | _routes = [] 96 | for (route, route_conf) in routes.items(): 97 | if 'remote' in route_conf: 98 | 99 | # build base_uri 100 | is_ssl = 'ssl' in route_conf 101 | remote = parse_address(route_conf.get('remote'), 80) 102 | host = get_host(remote, is_ssl=is_ssl) 103 | 104 | route_conf.update(dict( 105 | host = host, 106 | base_uri = base_uri(host, is_ssl=is_ssl), 107 | remote = remote, 108 | listen = self.address, 109 | listen_ssl = self.is_listen_ssl())) 110 | 111 | if ROUTE_RE.match(route): 112 | spec = re.compile(route) 113 | else: 114 | spec = re.compile("%s(.*)" % route) 115 | _routes.append((route, spec, route_conf)) 116 | 117 | _routes.sort() 118 | _routes.reverse() 119 | self.routes[name] = _routes 120 | self.hosts.sort() 121 | self.hosts.reverse() 122 | -------------------------------------------------------------------------------- /hroute/lookup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | import os 7 | 8 | try: 9 | import simplejson as json 10 | except ImportError: 11 | import json 12 | 13 | from .util import base_uri 14 | 15 | DEFAULT_CONTROLS = { 16 | "rewrite_location": True, 17 | "rewrite_response": False 18 | } 19 | 20 | class HttpRoute(object): 21 | 22 | def __init__(self, cfg): 23 | self.cfg = cfg 24 | self.cfg.load_routes() 25 | 26 | def execute(self, host, path): 27 | # refresh routes if needed. 28 | self.cfg.load_routes() 29 | 30 | extra = {} 31 | found_name = None 32 | route_conf = None 33 | for rhost, name in self.cfg.hosts: 34 | if rhost.match(host): 35 | found_name = name 36 | routes = self.cfg.routes.get(name) 37 | if not routes: 38 | break 39 | for route, spec, conf in routes: 40 | m = spec.match(path) 41 | if m: 42 | route_conf = conf 43 | extra = DEFAULT_CONTROLS.copy() 44 | extra.update(conf) 45 | 46 | extra['vhost'] = host 47 | extra['vhost_uri'] = base_uri(host, 48 | is_ssl=self.cfg.is_listen_ssl()) 49 | 50 | if m.group(1): 51 | extra['prefix'] = path.rsplit(m.group(1), 1)[0] 52 | else: 53 | extra['prefix'] = path 54 | 55 | route_conf['extra'] = extra 56 | break 57 | if not route_conf: 58 | return {'close': 'HTTP/1.1 502 Gateway Error\r\n\r\nNo target found'} 59 | 60 | return route_conf 61 | -------------------------------------------------------------------------------- /hroute/proxy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | """ 7 | main proxy object used by tproxy. 8 | """ 9 | import io 10 | import socket 11 | 12 | from http_parser.parser import HttpParser 13 | from http_parser.http import HttpStream, NoMoreData, ParserError 14 | 15 | from .lookup import HttpRoute 16 | from .rewrite import rewrite_headers, RewriteResponse 17 | from .util import headers_lines, send_body, get_host 18 | 19 | class Route(object): 20 | 21 | def __init__(self, cfg): 22 | self.cfg = cfg 23 | self._route = HttpRoute(cfg) 24 | 25 | def lookup(self, parser): 26 | headers = parser.get_headers() 27 | 28 | # get host headers 29 | host = headers.get('host', get_host(self.cfg.address, 30 | is_ssl=self.cfg.is_listen_ssl())) 31 | 32 | return self._route.execute(host, parser.get_path()) 33 | 34 | def rewrite_request(self, req, extra): 35 | 36 | try: 37 | while True: 38 | if extra.get('rewrite_location', True): 39 | parser = HttpStream(req) 40 | 41 | prefix = extra.get('prefix', '') 42 | location = parser.url() 43 | if prefix: 44 | try: 45 | location = location.split(prefix, 1)[1] or '/' 46 | except IndexError: 47 | pass 48 | 49 | headers = rewrite_headers(parser, location, 50 | [('host', extra.get('host'))]) 51 | 52 | if headers is None: 53 | break 54 | 55 | extra['path'] = parser.path() 56 | 57 | req.writeall(headers) 58 | body = parser.body_file() 59 | while True: 60 | data = body.read(8192) 61 | if not data: 62 | break 63 | req.writeall(data) 64 | else: 65 | while True: 66 | data = req.read(io.DEFAULT_BUFFER_SIZE) 67 | if not data: 68 | break 69 | req.writeall(data) 70 | except (socket.error, NoMoreData): 71 | pass 72 | 73 | def rewrite_response(self, resp, extra): 74 | try: 75 | if extra.get('rewrite_response', False): 76 | parser = HttpStream(resp, decompress=True) 77 | 78 | rw = RewriteResponse(parser, resp, extra) 79 | rw.execute() 80 | 81 | else: 82 | parser = HttpStream(resp) 83 | headers = parser.headers() 84 | headers['connection'] = 'close' 85 | 86 | new_headers = headers_lines(parser, headers) 87 | resp.writeall("".join(new_headers) + "\r\n") 88 | 89 | body = parser.body_file() 90 | send_body(resp, body, parser.is_chunked()) 91 | except (socket.error, NoMoreData, ParserError): 92 | pass 93 | 94 | def proxy_error(self, client, e): 95 | msg = "HTTP/1.1 500 Server Error\r\n\r\n Server Error: '%s'" % str(e) 96 | client.sock.send(msg) 97 | 98 | def proxy(self, data): 99 | # parse headers 100 | recved = len(data) 101 | parser = HttpParser() 102 | nparsed = parser.execute(data, recved) 103 | if nparsed != recved: 104 | return {"close": True} 105 | 106 | if not parser.is_headers_complete(): 107 | return 108 | 109 | # get remote 110 | return self.lookup(parser) 111 | -------------------------------------------------------------------------------- /hroute/rewrite.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | import io 7 | import urlparse 8 | 9 | from http_parser.http import ParserError, NoMoreData 10 | try: 11 | from lxml import etree 12 | import lxml.html 13 | except ImportError: 14 | raise ImportError("""lxml isn't installed 15 | 16 | pip install lxml 17 | """) 18 | 19 | 20 | from .util import headers_lines, normalize, send_body, \ 21 | absolute_http_url_re 22 | 23 | HTML_CTYPES = ( 24 | "text/html", 25 | "application/xhtml+xml", 26 | "application/xml" 27 | ) 28 | 29 | def rewrite_headers(parser, location, values=None): 30 | headers = parser.headers() 31 | values = values or [] 32 | 33 | new_headers = [] 34 | 35 | if values and values is not None: 36 | for hname, hvalue in values: 37 | headers[hname] = hvalue 38 | 39 | httpver = "HTTP/%s" % ".".join(map(str, 40 | parser.version())) 41 | 42 | new_headers = ["%s %s %s\r\n" % (parser.method(), location, 43 | httpver)] 44 | 45 | new_headers.extend(["%s: %s\r\n" % (hname, hvalue) \ 46 | for hname, hvalue in headers.items()]) 47 | 48 | return bytes("".join(new_headers) + "\r\n") 49 | 50 | 51 | class RewriteResponse(object): 52 | 53 | def __init__(self, parser, resp, extra): 54 | self.parser = parser 55 | self.resp = resp 56 | self.extra = extra 57 | self.prefix = extra.get('prefix') 58 | if self.prefix is not None and self.prefix.endswith('/'): 59 | self.prefix = self.prefix[:-1] 60 | 61 | self.base = extra['base_uri'] 62 | self.local_base = extra['vhost_uri'] 63 | 64 | def rewrite_headers(self): 65 | try: 66 | headers = self.parser.headers() 67 | except (ParserError, NoMoreData): 68 | return False, None 69 | 70 | # handle redirection 71 | status_int = self.parser.status_code() 72 | 73 | if status_int in (301, 302, 303): 74 | location = headers.get('location') 75 | if location.startswith(self.base): 76 | rel = "%s%s" % (self.prefix, location.split(self.base)[1]) 77 | headers['location'] = urlparse.urljoin(self.local_base, rel) 78 | elif location.startswith("/"): 79 | # bugged server 80 | rel = "%s%s" % (self.prefix, location) 81 | headers['location'] = urlparse.urljoin(self.local_base, rel) 82 | 83 | return False, headers_lines(self.parser, headers) 84 | 85 | # rewrite location 86 | prefix = self.extra.get('prefix') 87 | if prefix is not None and "location" in headers: 88 | location = headers['location'] 89 | if not location.startswith('/'): 90 | location = "/%s" % location 91 | headers['location'] = "%s%s" % (self.prefix, location) 92 | 93 | # can we rewrite the links? 94 | ctype = headers.get('content-type') 95 | if ctype is not None: 96 | ctype = ctype.split(';', 1)[0].strip() 97 | 98 | if ctype in HTML_CTYPES: 99 | rewrite = True 100 | for h in ('content-length', 'transfer-encoding'): 101 | if h in headers: 102 | del headers[h] 103 | else: 104 | rewrite = False 105 | headers['connection'] = 'close' 106 | return (rewrite, headers_lines(self.parser, headers)) 107 | 108 | def rewrite_link(self, link): 109 | if not absolute_http_url_re.match(link) and \ 110 | not link.startswith('mailto:') and \ 111 | not link.startswith('javascript:'): 112 | link = normalize(self.prefix, link) 113 | elif link.startswith(self.base): 114 | rel = "%s%s" % (self.prefix, link.split(self.base)[1]) 115 | link = urlparse.urljoin(self.local_base, rel) 116 | return link 117 | 118 | def execute(self): 119 | rewrite, headers = self.rewrite_headers() 120 | if not headers: 121 | msg = "HTTP/1.1 502 Gateway Error\r\n\r\n bad request." 122 | self.resp.send(msg) 123 | return 124 | 125 | if rewrite: 126 | body = self.parser.body_string() 127 | if not body: 128 | rewritten_body = '' 129 | else: 130 | html = lxml.html.fromstring(body) 131 | 132 | # rewrite links to absolute 133 | html.rewrite_links(self.rewrite_link) 134 | 135 | # add base 136 | absolute_path = "%s%s" % (self.local_base, 137 | self.extra.get('path', '')) 138 | 139 | old_base = html.find(".//base") 140 | base = etree.Element("base") 141 | base.attrib['href'] = absolute_path 142 | 143 | if not old_base: 144 | head = html.find(".//head") 145 | head.append(base) 146 | 147 | # modify response 148 | rewritten_body = bytes(lxml.html.tostring(html)) 149 | 150 | # finally send response. 151 | headers.extend([ 152 | 'Content-Length: %s\r\n' % len(rewritten_body), 153 | "\r\n"]) 154 | 155 | self.resp.writeall(bytes("".join(headers))) 156 | stream = io.BytesIO(rewritten_body) 157 | while True: 158 | data = stream.read(io.DEFAULT_BUFFER_SIZE) 159 | if not data: 160 | break 161 | self.resp.writeall(data) 162 | else: 163 | self.resp.writeall(bytes("".join(headers) + "\r\n")) 164 | body = self.parser.body_file() 165 | send_body(self.resp, body, self.parser.is_chunked()) 166 | -------------------------------------------------------------------------------- /hroute/util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 - 2 | # 3 | # This file is part of hroute released under the MIT license. 4 | # See the NOTICE for more information. 5 | 6 | import io 7 | import os 8 | import posixpath 9 | import re 10 | 11 | from tproxy.util import parse_address 12 | 13 | absolute_http_url_re = re.compile(r"^https?://", re.I) 14 | 15 | def normalize(prefix, link): 16 | """ normalize a path """ 17 | # anchors 18 | if link.startswith("#"): 19 | return link 20 | 21 | if not link.startswith('/'): 22 | link = "/%s" % link 23 | path = posixpath.normpath("%s%s" % (prefix, link)) 24 | return path 25 | 26 | def headers_lines(parser, headers): 27 | """ build list of header lines """ 28 | httpver = "HTTP/%s" % ".".join(map(str, parser.version())) 29 | new_headers = ["%s %s\r\n" % (httpver, parser.status())] 30 | new_headers.extend(["%s: %s\r\n" % (hname, hvalue) \ 31 | for hname, hvalue in headers.items()]) 32 | return new_headers 33 | 34 | def get_host(addr, is_ssl=False): 35 | """ return a correct Host header """ 36 | host = addr[0] 37 | if addr[1] != (is_ssl and 443 or 80): 38 | host = "%s:%s" % (host, addr[1]) 39 | return host 40 | 41 | def base_uri(host, is_ssl=False): 42 | """ return the host uri """ 43 | if is_ssl: 44 | scheme = "https" 45 | else: 46 | scheme = "http" 47 | return "%s://%s" % (scheme, host) 48 | 49 | def write_chunk(to, data): 50 | """ send a chunk encoded """ 51 | chunk = "".join(("%X\r\n" % len(data), data, "\r\n")) 52 | to.writeall(chunk) 53 | 54 | def write(to, data): 55 | to.writeall(data) 56 | 57 | def send_body(to, body, chunked=False): 58 | if chunked: 59 | _write = write_chunk 60 | else: 61 | _write = write 62 | 63 | while True: 64 | data = body.read(io.DEFAULT_BUFFER_SIZE) 65 | if not data: 66 | break 67 | _write(to, data) 68 | 69 | if chunked: 70 | _write(to, "") 71 | 72 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gevent>=0.13.6 2 | setproctitle>=1.1.2 3 | tproxy>=0.5.2 4 | http-parser>=0.3.3 5 | lxml 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_rpm] 2 | packager = Randall Leeds 3 | build-requires = python2-devel python-setuptools 4 | requires = python-setuptools >= 0.6c6 python-ctypes 5 | install_script = rpm/install 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 - 3 | # 4 | # This file is part of tproxy released under the MIT license. 5 | # See the NOTICE for more information. 6 | 7 | from __future__ import with_statement 8 | 9 | from glob import glob 10 | from imp import load_source 11 | import os 12 | import sys 13 | 14 | 15 | CLASSIFIERS = [ 16 | 'Development Status :: 4 - Beta', 17 | 'Environment :: Other Environment', 18 | 'Intended Audience :: Developers', 19 | 'License :: OSI Approved :: MIT License', 20 | 'Operating System :: MacOS :: MacOS X', 21 | 'Operating System :: POSIX', 22 | 'Programming Language :: Python', 23 | 'Topic :: Internet', 24 | 'Topic :: Internet :: Proxy Servers', 25 | 'Topic :: Utilities', 26 | 'Topic :: Software Development :: Libraries :: Python Modules', 27 | 'Topic :: System :: Networking' 28 | ] 29 | 30 | MODULES = ( 31 | 'hroute', 32 | ) 33 | 34 | SCRIPTS = glob("bin/hroute*") 35 | 36 | def main(): 37 | if "--setuptools" in sys.argv: 38 | sys.argv.remove("--setuptools") 39 | from setuptools import setup 40 | use_setuptools = True 41 | else: 42 | from distutils.core import setup 43 | use_setuptools = False 44 | 45 | hroute = load_source("hroute", os.path.join("hroute", 46 | "__init__.py")) 47 | 48 | # read long description 49 | with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: 50 | long_description = f.read() 51 | 52 | 53 | PACKAGES = {} 54 | for name in MODULES: 55 | PACKAGES[name] = name.replace(".", "/") 56 | 57 | DATA_FILES = [ 58 | ('tproxy', ["LICENSE", "MANIFEST.in", "NOTICE", "README.rst", 59 | "THANKS",]) 60 | ] 61 | 62 | options = dict( 63 | name = 'hroute', 64 | version = hroute.__version__, 65 | description = 'HTTP router', 66 | long_description = long_description, 67 | author = 'Benoit Chesneau', 68 | author_email = 'benoitc@e-engura.com', 69 | license = 'MIT', 70 | url = 'http://github.com/benoitc/hroute', 71 | classifiers = CLASSIFIERS, 72 | packages = PACKAGES.keys(), 73 | package_dir = PACKAGES, 74 | scripts = SCRIPTS, 75 | data_files = DATA_FILES, 76 | ) 77 | 78 | 79 | setup(**options) 80 | 81 | if __name__ == "__main__": 82 | main() 83 | 84 | --------------------------------------------------------------------------------