##// END OF EJS Templates
This patch make several WSGI related alterations....
Eric Hopper -
r2506:d0db3462 default
parent child Browse files
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=hgrequest()):
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=hgrequest()):
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 hgrequest(object):
13 class wsgiapplication(object):
14 def __init__(self, inp=None, out=None, env=None):
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.out.write(str(thing))
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:hgrequest")
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_and_run_handler(self, req):
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.run(req)
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