I'm using the Bottle web app framework for Python (pip install bottle
) and want to run a web app that will just be accessed from the local machine (it's essentially a desktop app that uses the browser for the GUI). To start the bottle web app, I have to call bottle.run()
but this blocks for as long as the script is running. You stop it by pressing Ctrl-C.
However, I also want this app to open a web browser to localhost by calling webbrowser.open()
. The problem is, I can't call webbrowser.open()
first because the web app won't be running, but if I call bottle.run()
first it won't return as long as the web app is running and I can't continue on to call webbrowser.open()
.
My solution was to put the call to webbrowser.open()
inside of a thread:
import bottle
import threading
import webbrowser
import time
class BrowserOpener(threading.Thread):
def run(self):
time.sleep(1) # waiting 1 sec is a hack, but it works
webbrowser.open('http://localhost:8042')
print('Browser opened')
@bottle.route('/')
def index():
return 'hello world!'
BrowserOpener().start()
bottle.run(host='localhost', port=8042)
The problem with this is now pressing Ctrl-C in the terminal doesn't seem to work, so I have no way of stopping the web app other than closing the terminal entirely. I'm not sure why this is: 'Browser opened'
gets printed to the screen so I know webbrowser.open()
is returning.
I'm on Windows 7.
I've tried the solution from how to terminate a thread which calls the webbrowser in python of setting self._running = False
but that doesn't change anything. There's also no place outside the thread I can call join()
from.
Even if I get rid of the separate thread and use os.system('python openbrowser.py')
to run a script that waits a second and opens the webbrowser, this still prevents Ctrl-C from working.
I also tried launching the browser using threading.Timer(1, webbrowser.open, ['http://localhost:8042']).start()
but this still prevents Ctrl-C from working too.
Is there a solution I'm not seeing?
Two immediate caveats with this answer:
With that out of the way:
You may find that you have an easier time by placing the server in a seperate thread and then controlling it from the main (non-blocked) thread via some simple signals. I've created a toy example below to demonstrate what I mean. You may prefer to put the class in a file by itself and then simply import it and instantiate a new instance of the class into your other scripts.
import ctypes
import threading
import webbrowser
import bottle
class SimpleExampleApp():
def __init__(self):
self.app = bottle.Bottle()
#define all of the routes for your app inside the init method
@self.app.get('/')
def index():
return 'It works!'
@self.app.get('/other_route')
def alternative():
return 'This Works Too'
def run(self):
self.start_server_thread()
#depending upon how much configuration you are doing
#when you start the server you may need to add a brief
#delay before opening the browser to make sure that it
#is ready to receive the initial request
webbrowser.open('http://localhost:8042')
def start_server_thread(self):
self.server_thread = threading.Thread(
target = self.app.run,
kwargs = {'host': 'localhost', 'port': 8042}
)
self.server_thread.start()
def stop_server_thread(self):
stop = ctypes.pythonapi.PyThreadState_SetAsyncExc(
ctypes.c_long(self.server_thread.get_ident()),
ctypes.py_object(KeyboardInterrupt)
)
#adding a print statement for debugging purposes since I
#do not know how well this will work on Windows platform
print('Return value of stop was {0}'.format(stop))
self.server_thread.join()
my_app = SimpleExampleApp()
my_app.run()
#when you're ready to stop the app, simply call
#my_app.stop_server_thread()
You'll likely need to modify this fairly heavily for the purposes of your actual application, but it should hopefully get you started. Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With