Index: xpra/log.py
===================================================================
--- xpra/log.py	(revision 12661)
+++ xpra/log.py	(working copy)
@@ -224,6 +224,7 @@
                 ("network"      , "All network code"),
                 ("mmap"         , "mmap transfers"),
                 ("protocol"     , "Packet input and output (formatting, parsing, sending and receiving)"),
+                ("websocket"    , "Websocket layer"),
                 ("crypto"       , "Encryption"),
                 ("auth"         , "Authentication"),
                 ])),
Index: xpra/net/bytestreams.py
===================================================================
--- xpra/net/bytestreams.py	(revision 12661)
+++ xpra/net/bytestreams.py	(working copy)
@@ -151,6 +151,10 @@
     def untilConcludes(self, *args):
         return untilConcludes(self.is_active, *args)
 
+    def peek(self, n):
+        #not implemented
+        return None
+
     def _write(self, *args):
         w = self.untilConcludes(*args)
         self.output_bytecount += w or 0
@@ -245,9 +249,14 @@
         self._socket = socket
         self.local = local
         self.remote = remote
+        self.socket_type = "socket"
         if type(remote)==str:
             self.filename = remote
 
+    def peek(self, n):
+        self._socket.settimeout(None)
+        return self._socket.recv(n, socket.MSG_PEEK)
+
     def read(self, n):
         return self._read(self._socket.recv, n)
 
@@ -263,13 +272,13 @@
 
     def __repr__(self):
         if self.remote:
-            return "%s socket: %s <- %s" % (self.info, pretty_socket(self.local), pretty_socket(self.remote))
-        return "%s socket:%s" % (self.info, pretty_socket(self.local))
+            return "%s %s: %s <- %s" % (self.info, self.socket_type, pretty_socket(self.local), pretty_socket(self.remote))
+        return "%s %s:%s" % (self.info, self.socket_type, pretty_socket(self.local))
 
     def get_info(self):
         d = Connection.get_info(self)
         try:
-            d["type"] = "socket"
+            d["type"] = self.socket_type
             s = self._socket
             if s:
                 d["socket"] = {
Index: xpra/net/websocket.py
===================================================================
--- xpra/net/websocket.py	(revision 0)
+++ xpra/net/websocket.py	(working copy)
@@ -0,0 +1,97 @@
+# This file is part of Xpra.
+# Copyright (C) 2016 Antoine Martin <antoine@devloop.org.uk>
+# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
+# later version. See the file COPYING for details.
+
+import os
+import posixpath
+import urllib
+
+from xpra.log import Logger
+log = Logger("network", "websocket")
+
+from xpra.util import AdHocStruct
+from xpra.net.bytestreams import SocketConnection
+from websockify.websocket import WebSocketRequestHandler
+
+WEBSOCKET_TCP_NODELAY = int(os.environ.get("WEBSOCKET_TCP_NODELAY", "1"))
+WEBSOCKET_TCP_KEEPALIVE = int(os.environ.get("WEBSOCKET_TCP_KEEPALIVE", "1"))
+
+
+
+class WSRequestHandler(WebSocketRequestHandler):
+
+    disable_nagle_algorithm = WEBSOCKET_TCP_NODELAY
+    keep_alive = WEBSOCKET_TCP_KEEPALIVE
+
+    def __init__(self, sock, addr, new_websocket_client):
+        self.web_root = "/usr/share/xpra/www/"
+        server = AdHocStruct()
+        server.logger = log
+        server.run_once = True
+        self._new_websocket_client = new_websocket_client
+        WebSocketRequestHandler.__init__(self, sock, addr, server)
+
+    def new_websocket_client(self):
+        self._new_websocket_client(self)
+
+    def translate_path(self, path):
+        s = path
+        # abandon query parameters
+        path = path.split('?',1)[0]
+        path = path.split('#',1)[0]
+        # Don't forget explicit trailing slash when normalizing. Issue17324
+        trailing_slash = path.rstrip().endswith('/')
+        path = posixpath.normpath(urllib.unquote(path))
+        words = path.split('/')
+        words = filter(None, words)
+        path = self.web_root
+        for word in words:
+            word = os.path.splitdrive(word)[1]
+            word = os.path.split(word)[1]
+            if word in (os.curdir, os.pardir):
+                continue
+            path = os.path.join(path, word)
+        if trailing_slash:
+            path += '/'
+        log("translate_path(%s)=%s", s, path)
+        return path
+
+
+    def log_error(self, fmt, *args):
+        log.error(fmt, *args)
+
+    def log_message(self, fmt, *args):
+        #log.warn("%s", (fmt, args))
+        log(fmt, *args)
+
+    def print_traffic(self, token="."):
+        """ Show traffic flow mode. """
+        if self.traffic:
+            log(token)
+
+
+class WebSocketConnection(SocketConnection):
+
+    def __init__(self, socket, local, remote, target, info, ws_handler):
+        SocketConnection.__init__(self, socket, local, remote, target, info)
+        self.socket_type = "websocket"
+        self.ws_handler = ws_handler
+
+    def read(self, n):
+        while self.is_active():
+            bufs, closed_string = self.ws_handler.recv_frames()
+            if closed_string:
+                self.active = False
+            if len(bufs) == 1:
+                self.input_bytecount += len(bufs[0])
+                return bufs[0]
+            elif len(bufs) > 1:
+                buf = b''.join(bufs)
+                self.input_bytecount += len(buf)
+                return buf
+
+    def write(self, buf):
+        self.ws_handler.send_frames([buf])
+        self.output_bytecount += len(buf)
+        return len(buf)
Index: xpra/platform/win32/shadow_server.py
===================================================================
--- xpra/platform/win32/shadow_server.py	(revision 12661)
+++ xpra/platform/win32/shadow_server.py	(working copy)
@@ -17,6 +17,7 @@
 log = Logger("shadow", "win32")
 traylog = Logger("tray")
 shapelog = Logger("shape")
+netlog = Logger("network")
 
 from xpra.os_util import StringIOClass
 from xpra.server.gtk_server_base import GTKServerBase
@@ -248,7 +249,8 @@
             return GTKServerBase._new_connection(self, listener)
         pipe_handle = args[0]
         conn = NamedPipeConnection(listener.pipe_name, pipe_handle)
-        return self.make_protocol(socktype, conn, frominfo=" on %s" % conn.target)
+        netlog.info("New %s connection received on %s", socktype, conn.target)
+        return self.make_protocol(socktype, conn, frominfo=conn.target)
 
 
     def make_tray_widget(self):
Index: xpra/scripts/server.py
===================================================================
--- xpra/scripts/server.py	(revision 12661)
+++ xpra/scripts/server.py	(working copy)
@@ -510,88 +510,6 @@
     s.close()
     return port
 
-def start_websockify(child_reaper, opts, tcp_sockets):
-    from xpra.log import Logger
-    log = Logger("server")
-    # start websockify?
-    log("html=%s", opts.html)
-    if not opts.html:
-        return
-    html = opts.html
-    if type(html)==str:
-        html = html.lower()
-    from xpra.scripts.config import FALSE_OPTIONS, TRUE_OPTIONS
-    if html in FALSE_OPTIONS:
-        #html disabled
-        return
-    log("tcp_proxy=%s", opts.tcp_proxy)
-    if opts.tcp_proxy:
-        raise InitException("cannot use tcp-proxy mode with html, use one or the other")
-        return 1
-    from xpra.platform.paths import get_resources_dir, get_websockify_command
-    www_dir = os.path.abspath(os.path.join(get_resources_dir(), "www"))
-    if not os.path.exists(www_dir):
-        raise InitException("cannot find xpra's html directory (not found in '%s')" % www_dir)
-    log("www_dir=%s", www_dir)
-    import websockify           #@UnresolvedImport
-    log("websockify=%s", websockify)
-    assert websockify
-    html_port = -1
-    html_host = "127.0.0.1"
-    if html not in TRUE_OPTIONS:
-        #we expect either HOST:PORT, or just PORT
-        if html.find(":")>=0:
-            html_host, html = html.split(":", 1)
-        try:
-            html_port = int(html)
-        except Exception as e:
-            raise InitException("invalid html port: %s" % e)
-        #we now have the host and port (port may be -1 to mean auto..)
-    if html_port==-1:
-        #try to find a free port and hope that websockify can then use it..
-        html_port = get_free_tcp_port()
-    elif os.name=="posix" and html_port<1024 and os.geteuid()!=0:
-        log.warn("Warning: the html port specified may require special privileges (%s:%s)", html_host, html_port)
-    if len(tcp_sockets)<1:
-        raise InitException("html web server requires at least one tcp socket, see 'bind-tcp'")
-    #use the first tcp socket for websockify to talk back to us:
-    _, xpra_tcp_port = list(tcp_sockets)[0]
-    kwargs = {}
-    if sys.platform.startswith("win"):
-        #this is a "DOS" command, but we want to hide the shell window
-        startupinfo = subprocess.STARTUPINFO()
-        startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
-        kwargs = {"startupinfo" : startupinfo}
-    #add bundler bin directories to path (for OSX):
-    websockify_command = get_websockify_command() + ["--web", www_dir, "%s:%s" % (html_host, html_port), "127.0.0.1:%s" % xpra_tcp_port]
-    log("websockify_command: %s, kwargs=%s, sys.path=%s", websockify_command, kwargs, sys.path)
-    websockify_proc = subprocess.Popen(websockify_command, close_fds=True, **kwargs)
-    websockify_proc._closed = False
-    start_time = time.time()
-    def websockify_ended(proc):
-        elapsed = time.time()-start_time
-        log("websockify_ended(%s) after %i seconds", proc, elapsed)
-        if not websockify_proc._closed:
-            log.warn("Warning: websockify has terminated,")
-            log.warn(" the html web server will not be available.")
-            log.warn(" the command used was:")
-            log.warn(" %s", " ".join(websockify_command))
-        return False
-    child_reaper.add_process(websockify_proc, "websockify", websockify_command, ignore=True, callback=websockify_ended)
-    log.info("websockify started, serving %s on %s:%s", www_dir, html_host, html_port)
-    def cleanup_websockify():
-        log("cleanup_websockify() process.poll()=%s, pid=%s", websockify_proc.poll(), websockify_proc.pid)
-        if websockify_proc.poll() is None and not websockify_proc._closed:
-            log.info("stopping websockify with pid %s", websockify_proc.pid)
-            try:
-                websockify_proc._closed = True
-                websockify_proc.terminate()
-            except:
-                log.warn("error trying to stop websockify", exc_info=True)
-    _cleanups.append(cleanup_websockify)
-    opts.tcp_proxy = "%s:%s" % (html_host, html_port)
-
-
 def close_all_fds(exceptions=[]):
     fd_dirs = ["/dev/fd", "/proc/self/fd"]
     for fd_dir in fd_dirs:
@@ -1303,13 +1221,6 @@
 
     #honour start child, html webserver, and setup child reaper
     if not proxying and not upgrading:
-        # start websockify?
-        try:
-            start_websockify(app.child_reaper, opts, bind_tcp)
-            #websockify overrides the tcp proxy, so we must re-set it:
-            app._tcp_proxy = opts.tcp_proxy
-        except Exception as e:
-            error_cb("failed to setup websockify html server: %s" % e)
         if opts.exit_with_children:
             assert opts.start_child, "exit-with-children was specified but start-child is missing!"
         app.start_commands              = opts.start
Index: xpra/server/server_core.py
===================================================================
--- xpra/server/server_core.py	(revision 12661)
+++ xpra/server/server_core.py	(working copy)
@@ -28,10 +28,10 @@
 from xpra.server import ClientException
 from xpra.scripts.main import SOCKET_TIMEOUT, _socket_connect
 from xpra.scripts.server import deadly_signal
-from xpra.scripts.config import InitException
+from xpra.scripts.config import InitException, parse_bool
 from xpra.net.bytestreams import SocketConnection, pretty_socket, set_socket_timeout
 from xpra.platform import set_name
-from xpra.os_util import load_binary_file, get_machine_id, get_user_uuid, platform_name, SIGNAMES, Queue
+from xpra.os_util import load_binary_file, get_machine_id, get_user_uuid, platform_name, SIGNAMES
 from xpra.version_util import version_compat_check, get_version_info_full, get_platform_info, get_host_info, local_version
 from xpra.net.protocol import Protocol, get_network_caps, sanity_checks
 from xpra.net.crypto import crypto_backend_init, new_cipher_caps, \
@@ -40,7 +40,7 @@
 from xpra.make_thread import make_thread
 from xpra.scripts.fdproxy import XpraProxy
 from xpra.server.control_command import ControlError, HelloCommand, HelpCommand, DebugControl
-from xpra.util import csv, merge_dicts, typedict, notypedict, flatten_dict, parse_simple_dict, repr_ellipsized, dump_all_frames, \
+from xpra.util import csv, merge_dicts, typedict, notypedict, flatten_dict, parse_simple_dict, repr_ellipsized, dump_all_frames, nonl, \
         SERVER_SHUTDOWN, SERVER_UPGRADE, LOGIN_TIMEOUT, DONE, PROTOCOL_ERROR, SERVER_ERROR, VERSION_ERROR, CLIENT_REQUEST
 
 main_thread = threading.current_thread()
@@ -147,6 +147,7 @@
         self._potential_protocols = []
         self._tcp_proxy_clients = []
         self._tcp_proxy = ""
+        self._html = False
         self._aliases = {}
         self._reverse_aliases = {}
         self.socket_types = {}
@@ -191,6 +192,19 @@
         self.unix_socket_paths = []
         self._socket_dir = opts.socket_dir or opts.socket_dirs[0]
         self._tcp_proxy = opts.tcp_proxy
+        self._html = parse_bool("html", opts.html)
+        if self._html:
+            try:
+                from xpra.net.websocket import WebSocketConnection
+                assert WebSocketConnection
+            except ImportError as e:
+                log.error("Error: cannot import websockify connection handler:")
+                log.error(" %s", e)
+                log.error(" the html server will not be available")
+                self._html = False
+            if self._html and self._tcp_proxy:
+                log.warn("Warning: the built in html server is enabled,")
+                log.warn(" disabling the tcp-proxy option")
         self.encryption = opts.encryption
         self.encryption_keyfile = opts.encryption_keyfile
         self.tcp_encryption = opts.tcp_encryption
@@ -472,15 +486,44 @@
         netlog("new_connection(%s) sock=%s, timeout=%s, sockname=%s, address=%s, peername=%s", args, sock, self._socket_timeout, sockname, address, peername)
         conn = SocketConnection(sock, sockname, address, target, socktype)
         netlog("socket connection: %s", conn)
-        frominfo = ""
         if peername:
-            frominfo = " from %s" % pretty_socket(peername)
+            frominfo = pretty_socket(peername)
+            info_msg = "New %s connection received from %s" % (socktype, frominfo)
         elif socktype=="unix-domain":
-            frominfo = " on %s" % sockname
+            frominfo = sockname
+            info_msg = "New %s connection received on %s" % (socktype, frominfo)
+        else:
+            frominfo = ""
+            info_msg = "New %s connection received"
+
+        if socktype=="tcp" and self._html or self._tcp_proxy:
+            #see if the packet data is actually xpra or something else
+            #that we need to handle via a tcp proxy or the websockify adapter:
+            conn._socket.settimeout(5)
+            v = conn.peek(128)
+            netlog("peek()=%s", nonl(v))
+            if v and v[0] not in ("P", ord("P")):
+                if self._html:
+                    line1 = v.splitlines()[0]
+                    if line1.find("HTTP/")>0:
+                        log.info("New HTTP connection received from %s", frominfo)
+                        def run_websockify():
+                            self.start_websockify(conn, frominfo)
+                        make_thread(run_websockify, "websockify-proxy-for-%s" % frominfo, daemon=True).start()
+                        return True
+                elif self._tcp_proxy:
+                    log.info("New TCP proxy connection received from %s", frominfo)
+                    def run_proxy():
+                        self.start_tcp_proxy(conn, frominfo)
+                    make_thread(run_proxy, "tcp-proxy-for-%s" % frominfo, daemon=True).start()
+                    return True
+                netlog("")
+        #FIXME: if we have peek data and it isn't an xpra client,
+        #we can bail out early without making a protocol
+        netlog.info(info_msg)
         return self.make_protocol(socktype, conn, frominfo)
 
     def make_protocol(self, socktype, conn, frominfo=""):
-        netlog.info("New %s connection received%s", socktype, frominfo)
         protocol = Protocol(self, conn, self.process_packet)
         self._potential_protocols.append(protocol)
         protocol.large_packets.append("info-response")
@@ -509,65 +552,63 @@
         self.timeout_add(SOCKET_TIMEOUT*1000, self.verify_connection_accepted, protocol)
         return True
 
-
     def invalid_header(self, proto, data):
-        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)
-        if proto.input_packetcount==0 and self._tcp_proxy and not proto._closed:
-            #start a new proxy in a thread
-            def run_proxy():
-                self.start_tcp_proxy(proto, data)
-            make_thread(run_proxy, "web-proxy-for-%s" % proto, daemon=True).start()
-            return
+        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)
         err = "invalid packet format, not an xpra client?"
         proto.gibberish(err, data)
 
