121

this is my Python3 project hiearchy:

projet
  \
  script.py
  web
    \
    index.html

From script.py, I would like to run a http server which serve the content of the web folder.

Here is suggested this code to run a simple http server:

import http.server
import socketserver

PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler)
print("serving at port", PORT)
httpd.serve_forever()

but this actually serve project, not web. How can I specify the path of the folder I want to serve?

minghua
  • 5,981
  • 6
  • 45
  • 71
roipoussiere
  • 5,142
  • 3
  • 28
  • 37
  • 11
    Have you considered running `python3 -m http.server -d /path/to/web/dir` on command line to do the job? Props to @kyle-barron who gave this perfect solution in [a comment](https://stackoverflow.com/a/43292151/638546) deep below. – Akseli Palén Mar 07 '20 at 20:19
  • I suggest this is the best answer: https://stackoverflow.com/a/58217918/835098 – Konrad Kleine Mar 09 '20 at 19:11
  • @AkseliPalén I get `server.py: error: argument port: invalid int value: '/media/EHD/web_root'` error when I run that in Ubuntu 18.04 Terminal (with python3.6.9) – Lost Crotchet Jul 10 '21 at 11:10

8 Answers8

97

In Python 3.7 SimpleHTTPRequestHandler can take a directory argument:

import http.server
import socketserver

PORT = 8000
DIRECTORY = "web"


class Handler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=DIRECTORY, **kwargs)


with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

and from the command line:

python -m http.server --directory web

To get a little crazy... you could make handlers for arbitrary directories:

def handler_from(directory):
    def _init(self, *args, **kwargs):
        return http.server.SimpleHTTPRequestHandler.__init__(self, *args, directory=self.directory, **kwargs)
    return type(f'HandlerFrom<{directory}>',
                (http.server.SimpleHTTPRequestHandler,),
                {'__init__': _init, 'directory': directory})


with socketserver.TCPServer(("", PORT), handler_from("web")) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()
Andy Hayden
  • 359,921
  • 101
  • 625
  • 535
78

If you just want serve static file you can do it by running SimpleHTTPServer module using python 2:

 python -m SimpleHTTPServer

Or with python 3:

 python3 -m http.server

This way you do not need to write any script.

Jerzy Pawlikowski
  • 1,751
  • 19
  • 21
  • 3
    Note that you can also add a port at the end to specify the port of the server. – rbaleksandar Mar 14 '18 at 08:49
  • 1
    further note; you have to have an `index.html` or `index.htm` file in the directory you're running the server from in order for it to be served; otherwise you get a directory list. – cori Apr 25 '18 at 16:35
  • 3
    OP asks how to run it at specific folder path, not from present working directory. – Nikhil VJ Feb 08 '19 at 10:30
  • 54
    There's a directory argument to `http.server`. So you can do `python3 -m http.server -d /path/to/web/dir` – Kyle Barron Apr 04 '19 at 17:30
  • 2
    While this answer was accepted by the owner of the question as being the best answer, I think it should not be the best answer. The question was asking for a way how to code this in python and not how to invoke it from the command line. – Konrad Kleine Mar 09 '20 at 19:13
  • i wish to run a `index.py` or `main.py` as the default in a `./www` folder. the file is `www/index.py` what do i do? – Gary Dec 18 '22 at 04:57
  • This is completely different situation. Here we serve content of the files which you probably do not want. I guess you want to execute the file? – Jerzy Pawlikowski Dec 22 '22 at 11:39
50

https://docs.python.org/3/library/http.server.html#http.server.SimpleHTTPRequestHandler

This class serves files from the current directory and below, directly mapping the directory structure to HTTP requests.

So you just need to change the current directory prior to starting the server - see os.chdir

eg:

import http.server
import socketserver
import os

PORT = 8000

web_dir = os.path.join(os.path.dirname(__file__), 'web')
os.chdir(web_dir)

Handler = http.server.SimpleHTTPRequestHandler
httpd = socketserver.TCPServer(("", PORT), Handler)
print("serving at port", PORT)
httpd.serve_forever()
John Carter
  • 53,924
  • 26
  • 111
  • 144
  • 4
    Thank you! Note: I added try: `httpd.serve_forever(); except KeyboardInterrupt: pass; httpd.server_close()` to actually close the port. – roipoussiere Oct 01 '16 at 16:03
  • Thanks. Its worked. But how can I get the Host address for this? I mean to say that how can I get 0.0.0.0:8000 instead of my local router address 192.168.0.100:8000 – Istiyak Jan 07 '19 at 16:53
  • @roipoussiere You rather should use `with socketserver.TCPServer(("", PORT), Handler) as httpd:`, this way it closes automatically after being used – Daniel Nov 12 '19 at 16:21
  • 2
    For future readers: Andy Hayden's solution below is less 'hacky', and probably preferred, since it doesn't rely on side-effects. – Vinícius M Mar 12 '20 at 16:57
28

You also can run the command line with

python3 -m http.server -d web 8000
mutoe
  • 494
  • 6
  • 15
24

There's a shorter method for Python 3+:

import functools
    
Handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory='/my/dir/goes/here')
Habbie
  • 2,150
  • 15
  • 17
21

Just for completeness, here's how you can setup the actual server classes to serve files from an arbitrary directory:

try
    # python 2
    from SimpleHTTPServer import SimpleHTTPRequestHandler
    from BaseHTTPServer import HTTPServer as BaseHTTPServer
except ImportError:
    # python 3
    from http.server import HTTPServer as BaseHTTPServer, SimpleHTTPRequestHandler


class HTTPHandler(SimpleHTTPRequestHandler):
    """This handler uses server.base_path instead of always using os.getcwd()"""
    def translate_path(self, path):
        path = SimpleHTTPRequestHandler.translate_path(self, path)
        relpath = os.path.relpath(path, os.getcwd())
        fullpath = os.path.join(self.server.base_path, relpath)
        return fullpath


class HTTPServer(BaseHTTPServer):
    """The main server, you pass in base_path which is the path you want to serve requests from"""
    def __init__(self, base_path, server_address, RequestHandlerClass=HTTPHandler):
        self.base_path = base_path
        BaseHTTPServer.__init__(self, server_address, RequestHandlerClass)

Then you can set any arbitrary path in your code:

web_dir = os.path.join(os.path.dirname(__file__), 'web')
httpd = HTTPServer(web_dir, ("", 8000))
httpd.serve_forever()
Jaymon
  • 5,363
  • 3
  • 34
  • 34
8

Another easy method to serve from a specific directory.

Since you really only need to set the directory parameter for the SimpleHTTPRequestHandler, you can use functools.partial to prepare the handler class without instantiating the class.

from functools import partial
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path


def start_httpd(directory: Path, port: int = 8000):
    print(f"serving from {directory}...")
    handler = partial(SimpleHTTPRequestHandler, directory=directory)
    httpd = HTTPServer(('localhost', port), handler)
    httpd.serve_forever()
    
monkut
  • 42,176
  • 24
  • 124
  • 155
-8

If you just need a modern web static server,
deno is an alternative file server without any code.

install deno with single line

https://github.com/denoland/deno_install#deno_install

install file server with single line

deno install --allow-net --allow-read https://deno.land/std@0.125.0/http/file_server.ts

use deno file-server

file_server . --port=<port>
# Downloading https://deno.land/std@0.125.0/http/file_server.ts...
# HTTP server listening on http://0.0.0.0:<port>/

read more https://deno.land/manual/examples/file_server#using-the-codestdhttpcode-file-server

Tearf001
  • 95
  • 7