xpra icon
Bug tracker and wiki

Ticket #1136: websockify-inprocess.patch

File websockify-inprocess.patch, 22.0 KB (added by Antoine Martin, 4 years ago)

move websockify handling in process

  • xpra/log.py

     
    224224                ("network"      , "All network code"),
    225225                ("mmap"         , "mmap transfers"),
    226226                ("protocol"     , "Packet input and output (formatting, parsing, sending and receiving)"),
     227                ("websocket"    , "Websocket layer"),
    227228                ("crypto"       , "Encryption"),
    228229                ("auth"         , "Authentication"),
    229230                ])),
  • xpra/net/bytestreams.py

     
    151151    def untilConcludes(self, *args):
    152152        return untilConcludes(self.is_active, *args)
    153153
     154    def peek(self, n):
     155        #not implemented
     156        return None
     157
    154158    def _write(self, *args):
    155159        w = self.untilConcludes(*args)
    156160        self.output_bytecount += w or 0
     
    245249        self._socket = socket
    246250        self.local = local
    247251        self.remote = remote
     252        self.socket_type = "socket"
    248253        if type(remote)==str:
    249254            self.filename = remote
    250255
     256    def peek(self, n):
     257        self._socket.settimeout(None)
     258        return self._socket.recv(n, socket.MSG_PEEK)
     259
    251260    def read(self, n):
    252261        return self._read(self._socket.recv, n)
    253262
     
    263272
    264273    def __repr__(self):
    265274        if self.remote:
    266             return "%s socket: %s <- %s" % (self.info, pretty_socket(self.local), pretty_socket(self.remote))
    267         return "%s socket:%s" % (self.info, pretty_socket(self.local))
     275            return "%s %s: %s <- %s" % (self.info, self.socket_type, pretty_socket(self.local), pretty_socket(self.remote))
     276        return "%s %s:%s" % (self.info, self.socket_type, pretty_socket(self.local))
    268277
    269278    def get_info(self):
    270279        d = Connection.get_info(self)
    271280        try:
    272             d["type"] = "socket"
     281            d["type"] = self.socket_type
    273282            s = self._socket
    274283            if s:
    275284                d["socket"] = {
  • xpra/net/websocket.py

     
     1# This file is part of Xpra.
     2# Copyright (C) 2016 Antoine Martin <antoine@devloop.org.uk>
     3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
     4# later version. See the file COPYING for details.
     5
     6import os
     7import posixpath
     8import urllib
     9
     10from xpra.log import Logger
     11log = Logger("network", "websocket")
     12
     13from xpra.util import AdHocStruct
     14from xpra.net.bytestreams import SocketConnection
     15from websockify.websocket import WebSocketRequestHandler
     16
     17WEBSOCKET_TCP_NODELAY = int(os.environ.get("WEBSOCKET_TCP_NODELAY", "1"))
     18WEBSOCKET_TCP_KEEPALIVE = int(os.environ.get("WEBSOCKET_TCP_KEEPALIVE", "1"))
     19
     20
     21
     22class WSRequestHandler(WebSocketRequestHandler):
     23
     24    disable_nagle_algorithm = WEBSOCKET_TCP_NODELAY
     25    keep_alive = WEBSOCKET_TCP_KEEPALIVE
     26
     27    def __init__(self, sock, addr, new_websocket_client):
     28        self.web_root = "/usr/share/xpra/www/"
     29        server = AdHocStruct()
     30        server.logger = log
     31        server.run_once = True
     32        self._new_websocket_client = new_websocket_client
     33        WebSocketRequestHandler.__init__(self, sock, addr, server)
     34
     35    def new_websocket_client(self):
     36        self._new_websocket_client(self)
     37
     38    def translate_path(self, path):
     39        s = path
     40        # abandon query parameters
     41        path = path.split('?',1)[0]
     42        path = path.split('#',1)[0]
     43        # Don't forget explicit trailing slash when normalizing. Issue17324
     44        trailing_slash = path.rstrip().endswith('/')
     45        path = posixpath.normpath(urllib.unquote(path))
     46        words = path.split('/')
     47        words = filter(None, words)
     48        path = self.web_root
     49        for word in words:
     50            word = os.path.splitdrive(word)[1]
     51            word = os.path.split(word)[1]
     52            if word in (os.curdir, os.pardir):
     53                continue
     54            path = os.path.join(path, word)
     55        if trailing_slash:
     56            path += '/'
     57        log("translate_path(%s)=%s", s, path)
     58        return path
     59
     60
     61    def log_error(self, fmt, *args):
     62        log.error(fmt, *args)
     63
     64    def log_message(self, fmt, *args):
     65        #log.warn("%s", (fmt, args))
     66        log(fmt, *args)
     67
     68    def print_traffic(self, token="."):
     69        """ Show traffic flow mode. """
     70        if self.traffic:
     71            log(token)
     72
     73
     74class WebSocketConnection(SocketConnection):
     75
     76    def __init__(self, socket, local, remote, target, info, ws_handler):
     77        SocketConnection.__init__(self, socket, local, remote, target, info)
     78        self.socket_type = "websocket"
     79        self.ws_handler = ws_handler
     80
     81    def read(self, n):
     82        while self.is_active():
     83            bufs, closed_string = self.ws_handler.recv_frames()
     84            if closed_string:
     85                self.active = False
     86            if len(bufs) == 1:
     87                self.input_bytecount += len(bufs[0])
     88                return bufs[0]
     89            elif len(bufs) > 1:
     90                buf = b''.join(bufs)
     91                self.input_bytecount += len(buf)
     92                return buf
     93
     94    def write(self, buf):
     95        self.ws_handler.send_frames([buf])
     96        self.output_bytecount += len(buf)
     97        return len(buf)
  • xpra/platform/win32/shadow_server.py

     
    1717log = Logger("shadow", "win32")
    1818traylog = Logger("tray")
    1919shapelog = Logger("shape")
     20netlog = Logger("network")
    2021
    2122from xpra.os_util import StringIOClass
    2223from xpra.server.gtk_server_base import GTKServerBase
     
    248249            return GTKServerBase._new_connection(self, listener)
    249250        pipe_handle = args[0]
    250251        conn = NamedPipeConnection(listener.pipe_name, pipe_handle)
    251         return self.make_protocol(socktype, conn, frominfo=" on %s" % conn.target)
     252        netlog.info("New %s connection received on %s", socktype, conn.target)
     253        return self.make_protocol(socktype, conn, frominfo=conn.target)
    252254
    253255
    254256    def make_tray_widget(self):
  • xpra/scripts/server.py

     
    510510    s.close()
    511511    return port
    512512
    513 def start_websockify(child_reaper, opts, tcp_sockets):
    514     from xpra.log import Logger
    515     log = Logger("server")
    516     # start websockify?
    517     log("html=%s", opts.html)
    518     if not opts.html:
    519         return
    520     html = opts.html
    521     if type(html)==str:
    522         html = html.lower()
    523     from xpra.scripts.config import FALSE_OPTIONS, TRUE_OPTIONS
    524     if html in FALSE_OPTIONS:
    525         #html disabled
    526         return
    527     log("tcp_proxy=%s", opts.tcp_proxy)
    528     if opts.tcp_proxy:
    529         raise InitException("cannot use tcp-proxy mode with html, use one or the other")
    530         return 1
    531     from xpra.platform.paths import get_resources_dir, get_websockify_command
    532     www_dir = os.path.abspath(os.path.join(get_resources_dir(), "www"))
    533     if not os.path.exists(www_dir):
    534         raise InitException("cannot find xpra's html directory (not found in '%s')" % www_dir)
    535     log("www_dir=%s", www_dir)
    536     import websockify           #@UnresolvedImport
    537     log("websockify=%s", websockify)
    538     assert websockify
    539     html_port = -1
    540     html_host = "127.0.0.1"
    541     if html not in TRUE_OPTIONS:
    542         #we expect either HOST:PORT, or just PORT
    543         if html.find(":")>=0:
    544             html_host, html = html.split(":", 1)
    545         try:
    546             html_port = int(html)
    547         except Exception as e:
    548             raise InitException("invalid html port: %s" % e)
    549         #we now have the host and port (port may be -1 to mean auto..)
    550     if html_port==-1:
    551         #try to find a free port and hope that websockify can then use it..
    552         html_port = get_free_tcp_port()
    553     elif os.name=="posix" and html_port<1024 and os.geteuid()!=0:
    554         log.warn("Warning: the html port specified may require special privileges (%s:%s)", html_host, html_port)
    555     if len(tcp_sockets)<1:
    556         raise InitException("html web server requires at least one tcp socket, see 'bind-tcp'")
    557     #use the first tcp socket for websockify to talk back to us:
    558     _, xpra_tcp_port = list(tcp_sockets)[0]
    559     kwargs = {}
    560     if sys.platform.startswith("win"):
    561         #this is a "DOS" command, but we want to hide the shell window
    562         startupinfo = subprocess.STARTUPINFO()
    563         startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
    564         kwargs = {"startupinfo" : startupinfo}
    565     #add bundler bin directories to path (for OSX):
    566     websockify_command = get_websockify_command() + ["--web", www_dir, "%s:%s" % (html_host, html_port), "127.0.0.1:%s" % xpra_tcp_port]
    567     log("websockify_command: %s, kwargs=%s, sys.path=%s", websockify_command, kwargs, sys.path)
    568     websockify_proc = subprocess.Popen(websockify_command, close_fds=True, **kwargs)
    569     websockify_proc._closed = False
    570     start_time = time.time()
    571     def websockify_ended(proc):
    572         elapsed = time.time()-start_time
    573         log("websockify_ended(%s) after %i seconds", proc, elapsed)
    574         if not websockify_proc._closed:
    575             log.warn("Warning: websockify has terminated,")
    576             log.warn(" the html web server will not be available.")
    577             log.warn(" the command used was:")
    578             log.warn(" %s", " ".join(websockify_command))
    579         return False
    580     child_reaper.add_process(websockify_proc, "websockify", websockify_command, ignore=True, callback=websockify_ended)
    581     log.info("websockify started, serving %s on %s:%s", www_dir, html_host, html_port)
    582     def cleanup_websockify():
    583         log("cleanup_websockify() process.poll()=%s, pid=%s", websockify_proc.poll(), websockify_proc.pid)
    584         if websockify_proc.poll() is None and not websockify_proc._closed:
    585             log.info("stopping websockify with pid %s", websockify_proc.pid)
    586             try:
    587                 websockify_proc._closed = True
    588                 websockify_proc.terminate()
    589             except:
    590                 log.warn("error trying to stop websockify", exc_info=True)
    591     _cleanups.append(cleanup_websockify)
    592     opts.tcp_proxy = "%s:%s" % (html_host, html_port)
    593 
    594 
    595513def close_all_fds(exceptions=[]):
    596514    fd_dirs = ["/dev/fd", "/proc/self/fd"]
    597515    for fd_dir in fd_dirs:
     
    13031221
    13041222    #honour start child, html webserver, and setup child reaper
    13051223    if not proxying and not upgrading:
    1306         # start websockify?
    1307         try:
    1308             start_websockify(app.child_reaper, opts, bind_tcp)
    1309             #websockify overrides the tcp proxy, so we must re-set it:
    1310             app._tcp_proxy = opts.tcp_proxy
    1311         except Exception as e:
    1312             error_cb("failed to setup websockify html server: %s" % e)
    13131224        if opts.exit_with_children:
    13141225            assert opts.start_child, "exit-with-children was specified but start-child is missing!"
    13151226        app.start_commands              = opts.start
  • xpra/server/server_core.py

     
    2828from xpra.server import ClientException
    2929from xpra.scripts.main import SOCKET_TIMEOUT, _socket_connect
    3030from xpra.scripts.server import deadly_signal
    31 from xpra.scripts.config import InitException
     31from xpra.scripts.config import InitException, parse_bool
    3232from xpra.net.bytestreams import SocketConnection, pretty_socket, set_socket_timeout
    3333from xpra.platform import set_name
    34 from xpra.os_util import load_binary_file, get_machine_id, get_user_uuid, platform_name, SIGNAMES, Queue
     34from xpra.os_util import load_binary_file, get_machine_id, get_user_uuid, platform_name, SIGNAMES
    3535from xpra.version_util import version_compat_check, get_version_info_full, get_platform_info, get_host_info, local_version
    3636from xpra.net.protocol import Protocol, get_network_caps, sanity_checks
    3737from xpra.net.crypto import crypto_backend_init, new_cipher_caps, \
     
    4040from xpra.make_thread import make_thread
    4141from xpra.scripts.fdproxy import XpraProxy
    4242from xpra.server.control_command import ControlError, HelloCommand, HelpCommand, DebugControl
    43 from xpra.util import csv, merge_dicts, typedict, notypedict, flatten_dict, parse_simple_dict, repr_ellipsized, dump_all_frames, \
     43from xpra.util import csv, merge_dicts, typedict, notypedict, flatten_dict, parse_simple_dict, repr_ellipsized, dump_all_frames, nonl, \
    4444        SERVER_SHUTDOWN, SERVER_UPGRADE, LOGIN_TIMEOUT, DONE, PROTOCOL_ERROR, SERVER_ERROR, VERSION_ERROR, CLIENT_REQUEST
    4545
    4646main_thread = threading.current_thread()
     
    147147        self._potential_protocols = []
    148148        self._tcp_proxy_clients = []
    149149        self._tcp_proxy = ""
     150        self._html = False
    150151        self._aliases = {}
    151152        self._reverse_aliases = {}
    152153        self.socket_types = {}
     
    191192        self.unix_socket_paths = []
    192193        self._socket_dir = opts.socket_dir or opts.socket_dirs[0]
    193194        self._tcp_proxy = opts.tcp_proxy
     195        self._html = parse_bool("html", opts.html)
     196        if self._html:
     197            try:
     198                from xpra.net.websocket import WebSocketConnection
     199                assert WebSocketConnection
     200            except ImportError as e:
     201                log.error("Error: cannot import websockify connection handler:")
     202                log.error(" %s", e)
     203                log.error(" the html server will not be available")
     204                self._html = False
     205            if self._html and self._tcp_proxy:
     206                log.warn("Warning: the built in html server is enabled,")
     207                log.warn(" disabling the tcp-proxy option")
    194208        self.encryption = opts.encryption
    195209        self.encryption_keyfile = opts.encryption_keyfile
    196210        self.tcp_encryption = opts.tcp_encryption
     
    472486        netlog("new_connection(%s) sock=%s, timeout=%s, sockname=%s, address=%s, peername=%s", args, sock, self._socket_timeout, sockname, address, peername)
    473487        conn = SocketConnection(sock, sockname, address, target, socktype)
    474488        netlog("socket connection: %s", conn)
    475         frominfo = ""
    476489        if peername:
    477             frominfo = " from %s" % pretty_socket(peername)
     490            frominfo = pretty_socket(peername)
     491            info_msg = "New %s connection received from %s" % (socktype, frominfo)
    478492        elif socktype=="unix-domain":
    479             frominfo = " on %s" % sockname
     493            frominfo = sockname
     494            info_msg = "New %s connection received on %s" % (socktype, frominfo)
     495        else:
     496            frominfo = ""
     497            info_msg = "New %s connection received"
     498
     499        if socktype=="tcp" and self._html or self._tcp_proxy:
     500            #see if the packet data is actually xpra or something else
     501            #that we need to handle via a tcp proxy or the websockify adapter:
     502            conn._socket.settimeout(5)
     503            v = conn.peek(128)
     504            netlog("peek()=%s", nonl(v))
     505            if v and v[0] not in ("P", ord("P")):
     506                if self._html:
     507                    line1 = v.splitlines()[0]
     508                    if line1.find("HTTP/")>0:
     509                        log.info("New HTTP connection received from %s", frominfo)
     510                        def run_websockify():
     511                            self.start_websockify(conn, frominfo)
     512                        make_thread(run_websockify, "websockify-proxy-for-%s" % frominfo, daemon=True).start()
     513                        return True
     514                elif self._tcp_proxy:
     515                    log.info("New TCP proxy connection received from %s", frominfo)
     516                    def run_proxy():
     517                        self.start_tcp_proxy(conn, frominfo)
     518                    make_thread(run_proxy, "tcp-proxy-for-%s" % frominfo, daemon=True).start()
     519                    return True
     520                netlog("")
     521        #FIXME: if we have peek data and it isn't an xpra client,
     522        #we can bail out early without making a protocol
     523        netlog.info(info_msg)
    480524        return self.make_protocol(socktype, conn, frominfo)
    481525
    482526    def make_protocol(self, socktype, conn, frominfo=""):
    483         netlog.info("New %s connection received%s", socktype, frominfo)
    484527        protocol = Protocol(self, conn, self.process_packet)
    485528        self._potential_protocols.append(protocol)
    486529        protocol.large_packets.append("info-response")
     
    509552        self.timeout_add(SOCKET_TIMEOUT*1000, self.verify_connection_accepted, protocol)
    510553        return True
    511554
    512 
    513555    def invalid_header(self, proto, data):
    514         netlog("invalid_header(%s, %s bytes: '%s') input_packetcount=%s, tcp_proxy=%s", proto, len(data or ""), repr_ellipsized(data), proto.input_packetcount, self._tcp_proxy)
    515         if proto.input_packetcount==0 and self._tcp_proxy and not proto._closed:
    516             #start a new proxy in a thread
    517             def run_proxy():
    518                 self.start_tcp_proxy(proto, data)
    519             make_thread(run_proxy, "web-proxy-for-%s" % proto, daemon=True).start()
    520             return
     556        netlog("invalid_header(%s, %s bytes: '%s') input_packetcount=%s, tcp_proxy=%s, html=%s", proto, len(data or ""), repr_ellipsized(data), proto.input_packetcount, self._tcp_proxy, self._html)
    521557        err = "invalid packet format, not an xpra client?"
    522558        proto.gibberish(err, data)
    523559
    524     def start_tcp_proxy(self, proto, data):
    525         proxylog("start_tcp_proxy(%s, '%s')", proto, repr_ellipsized(data))
     560
     561    def start_websockify(self, conn, frominfo):
     562        log("start_websockify(%s, %s)", conn, frominfo)
     563        from xpra.net.websocket import WebSocketConnection, WSRequestHandler
    526564        try:
    527             self._potential_protocols.remove(proto)
    528         except:
    529             pass        #might already have been removed by now
    530         proxylog("start_tcp_proxy: protocol state before stealing: %s", proto.get_info(alias_info=False))
    531         #any buffers read after we steal the connection will be placed in this temporary queue:
    532         temp_read_buffer = Queue()
    533         client_connection = proto.steal_connection(temp_read_buffer.put)
     565            def new_websocket_client(wsh):
     566                log("new_websocket_client(%s)", wsh)
     567                wsc = WebSocketConnection(conn._socket, conn.local, conn.remote, conn.target, conn.info, wsh)
     568                set_socket_timeout(conn, self._socket_timeout)
     569                self.make_protocol("tcp", wsc, frominfo)
     570            WSRequestHandler(conn._socket, frominfo, new_websocket_client)
     571            return
     572        except IOError as e:
     573            netlog.error("Error: http failure responding to %s:", frominfo)
     574            netlog.error(" %s", e)
     575            netlog("", exc_info=True)
     576        except Exception as e:
     577            netlog.error("Error: http failure responding to %s:", frominfo, exc_info=True)
     578        try:
     579            conn.close()
     580        except Exception as ce:
     581            netlog("error closing connection following error: %s", ce)
     582
     583    def start_tcp_proxy(self, conn, frominfo):
     584        proxylog("start_tcp_proxy(%s, %s)", conn, frominfo)
    534585        #connect to web server:
    535         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    536         sock.settimeout(10)
    537586        host, port = self._tcp_proxy.split(":", 1)
    538587        try:
    539             web_server_connection = _socket_connect(sock, (host, int(port)), "web-proxy-for-%s" % proto, "tcp")
     588            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
     589            sock.settimeout(10)
     590            host, port = self._tcp_proxy.split(":", 1)
     591            tcp_server_connection = _socket_connect(sock, (host, int(port)), "web-proxy-for-%s" % frominfo, "tcp")
    540592        except:
    541593            proxylog.warn("failed to connect to proxy: %s:%s", host, port)
    542             proto.gibberish("invalid packet header", data)
     594            conn.close()
    543595            return
    544         proxylog("proxy connected to tcp server at %s:%s : %s", host, port, web_server_connection)
     596        proxylog("proxy connected to tcp server at %s:%s : %s", host, port, tcp_server_connection)
     597        sock = tcp_server_connection._socket
    545598        sock.settimeout(self._socket_timeout)
    546599
    547         ioe = proto.wait_for_io_threads_exit(0.5+self._socket_timeout)
    548         if not ioe:
    549             proxylog.warn("proxy failed to stop all existing network threads!")
    550             self.disconnect_protocol(proto, "internal threading error")
    551             return
    552         #now that we own it, we can start it again:
    553         client_connection.set_active(True)
    554         #and we can use blocking sockets:
    555         set_socket_timeout(client_connection, None)
    556         #prevent deadlocks on exit:
     600        #we can use blocking sockets for the client:
     601        conn.settimeout(None)
     602        #but not for the server, which could deadlock on exit:
    557603        sock.settimeout(1)
    558604
    559         proxylog("pushing initial buffer to its new destination: %s", repr_ellipsized(data))
    560         web_server_connection.write(data)
    561         while not temp_read_buffer.empty():
    562             buf = temp_read_buffer.get()
    563             if buf:
    564                 proxylog("pushing read buffer to its new destination: %s", repr_ellipsized(buf))
    565                 web_server_connection.write(buf)
    566         p = XpraProxy(client_connection.target, client_connection, web_server_connection, self.tcp_proxy_quit)
     605        #now start forwarding:
     606        p = XpraProxy(frominfo, conn, tcp_server_connection, self.tcp_proxy_quit)
    567607        self._tcp_proxy_clients.append(p)
    568         proxylog.info("client connection from %s forwarded to proxy server on %s:%s", client_connection.target, host, port)
     608        proxylog.info("client connection from %s forwarded to proxy server on %s:%s", frominfo, host, port)
    569609        p.start_threads()
    570610
     611
    571612    def tcp_proxy_quit(self, proxy):
    572613        proxylog("tcp_proxy_quit(%s)", proxy)
    573614        if proxy in self._tcp_proxy_clients: