root/lang/python/misc/woof @ 4741

Revision 4741, 11.0 kB (checked in by elim, 7 years ago)

lang/python/misc/woof: added zip support.

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2# -*- encoding: utf-8 -*-
3#
4#  woof -- an ad-hoc single file webserver
5#  Copyright (C) 2004 Simon Budig  <simon@budig.de>
6#
7#  This program is free software; you can redistribute it and/or modify
8#  it under the terms of the GNU General Public License as published by
9#  the Free Software Foundation; either version 2 of the License, or
10#  (at your option) any later version.
11#
12#  This program is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15#  GNU General Public License for more details.
16#
17#  A copy of the GNU General Public License is available at
18#  http://www.fsf.org/licenses/gpl.txt, you can also write to the
19#  Free Software  Foundation, Inc., 59 Temple Place - Suite 330,
20#  Boston, MA 02111-1307, USA.
21
22# Darwin support with the help from Mat Caughron, <mat@phpconsulting.com>
23# Solaris support by Colin Marquardt, <colin.marquardt@zmd.de>
24# FreeBSD support with the help from Andy Gimblett, <A.M.Gimblett@swansea.ac.uk>
25# Cygwin support by Stefan Reichör <stefan@xsteve.at>
26
27import sys, os, popen2, signal, select, socket, getopt, commands
28import urllib, BaseHTTPServer
29import ConfigParser
30
31maxdownloads = 1
32cpid = -1
33compress_type_alist = {'tar.gz': 'tar czf - "$woof_file"',
34                       'tar'   : 'tar cf - "$woof_file"',
35                       'zip'   : 'zip -qr - "$woof_file"'}
36compress_type = 'tar.gz'
37
38
39# Utility function to guess the IP (as a string) where the server can be
40# reached from the outside. Quite nasty problem actually.
41
42def find_ip ():
43   if sys.platform == "cygwin":
44      ipcfg = os.popen("ipconfig").readlines()
45      for l in ipcfg:
46         try:
47            candidat = l.split(":")[1].strip()
48            if candidat[0].isdigit():
49               break
50         except:
51            pass
52      return candidat
53
54   os.environ["PATH"] = "/sbin:/usr/sbin:/usr/local/sbin:" + os.environ["PATH"]
55   platform = os.uname()[0];
56
57   if platform == "Linux":
58      netstat = commands.getoutput ("LC_MESSAGES=C netstat -rn")
59      defiface = [i.split ()[-1] for i in netstat.split ('\n')
60                                    if i.split ()[0] == "0.0.0.0"]
61   elif platform in ("Darwin", "FreeBSD"):
62      netstat = commands.getoutput ("LC_MESSAGES=C netstat -rn")
63      defiface = [i.split ()[-1] for i in netstat.split ('\n')
64                                    if len(i) > 2 and i.split ()[0] == "default"]
65   elif platform == "SunOS":
66      netstat = commands.getoutput ("LC_MESSAGES=C netstat -arn")
67      defiface = [i.split ()[-1] for i in netstat.split ('\n')
68                                    if len(i) > 2 and i.split ()[0] == "0.0.0.0"]
69   else:
70      print >>sys.stderr, "Unsupported platform; please add support for your platform in find_ip().";
71      return None
72
73   if not defiface:
74      return None
75
76   if platform == "Linux":
77      ifcfg = commands.getoutput ("LC_MESSAGES=C ifconfig "
78                                  + defiface[0]).split ("inet addr:")
79   elif platform in ("Darwin", "FreeBSD", "SunOS"):
80      ifcfg = commands.getoutput ("LC_MESSAGES=C ifconfig "
81                                  + defiface[0]).split ("inet ")
82
83   if len (ifcfg) != 2:
84      return None
85   ip_addr = ifcfg[1].split ()[0]
86
87   # sanity check
88   try:
89      ints = [ i for i in ip_addr.split (".") if 0 <= int(i) <= 255]
90      if len (ints) != 4:
91         return None
92   except ValueError:
93      return None
94
95   return ip_addr
96
97   
98# Main class implementing an HTTP-Requesthandler, that serves just a single
99# file and redirects all other requests to this file (this passes the actual
100# filename to the client).
101# Currently it is impossible to serve different files with different
102# instances of this class.
103
104class FileServHTTPRequestHandler (BaseHTTPServer.BaseHTTPRequestHandler):
105   server_version = "Simons FileServer"
106   protocol_version = "HTTP/1.0"
107
108   filename = "."
109
110   def log_request (self, code='-', size='-'):
111      if code == 200:
112         BaseHTTPServer.BaseHTTPRequestHandler.log_request (self, code, size)
113
114
115   def do_GET (self):
116      global maxdownloads, cpid, compress_type
117
118      # Redirect any request to the filename of the file to serve.
119      # This hands over the filename to the client.
120
121      self.path = urllib.quote (urllib.unquote (self.path))
122      location = "/" + urllib.quote (os.path.basename (self.filename))
123      if os.path.isdir (self.filename):
124         location += ("." + compress_type)
125
126      if self.path != location:
127         txt = """\
128                <html>
129                   <head><title>302 Found</title></head>
130                   <body>302 Found <a href="%s">here</a>.</body>
131                </html>\n""" % location
132         self.send_response (302)
133         self.send_header ("Location", location)
134         self.send_header ("Content-type", "text/html")
135         self.send_header ("Content-Length", str (len (txt)))
136         self.end_headers ()
137         self.wfile.write (txt)
138         return
139
140      maxdownloads -= 1
141
142      # let a separate process handle the actual download, so that
143      # multiple downloads can happen simultaneously.
144
145      cpid = os.fork ()
146      os.setpgrp ()
147
148      if cpid == 0:
149         # Child process
150         size = -1
151         datafile = None
152         child = None
153         
154         if os.path.isfile (self.filename):
155            size = os.path.getsize (self.filename)
156            datafile = open (self.filename)
157         elif os.path.isdir (self.filename):
158            os.environ['woof_dir'], os.environ['woof_file'] = os.path.split (self.filename)
159            cmdline = compress_type_alist[compress_type]
160            child = popen2.Popen3 ('cd "$woof_dir";%(cmdline)s' % locals())
161            datafile = child.fromchild
162
163         self.send_response (200)
164         self.send_header ("Content-type", "application/octet-stream")
165         if size >= 0:
166            self.send_header ("Content-Length", size)
167         self.end_headers ()
168
169         try:
170            try:
171               while 1:
172                  if select.select ([datafile], [], [], 2)[0]:
173                     c = datafile.read (1024)
174                     if c:
175                        self.wfile.write (c)
176                     else:
177                        datafile.close ()
178                        break
179            except:
180               print >>sys.stderr, "Connection broke. Aborting"
181
182         finally:
183            # for some reason tar doesnt stop working when the pipe breaks
184            if child:
185               if child.poll ():
186                  os.killpg (os.getpgid (child.pid), signal.SIGTERM)
187               
188
189def serve_files (filename, maxdown = 1, ip_addr = '', port = 8080):
190   global maxdownloads
191
192   maxdownloads = maxdown
193
194   # We have to somehow push the filename of the file to serve to the
195   # class handling the requests. This is an evil way to do this...
196
197   FileServHTTPRequestHandler.filename = filename
198
199   try:
200      httpd = BaseHTTPServer.HTTPServer ((ip_addr, port),
201                                         FileServHTTPRequestHandler)
202   except socket.error:
203      print >>sys.stderr, "cannot bind to IP address '%s' port %d" % (ip_addr, port)
204      sys.exit (1)
205
206   if not ip_addr:
207      ip_addr = find_ip ()
208   if ip_addr:
209      print "Now serving on http://%s:%s/" % (ip_addr, httpd.server_port)
210
211   while cpid != 0 and maxdownloads > 0:
212      httpd.handle_request ()
213
214
215
216def usage (defport, defmaxdown, errmsg = None):
217   name = os.path.basename (sys.argv[0])
218   print >>sys.stderr, """
219    Usage: %s [-i <ip_addr>] [-p <port>] [-c <count>] [-u <suffix>] <file/dir>
220           %s [-i <ip_addr>] [-p <port>] [-c <count>] [-u <suffix>] -s
221   
222    Serves a single file <count> times via http on port <port> on IP
223    address <ip_addr>.
224    When a directory is specified, a .tar.gz archive gets served (or an
225    uncompressed tar archive when -u is specified), when -s is specified
226    instead of a filename, %s distributes itself.
227   
228    defaults: count = %d, port = %d
229
230    You can specify different defaults in two locations: /etc/woofrc
231    and ~/.woofrc can be INI-style config files containing the default
232    port and the default count. The file in the home directory takes
233    precedence.
234
235    Sample file:
236
237        [main]
238        port = 8008
239        count = 2
240        ip = 127.0.0.1
241        compress_type = 'tar.gz'
242   """ % (name, name, name, defmaxdown, defport)
243   if errmsg:
244      print >>sys.stderr, errmsg
245      print >>sys.stderr
246   sys.exit (1)
247
248
249def main ():
250   global cpid, compress_type
251
252   maxdown = 1
253   port = 8080
254   ip_addr = ''
255
256   config = ConfigParser.ConfigParser()
257   config.read (['/etc/woofrc', os.path.expanduser('~/.woofrc')])
258
259   if config.has_option ('main', 'port'):
260      port = config.getint ('main', 'port')
261
262   if config.has_option ('main', 'count'):
263      maxdown = config.getint ('main', 'count')
264
265   if config.has_option ('main', 'ip'):
266      ip_addr = config.get ('main', 'ip')
267
268   if config.has_option ('main', 'compress_type'):
269      compress_type = config.get ('main', 'compress_type')
270
271   defaultport = port
272   defaultmaxdown = maxdown
273
274   try:
275      options, filenames = getopt.getopt (sys.argv[1:], "hsu:i:c:p:")
276   except getopt.GetoptError, desc:
277      usage (defaultport, defaultmaxdown, desc)
278
279   for option, val in options:
280      if option == '-c':
281         try:
282            maxdown = int (val)
283            if maxdown <= 0:
284               raise ValueError
285         except ValueError:
286            usage (defaultport, defaultmaxdown,
287                   "invalid download count: %r. "
288                   "Please specify an integer >= 0." % val)
289
290      elif option == '-i':
291         ip_addr = val
292
293      elif option == '-p':
294         try:
295            port = int (val)
296         except ValueError:
297            usage (defaultport, defaultmaxdown,
298                   "invalid port number: %r. Please specify an integer" % val)
299
300      elif option == '-s':
301         filenames.append (__file__)
302
303      elif option == '-h':
304         usage (defaultport, defaultmaxdown)
305
306      elif option == '-u':
307         if val[0] == ".":
308            val = val[1:]
309         try:
310            compress_type = val
311            compress_type_alist[compress_type]
312         except KeyError:
313            usage (defaultport, defaultmaxdown,
314                   "acceptable suffix is: %s" % compress_type_alist.keys())
315      else:
316         usage (defaultport, defaultmaxdown, "Unknown option: %r" % option)
317
318   if len (filenames) == 1:
319      filename = os.path.abspath (filenames[0])
320   else:
321      usage (defaultport, defaultmaxdown,
322             "Can only serve single files/directories.")
323
324   if not os.path.exists (filename):
325      usage (defaultport, defaultmaxdown,
326             "%s: No such file or directory" % filenames[0])
327
328   if not (os.path.isfile (filename) or os.path.isdir (filename)):
329      usage (defaultport, defaultmaxdown,
330             "%s: Neither file nor directory" % filenames[0])
331
332   serve_files (filename, maxdown, ip_addr, port)
333
334   # wait for child processes to terminate
335   if cpid != 0:
336      try:
337         while 1:
338            os.wait ()
339      except OSError:
340         pass
341
342
343
344if __name__=='__main__':
345   try:
346      main ()
347   except KeyboardInterrupt:
348      pass
Note: See TracBrowser for help on using the browser.