1import logging 2import time 3import cgi 4import socket 5import sys 6import gevent 7import urllib 8 9from gevent.pywsgi import WSGIServer 10from gevent.pywsgi import WSGIHandler 11from geventwebsocket.handler import WebSocketHandler 12 13from .UiRequest import UiRequest 14from Site import SiteManager 15from Config import config 16from Debug import Debug 17import importlib 18 19 20# Skip websocket handler if not necessary 21class UiWSGIHandler(WSGIHandler): 22 23 def __init__(self, *args, **kwargs): 24 self.server = args[2] 25 super(UiWSGIHandler, self).__init__(*args, **kwargs) 26 self.args = args 27 self.kwargs = kwargs 28 29 def run_application(self): 30 if "HTTP_UPGRADE" in self.environ: # Websocket request 31 try: 32 ws_handler = WebSocketHandler(*self.args, **self.kwargs) 33 ws_handler.__dict__ = self.__dict__ # Match class variables 34 ws_handler.run_application() 35 except Exception as err: 36 logging.error("UiWSGIHandler websocket error: %s" % Debug.formatException(err)) 37 if config.debug: # Allow websocket errors to appear on /Debug 38 import main 39 main.DebugHook.handleError() 40 else: # Standard HTTP request 41 try: 42 super(UiWSGIHandler, self).run_application() 43 except Exception as err: 44 logging.error("UiWSGIHandler error: %s" % Debug.formatException(err)) 45 if config.debug: # Allow websocket errors to appear on /Debug 46 import main 47 main.DebugHook.handleError() 48 49 def handle(self): 50 # Save socket to be able to close them properly on exit 51 self.server.sockets[self.client_address] = self.socket 52 super(UiWSGIHandler, self).handle() 53 del self.server.sockets[self.client_address] 54 55 56class UiServer: 57 58 def __init__(self): 59 self.ip = config.ui_ip 60 self.port = config.ui_port 61 self.running = False 62 if self.ip == "*": 63 self.ip = "0.0.0.0" # Bind all 64 if config.ui_host: 65 self.allowed_hosts = set(config.ui_host) 66 elif config.ui_ip == "127.0.0.1": 67 # IP Addresses are inherently allowed as they are immune to DNS 68 # rebinding attacks. 69 self.allowed_hosts = set(["zero", "localhost:%s" % config.ui_port]) 70 # "URI producers and normalizers should omit the port component and 71 # its ':' delimiter if port is empty or if its value would be the 72 # same as that of the scheme's default." 73 # Source: https://tools.ietf.org/html/rfc3986#section-3.2.3 74 # As a result, we need to support portless hosts if port 80 is in 75 # use. 76 if config.ui_port == 80: 77 self.allowed_hosts.update(["localhost"]) 78 else: 79 self.allowed_hosts = set([]) 80 self.allowed_ws_origins = set() 81 self.allow_trans_proxy = config.ui_trans_proxy 82 83 self.wrapper_nonces = [] 84 self.add_nonces = [] 85 self.websockets = [] 86 self.site_manager = SiteManager.site_manager 87 self.sites = SiteManager.site_manager.list() 88 self.log = logging.getLogger(__name__) 89 90 # After WebUI started 91 def afterStarted(self): 92 from util import Platform 93 Platform.setMaxfilesopened(config.max_files_opened) 94 95 # Handle WSGI request 96 def handleRequest(self, env, start_response): 97 path = bytes(env["PATH_INFO"], "raw-unicode-escape").decode("utf8") 98 if env.get("QUERY_STRING"): 99 get = dict(urllib.parse.parse_qsl(env['QUERY_STRING'])) 100 else: 101 get = {} 102 ui_request = UiRequest(self, get, env, start_response) 103 if config.debug: # Let the exception catched by werkezung 104 return ui_request.route(path) 105 else: # Catch and display the error 106 try: 107 return ui_request.route(path) 108 except Exception as err: 109 logging.debug("UiRequest error: %s" % Debug.formatException(err)) 110 return ui_request.error500("Err: %s" % Debug.formatException(err)) 111 112 # Reload the UiRequest class to prevent restarts in debug mode 113 def reload(self): 114 global UiRequest 115 import imp 116 import sys 117 importlib.reload(sys.modules["User.UserManager"]) 118 importlib.reload(sys.modules["Ui.UiWebsocket"]) 119 UiRequest = imp.load_source("UiRequest", "src/Ui/UiRequest.py").UiRequest 120 # UiRequest.reload() 121 122 # Bind and run the server 123 def start(self): 124 self.running = True 125 handler = self.handleRequest 126 127 if config.debug: 128 # Auto reload UiRequest on change 129 from Debug import DebugReloader 130 DebugReloader.watcher.addCallback(self.reload) 131 132 # Werkzeug Debugger 133 try: 134 from werkzeug.debug import DebuggedApplication 135 handler = DebuggedApplication(self.handleRequest, evalex=True) 136 except Exception as err: 137 self.log.info("%s: For debugging please enable the port option: DEBUG=on" % err) 138 from Debug import DebugReloader 139 self.log.write = lambda msg: self.log.debug(msg.strip()) # For Wsgi access.log 140 self.log.info("--------------------------------------") 141 if ":" in config.ui_ip: 142 self.log.info("Web interface: http://[%s]:%s/" % (config.ui_ip, config.ui_port)) 143 else: 144 self.log.info("Web interface: http://%s:%s/" % (config.ui_ip, config.ui_port)) 145 self.log.info("--------------------------------------") 146 147 if config.open_browser and config.open_browser != "False": 148 logging.info("Opening browser: %s...", config.open_browser) 149 import webbrowser 150 try: 151 if config.open_browser == "default_browser": 152 browser = webbrowser.get() 153 else: 154 browser = webbrowser.get(config.open_browser) 155 url = "http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage) 156 gevent.spawn_later(0.3, browser.open, url, new=2) 157 except Exception as err: 158 print("Error starting browser: %s" % err) 159 160 self.server = WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log) 161 self.server.sockets = {} 162 self.afterStarted() 163 try: 164 self.server.serve_forever() 165 except Exception as err: 166 self.log.error("Web interface bind error, must be running already, exiting.... %s" % err) 167 import main 168 main.file_server.stop() 169 self.log.debug("Stopped.") 170 171 def stop(self): 172 self.log.debug("Stopping...") 173 # Close WS sockets 174 if "clients" in dir(self.server): 175 for client in list(self.server.clients.values()): 176 client.ws.close() 177 # Close http sockets 178 sock_closed = 0 179 for sock in list(self.server.sockets.values()): 180 try: 181 sock.send(b"bye") 182 sock.shutdown(socket.SHUT_RDWR) 183 # sock._sock.close() 184 # sock.close() 185 sock_closed += 1 186 except Exception as err: 187 self.log.debug("Http connection close error: %s" % err) 188 self.log.debug("Socket closed: %s" % sock_closed) 189 time.sleep(0.1) 190 if config.debug: 191 from Debug import DebugReloader 192 DebugReloader.watcher.stop() 193 194 self.server.socket.close() 195 self.server.stop() 196 self.running = False 197 time.sleep(1) 198 199 def updateWebsocket(self, **kwargs): 200 for ws in self.websockets: 201 ws.event("serverChanged", kwargs) 202