Skip to content

Instantly share code, notes, and snippets.

@fabiand
Created May 22, 2013 14:34
Show Gist options
  • Save fabiand/5628006 to your computer and use it in GitHub Desktop.
Save fabiand/5628006 to your computer and use it in GitHub Desktop.
A simple HTTP Server supporting put
# python -m SimpleHTTPPutServer 8080
import SimpleHTTPServer
import BaseHTTPServer
class SputHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
def do_PUT(self):
print self.headers
length = int(self.headers["Content-Length"])
path = self.translate_path(self.path)
with open(path, "wb") as dst:
dst.write(self.rfile.read(length))
if __name__ == '__main__':
SimpleHTTPServer.test(HandlerClass=SputHTTPRequestHandler)
@ssjoy320
Copy link

ssjoy320 commented Nov 6, 2020

Thank you

@fabiand
Copy link
Author

fabiand commented Nov 6, 2020

yw

@LocalAlloc
Copy link

no module named SimpleHTTPServer please help

@LocalAlloc
Copy link

please mail me if you can on testingtee321@gmail.com

@heyxmirko
Copy link

For Python3 You Can Use Code From My WPG Project:

https://github.com/heyxmirko/Simple-Wifi-Password-Grabber

@cuzimrave
Copy link

Hey Maruf you gotta execute the script in python not python3

@EricBuist
Copy link

Python 2 is end of life. Why not align to Python 3? If there is something built into Python 3 that could be used instead, would be worth mentioning this here.

@fabiand
Copy link
Author

fabiand commented May 25, 2022

Yes this would be great!

@orgads
Copy link

orgads commented Aug 4, 2022

Here, based on https://gist.github.com/mdonkers/63e115cc0c79b4f6b8b3a6b797e485c7

DISCLAIMER: I added protection for access outside the running directory, but I don't guarantee it cannot be bypassed.

#!/usr/bin/env python3
"""
Very simple HTTP file server in python
Usage::
    ./server.py [<port>]
"""
from http.server import BaseHTTPRequestHandler, HTTPServer
import mimetypes
import os
import pathlib

cwd = os.path.normcase(os.getcwd())

class S(BaseHTTPRequestHandler):
    def __init__(self, *args, directory=None, **kwargs):
        self.protocol_version = 'HTTP/1.1'
        super().__init__(*args, **kwargs)

    def _send_response(self, code = 200, msg='Done', content_type='text/plain'):
        body = msg.encode('utf-8')
        self.send_response(code)
        self.send_header('Content-type', content_type)
        self.send_header('Content-Length', len(body))
        self.end_headers()
        self.wfile.write(body)

    def req_path(self):
        fname = self.path[1:] # Strip leading slash
        path = os.path.normcase(os.path.dirname(os.path.realpath(fname)))
        if os.path.commonpath((path, cwd)) == cwd:
            return fname
        raise Exception("Access denied")

    def do_GET(self):
        try:
            with open(self.req_path(), "rb") as src:
                self._send_response(200, src.read(), mimetypes.guess_type(self.req_path()))
        except:
            self._send_response(404, '404 Not Found\r\n')

    def do_PUT(self):
        try:
            path = self.req_path()
            pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True)
            with open(path, "wb") as dst:
                content_length = int(self.headers['Content-Length'])
                dst.write(self.rfile.read(content_length))
            self._send_response(200, 'Done\r\n')
        except Exception as ex:
            print(ex)
            self._send_response(500, '500 Access Denied\r\n')

    def do_POST(self):
        return self.do_PUT()

def run(port=8000):
    server_address = ('', port)
    httpd = HTTPServer(server_address, S)
    print(f'Listening on port {port}')
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()

if __name__ == '__main__':
    from sys import argv

    if len(argv) == 2:
        run(port=int(argv[1]))
    else:
        run()

@janumejia
Copy link

Thanks @orgads. That is what I was looking for. It works fine for me.

@stefanstruik
Copy link

hello i have a question how can i make simpleHTTPServer work it give error when i use it give it print self.headers
^^^^^^^^^^^^^^^^^^^
SyntaxError: Missing parentheses in call to 'print'. Did you mean print(...)?
i use prython 3

@janumejia
Copy link

janumejia commented Nov 7, 2022

Hello @stefanstruik. Check the code that @orgads shared here. If you want to run the first code (of @fabiand ) you need to install python 2.7

@stefanstruik
Copy link

I can do you ok in a zip existence for me I still have it for my education

@stefanstruik
Copy link

het works thanks jou wel

@fabiand
Copy link
Author

fabiand commented Nov 8, 2022

Crazy how old this snippet is by now.

@stefanstruik
Copy link

stefanstruik commented Nov 22, 2022

