12

I've got this basic python3 server but can't figure out how to serve a directory.

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
        def do_GET(self):
            print(self.path)
            if self.path == '/up':
                self.send_response(200)
                self.end_headers()
                self.wfile.write(b'Going Up')
            if self.path == '/down':
                self.send_response(200)
                self.end_headers()
                self.wfile.write(B'Going Down')

httpd = socketserver.TCPServer(("", PORT), SimpleHTTPRequestHandler)
print("Server started on ", PORT)
httpd.serve_forever()

If Instead of the custom class above, I simply pass Handler = http.server.SimpleHTTPRequestHandler into TCPServer():, the default functionality is to serve a directory, but I want to serve that directory and have functionality on my two GETs above.

As an example, if someone were to go to localhost:8080/index.html, I'd want that file to be served to them

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
Wayneio
  • 3,466
  • 7
  • 42
  • 73
  • 1
    Please *always* use a generic [python] tag for any Python question. Use a version-specific tag if your question is version specific. Generally, on the python tag, it is assumed you are using Python 3. – juanpa.arrivillaga Mar 07 '19 at 21:28

4 Answers4

26

if you are using 3.7, you can simply serve up a directory where your html files, eg. index.html is still

python -m http.server 8080 --bind 127.0.0.1 --directory /path/to/dir

for the docs

evandrix
  • 6,041
  • 4
  • 27
  • 38
eddyizm
  • 535
  • 5
  • 12
  • 1
    Looks like plain `python -m http.server` also works – Erhannis Nov 21 '20 at 02:54
  • Is there a flag you need to add to get it to serve files in sub directories? I have a `.html` file that includes some `.js` scripts from a subfolder, using relative paths. This seems to work on apache and github pages, but the `GET`s return `404` when I test it with `python -m http.server` (loading in Firefox). – MRule Jan 30 '23 at 13:42
  • @MRule Where are you getting the 404, from the JS not being found or the html file? It is generally just serving up the directory structure eg very basic. – eddyizm Jan 30 '23 at 19:20
  • Sorry; I should delete my comment but I'll leave it up for posterity: The files were just missing. I forgot to initialize a git submodule. All is well. – MRule Feb 01 '23 at 20:47
4

The simple way

You want to extend the functionality of SimpleHTTPRequestHandler, so you subclass it! Check for your special condition(s), if none of them apply, call super().do_GET() and let it do the rest.

Example:

class MyHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/up':
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'up')
        else:
            super().do_GET()

The long way

To serve files, you basically just have to open them, read the contents and send it. To serve directories (indexes), use os.listdir(). (If you want, you can when receiving directories first check for an index.html and then, if that fails, serve an index listing).

Putting this into your code will give you:

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        print(self.path)
        if self.path == '/up':
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'Going up')
        elif os.path.isdir(self.path):
            try:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(str(os.listdir(self.path)).encode())
            except Exception:
                self.send_response(500)
                self.end_headers()
                self.wfile.write(b'error')
        else:
            try:
                with open(self.path, 'rb') as f:
                    data = f.read()
                self.send_response(200)
                self.end_headers()
                self.wfile.write(data)
            except FileNotFoundError:
                self.send_response(404)
                self.end_headers()
                self.wfile.write(b'not found')
            except PermissionError:
                self.send_response(403)
                self.end_headers()
                self.wfile.write(b'no permission')
            except Exception:
                self.send_response(500)
                self.end_headers()
                self.wfile.write(b'error')

This example has a lot of error handling. You might want to move it somewhere else. The problem is this serves from your root directory. To stop this, you'll have to (easy way) just add the serving directory to the beginning of self.path. Also check if .. cause you to land higher than you want. A way to do this is os.path.abspath(serve_from+self.path).startswith(serve_from)

Putting this inside (after the check for /up):

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        print(self.path)
        path = serve_from + self.path
        if self.path == '/up':
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'Going up')
        elif not os.path.abspath(path).startswith(serve_from):
            self.send_response(403)
            self.end_headers()
            self.wfile.write(b'Private!')
        elif os.path.isdir(path):
            try:
                self.send_response(200)
                self.end_headers()
                self.wfile.write(str(os.listdir(path)).encode())
            except Exception:
                self.send_response(500)
                self.end_headers()
                self.wfile.write(b'error')
        else:
            try:
                with open(path, 'rb') as f:
                    data = f.read()
                self.send_response(200)
                self.end_headers()
                self.wfile.write(data)
            # error handling skipped
            except Exception:
                self.send_response(500)
                self.end_headers()
                self.wfile.write(b'error')

Note you define path and use it subsequently, otherwise you will still serve from /

user24343
  • 892
  • 10
  • 19
3

With python3, you can serve the current directory by simply running:

python3 -m http.server 8080

Of course you can configure many parameters as per the documentation.

Ronenz
  • 2,048
  • 2
  • 16
  • 8
  • Is there a flag you need to add to get it to serve files in sub directories? I have a `.html` file that includes some `.js` scripts from a subfolder, using relative paths. This seems to work on apache and github pages, but the `GET`s return `404` when I test it with `python -m http.server` (loading in Firefox). – MRule Jan 30 '23 at 13:43
1

@user24343's answer to subclass SimpleHTTPRequestHandler is really helpful! One detail I couldn't figure out was how to customize the directory= constructor arg when I pass MyHandler into HTTPServer. Use any of these answers, i.e.

HTTPServer(('', 8001), lambda *_: MyHandler(*_, directory=sys.path[0]))
Carl Walsh
  • 6,100
  • 2
  • 46
  • 50