Commit | Line | Data |
---|---|---|
c638d827 CR |
1 | # mod_python interface for Roundup Issue Tracker |
2 | # | |
3 | # This module is free software, you may redistribute it | |
4 | # and/or modify under the same terms as Python. | |
5 | # | |
6 | # This module provides Roundup Web User Interface | |
7 | # using mod_python Apache module. Initially written | |
8 | # with python 2.3.3, mod_python 3.1.3, roundup 0.7.0. | |
9 | # | |
10 | # This module operates with only one tracker | |
11 | # and must be placed in the tracker directory. | |
12 | # | |
13 | ||
14 | import cgi | |
15 | import os | |
16 | import threading | |
17 | ||
18 | from mod_python import apache | |
19 | ||
20 | import roundup.instance | |
21 | from roundup.cgi import TranslationService | |
22 | ||
23 | class Headers(dict): | |
24 | ||
25 | """HTTP headers wrapper""" | |
26 | ||
27 | def __init__(self, headers): | |
28 | """Initialize with `apache.table`""" | |
29 | super(Headers, self).__init__(headers) | |
30 | self.getheader = self.get | |
31 | ||
32 | class Request(object): | |
33 | ||
34 | """`apache.Request` object wrapper providing roundup client interface""" | |
35 | ||
36 | def __init__(self, request): | |
37 | """Initialize with `apache.Request` object""" | |
38 | self._req = request | |
39 | # .headers.getheader() | |
40 | self.headers = Headers(request.headers_in) | |
41 | # .wfile.write() | |
42 | self.wfile = self._req | |
43 | ||
44 | def start_response(self, headers, response): | |
45 | self.send_response(response) | |
46 | for key, value in headers: | |
47 | self.send_header(key, value) | |
48 | self.end_headers() | |
49 | ||
50 | def send_response(self, response_code): | |
51 | """Set HTTP response code""" | |
52 | self._req.status = response_code | |
53 | ||
54 | def send_header(self, name, value): | |
55 | """Set output header""" | |
56 | # value may be an instance of roundup.cgi.exceptions.HTTPException | |
57 | value = str(value) | |
58 | # XXX default content_type is "text/plain", | |
59 | # and ain't overrided by "Content-Type" header | |
60 | if name == "Content-Type": | |
61 | self._req.content_type = value | |
62 | else: | |
63 | self._req.headers_out.add(name, value) | |
64 | ||
65 | def end_headers(self): | |
66 | """NOOP. There aint no such thing as 'end_headers' in mod_python""" | |
67 | pass | |
68 | ||
69 | ||
70 | def sendfile(self, filename, offset = 0, len = -1): | |
71 | """Send 'filename' to the user.""" | |
72 | ||
73 | return self._req.sendfile(filename, offset, len) | |
74 | ||
75 | __tracker_cache = {} | |
76 | """A cache of optimized tracker instances. | |
77 | ||
78 | The keys are strings giving the directories containing the trackers. | |
79 | The values are tracker instances.""" | |
80 | ||
81 | __tracker_cache_lock = threading.Lock() | |
82 | """A lock used to guard access to the cache.""" | |
83 | ||
84 | ||
85 | def handler(req): | |
86 | """HTTP request handler""" | |
87 | _options = req.get_options() | |
88 | _home = _options.get("TrackerHome") | |
89 | _lang = _options.get("TrackerLanguage") | |
90 | _timing = _options.get("TrackerTiming", "no") | |
91 | if _timing.lower() in ("no", "false"): | |
92 | _timing = "" | |
93 | _debug = _options.get("TrackerDebug", "no") | |
94 | _debug = _debug.lower() not in ("no", "false") | |
95 | ||
96 | # We do not need to take a lock here (the fast path) because reads | |
97 | # from dictionaries are atomic. | |
98 | if not _debug and _home in __tracker_cache: | |
99 | _tracker = __tracker_cache[_home] | |
100 | else: | |
101 | if not (_home and os.path.isdir(_home)): | |
102 | apache.log_error( | |
103 | "PythonOption TrackerHome missing or invalid for %(uri)s" | |
104 | % {'uri': req.uri}) | |
105 | return apache.HTTP_INTERNAL_SERVER_ERROR | |
106 | if _debug: | |
107 | _tracker = roundup.instance.open(_home, optimize=0) | |
108 | else: | |
109 | __tracker_cache_lock.acquire() | |
110 | try: | |
111 | # The tracker may have been added while we were acquiring | |
112 | # the lock. | |
113 | if _home in __tracker_cache: | |
114 | _tracker = __tracker_cache[home] | |
115 | else: | |
116 | _tracker = roundup.instance.open(_home, optimize=1) | |
117 | __tracker_cache[_home] = _tracker | |
118 | finally: | |
119 | __tracker_cache_lock.release() | |
120 | # create environment | |
121 | # Note: cookies are read from HTTP variables, so we need all HTTP vars | |
122 | req.add_common_vars() | |
123 | _env = dict(req.subprocess_env) | |
124 | # XXX classname must be the first item in PATH_INFO. roundup.cgi does: | |
125 | # path = string.split(os.environ.get('PATH_INFO', '/'), '/') | |
126 | # os.environ['PATH_INFO'] = string.join(path[2:], '/') | |
127 | # we just remove the first character ('/') | |
128 | _env["PATH_INFO"] = req.path_info[1:] | |
129 | if _timing: | |
130 | _env["CGI_SHOW_TIMING"] = _timing | |
131 | _form = cgi.FieldStorage(req, environ=_env) | |
132 | _client = _tracker.Client(_tracker, Request(req), _env, _form, | |
133 | translator=TranslationService.get_translation(_lang, | |
134 | tracker_home=_home)) | |
135 | _client.main() | |
136 | return apache.OK | |
137 | ||
138 | # vim: set et sts=4 sw=4 : |