Switch to unified view

a b/src/mediaserver/cdplugins/uprcl/uprclhttp.py
1
#!/usr/bin/env python
2
#
3
# Copyright (C) 2017 J.F.Dockes
4
#
5
# HTTP Range code:
6
#  Portions Copyright (C) 2009,2010  Xyne
7
#  Portions Copyright (C) 2011 Sean Goller
8
#  https://github.com/smgoller/rangehttpserver/blob/master/RangeHTTPServer.py
9
#
10
# This program is free software: you can redistribute it and/or modify
11
# it under the terms of the GNU General Public License as published by
12
# the Free Software Foundation, either version 3 of the License, or
13
# (at your option) any later version.
14
#
15
# This program is distributed in the hope that it will be useful,
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
# GNU General Public License for more details.
19
#
20
# You should have received a copy of the GNU General Public License
21
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23
import SocketServer
24
import BaseHTTPServer
25
import SimpleHTTPServer
26
import os
27
import posixpath
28
import BaseHTTPServer
29
import urllib
30
import cgi
31
import shutil
32
import mimetypes
33
try:
34
    from cStringIO import StringIO
35
except ImportError:
36
    from StringIO import StringIO
37
38
from uprclutils import uplog
39
40
41
42
__version__ = "0.1"
43
44
class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
45
46
    """Simple HTTP request handler with GET and HEAD commands.
47
48
    This serves files from the current directory and any of its
49
    subdirectories.  The MIME type for files is determined by
50
    calling the .guess_type() method.
51
52
    The GET and HEAD requests are identical except that the HEAD
53
    request omits the actual contents of the file.
54
55
    """
56
57
    server_version = "RangeHTTP/" + __version__
58
59
    def do_GET(self):
60
        """Serve a GET request."""
61
        f, start_range, end_range = self.send_head()
62
        if f:
63
            uplog("do_GET: Got (%d,%d)" % (start_range,end_range))
64
            f.seek(start_range, 0)
65
            chunk = 0x1000
66
            total = 0
67
            while chunk > 0:
68
                if start_range + chunk > end_range:
69
                    chunk = end_range - start_range
70
                try:
71
                    self.wfile.write(f.read(chunk))
72
                except:
73
                    break
74
                total += chunk
75
                start_range += chunk
76
            f.close()
77
78
    def do_HEAD(self):
79
        """Serve a HEAD request."""
80
        f, start_range, end_range = self.send_head()
81
        if f:
82
            f.close()
83
84
    def send_head(self):
85
        """Common code for GET and HEAD commands.
86
87
        This sends the response code and MIME headers.
88
89
        Return value is either a file object (which has to be copied
90
        to the outputfile by the caller unless the command was HEAD,
91
        and must be closed by the caller under all circumstances), or
92
        None, in which case the caller has nothing further to do.
93
94
        """
95
        uplog("HTTP: path: %s" % self.path)
96
        path = self.translate_path(self.path)
97
        if not path or not os.path.exists(path):
98
            self.send_error(404)
99
            return (None, 0, 0)
100
101
        if not os.path.isfile(path):
102
            self.send_error(405)
103
            return (None, 0, 0)
104
105
        f = None
106
        ctype = self.guess_type(path)
107
        try:
108
            f = open(path, 'rb')
109
        except:
110
            self.send_error(404, "File not found")
111
            return (None, 0, 0)
112
113
        if "Range" in self.headers:
114
            self.send_response(206)
115
        else:
116
            self.send_response(200)
117
118
        self.send_header("Content-type", ctype)
119
        fs = os.fstat(f.fileno())
120
        size = int(fs[6])
121
        start_range = 0
122
        end_range = size
123
        self.send_header("Accept-Ranges", "bytes")
124
        if "Range" in self.headers:
125
            s, e = self.headers['range'][6:].split('-', 1)
126
            sl = len(s)
127
            el = len(e)
128
            if sl > 0:
129
                start_range = int(s)
130
                if el > 0:
131
                    end_range = int(e) + 1
132
            elif el > 0:
133
                ei = int(e)
134
                if ei < size:
135
                    start_range = size - ei
136
        self.send_header("Content-Range",
137
                         'bytes ' + str(start_range) + '-' +
138
                         str(end_range - 1) + '/' + str(size))
139
        self.send_header("Content-Length", end_range - start_range)
140
        self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
141
        self.end_headers()
142
        #uplog("Sending Bytes %d to %d" % (start_range, end_range))
143
        return (f, start_range, end_range)
144
145
146
    def translate_path(self, opath):
147
        path = urllib.unquote(opath)
148
        for p in self.uprclpathmap.itervalues():
149
            if path.startswith(p):
150
                return path
151
        uplog("HTTP: translate_path: %s not found in path map" % opath)
152
        return None
153
154
    def guess_type(self, path):
155
        """Guess the type of a file.
156
157
        Argument is a PATH (a filename).
158
159
        Return value is a string of the form type/subtype,
160
        usable for a MIME Content-type header.
161
162
        The default implementation looks the file's extension
163
        up in the table self.extensions_map, using application/octet-stream
164
        as a default; however it would be permissible (if
165
        slow) to look inside the data to make a better guess.
166
167
        """
168
169
        base, ext = posixpath.splitext(path)
170
        if ext in self.extensions_map:
171
            return self.extensions_map[ext]
172
        ext = ext.lower()
173
        if ext in self.extensions_map:
174
            return self.extensions_map[ext]
175
        else:
176
            return self.extensions_map['']
177
178
    if not mimetypes.inited:
179
        mimetypes.init() # try to read system mime.types
180
    extensions_map = mimetypes.types_map.copy()
181
    extensions_map.update({
182
        '': 'application/octet-stream', # Default
183
        '.mp4': 'video/mp4',
184
        '.ogg': 'video/ogg',
185
        })
186
187
188
class ThreadingSimpleServer(SocketServer.ThreadingMixIn,
189
                            BaseHTTPServer.HTTPServer):
190
    pass
191
192
193
def runHttp(host='', port=8080, pathmap={}):
194
195
    # Set pathmap as request handler class variable
196
    RangeHTTPRequestHandler.uprclpathmap = pathmap
197
    
198
    server = ThreadingSimpleServer((host, port), RangeHTTPRequestHandler)
199
    while 1:
200
        server.handle_request()