roswww_server.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # Software License Agreement (BSD License)
00004 #
00005 # Copyright (c) 2013, Tokyo Opensource Robotics Kyokai Association
00006 # All rights reserved.
00007 #
00008 # Redistribution and use in source and binary forms, with or without
00009 # modification, are permitted provided that the following conditions
00010 # are met:
00011 #
00012 # * Redistributions of source code must retain the above copyright
00013 # notice, this list of conditions and the following disclaimer.
00014 # * Redistributions in binary form must reproduce the above
00015 # copyright notice, this list of conditions and the following
00016 # disclaimer in the documentation and/or other materials provided
00017 # with the distribution.
00018 # * Neither the name of Tokyo Opensource Robotics Kyokai Association. nor the
00019 # names of its contributors may be used to endorse or promote products
00020 # derived from this software without specific prior written permission.
00021 #
00022 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00023 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00024 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00025 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00026 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00027 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00028 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00029 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00030 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00031 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00032 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00033 # POSSIBILITY OF SUCH DAMAGE.
00034 #
00035 # Author: Jonathan Mace, Jihoon Lee, Isaac Isao Saito
00036 
00037 import logging
00038 
00039 import socket
00040 import tornado.ioloop  # rosbridge installs tornado
00041 import tornado.web
00042 from .webrequest_handler import WebRequestHandler
00043 from .utils import run_shellcommand, split_words, get_packages
00044 
00045 class ROSWWWServer():
00046 
00047     def __init__(self, name, webpath, ports):
00048         '''
00049           :param str name: webserver name
00050           :param str webpath: package relative path to web page source. 
00051           :param tuple ports: ports to use in webserver. Provides default and scan range (default, start, end)
00052         '''
00053         self._name = name
00054         self._webpath = webpath
00055         self._ports = ports
00056         self._logger = self._set_logger()
00057         self._packages = get_packages()
00058         self._application = self._create_webserver(self._packages)
00059         
00060     def _create_webserver(self, packages):
00061         '''
00062         @type packages: {str, str}
00063         @param packages: name and path of ROS packages.
00064         '''
00065         handlers = [(r"/", WebRequestHandler, {"packages": packages})]
00066 
00067         for package in packages:
00068             handler_root = ("/" + package['name'] + "/?()",
00069                             tornado.web.StaticFileHandler,
00070                             {"path": package['path'] + "/" + self._webpath + "/index.html"})
00071             handlers.append(handler_root)
00072             handler = ("/" + package['name'] + "/(.*)",
00073                        tornado.web.StaticFileHandler,
00074                        {"path": package['path'] + "/" + self._webpath,
00075                         "default_filename": "index.html"})
00076             handlers.append(handler)
00077 
00078         self.loginfo("# of packages : %s"%(len(packages)))
00079         self.loginfo("Weg Page root : %s"%(self._webpath))
00080         application = tornado.web.Application(handlers)
00081         return application
00082 
00083     def _bind_webserver(self):
00084         default, start, end = self._ports
00085         
00086         """ First, we try the default http port """
00087         bound = self._bind_to_port(self._application, default)
00088         if not bound:
00089             """ Otherwise bind any available port within the specified range """ 
00090             bound = self._bind_in_range(self._application, start, end)
00091         return True 
00092 
00093     def _bind_in_range(self, application, start_port, end_port):
00094         if (end_port > start_port):
00095             for i in range(start_port, end_port):
00096                 if self._bind_to_port(application, i):
00097                     return True
00098         return False
00099 
00100     def _bind_to_port(self, application, portno):
00101         self.loginfo("Attempting to start webserver on port %s"%portno)
00102         try:
00103             application.listen(portno)
00104             self.loginfo("Webserver successfully started on port %s"%portno)
00105         except socket.error as err:
00106             # Socket exceptions get handled, all other exceptions propagated
00107             if err.errno == 13:
00108                 self.logwarn("Insufficient priveliges to run webserver " +
00109                               "on port %s. Error: %s"%(portno, err.strerror))
00110                 self.loginfo("-- Try re-running as super-user: sudo su; " +
00111                               "source ~/.bashrc)")
00112             elif err.errno == 98:
00113                 self.logwarn("There is already a webserver running on port %s. " +
00114                               "Error: %s"%(portno, err.strerror))
00115                 self.loginfo("-- Try stopping your web server. For example, " +
00116                               "to stop apache: sudo /etc/init.d/apache2 stop")
00117             else:
00118                 self.logerr("An error occurred attempting to listen on " +
00119                              "port %s: %s"%(portno, err.strerror))
00120             return False
00121         return True
00122 
00123     def _start_webserver(self):
00124         try:
00125             tornado.ioloop.IOLoop.instance().start()
00126         except KeyboardInterrupt:
00127             self.loginfo("Webserver shutting down")
00128 
00129     def spin(self):
00130         try:
00131             bound = self._bind_webserver()
00132             if bound:
00133                 self._start_webserver()
00134             else:
00135                 raise Exception()
00136         except Exception as exc:
00137             self.logerr("Unable to bind webserver.  Exiting.  %s" % exc)
00138 
00139     def _set_logger(self):
00140         logger = logging.getLogger('roswww')
00141         logger.setLevel(logging.DEBUG)
00142 
00143         # create console handler and set level to debug
00144         ch = logging.StreamHandler()
00145         ch.setLevel(logging.DEBUG)
00146 
00147         # create formatter
00148         formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
00149 
00150         # add formatter to ch
00151         ch.setFormatter(formatter)
00152 
00153         # add ch to logger
00154         logger.addHandler(ch)
00155 
00156         return logger
00157 
00158 
00159     def loginfo(self, msg):
00160         self._logger.info('%s : %s'%(self._name, msg))
00161     
00162     def logwarn(self, msg):
00163         self._logger.warning('%s : %s'%(self._name, msg))
00164 
00165     def logerr(self, msg):
00166         self._logger.error('%s : %s'%(self._name, msg))


roswww
Author(s): Jonathan Mace, Jihoon Lee
autogenerated on Fri Aug 28 2015 12:45:19