-    def start_tcp_proxy(self, proto, data):
-        proxylog("start_tcp_proxy(%s, '%s')", proto, repr_ellipsized(data))
+
+    def start_websockify(self, conn, frominfo):
+        log("start_websockify(%s, %s)", conn, frominfo)
+        from xpra.net.websocket import WebSocketConnection, WSRequestHandler
         try:
-            self._potential_protocols.remove(proto)
-        except:
-            pass        #might already have been removed by now
-        proxylog("start_tcp_proxy: protocol state before stealing: %s", proto.get_info(alias_info=False))
-        #any buffers read after we steal the connection will be placed in this temporary queue:
-        temp_read_buffer = Queue()
-        client_connection = proto.steal_connection(temp_read_buffer.put)
+            def new_websocket_client(wsh):
+                log("new_websocket_client(%s)", wsh)
+                wsc = WebSocketConnection(conn._socket, conn.local, conn.remote, conn.target, conn.info, wsh)
+                set_socket_timeout(conn, self._socket_timeout)
+                self.make_protocol("tcp", wsc, frominfo)
+            WSRequestHandler(conn._socket, frominfo, new_websocket_client)
+            return
+        except IOError as e:
+            netlog.error("Error: http failure responding to %s:", frominfo)
+            netlog.error(" %s", e)
+            netlog("", exc_info=True)
+        except Exception as e:
+            netlog.error("Error: http failure responding to %s:", frominfo, exc_info=True)
+        try:
+            conn.close()
+        except Exception as ce:
+            netlog("error closing connection following error: %s", ce)
+
+    def start_tcp_proxy(self, conn, frominfo):
+        proxylog("start_tcp_proxy(%s, %s)", conn, frominfo)
         #connect to web server:
