Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added shutting down calls to the visualization server #241

Merged
merged 13 commits into from
Jun 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pydy/viz/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

# local
from .camera import PerspectiveCamera
from .server import run_server
from .server import Server
from .light import PointLight
from ..system import System
from ..utils import PyDyImportWarning
Expand Down Expand Up @@ -463,7 +463,8 @@ def remove_static_html(self, force=False):
def display(self):
"""Displays the scene in the default webbrowser."""
self.create_static_html()
run_server(scene_file=self._scene_json_file)
server = Server(scene_file=self._scene_json_file)
server.run_server()

def _rerun_button_callback(self, btn):
"""Callback for the "Rerun Simulation" button. When executed the
Expand Down
126 changes: 106 additions & 20 deletions pydy/viz/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,122 @@

import os
import sys
import signal
import socket
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to stay in for Python 2/3 compat.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally missed that. Thanks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make a comment in your PR that says why that is there.

import webbrowser

# For python 2 and python 3 compatibility
if sys.version_info < (3, 0):
from SimpleHTTPServer import SimpleHTTPRequestHandler
from BaseHTTPServer import HTTPServer
else:
from http.server import SimpleHTTPRequestHandler
from http.server import HTTPServer
raw_input = input

__all__ = ['Server']


class StoppableHTTPServer(HTTPServer):
"""
Overrides BaseHTTPServer.HTTPServer to include a stop
function.
"""

def server_bind(self):
HTTPServer.server_bind(self)
self.socket.settimeout(1)
self.run = True

def get_request(self):
while self.run:
try:
sock, addr = self.socket.accept()
sock.settimeout(None)
return (sock, addr)
except socket.timeout:
pass

def stop(self):
self.run = False

def serve(self):
while self.run:
try:
self.handle_request()
except TypeError:
# When server is being closed, while loop can run once
# after setting self.run = False depending on how it
# is scheduled.
pass


class Server(object):
"""
Parameters
----------
port : integer
Defines the port on which the server will run. If this port is
already bind, then it increment 1 until it finds a free port.
scene_file : name of the scene_file generated for visualization
A Valid PyDy generated scene file in 'directory' parameter.
directory : absolute path of a directory
Absolute path to the directory which contains scene_file with
all other static files.

Example
-------
>>> server = Server(scene_file=_scene_json_file)
>>> server.run_server()

__all__ = ['run_server']
"""
def __init__(self, scene_file, directory="static/", port=8000):
self.scene_file = scene_file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestion in the previous commit was to have this call sig:

def __init__(scene_file, directory=None, port=8000)

The scene file should be required and the directory can default to the cwd if None is passed in.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add that in my new pr on static directory creation. Trying to make it atomic

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is a whole new server code. It seems atomic to add this.

self.port = port
self.directory = directory

def run_server(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if port 8000 is taken?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It throws an error. This needs to be fixed.

# Change dir to static first.
os.chdir(self.directory)
# Get a free port
while self._check_port(self.port):
self.port += 1
handler_class = SimpleHTTPRequestHandler
server_class = StoppableHTTPServer
protocol = "HTTP/1.0"
server_address = ('127.0.0.1', self.port)
handler_class.protocol_version = protocol
self.httpd = server_class(server_address, handler_class)
sa = self.httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
print("To view visualization, open:\n")
url = "http://localhost:"+str(sa[1]) + "/index.html?load=" + \
self.scene_file
print(url)
webbrowser.open(url)
print("Hit Ctrl+C to stop the server...")
signal.signal(signal.SIGINT, self._stop_server)
self.httpd.serve()

def run_server(port=8000,scene_file="Null"):
#change dir to static first.
os.chdir("static/")
HandlerClass = SimpleHTTPRequestHandler
ServerClass = HTTPServer
Protocol = "HTTP/1.0"
server_address = ('127.0.0.1', port)
HandlerClass.protocol_version = Protocol
httpd = ServerClass(server_address, HandlerClass)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
print("hit ctrl+c to stop the server..")
print("To view visualization, open:\n")
url = "http://localhost:"+ str(sa[1]) + "/index.html?load=" + scene_file
print(url)
webbrowser.open(url)
httpd.serve_forever()
def _check_port(self, port):
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = soc.connect_ex(('127.0.0.1', port))
return result == 0

def _stop_server(self, signal, frame):
"""
Confirms and stops the visulisation server
signal:
Required by signal.signal
frame:
Required by signal.signal

if __name__ == "__main__":
run_server()
"""
res = raw_input("Shutdown this visualization server ([y]/n)? ")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail in Python 3. Oliver had the fix for this in this file before this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the options then, should I use input()?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it!

res = res.lower()[0]
if res == '' or res == 'y':
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works even for strings like "Yes"

print("Shutdown confirmed")
print("Shutting down server...")
self.httpd.stop()
else:
print("Resuming operations...")