Show More
@@ -0,0 +1,69 b'' | |||||
|
1 | # hgweb/wsgicgi.py - CGI->WSGI translator | |||
|
2 | # | |||
|
3 | # Copyright 2006 Eric Hopper <hopper@omnifarious.org> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms | |||
|
6 | # of the GNU General Public License, incorporated herein by reference. | |||
|
7 | # | |||
|
8 | # This was originally copied from the public domain code at | |||
|
9 | # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side | |||
|
10 | ||||
|
11 | import os, sys | |||
|
12 | ||||
|
13 | def launch(application): | |||
|
14 | ||||
|
15 | environ = dict(os.environ.items()) | |||
|
16 | environ['wsgi.input'] = sys.stdin | |||
|
17 | environ['wsgi.errors'] = sys.stderr | |||
|
18 | environ['wsgi.version'] = (1,0) | |||
|
19 | environ['wsgi.multithread'] = False | |||
|
20 | environ['wsgi.multiprocess'] = True | |||
|
21 | environ['wsgi.run_once'] = True | |||
|
22 | ||||
|
23 | if environ.get('HTTPS','off') in ('on','1'): | |||
|
24 | environ['wsgi.url_scheme'] = 'https' | |||
|
25 | else: | |||
|
26 | environ['wsgi.url_scheme'] = 'http' | |||
|
27 | ||||
|
28 | headers_set = [] | |||
|
29 | headers_sent = [] | |||
|
30 | ||||
|
31 | def write(data): | |||
|
32 | if not headers_set: | |||
|
33 | raise AssertionError("write() before start_response()") | |||
|
34 | ||||
|
35 | elif not headers_sent: | |||
|
36 | # Before the first output, send the stored headers | |||
|
37 | status, response_headers = headers_sent[:] = headers_set | |||
|
38 | sys.stdout.write('Status: %s\r\n' % status) | |||
|
39 | for header in response_headers: | |||
|
40 | sys.stdout.write('%s: %s\r\n' % header) | |||
|
41 | sys.stdout.write('\r\n') | |||
|
42 | ||||
|
43 | sys.stdout.write(data) | |||
|
44 | sys.stdout.flush() | |||
|
45 | ||||
|
46 | def start_response(status,response_headers,exc_info=None): | |||
|
47 | if exc_info: | |||
|
48 | try: | |||
|
49 | if headers_sent: | |||
|
50 | # Re-raise original exception if headers sent | |||
|
51 | raise exc_info[0], exc_info[1], exc_info[2] | |||
|
52 | finally: | |||
|
53 | exc_info = None # avoid dangling circular ref | |||
|
54 | elif headers_set: | |||
|
55 | raise AssertionError("Headers already set!") | |||
|
56 | ||||
|
57 | headers_set[:] = [status,response_headers] | |||
|
58 | return write | |||
|
59 | ||||
|
60 | result = application(environ, start_response) | |||
|
61 | try: | |||
|
62 | for data in result: | |||
|
63 | if data: # don't send headers until body appears | |||
|
64 | write(data) | |||
|
65 | if not headers_sent: | |||
|
66 | write('') # send headers now if body was empty | |||
|
67 | finally: | |||
|
68 | if hasattr(result,'close'): | |||
|
69 | result.close() |
@@ -6,7 +6,11 b' import cgitb, os, sys' | |||||
6 | cgitb.enable() |
|
6 | cgitb.enable() | |
7 |
|
7 | |||
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install |
|
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install | |
9 | from mercurial import hgweb |
|
9 | from mercurial.hgweb.hgweb_mod import hgweb | |
|
10 | from mercurial.hgweb.request import wsgiapplication | |||
|
11 | import mercurial.hgweb.wsgicgi as wsgicgi | |||
10 |
|
12 | |||
11 | h = hgweb.hgweb("/path/to/repo", "repository name") |
|
13 | def make_web_app(): | |
12 | h.run() |
|
14 | return hgweb("/path/to/repo", "repository name") | |
|
15 | ||||
|
16 | wsgicgi.launch(wsgiapplication(make_web_app)) |
@@ -6,7 +6,9 b' import cgitb, sys' | |||||
6 | cgitb.enable() |
|
6 | cgitb.enable() | |
7 |
|
7 | |||
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install |
|
8 | # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install | |
9 | from mercurial import hgweb |
|
9 | from mercurial.hgweb.hgwebdir_mod import hgwebdir | |
|
10 | from mercurial.hgweb.request import wsgiapplication | |||
|
11 | import mercurial.hgweb.wsgicgi as wsgicgi | |||
10 |
|
12 | |||
11 | # The config file looks like this. You can have paths to individual |
|
13 | # The config file looks like this. You can have paths to individual | |
12 | # repos, collections of repos in a directory tree, or both. |
|
14 | # repos, collections of repos in a directory tree, or both. | |
@@ -27,5 +29,7 b' from mercurial import hgweb' | |||||
27 | # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples |
|
29 | # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples | |
28 | # or use a dictionary with entries like 'virtual/path': '/real/path' |
|
30 | # or use a dictionary with entries like 'virtual/path': '/real/path' | |
29 |
|
31 | |||
30 | h = hgweb.hgwebdir("hgweb.config") |
|
32 | def make_web_app(): | |
31 | h.run() |
|
33 | return hgwebdir("hgweb.config") | |
|
34 | ||||
|
35 | wsgicgi.launch(wsgiapplication(make_web_app)) |
@@ -12,7 +12,6 b' import mimetypes' | |||||
12 | from mercurial.demandload import demandload |
|
12 | from mercurial.demandload import demandload | |
13 | demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile") |
|
13 | demandload(globals(), "re zlib ConfigParser cStringIO sys tempfile") | |
14 | demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater") |
|
14 | demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater") | |
15 | demandload(globals(), "mercurial.hgweb.request:hgrequest") |
|
|||
16 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") |
|
15 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") | |
17 | from mercurial.node import * |
|
16 | from mercurial.node import * | |
18 | from mercurial.i18n import gettext as _ |
|
17 | from mercurial.i18n import gettext as _ | |
@@ -652,7 +651,7 b' class hgweb(object):' | |||||
652 | raise Exception("suspicious path") |
|
651 | raise Exception("suspicious path") | |
653 | return p |
|
652 | return p | |
654 |
|
653 | |||
655 |
def run(self, req |
|
654 | def run(self, req): | |
656 | def header(**map): |
|
655 | def header(**map): | |
657 | yield self.t("header", **map) |
|
656 | yield self.t("header", **map) | |
658 |
|
657 | |||
@@ -725,7 +724,6 b' class hgweb(object):' | |||||
725 | method(req) |
|
724 | method(req) | |
726 | else: |
|
725 | else: | |
727 | req.write(self.t("error")) |
|
726 | req.write(self.t("error")) | |
728 | req.done() |
|
|||
729 |
|
727 | |||
730 | def do_changelog(self, req): |
|
728 | def do_changelog(self, req): | |
731 | hi = self.repo.changelog.count() - 1 |
|
729 | hi = self.repo.changelog.count() - 1 |
@@ -11,7 +11,6 b' from mercurial.demandload import demandl' | |||||
11 | demandload(globals(), "ConfigParser") |
|
11 | demandload(globals(), "ConfigParser") | |
12 | demandload(globals(), "mercurial:ui,hg,util,templater") |
|
12 | demandload(globals(), "mercurial:ui,hg,util,templater") | |
13 | demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") |
|
13 | demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") | |
14 | demandload(globals(), "mercurial.hgweb.request:hgrequest") |
|
|||
15 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") |
|
14 | demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") | |
16 | from mercurial.i18n import gettext as _ |
|
15 | from mercurial.i18n import gettext as _ | |
17 |
|
16 | |||
@@ -47,7 +46,7 b' class hgwebdir(object):' | |||||
47 | self.repos.append((name.lstrip(os.sep), repo)) |
|
46 | self.repos.append((name.lstrip(os.sep), repo)) | |
48 | self.repos.sort() |
|
47 | self.repos.sort() | |
49 |
|
48 | |||
50 |
def run(self, req |
|
49 | def run(self, req): | |
51 | def header(**map): |
|
50 | def header(**map): | |
52 | yield tmpl("header", **map) |
|
51 | yield tmpl("header", **map) | |
53 |
|
52 |
@@ -10,40 +10,73 b' from mercurial.demandload import demandl' | |||||
10 | demandload(globals(), "socket sys cgi os errno") |
|
10 | demandload(globals(), "socket sys cgi os errno") | |
11 | from mercurial.i18n import gettext as _ |
|
11 | from mercurial.i18n import gettext as _ | |
12 |
|
12 | |||
13 |
class |
|
13 | class wsgiapplication(object): | |
14 |
def __init__(self, |
|
14 | def __init__(self, destmaker): | |
15 | self.inp = inp or sys.stdin |
|
15 | self.destmaker = destmaker | |
16 | self.out = out or sys.stdout |
|
16 | ||
17 | self.env = env or os.environ |
|
17 | def __call__(self, wsgienv, start_response): | |
|
18 | return _wsgirequest(self.destmaker(), wsgienv, start_response) | |||
|
19 | ||||
|
20 | class _wsgioutputfile(object): | |||
|
21 | def __init__(self, request): | |||
|
22 | self.request = request | |||
|
23 | ||||
|
24 | def write(self, data): | |||
|
25 | self.request.write(data) | |||
|
26 | def writelines(self, lines): | |||
|
27 | for line in lines: | |||
|
28 | self.write(line) | |||
|
29 | def flush(self): | |||
|
30 | return None | |||
|
31 | def close(self): | |||
|
32 | return None | |||
|
33 | ||||
|
34 | class _wsgirequest(object): | |||
|
35 | def __init__(self, destination, wsgienv, start_response): | |||
|
36 | version = wsgienv['wsgi.version'] | |||
|
37 | if (version < (1,0)) or (version >= (2, 0)): | |||
|
38 | raise RuntimeError("Unknown and unsupported WSGI version %d.%d" \ | |||
|
39 | % version) | |||
|
40 | self.inp = wsgienv['wsgi.input'] | |||
|
41 | self.out = _wsgioutputfile(self) | |||
|
42 | self.server_write = None | |||
|
43 | self.err = wsgienv['wsgi.errors'] | |||
|
44 | self.threaded = wsgienv['wsgi.multithread'] | |||
|
45 | self.multiprocess = wsgienv['wsgi.multiprocess'] | |||
|
46 | self.run_once = wsgienv['wsgi.run_once'] | |||
|
47 | self.env = wsgienv | |||
18 | self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) |
|
48 | self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) | |
19 | self.will_close = True |
|
49 | self.start_response = start_response | |
|
50 | self.headers = [] | |||
|
51 | destination.run(self) | |||
|
52 | ||||
|
53 | def __iter__(self): | |||
|
54 | return iter([]) | |||
20 |
|
55 | |||
21 | def read(self, count=-1): |
|
56 | def read(self, count=-1): | |
22 | return self.inp.read(count) |
|
57 | return self.inp.read(count) | |
23 |
|
58 | |||
24 | def write(self, *things): |
|
59 | def write(self, *things): | |
|
60 | if self.server_write is None: | |||
|
61 | if not self.headers: | |||
|
62 | raise RuntimeError("request.write called before headers sent.") | |||
|
63 | self.server_write = self.start_response('200 Script output follows', | |||
|
64 | self.headers) | |||
|
65 | self.start_response = None | |||
|
66 | self.headers = None | |||
25 | for thing in things: |
|
67 | for thing in things: | |
26 | if hasattr(thing, "__iter__"): |
|
68 | if hasattr(thing, "__iter__"): | |
27 | for part in thing: |
|
69 | for part in thing: | |
28 | self.write(part) |
|
70 | self.write(part) | |
29 | else: |
|
71 | else: | |
30 | try: |
|
72 | try: | |
31 |
self. |
|
73 | self.server_write(str(thing)) | |
32 | except socket.error, inst: |
|
74 | except socket.error, inst: | |
33 | if inst[0] != errno.ECONNRESET: |
|
75 | if inst[0] != errno.ECONNRESET: | |
34 | raise |
|
76 | raise | |
35 |
|
77 | |||
36 | def done(self): |
|
|||
37 | if self.will_close: |
|
|||
38 | self.inp.close() |
|
|||
39 | self.out.close() |
|
|||
40 | else: |
|
|||
41 | self.out.flush() |
|
|||
42 |
|
||||
43 | def header(self, headers=[('Content-type','text/html')]): |
|
78 | def header(self, headers=[('Content-type','text/html')]): | |
44 | for header in headers: |
|
79 | self.headers.extend(headers) | |
45 | self.out.write("%s: %s\r\n" % header) |
|
|||
46 | self.out.write("\r\n") |
|
|||
47 |
|
80 | |||
48 | def httphdr(self, type, filename=None, length=0, headers={}): |
|
81 | def httphdr(self, type, filename=None, length=0, headers={}): | |
49 | headers = headers.items() |
|
82 | headers = headers.items() | |
@@ -51,12 +84,6 b' class hgrequest(object):' | |||||
51 | if filename: |
|
84 | if filename: | |
52 | headers.append(('Content-disposition', 'attachment; filename=%s' % |
|
85 | headers.append(('Content-disposition', 'attachment; filename=%s' % | |
53 | filename)) |
|
86 | filename)) | |
54 | # we do not yet support http 1.1 chunked transfer, so we have |
|
|||
55 | # to force connection to close if content-length not known |
|
|||
56 | if length: |
|
87 | if length: | |
57 | headers.append(('Content-length', str(length))) |
|
88 | headers.append(('Content-length', str(length))) | |
58 | self.will_close = False |
|
|||
59 | else: |
|
|||
60 | headers.append(('Connection', 'close')) |
|
|||
61 | self.will_close = True |
|
|||
62 | self.header(headers) |
|
89 | self.header(headers) |
@@ -10,7 +10,7 b' from mercurial.demandload import demandl' | |||||
10 | import os, sys, errno |
|
10 | import os, sys, errno | |
11 | demandload(globals(), "urllib BaseHTTPServer socket SocketServer") |
|
11 | demandload(globals(), "urllib BaseHTTPServer socket SocketServer") | |
12 | demandload(globals(), "mercurial:ui,hg,util,templater") |
|
12 | demandload(globals(), "mercurial:ui,hg,util,templater") | |
13 |
demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request: |
|
13 | demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication") | |
14 | from mercurial.i18n import gettext as _ |
|
14 | from mercurial.i18n import gettext as _ | |
15 |
|
15 | |||
16 | def _splitURI(uri): |
|
16 | def _splitURI(uri): | |
@@ -25,6 +25,17 b' def _splitURI(uri):' | |||||
25 | path, query = uri, '' |
|
25 | path, query = uri, '' | |
26 | return urllib.unquote(path), query |
|
26 | return urllib.unquote(path), query | |
27 |
|
27 | |||
|
28 | class _error_logger(object): | |||
|
29 | def __init__(self, handler): | |||
|
30 | self.handler = handler | |||
|
31 | def flush(self): | |||
|
32 | pass | |||
|
33 | def write(str): | |||
|
34 | self.writelines(str.split('\n')) | |||
|
35 | def writelines(seq): | |||
|
36 | for msg in seq: | |||
|
37 | self.handler.log_error("HG error: %s", msg) | |||
|
38 | ||||
28 | class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): |
|
39 | class _hgwebhandler(object, BaseHTTPServer.BaseHTTPRequestHandler): | |
29 | def __init__(self, *args, **kargs): |
|
40 | def __init__(self, *args, **kargs): | |
30 | self.protocol_version = 'HTTP/1.1' |
|
41 | self.protocol_version = 'HTTP/1.1' | |
@@ -84,10 +95,50 b' class _hgwebhandler(object, BaseHTTPServ' | |||||
84 | if hval: |
|
95 | if hval: | |
85 | env[hkey] = hval |
|
96 | env[hkey] = hval | |
86 | env['SERVER_PROTOCOL'] = self.request_version |
|
97 | env['SERVER_PROTOCOL'] = self.request_version | |
|
98 | env['wsgi.version'] = (1, 0) | |||
|
99 | env['wsgi.url_scheme'] = 'http' | |||
|
100 | env['wsgi.input'] = self.rfile | |||
|
101 | env['wsgi.errors'] = _error_logger(self) | |||
|
102 | env['wsgi.multithread'] = isinstance(self.server, | |||
|
103 | SocketServer.ThreadingMixIn) | |||
|
104 | env['wsgi.multiprocess'] = isinstance(self.server, | |||
|
105 | SocketServer.ForkingMixIn) | |||
|
106 | env['wsgi.run_once'] = 0 | |||
87 |
|
107 | |||
88 | req = hgrequest(self.rfile, self.wfile, env) |
|
108 | self.close_connection = True | |
89 | self.send_response(200, "Script output follows") |
|
109 | self.saved_status = None | |
90 | self.close_connection = self.server.make_and_run_handler(req) |
|
110 | self.saved_headers = [] | |
|
111 | self.sent_headers = False | |||
|
112 | req = self.server.reqmaker(env, self._start_response) | |||
|
113 | for data in req: | |||
|
114 | if data: | |||
|
115 | self._write(data) | |||
|
116 | ||||
|
117 | def send_headers(self): | |||
|
118 | if not self.saved_status: | |||
|
119 | raise AssertionError("Sending headers before start_response() called") | |||
|
120 | saved_status = self.saved_status.split(None, 1) | |||
|
121 | saved_status[0] = int(saved_status[0]) | |||
|
122 | self.send_response(*saved_status) | |||
|
123 | for h in self.saved_headers: | |||
|
124 | self.send_header(*h) | |||
|
125 | self.end_headers() | |||
|
126 | self.sent_headers = True | |||
|
127 | ||||
|
128 | def _start_response(self, http_status, headers, exc_info=None): | |||
|
129 | code, msg = http_status.split(None, 1) | |||
|
130 | code = int(code) | |||
|
131 | self.saved_status = http_status | |||
|
132 | self.saved_headers = headers | |||
|
133 | return self._write | |||
|
134 | ||||
|
135 | def _write(self, data): | |||
|
136 | if not self.saved_status: | |||
|
137 | raise AssertionError("data written before start_response() called") | |||
|
138 | elif not self.sent_headers: | |||
|
139 | self.send_headers() | |||
|
140 | self.wfile.write(data) | |||
|
141 | self.wfile.flush() | |||
91 |
|
142 | |||
92 | def create_server(ui, repo): |
|
143 | def create_server(ui, repo): | |
93 | use_threads = True |
|
144 | use_threads = True | |
@@ -127,8 +178,9 b' def create_server(ui, repo):' | |||||
127 | self.webdir_conf = webdir_conf |
|
178 | self.webdir_conf = webdir_conf | |
128 | self.webdirmaker = hgwebdir |
|
179 | self.webdirmaker = hgwebdir | |
129 | self.repoviewmaker = hgweb |
|
180 | self.repoviewmaker = hgweb | |
|
181 | self.reqmaker = wsgiapplication(self.make_handler) | |||
130 |
|
182 | |||
131 |
def make_ |
|
183 | def make_handler(self): | |
132 | if self.webdir_conf: |
|
184 | if self.webdir_conf: | |
133 | hgwebobj = self.webdirmaker(self.webdir_conf) |
|
185 | hgwebobj = self.webdirmaker(self.webdir_conf) | |
134 | elif self.repo is not None: |
|
186 | elif self.repo is not None: | |
@@ -136,8 +188,7 b' def create_server(ui, repo):' | |||||
136 | repo.origroot)) |
|
188 | repo.origroot)) | |
137 | else: |
|
189 | else: | |
138 | raise hg.RepoError(_('no repo found')) |
|
190 | raise hg.RepoError(_('no repo found')) | |
139 |
hgwebobj |
|
191 | return hgwebobj | |
140 | return req.will_close |
|
|||
141 |
|
192 | |||
142 | class IPv6HTTPServer(MercurialHTTPServer): |
|
193 | class IPv6HTTPServer(MercurialHTTPServer): | |
143 | address_family = getattr(socket, 'AF_INET6', None) |
|
194 | address_family = getattr(socket, 'AF_INET6', None) |
General Comments 0
You need to be logged in to leave comments.
Login now