-        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        sock.settimeout(10)
         host, port = self._tcp_proxy.split(":", 1)
         try:
-            web_server_connection = _socket_connect(sock, (host, int(port)), "web-proxy-for-%s" % proto, "tcp")
+            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            sock.settimeout(10)
+            host, port = self._tcp_proxy.split(":", 1)
+            tcp_server_connection = _socket_connect(sock, (host, int(port)), "web-proxy-for-%s" % frominfo, "tcp")
         except:
             proxylog.warn("failed to connect to proxy: %s:%s", host, port)
-            proto.gibberish("invalid packet header", data)
+            conn.close()
             return
-        proxylog("proxy connected to tcp server at %s:%s : %s", host, port, web_server_connection)
+        proxylog("proxy connected to tcp server at %s:%s : %s", host, port, tcp_server_connection)
+        sock = tcp_server_connection._socket
         sock.settimeout(self._socket_timeout)
 
-        ioe = proto.wait_for_io_threads_exit(0.5+self._socket_timeout)
-        if not ioe:
-            proxylog.warn("proxy failed to stop all existing network threads!")
-            self.disconnect_protocol(proto, "internal threading error")
-            return
-        #now that we own it, we can start it again:
-        client_connection.set_active(True)
-        #and we can use blocking sockets:
-        set_socket_timeout(client_connection, None)
-        #prevent deadlocks on exit:
+        #we can use blocking sockets for the client:
+        conn.settimeout(None)
+        #but not for the server, which could deadlock on exit:
         sock.settimeout(1)
 
-        proxylog("pushing initial buffer to its new destination: %s", repr_ellipsized(data))
-        web_server_connection.write(data)
-        while not temp_read_buffer.empty():
-            buf = temp_read_buffer.get()
-            if buf:
-                proxylog("pushing read buffer to its new destination: %s", repr_ellipsized(buf))
-                web_server_connection.write(buf)
-        p = XpraProxy(client_connection.target, client_connection, web_server_connection, self.tcp_proxy_quit)
+        #now start forwarding:
+        p = XpraProxy(frominfo, conn, tcp_server_connection, self.tcp_proxy_quit)
         self._tcp_proxy_clients.append(p)
-        proxylog.info("client connection from %s forwarded to proxy server on %s:%s", client_connection.target, host, port)
+        proxylog.info("client connection from %s forwarded to proxy server on %s:%s", frominfo, host, port)
         p.start_threads()
 
+
     def tcp_proxy_quit(self, proxy):
         proxylog("tcp_proxy_quit(%s)", proxy)
         if proxy in self._tcp_proxy_clients:
