8

I am trying to use http.server to test all the links in a Python project. I can get my script to work if I start the server before running my script, and then the server stops when I close the terminal window. But I'd really like the script itself to start and stop the server.

I made a test script to simply start the server, get a page and prove the server is running, and then stop the server. I can't seem to get the pid of the server. When I try to kill the pid that this script reports after the script runs, I get a message that there is no such process; but the server is still running.

How do I get the correct pid for the server, or more generally how do I stop the server from the script?

import os
import requests
from time import sleep

# Start server, in background.
print("Starting server...")
os.system('python -m http.server &')
# Make sure server has a chance to start before making request.
sleep(1)

print "Server pid: "
os.system('echo $$')

url = 'http://localhost:8000/index.html'
print("Testing request: ", url)
r = requests.get(url)
print("Status code: ", r.status_code)
Matt
  • 27,170
  • 6
  • 80
  • 74
japhyr
  • 1,710
  • 2
  • 18
  • 24
  • 2
    You could use `subprocess.Popen` instead of `os.system`, it offers a lot of additional functionality including termination of the spawned subprocess. Or you could just `import SimpleHTTPServer` and use it directly in your script... – l4mpi Oct 24 '13 at 15:56
  • That is helpful, but do you mind saying a little more? The only references I seem to find about using SimpleHTTPServer talk about using it from the command line; I'm not quite sure how to stop and start the server from within a python script. I have also never used processes before, so I'm not quite sure how to start the server subprocess, and then stop it. Actively googling... – japhyr Oct 24 '13 at 16:24
  • 1
    The [`subprocess` documentation](http://docs.python.org/2/library/subprocess.html) has all the info you need, see the sections about the `Popen` constructor and using `Popen` objects - you could do something like `server = Popen(["python", "-m", "SimpleHTTPServer"])` and then use `server.terminate()` or `server.kill()` to end the process. As for starting a `SimpleHTTPServer` in a script, just look at the `if __name__ == "__main__":` block in SimpleHTTPServer.py - just keep a reference to the BaseServer instance and close it (it's a TCPServer subclass, so the TCPServer docs should help). – l4mpi Oct 24 '13 at 16:43

8 Answers8

10

Here is what I am doing:

import threading

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

server = HTTPServer(('localhost', 0), SimpleHTTPRequestHandler)
thread = threading.Thread(target = server.serve_forever)
thread.daemon = True
thread.start()

def fin():
    server.shutdown()

print('server running on port {}'.format(server.server_port))

# here is your program

If you call fin in your program, then the server shuts down.

User
  • 14,131
  • 2
  • 40
  • 59
  • I am using Python 2 at the moment, but I'd love this to run on Python 3 as well. I got an error 'no module named HTTPServer', so I changed the Python 2 import to `from BaseHTTPServer import HTTPServer`. The import worked, but then I got an error that "__init__() got an unexpected keyword argument 'daemon'". I get this if I use my own version of this script, or if I just copy your script and fix the import. Any suggestions? – japhyr Oct 24 '13 at 23:38
  • I fixed the deamon argument with an additional line. This should work under Python 3 and 2 now - hopefully. – User Oct 25 '13 at 20:43
  • Are you able to kill the server with Ctrl-C? – Ciro Santilli OurBigBook.com Aug 05 '16 at 08:32
  • 1
    @CiroSantilli巴拿馬文件六四事件法轮功 Since deamon is set to true, the program should finish once the main thread ends. Usually, the main thread can be terminated with Control+C. – User Aug 30 '16 at 20:56
  • @User I didn't reproduce on Ubuntu 16.04, Python 2.7, gnome terminal. Nothing happens on Ctrl+C, the server just keeps running, and I have to close the terminal for it to stop. – Ciro Santilli OurBigBook.com Aug 30 '16 at 21:22
  • @CiroSantilli烏坎事件2016六四事件法轮功 There is an [edit which points at the problem](http://stackoverflow.com/review/suggested-edits/14049374) I regularily misspell "deamon" and "daemon". Now, I updated the code and it should work again for Python 2 and 3. – User Dec 04 '16 at 22:25
3

A slight modification to User's code above:

import threading
try: 
  from http.server import HTTPServer, BaseHTTPRequestHandler # Python 3
except ImportError: 
  import SimpleHTTPServer
  from BaseHTTPServer import HTTPServer # Python 2
  from SimpleHTTPServer import SimpleHTTPRequestHandler as BaseHTTPRequestHandler
server = HTTPServer(('localhost', 0), BaseHTTPRequestHandler)
thread = threading.Thread(target = server.serve_forever)
thread.deamon = True
def up():
  thread.start()
  print('starting server on port {}'.format(server.server_port))
def down():
  server.shutdown()
  print('stopping server on port {}'.format(server.server_port))
2

This is a closure solution to the problem. Works on python 3.

import os
import threading
import webbrowser
from http.server import HTTPServer, SimpleHTTPRequestHandler


def simple_http_server(host='localhost', port=4001, path='.'):

    server = HTTPServer((host, port), SimpleHTTPRequestHandler)
    thread = threading.Thread(target=server.serve_forever)
    thread.deamon = True

    cwd = os.getcwd()

    def start():
        os.chdir(path)
        thread.start()
        webbrowser.open_new_tab('http://{}:{}'.format(host, port))
        print('starting server on port {}'.format(server.server_port))

    def stop():
        os.chdir(cwd)
        server.shutdown()
        server.socket.close()
        print('stopping server on port {}'.format(server.server_port))

    return start, stop

simple_http_server which will return start and stop functions

>>> start, stop = simple_http_server(port=4005, path='/path/to/folder')

which you can use as

>>> start()
starting server on port 4005

127.0.0.1 - - [14/Aug/2016 17:49:31] "GET / HTTP/1.1" 200 -

>>> stop()
stopping server on port 4005
Levon
  • 10,408
  • 4
  • 47
  • 42
2

Here's a context-flavored version which I prefer because it cleans up automatically and you can specify the directory to serve:

from contextlib import contextmanager
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from threading import Thread


@contextmanager
def http_server(host: str, port: int, directory: str):
    server = ThreadingHTTPServer(
        (host, port), partial(SimpleHTTPRequestHandler, directory=directory)
    )
    server_thread = Thread(target=server.serve_forever, name="http_server")
    server_thread.start()

    try:
        yield
    finally:
        server.shutdown()
        server_thread.join()


def usage_example():
    import time

    with http_server("127.0.0.1", 8087, "."):
        # now you can use the web server
        time.sleep(100)
Tom Pohl
  • 2,711
  • 3
  • 27
  • 34
1

I got this to run, but I'm curious to hear how this compares to User's answer above. I came up with this after looking at the accepted answer here.

import subprocess
import requests
import os
import signal
from time import sleep

print "Starting server..."
cmd = 'python -m SimpleHTTPServer'
pro = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid)

# Make sure server has a chance to start before making request.
sleep(1)

url = 'http://localhost:8000/index.html'
print "Testing request: ", url
r = requests.get(url)
print "Status code: ", r.status_code

os.killpg(pro.pid, signal.SIGTERM)
Community
  • 1
  • 1
japhyr
  • 1,710
  • 2
  • 18
  • 24
0

My solution with browser opening:

File: http.py

import SimpleHTTPServer
import SocketServer
import threading
import webbrowser
import platform
from socket import SOL_SOCKET,SO_REUSEADDR

class HTTPServer():

    def __init__(self,port=8000,url='http://localhost'):
        self.port = port
        self.thread = None
        self.httpd = None                
        self.run = False
        self.url = url

        os = platform.system()
        if os=='Linux':
            self.browser_path = "/usr/bin/google-chrome %s"            
        elif os == 'Windows':
            self.browser_path = "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s"   
        else:
            print("Chrome not found!")


    def start(self):        
        self.run = True     
        self.httpd = SocketServer.TCPServer(("", self.port), SimpleHTTPServer.SimpleHTTPRequestHandler)
        self.httpd.socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.thread = threading.Thread(target = self._serve)
        self.thread.start()
        webbrowser.get(str(self.browser_path)).open(self.url+":"+str(self.port)+"/")


    def _serve(self):        
        while self.run:
            self.httpd.handle_request()

    def stop(self):
        self.run = False
        self.httpd.server_close()

After, just run:

from http import HTTPServer

server = HTTPServer()
server.start()

raw_input("Enter to close")

server.stop()
heltonitba
  • 35
  • 1
  • 8
  • Instead of opening Chrome you can use `start %s` on Windows and `open %s` on OSX (not sure for Linux) to open the default browser. – Josep Valls Feb 18 '17 at 00:10
0

In my opinion, what I did is:

  1. go to activity monitor
  2. then go to network
  3. search for word 'python'

(only if you ran the cmd/terminal command in python, because I think the way to stop it in cmd/terminal is ctrl+c or just quit the cmd/terminal)

note: the pid is also there

  1. quit the process 'python' (if it doesn't, use force quit process)
  2. if this works in cmd/terminal, then you are done! enjoii~

Hope this helps :D!!

  • 1
    And how is this an "internal" approach? – ZF007 Nov 24 '19 at 19:03
  • well I have the same problem as yours, so I searched what is PID in google and shows the result in google like this: https://www.google.com/search?q=what+is+pid+computer&client=safari&rls=en&sxsrf=ACYBGNT9EBPW3BGSdQZMYaeB3JEBtd-qdw:1574675365899&source=lnms&sa=X&ved=0ahUKEwjpqsjuioXmAhVjzzgGHd_vAfgQ_AUIDCgA&biw=1139&bih=532&dpr=1 then I have an idea of just quitting it then it does work lol – Dunno dotcom Nov 25 '19 at 09:50
  • You're approach is in essence giving the result via an external "forced" process. Teh destinction here is that OP is asking for a solution from within the serverscript. This can be done via threading routines or latest asyncio methods. I don't have that problem and merely reviewed your answer as it was flagged for "low quality answer".. and judging whether it was needed to be removed or not. I hope my comment helps you to carefully look at the question again and revaluate whether your answer provides the correct solution OP was asking for. – ZF007 Nov 25 '19 at 10:43
  • Ok, I understand :) – Dunno dotcom Nov 25 '19 at 11:09
0

Using Python 3.8

import http.server
import socketserver
import threading
PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler
server = socketserver.TCPServer(("", PORT), Handler)
thread = threading.Thread(target = server.serve_forever)
thread.daemon = True
thread.start()
Aman Kejriwal
  • 521
  • 1
  • 7
  • 28