comparison roundup/scripts/roundup_server.py @ 1356:83f33642d220 maint-0.5

[[Metadata associated with this commit was garbled during conversion from CVS to Subversion.]]
author Richard Jones <richard@users.sourceforge.net>
date Thu, 09 Jan 2003 22:59:22 +0000
parents
children 434708fc4c29
comparison
equal deleted inserted replaced
1242:3d0158c8c32b 1356:83f33642d220
1 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
2 # This module is free software, and you may redistribute it and/or modify
3 # under the same terms as Python, so long as this copyright message and
4 # disclaimer are retained in their original form.
5 #
6 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
7 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
8 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
9 # POSSIBILITY OF SUCH DAMAGE.
10 #
11 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
12 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
13 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
14 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
15 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
16 #
17 """ HTTP Server that serves roundup.
18
19 $Id: roundup_server.py,v 1.16 2002-11-28 06:55:57 richard Exp $
20 """
21
22 # python version check
23 from roundup import version_check
24
25 import sys, os, urllib, StringIO, traceback, cgi, binascii, getopt, imp
26 import BaseHTTPServer
27
28 # Roundup modules of use here
29 from roundup.cgi import cgitb, client
30 import roundup.instance
31 from roundup.i18n import _
32
33 #
34 ## Configuration
35 #
36
37 # This indicates where the Roundup trackers live. They're given as NAME ->
38 # TRACKER_HOME, where the NAME part is used in the URL to select the
39 # appropriate reacker.
40 # Make sure the NAME part doesn't include any url-unsafe characters like
41 # spaces, as these confuse the cookie handling in browsers like IE.
42 TRACKER_HOMES = {
43 'bar': '/tmp/bar',
44 }
45
46 ROUNDUP_USER = None
47
48
49 # Where to log debugging information to. Use an instance of DevNull if you
50 # don't want to log anywhere.
51 # TODO: actually use this stuff
52 #class DevNull:
53 # def write(self, info):
54 # pass
55 #LOG = open('/var/log/roundup.cgi.log', 'a')
56 #LOG = DevNull()
57
58 #
59 ## end configuration
60 #
61
62 class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
63 TRACKER_HOMES = TRACKER_HOMES
64 ROUNDUP_USER = ROUNDUP_USER
65
66 def run_cgi(self):
67 """ Execute the CGI command. Wrap an innner call in an error
68 handler so all errors can be caught.
69 """
70 save_stdin = sys.stdin
71 sys.stdin = self.rfile
72 try:
73 self.inner_run_cgi()
74 except client.NotFound:
75 self.send_error(404, self.path)
76 except client.Unauthorised:
77 self.send_error(403, self.path)
78 except:
79 # it'd be nice to be able to detect if these are going to have
80 # any effect...
81 self.send_response(400)
82 self.send_header('Content-Type', 'text/html')
83 self.end_headers()
84 try:
85 reload(cgitb)
86 self.wfile.write(cgitb.breaker())
87 self.wfile.write(cgitb.html())
88 except:
89 self.wfile.write("<pre>")
90 s = StringIO.StringIO()
91 traceback.print_exc(None, s)
92 self.wfile.write(cgi.escape(s.getvalue()))
93 self.wfile.write("</pre>\n")
94 sys.stdin = save_stdin
95
96 do_GET = do_POST = do_HEAD = send_head = run_cgi
97
98 def index(self):
99 ''' Print up an index of the available trackers
100 '''
101 self.send_response(200)
102 self.send_header('Content-Type', 'text/html')
103 self.end_headers()
104 w = self.wfile.write
105 w(_('<html><head><title>Roundup trackers index</title></head>\n'))
106 w(_('<body><h1>Roundup trackers index</h1><ol>\n'))
107 keys = self.TRACKER_HOMES.keys()
108 keys.sort()
109 for tracker in keys:
110 w(_('<li><a href="%(tracker_url)s/index">%(tracker_name)s</a>\n')%{
111 'tracker_url': urllib.quote(tracker),
112 'tracker_name': cgi.escape(tracker)})
113 w(_('</ol></body></html>'))
114
115 def inner_run_cgi(self):
116 ''' This is the inner part of the CGI handling
117 '''
118
119 rest = self.path
120 i = rest.rfind('?')
121 if i >= 0:
122 rest, query = rest[:i], rest[i+1:]
123 else:
124 query = ''
125
126 # figure the tracker
127 if rest == '/':
128 return self.index()
129 l_path = rest.split('/')
130 tracker_name = urllib.unquote(l_path[1])
131 if self.TRACKER_HOMES.has_key(tracker_name):
132 tracker_home = self.TRACKER_HOMES[tracker_name]
133 tracker = roundup.instance.open(tracker_home)
134 else:
135 raise client.NotFound
136
137 # figure out what the rest of the path is
138 if len(l_path) > 2:
139 rest = '/'.join(l_path[2:])
140 else:
141 rest = '/'
142
143 # Set up the CGI environment
144 env = {}
145 env['TRACKER_NAME'] = tracker_name
146 env['REQUEST_METHOD'] = self.command
147 env['PATH_INFO'] = urllib.unquote(rest)
148 if query:
149 env['QUERY_STRING'] = query
150 host = self.address_string()
151 if self.headers.typeheader is None:
152 env['CONTENT_TYPE'] = self.headers.type
153 else:
154 env['CONTENT_TYPE'] = self.headers.typeheader
155 length = self.headers.getheader('content-length')
156 if length:
157 env['CONTENT_LENGTH'] = length
158 co = filter(None, self.headers.getheaders('cookie'))
159 if co:
160 env['HTTP_COOKIE'] = ', '.join(co)
161 env['HTTP_AUTHORIZATION'] = self.headers.getheader('authorization')
162 env['SCRIPT_NAME'] = ''
163 env['SERVER_NAME'] = self.server.server_name
164 env['SERVER_PORT'] = str(self.server.server_port)
165 env['HTTP_HOST'] = self.headers['host']
166
167 decoded_query = query.replace('+', ' ')
168
169 # do the roundup thang
170 c = tracker.Client(tracker, self, env)
171 c.main()
172
173 def usage(message=''):
174 if message:
175 message = _('Error: %(error)s\n\n')%{'error': message}
176 print _('''%(message)sUsage:
177 roundup-server [-n hostname] [-p port] [-l file] [-d file] [name=tracker home]*
178
179 -n: sets the host name
180 -p: sets the port to listen on
181 -l: sets a filename to log to (instead of stdout)
182 -d: daemonize, and write the server's PID to the nominated file
183
184 name=tracker home
185 Sets the tracker home(s) to use. The name is how the tracker is
186 identified in the URL (it's the first part of the URL path). The
187 tracker home is the directory that was identified when you did
188 "roundup-admin init". You may specify any number of these name=home
189 pairs on the command-line. For convenience, you may edit the
190 TRACKER_HOMES variable in the roundup-server file instead.
191 Make sure the name part doesn't include any url-unsafe characters like
192 spaces, as these confuse the cookie handling in browsers like IE.
193 ''')%locals()
194 sys.exit(0)
195
196 def daemonize(pidfile):
197 ''' Turn this process into a daemon.
198 - make sure the sys.std(in|out|err) are completely cut off
199 - make our parent PID 1
200
201 Write our new PID to the pidfile.
202
203 From A.M. Kuuchling (possibly originally Greg Ward) with
204 modification from Oren Tirosh, and finally a small mod from me.
205 '''
206 # Fork once
207 if os.fork() != 0:
208 os._exit(0)
209
210 # Create new session
211 os.setsid()
212
213 # Second fork to force PPID=1
214 pid = os.fork()
215 if pid:
216 pidfile = open(pidfile, 'w')
217 pidfile.write(str(pid))
218 pidfile.close()
219 os._exit(0)
220
221 os.chdir("/")
222 os.umask(0)
223
224 # close off sys.std(in|out|err), redirect to devnull so the file
225 # descriptors can't be used again
226 devnull = os.open('/dev/null', 0)
227 os.dup2(devnull, 0)
228 os.dup2(devnull, 1)
229 os.dup2(devnull, 2)
230
231 def abspath(path):
232 ''' Make the given path an absolute path.
233
234 Code from Zope-Coders posting of 2002-10-06 by GvR.
235 '''
236 if not os.path.isabs(path):
237 path = os.path.join(os.getcwd(), path)
238 return os.path.normpath(path)
239
240 def run():
241 ''' Script entry point - handle args and figure out what to to.
242 '''
243 hostname = ''
244 port = 8080
245 pidfile = None
246 logfile = None
247 try:
248 # handle the command-line args
249 try:
250 optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:d:l:')
251 except getopt.GetoptError, e:
252 usage(str(e))
253
254 user = ROUNDUP_USER
255 for (opt, arg) in optlist:
256 if opt == '-n': hostname = arg
257 elif opt == '-p': port = int(arg)
258 elif opt == '-u': user = arg
259 elif opt == '-d': pidfile = abspath(arg)
260 elif opt == '-l': logfile = abspath(arg)
261 elif opt == '-h': usage()
262
263 if hasattr(os, 'getuid'):
264 # if root, setuid to the running user
265 if not os.getuid() and user is not None:
266 try:
267 import pwd
268 except ImportError:
269 raise ValueError, _("Can't change users - no pwd module")
270 try:
271 uid = pwd.getpwnam(user)[2]
272 except KeyError:
273 raise ValueError, _("User %(user)s doesn't exist")%locals()
274 os.setuid(uid)
275 elif os.getuid() and user is not None:
276 print _('WARNING: ignoring "-u" argument, not root')
277
278 # People can remove this check if they're really determined
279 if not os.getuid() and user is None:
280 raise ValueError, _("Can't run as root!")
281
282 # handle tracker specs
283 if args:
284 d = {}
285 for arg in args:
286 try:
287 name, home = arg.split('=')
288 except ValueError:
289 raise ValueError, _("Instances must be name=home")
290 d[name] = home
291 RoundupRequestHandler.TRACKER_HOMES = d
292 except SystemExit:
293 raise
294 except:
295 exc_type, exc_value = sys.exc_info()[:2]
296 usage('%s: %s'%(exc_type, exc_value))
297
298 # we don't want the cgi module interpreting the command-line args ;)
299 sys.argv = sys.argv[:1]
300 address = (hostname, port)
301
302 # fork?
303 if pidfile:
304 daemonize(pidfile)
305
306 # redirect stdout/stderr to our logfile
307 if logfile:
308 sys.stdout = sys.stderr = open(logfile, 'a')
309
310 httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
311 print _('Roundup server started on %(address)s')%locals()
312 try:
313 httpd.serve_forever()
314 except KeyboardInterrupt:
315 print 'Keyboard Interrupt: exiting'
316
317 if __name__ == '__main__':
318 run()
319
320 # vim: set filetype=python ts=4 sw=4 et si

Roundup Issue Tracker: http://roundup-tracker.org/