diff --git a/CMakeLists.txt b/CMakeLists.txt
index 92371a5..478677c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,4 +4,8 @@ find_package(catkin REQUIRED)
catkin_package()
catkin_python_setup()
-install(DIRECTORY script DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} USE_SOURCE_PERMISSIONS)
+install(
+ PROGRAMS
+ script/webserver.py
+ DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
+)
diff --git a/README.md b/README.md
index 00fce70..edae15c 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
roswww
======
-roswww and roswww_pack. Convenient tool to develop the web apps under ROS infrastructure
\ No newline at end of file
+roswww Convenient tool to develop the web apps under ROS infrastructure
+
+## Parameters ##
+
+* `--port` : Web server port
+* `--name` : Web server name
+* `--webpath` : relative path to web page
diff --git a/package.xml b/package.xml
index f586a08..8840ee5 100644
--- a/package.xml
+++ b/package.xml
@@ -14,6 +14,7 @@
catkin
rosbridge_server
+ rospack
rospy
diff --git a/script/webserver.py b/script/webserver.py
index a8339d7..b76abb4 100755
--- a/script/webserver.py
+++ b/script/webserver.py
@@ -34,139 +34,29 @@
#
# Author: Jonathan Mace, Jihoon Lee, Isaac Isao Saito
-import socket
-import subprocess
-import tornado.ioloop # rosbridge installs tornado
-import tornado.web
+import sys
+import argparse
+import roswww
-import rospy
+def parse_argument(argv):
+ """
+ argument parser for roswww server configuration
+ """
+ parser = argparse.ArgumentParser(description="ROSWWW Server")
+ parser.add_argument('-n', '--name', default='80', help='Webserver name')
+ parser.add_argument('-p', '--port', default='80', help='Webserver Port number')
+ parser.add_argument('-w', '--webpath', default='www', help='package relative path to web pages')
+ parser.add_argument('--start_port', default='8000', help='setting up port scan range')
+ parser.add_argument('--end_port', default='9000', help='setting up port scan range')
+ parsed_args = parser.parse_args(argv)
-from roswww.webrequest_handler import WebRequestHandler
-
-
-def run_shellcommand(*args):
- '''run the provided command and return its stdout'''
- args = sum([(arg if type(arg) == list else [arg]) for arg in args], [])
- return subprocess.Popen(args,
- stdout=subprocess.PIPE).communicate()[0].strip()
-
-def split_words(text):
- '''return a list of lines where each line is a list of words'''
- return [line.strip().split() for line in text.split('\n')]
-
-def get_packages():
- '''
- Find the names and locations of all ROS packages
-
- @rtype: {str, str}
- @return: name and path of ROS packages
- '''
- lines = split_words(run_shellcommand('rospack', 'list'))
- packages = [{'name': name, 'path': path} for name, path in lines]
- return packages
-
-def create_webserver(packages):
- '''
- @type packages: {str, str}
- @param packages: name and path of ROS packages.
- '''
- handlers = [(r"/", WebRequestHandler, {"packages": packages})]
-
- for package in packages:
- handler_root = ("/" + package['name'] + "/?()",
- tornado.web.StaticFileHandler,
- {"path": package['path'] + "/www/index.html"})
- handlers.append(handler_root)
- handler = ("/" + package['name'] + "/(.*)",
- tornado.web.StaticFileHandler,
- {"path": package['path'] + "/www",
- "default_filename": "index.html"})
- handlers.append(handler)
-
- rospy.loginfo("Webserver initialized for %d packages", len(packages))
- application = tornado.web.Application(handlers)
-
- return application
-
-def bind_webserver(application):
- """ See if there's a default port, use 80 if not """
- default_port, start_port, end_port = get_webserver_params()
-
- """ First, we try the default http port 80 """
- bound = bind_to_port(application, default_port)
-
- if not bound:
- """ Otherwise bind any available port within the specified range """
- bound = bind_in_range(application, start_port, end_port)
-
- return bound
-
-def get_webserver_params():
- try:
- default_port = rospy.get_param("http/default", 80)
- start_port = rospy.get_param("http/range_start", 8000)
- end_port = rospy.get_param("http/range_end", 9000)
- return (default_port, start_port, end_port)
- except socket.error as err:
- if err.errno == 111:
- # Roscore isn't started or cannot be contacted
- rospy.logwarn("Could not contact ROS master." + \
- " Is a roscore running? Error: %s", err.strerror)
- return 80, 8000, 9000
- else:
- raise
-
-def start_webserver(application):
- try:
- tornado.ioloop.IOLoop.instance().start()
- except KeyboardInterrupt:
- rospy.loginfo("Webserver shutting down")
-
-
-def bind_to_port(application, portno):
- rospy.loginfo("Attempting to start webserver on port %d", portno)
- try:
- application.listen(portno)
- rospy.loginfo("Webserver successfully started on port %d", portno)
- except socket.error as err:
- # Socket exceptions get handled, all other exceptions propagated
- if err.errno == 13:
- rospy.logwarn("Insufficient priveliges to run webserver " +
- "on port %d. Error: %s", portno, err.strerror)
- rospy.loginfo("-- Try re-running as super-user: sudo su; " +
- "source ~/.bashrc)")
- elif err.errno == 98:
- rospy.logwarn("There is already a webserver running on port %d. " +
- "Error: %s", portno, err.strerror)
- rospy.loginfo("-- Try stopping your web server. For example, " +
- "to stop apache: sudo /etc/init.d/apache2 stop")
- else:
- rospy.logerr("An error occurred attempting to listen on " +
- "port %d: %s", portno, err.strerror)
- return False
- return True
-
-
-def bind_in_range(application, start_port, end_port):
- if (end_port > start_port):
- for i in range(start_port, end_port):
- if bind_to_port(application, i):
- return True
- return False
-
-
-def run_webserver():
- try:
- packages = get_packages()
- server = create_webserver(packages)
- bound = bind_webserver(server)
- if (bound):
- start_webserver(server)
- else:
- raise Exception()
- except Exception as exc:
- rospy.logerr("Unable to bind webserver. Exiting. %s" % exc)
+ return parsed_args.name, parsed_args.webpath, (parsed_args.port, parsed_args.start_port, parsed_args.end_port)
if __name__ == '__main__':
- run_webserver()
+ argv = sys.argv
+ name, webpath, port = parse_argument(argv[1:])
+
+ webserver = roswww.ROSWWWServer(name, webpath, port)
+ webserver.loginfo("Initialised")
+ webserver.spin()
diff --git a/src/roswww/__init__.py b/src/roswww/__init__.py
index e69de29..0b994b4 100644
--- a/src/roswww/__init__.py
+++ b/src/roswww/__init__.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2013, Tokyo Opensource Robotics Kyokai Association
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+# * Neither the name of Tokyo Opensource Robotics Kyokai Association. nor the
+# names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# Author: Jonathan Mace, Jihoon Lee, Isaac Isao Saito
+
+from .roswww_server import ROSWWWServer
diff --git a/src/roswww/roswww_server.py b/src/roswww/roswww_server.py
new file mode 100644
index 0000000..6d3c425
--- /dev/null
+++ b/src/roswww/roswww_server.py
@@ -0,0 +1,166 @@
+#!/usr/bin/env python
+
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2013, Tokyo Opensource Robotics Kyokai Association
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+# * Neither the name of Tokyo Opensource Robotics Kyokai Association. nor the
+# names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# Author: Jonathan Mace, Jihoon Lee, Isaac Isao Saito
+
+import logging
+
+import socket
+import tornado.ioloop # rosbridge installs tornado
+import tornado.web
+from .webrequest_handler import WebRequestHandler
+from .utils import run_shellcommand, split_words, get_packages
+
+class ROSWWWServer():
+
+ def __init__(self, name, webpath, ports):
+ '''
+ :param str name: webserver name
+ :param str webpath: package relative path to web page source.
+ :param tuple ports: ports to use in webserver. Provides default and scan range (default, start, end)
+ '''
+ self._name = name
+ self._webpath = webpath
+ self._ports = ports
+ self._logger = self._set_logger()
+ self._packages = get_packages()
+ self._application = self._create_webserver(self._packages)
+
+ def _create_webserver(self, packages):
+ '''
+ @type packages: {str, str}
+ @param packages: name and path of ROS packages.
+ '''
+ handlers = [(r"/", WebRequestHandler, {"packages": packages})]
+
+ for package in packages:
+ handler_root = ("/" + package['name'] + "/?()",
+ tornado.web.StaticFileHandler,
+ {"path": package['path'] + "/" + self._webpath + "/index.html"})
+ handlers.append(handler_root)
+ handler = ("/" + package['name'] + "/(.*)",
+ tornado.web.StaticFileHandler,
+ {"path": package['path'] + "/" + self._webpath,
+ "default_filename": "index.html"})
+ handlers.append(handler)
+
+ self.loginfo("# of packages : %s"%(len(packages)))
+ self.loginfo("Weg Page root : %s"%(self._webpath))
+ application = tornado.web.Application(handlers)
+ return application
+
+ def _bind_webserver(self):
+ default, start, end = self._ports
+
+ """ First, we try the default http port """
+ bound = self._bind_to_port(self._application, default)
+ if not bound:
+ """ Otherwise bind any available port within the specified range """
+ bound = self._bind_in_range(self._application, start, end)
+ return True
+
+ def _bind_in_range(self, application, start_port, end_port):
+ if (end_port > start_port):
+ for i in range(start_port, end_port):
+ if self._bind_to_port(application, i):
+ return True
+ return False
+
+ def _bind_to_port(self, application, portno):
+ self.loginfo("Attempting to start webserver on port %s"%portno)
+ try:
+ application.listen(portno)
+ self.loginfo("Webserver successfully started on port %s"%portno)
+ except socket.error as err:
+ # Socket exceptions get handled, all other exceptions propagated
+ if err.errno == 13:
+ self.logwarn("Insufficient priveliges to run webserver " +
+ "on port %s. Error: %s"%(portno, err.strerror))
+ self.loginfo("-- Try re-running as super-user: sudo su; " +
+ "source ~/.bashrc)")
+ elif err.errno == 98:
+ self.logwarn("There is already a webserver running on port %s. " +
+ "Error: %s"%(portno, err.strerror))
+ self.loginfo("-- Try stopping your web server. For example, " +
+ "to stop apache: sudo /etc/init.d/apache2 stop")
+ else:
+ self.logerr("An error occurred attempting to listen on " +
+ "port %s: %s"%(portno, err.strerror))
+ return False
+ return True
+
+ def _start_webserver(self):
+ try:
+ tornado.ioloop.IOLoop.instance().start()
+ except KeyboardInterrupt:
+ self.loginfo("Webserver shutting down")
+
+ def spin(self):
+ try:
+ bound = self._bind_webserver()
+ if bound:
+ self._start_webserver()
+ else:
+ raise Exception()
+ except Exception as exc:
+ self.logerr("Unable to bind webserver. Exiting. %s" % exc)
+
+ def _set_logger(self):
+ logger = logging.getLogger('roswww')
+ logger.setLevel(logging.DEBUG)
+
+ # create console handler and set level to debug
+ ch = logging.StreamHandler()
+ ch.setLevel(logging.DEBUG)
+
+ # create formatter
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+
+ # add formatter to ch
+ ch.setFormatter(formatter)
+
+ # add ch to logger
+ logger.addHandler(ch)
+
+ return logger
+
+
+ def loginfo(self, msg):
+ self._logger.info('%s : %s'%(self._name, msg))
+
+ def logwarn(self, msg):
+ self._logger.warning('%s : %s'%(self._name, msg))
+
+ def logerr(self, msg):
+ self._logger.error('%s : %s'%(self._name, msg))
diff --git a/src/roswww/utils.py b/src/roswww/utils.py
new file mode 100644
index 0000000..c540ef0
--- /dev/null
+++ b/src/roswww/utils.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+# Software License Agreement (BSD License)
+#
+# Copyright (c) 2013, Tokyo Opensource Robotics Kyokai Association
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+# * Neither the name of Tokyo Opensource Robotics Kyokai Association. nor the
+# names of its contributors may be used to endorse or promote products
+# derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# Author: Jonathan Mace, Jihoon Lee, Isaac Isao Saito
+
+#############################################
+# Methods
+#############################################
+import subprocess
+
+def get_packages():
+ '''
+ Find the names and locations of all ROS packages
+
+ @rtype: {str, str}
+ @return: name and path of ROS packages
+ '''
+ lines = split_words(run_shellcommand('rospack', 'list'))
+ packages = [{'name': name, 'path': path} for name, path in lines]
+ return packages
+
+def run_shellcommand(*args):
+ '''run the provided command and return its stdout'''
+ args = sum([(arg if type(arg) == list else [arg]) for arg in args], [])
+ return subprocess.Popen(args,
+ stdout=subprocess.PIPE).communicate()[0].strip()
+
+def split_words(text):
+ '''return a list of lines where each line is a list of words'''
+ return [line.strip().split() for line in text.split('\n')]
+
diff --git a/src/roswww/webrequest_handler.py b/src/roswww/webrequest_handler.py
index 6e25263..ab83228 100755
--- a/src/roswww/webrequest_handler.py
+++ b/src/roswww/webrequest_handler.py
@@ -36,7 +36,6 @@
import tornado.web
-
class WebRequestHandler(tornado.web.RequestHandler):
def initialize(self, packages):