A simple Python HTTP server that supports Range Requests

You can start a simple HTTP server included with Python with the following command.

$ python -m http.server 8080

However, this HTTP server does not support Range Requests. It was inconvenient to try playing the video locally.

-Chrome cannot seek videos on servers that do not support Range Requests

Others have pointed out similarities.

-When displaying a video in Safari, HTTP Range Request support of the server is required --Qiita -Python http.server did not support Range Request --Qiita (Video cannot be played on iOS Safari)

So I wrote a Python script that added the Range Request function to this HTTP server. This makes it possible to seek videos in Chrome.

This script also contains the code I wrote in the following article to disable the browser cache. An HTTP server that can be used easily and conveniently when developing a web application locally.

-To disable the browser cache on Python's simple HTTP server

Python script

import http.server
import socketserver
import os
import re
import urllib
import sys

def main(port):
    httpServer = ThreadingHTTPServer(('', port), RangeRequestNoCacheHTTPRequestHandler)
    httpServer.serve_forever()

class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
    pass

RANGE_BYTES_RE = re.compile(r'bytes=(\d*)-(\d*)?\Z')

class RangeRequestNoCacheHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
    # overriding
    def send_head(self):
        if 'Range' not in self.headers:
            self.range = None
            return super().send_head()
        try:
            self.range = self._parse_range_bytes(self.headers['Range'])
        except ValueError as e:
            self.send_error(416, 'Requested Range Not Satisfiable')
            return None
        start, end = self.range

        path = self.translate_path(self.path)
        if os.path.isdir(path):
            parts = urllib.parse.urlsplit(self.path)
            print(parts)
            if not parts.path.endswith('/'):
                self.send_response(301)
                new_parts = (parts[0], parts[1], parts[2] + '/',
                             parts[3], parts[4])
                new_url = urllib.parse.urlunsplit(new_parts)
                self.send_header("Location", new_url)
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break

        f = None
        try:
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, 'Not Found')
            return None

        self.send_response(206)

        ctype = self.guess_type(path)
        self.send_header('Content-type', ctype)
        self.send_header('Accept-Ranges', 'bytes')

        fs = os.fstat(f.fileno())
        file_len = fs[6]
        if start != None and start >= file_len:
            self.send_error(416, 'Requested Range Not Satisfiable')
            return None
        if end == None or end > file_len:
            end = file_len

        self.send_header('Content-Range', 'bytes %s-%s/%s' % (start, end - 1, file_len))
        self.send_header('Content-Length', str(end - start))
        self.send_header('Last-Modified', self.date_time_string(fs.st_mtime))
        self.end_headers()
        return f

    def _parse_range_bytes(self, range_bytes):
        if range_bytes == '':
            return None, None

        m = RANGE_BYTES_RE.match(range_bytes)
        if not m:
            raise ValueError('Invalid byte range %s' % range_bytes)

        if m.group(1) == '':
            start = None
        else:
            start = int(m.group(1))
        if m.group(2) == '':
            end = None
        else:
            end = int(m.group(2)) + 1

        return start, end

    # overriding
    def end_headers(self):
        #Code to disable the browser cache
        self.send_header('Cache-Control', 'max-age=0')
        self.send_header('Expires', '0')
        super().end_headers()

    # overriding
    def copyfile(self, source, outputfile):
        try:
            if not self.range:
                return super().copyfile(source, outputfile)

            start, end = self.range
            self._copy_range(source, outputfile, start, end)
        except BrokenPipeError:
            #When you seek a video on your browser
            #The browser interrupts the response reception of the video file
            #Because this error will occur
            #Ignore this
            pass

    def _copy_range(self, infile, outfile, start, end):
        bufsize = 16 * 1024
        if start != None:
            infile.seek(start)
        while True:
            size = bufsize
            if end != None:
                left = end - infile.tell()
                if left < size:
                    size = left
            buf = infile.read(size)
            if not buf:
                break
            outfile.write(buf)


port = int(sys.argv[1])
main(port)

Run

If you start this script with a command like the following, you will be able to access port 8080.

$ python server.py 8080

Recommended Posts

A simple Python HTTP server that supports Range Requests
Simple HTTP Server for python
How to specify a public directory Python simple HTTP server
[Mac] I want to make a simple HTTP server that runs CGI with Python
A simple HTTP client implemented in Python
Set up a simple HTTPS server in Python 3
Start a simple Python web server with Docker
Python: Create a class that supports unpacked assignment
Set up a simple SMTP server in Python
A server that echoes data POSTed with flask / python
[Vagrant] Set up a simple API server with python
MALSS, a tool that supports machine learning in Python
Hello World is a simple web server that follows WSGI (Web Server Gateway Interface) in Python.
Create a (simple) REST server
Easy HTTP server with Python
Create a simple textlint server
Note that it supports Python 3
Launch a simple WEB server that can check the header
Creating a Python script that supports the e-Stat API (ver.2)
Explosive speed! Using Python Simple HTTP Server for kintone development
How to write a metaclass that supports both python2 and python3
How to start a simple WEB server that can execute cgi of php and python
How to set up a simple SMTP server that can be tested locally in Python
Implementing a simple algorithm in Python 2
Write a super simple TCP server
Run a simple algorithm in Python
How to call a POST request that supports Japanese (Shift-JIS) with requests
[Python] I wrote a simple code that automatically generates AA (ASCII art)
Created Simple SQLite, a Python library that simplifies SQLite table creation / data insertion
Build a lightweight server in Python and listen for Scratch 2 HTTP extensions
Creating a simple PowerPoint file with Python
[Python] A program that creates stairs with #
Try drawing a simple animation in Python
Build a simple WebDAV server on Linux
Write a simple greedy algorithm in Python
Technology that supports Python Descriptor edition #pyconjp
[Python] Create a LineBot that runs regularly
Write a simple Vim Plugin in Python 3
A typed world that begins with Python
A program that plays rock-paper-scissors using Python
[Python] A program that rounds the score
I made a simple blackjack with Python
A simple mock server that simply embeds the HTTP request header in the body of the response and returns it.
Put Docker in Windows Home and run a simple web server with Python
Supports Python 2.4
Start a temporary http server locally with Pytest
Set up a simple HTTPS server with asyncio
[Python] A tool that allows intuitive relative import
A nice nimporter that connects nim and python
Create a page that loads infinitely with python
A program that removes duplicate statements in Python
Publish a Python module that calculates meteorological factors
Set up a test SMTP server in Python.
[Python] Make a simple maze game with Pyxel
A simple Pub / Sub program note in Python
"Python Kit" that calls a Python script from Swift
Build a simple Python virtual environment without pyenv
Launch a web server with Python and Flask
Create a simple momentum investment model in Python
A Vim plugin that automatically formats Python styles
Simple comparison of Python libraries that operate Excel