help i have a question how can i make simpleHTTPServer work it give error when i use it give python: can't open file SimpleHTTPPutServer': [Errno 2] No such file or directory i use Python 3.10.8
gr stefan

@TijlE-1951707
Copy link

TijlE-1951707 commented Mar 27, 2024

I made a few changes to @orgads' implementation --- thank you! --- that suit my needs. The GET method didn't work, because the file content was already binary while _send_response expected the message to be still a string. If you want to read it binary for optimal efficiency, just edit _send_response instead.

Also, I used it in a web-browser, where multiple files where being retrieved. It made me wait for 2 minutes before axios fetched it and I never saw it in the log. This is because the non-threaded version of the previous authors will simply not see the request coming in and ignore it, until axios tries another time (apparently, that is after 2 minutes). I solved it by using a threaded server from the same http.server library.

The disclaimer still holds:

DISCLAIMER: I added protection for access outside the running directory, but I don't guarantee it cannot be bypassed.

I recommend looking at the script --- it's not hard. But for those who don't have the time, the directory being served is expected to be the current working directory of the script (cwd = os.path.normcase(os.getcwd())), so either move to the directory and execute the script from there, or configure it so you can pass a parameter and share your updated version here ;-) I did the former with one of my scripts ("serve": "cd data; python3 ./server.py 8756")

#!/usr/bin/env python3
"""
Very simple HTTP file server in python
Usage::
    ./server.py [<port>]
Authors::
    fabiand, orgads, TijlE-1951707 (https://gist.github.com/fabiand/5628006)
"""
from http.server import BaseHTTPRequestHandler, HTTPServer, ThreadingHTTPServer
import mimetypes
import os
import pathlib

# # https://stackoverflow.com/questions/62599036/python-requests-is-slow-and-takes-very-long-to-complete-http-or-https-request
# import logging
# import http.server
# BaseHTTPRequestHandler=http.server.BaseHTTPRequestHandler
# HTTPServer=http.server.HTTPServer
# http.server.BaseHTTPRequestHandler.debuglevel = 1
# http.server.HTTPServer.debuglevel = 1
# logging.basicConfig()
# logging.getLogger().setLevel(logging.DEBUG)

cwd = os.path.normcase(os.getcwd())
print(cwd)

class S(BaseHTTPRequestHandler):
    def __init__(self, *args, directory=None, **kwargs):
        self.protocol_version = 'HTTP/1.1'
        super().__init__(*args, **kwargs)

    def _send_response(self, code = 200, msg='Done', content_type='text/plain'):
        body = msg.encode('utf-8')
        self.send_response(code)
        self.send_header('Content-type', content_type)
        self.send_header('Content-Length', len(body))
        # self.send_header('Keep-Alive', "timeout=5")
        # self.send_header('Connection', "keep-alive")
        self.send_header('Cache-Control', "no-store") # seemed to be necessary to GET through axios, and added in GET through Insomnia
        # self.send_header('Cache-Control', "no-cache, no-store, must-revalidate") # is all that Insomnia added to custom requests
        self.end_headers()
        self.wfile.write(body)

    def req_path(self):
        fname = self.path[1:] # Strip leading slash
        path = os.path.normcase(os.path.dirname(os.path.realpath(fname)))
        if os.path.commonpath((path, cwd)) == cwd:
            return fname.replace("%20", " ")
        raise Exception("Access denied")

    def do_GET(self):
        try:
            path = self.req_path()
            mime_type = mimetypes.guess_type(self.req_path())
            content_type = ('text/plain' if (t:= mime_type[0]) == None else t) + ('' if (e := mime_type[1]) == None else f"; charset={e}")
            with open(path, "r") as src:
                self._send_response(200, src.read(), content_type)
        except FileNotFoundError as ex:
            self._send_response(404, '404 Not Found\r\n')
        except BrokenPipeError as ex:
            print("client disconnected")
            print(ex)
        except Exception as ex:
            print(ex, type(ex))
            self._send_response(500, 'Internal server error\r\n')

    def do_PUT(self):
        try:
            path = self.req_path()
            pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True)
            with open(path, "wb") as dst:
                content_length = int(self.headers['Content-Length'])
                dst.write(self.rfile.read(content_length))
            self._send_response(200, 'Done\r\n')
        except Exception as ex:
            print(ex)
            self._send_response(403, '403 Access Denied\r\n')

    # def do_POST(self):
    #     print("infinite loop?")
    #     return self.do_PUT()

def run(port=8000):
    server_address = ('', port)
    # httpd = HTTPServer(server_address, S) # WARNING: if the server is busy, it will completely miss incoming requests!
    httpd = ThreadingHTTPServer(server_address, S)
    print(f'Listening on port {port}')
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
    httpd.server_close()

if __name__ == '__main__':
    from sys import argv

    if len(argv) == 2:
        run(port=int(argv[1]))
    else:
        run()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment