summaryrefslogtreecommitdiff
path: root/fs/expose/http.py
blob: fca3348763ac4c7f0402ef2bd4e44f9b05e58922 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
__all__ = ["serve_fs"]

import SimpleHTTPServer
import SocketServer
from fs.path import pathjoin, dirname
from fs.errors import FSError
from time import mktime
from cStringIO import StringIO
import cgi
import urllib
import posixpath
import time
import threading
import socket

def _datetime_to_epoch(d):
    return mktime(d.timetuple())

class FSHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
    
    """A hacked together version of SimpleHTTPRequestHandler"""
    
    def __init__(self, fs, request, client_address, server):
        self._fs = fs
        SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, request, client_address, server)
    
    def do_GET(self):
        """Serve a GET request."""  
        f = None
        try:
            f = self.send_head()
            if f:
                try:
                    self.copyfile(f, self.wfile)
                except socket.error:
                    pass                
        finally:
            if f is not None:
                f.close()             

    def send_head(self):
        """Common code for GET and HEAD commands.

        This sends the response code and MIME headers.

        Return value is either a file object (which has to be copied
        to the outputfile by the caller unless the command was HEAD,
        and must be closed by the caller under all circumstances), or
        None, in which case the caller has nothing further to do.

        """
        path = self.translate_path(self.path)
        f = None
        if self._fs.isdir(path):
            if not self.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(301)
                self.send_header("Location", self.path + "/")
                self.end_headers()
                return None
            for index in ("index.html", "index.htm"):
                index = pathjoin(path, index)
                if self._fs.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)        
        try:
            info = self._fs.getinfo(path)
            f = self._fs.open(path, 'r')
        except FSError, e:
            self.send_error(404, str(e))
            return None
        self.send_response(200)        
        self.send_header("Content-type", ctype)        
        self.send_header("Content-Length", str(info['size']))
        if 'modified_time' in info:
            self.send_header("Last-Modified", self.date_time_string(_datetime_to_epoch(info['modified_time'])))
        self.end_headers()
        return f


    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).

        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().

        """
        try:
            dir_paths = self._fs.listdir(path, dirs_only=True)
            file_paths = self._fs.listdir(path, files_only=True)
        except FSError:
            self.send_error(404, "No permission to list directory")
            return None
        paths = [p+'/' for p in sorted(dir_paths, key=lambda p:p.lower())] + sorted(file_paths, key=lambda p:p.lower())
        #list.sort(key=lambda a: a.lower())
        f = StringIO()
        displaypath = cgi.escape(urllib.unquote(self.path))
        f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
        f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
        f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
        f.write("<hr>\n<ul>\n")
        
        parent = dirname(path)
        if path != parent:
            f.write('<li><a href="%s">../</a></li>' % urllib.quote(parent.rstrip('/') + '/'))
        
        for path in paths:
            f.write('<li><a href="%s">%s</a>\n'
                    % (urllib.quote(path), cgi.escape(path)))
        f.write("</ul>\n<hr>\n</body>\n</html>\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f
    
    def translate_path(self, path):
        # abandon query parameters
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        path = posixpath.normpath(urllib.unquote(path))
        return path
        

def serve_fs(fs, address='', port=8000):
    
    """Serve an FS instance over http
    
    :param fs: an FS object
    :param address: IP address to serve on
    :param port: port number
    
    """
    
    def Handler(request, client_address, server):
        return FSHTTPRequestHandler(fs, request, client_address, server)
    
    #class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    #    pass    
    httpd = SocketServer.TCPServer((address, port), Handler, bind_and_activate=False)    
    #httpd = ThreadedTCPServer((address, port), Handler, bind_and_activate=False)
    httpd.allow_reuse_address = True
    httpd.server_bind()
    httpd.server_activate()
    
    server_thread = threading.Thread(target=httpd.serve_forever)        
    server_thread.start()    
    try:
        while True:
            time.sleep(0.1)
    except (KeyboardInterrupt, SystemExit):
        httpd.shutdown()
    
if __name__ == "__main__":
    
    from fs.osfs import OSFS
    serve_fs(OSFS('~/'))