]> git.proxmox.com Git - mirror_novnc.git/commitdiff
wsproxy.py: add web serving capability.
authorJoel Martin <github@martintribe.org>
Fri, 7 Jan 2011 00:26:54 +0000 (18:26 -0600)
committerJoel Martin <github@martintribe.org>
Fri, 7 Jan 2011 00:26:54 +0000 (18:26 -0600)
- Added ability to respond to normal web requests. This is basically
  integrating web.py functionality into wsproxy. This is only in the
  python version and it is off by default when calling wsproxy. Turn
  it on with --web DIR where DIR is the web root directory.

Next task is to clean up wsproxy.py. It's gotten unwieldy and it
really no longer needs to be parallel to the C version.

README.md
utils/launch.sh
utils/websocket.py
utils/wsproxy.py

index 4d1b1e5173bb37941356f3c218e17c6d82c9d3e5..ead2abd6d88cd857d0fbdf17c5b87b2991ca0e0d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -137,7 +137,7 @@ There a few reasons why a proxy is required:
 
     `./utils/wsproxy.py -f 8787 localhost:5901`
 
-* To run the mini python web server without the launch script:
+* To run a mini python web server without the launch script:
 
     `./utils/web.py PORT`
 
index 1ee7d15b502f2485d52bdb79754f5940803c669c..4ed9f92d467239caac7d6dedca36442c5bc99645 100755 (executable)
@@ -5,26 +5,25 @@ usage() {
         echo "$*"
         echo
     fi
-    echo "Usage: ${NAME} [--web WEB_PORT] [--proxy PROXY_PORT] [--vnc VNC_HOST:PORT]"
+    echo "Usage: ${NAME} [--listen PORT] [--vnc VNC_HOST:PORT] [--cert CERT]"
     echo
     echo "Starts a mini-webserver and the WebSockets proxy and"
     echo "provides a cut and paste URL to go to."
     echo 
-    echo "    --web WEB_PORT        Port to serve web pages at"
-    echo "                          Default: 8080"
-    echo "    --proxy PROXY_PORT    Port for proxy to listen on"
-    echo "                          Default: 8081"
+    echo "    --listen PORT         Port for webserver/proxy to listen on"
+    echo "                          Default: 6080"
     echo "    --vnc VNC_HOST:PORT   VNC server host:port proxy target"
     echo "                          Default: localhost:5900"
+    echo "    --cert CERT           Path to combined cert/key file"
+    echo "                          Default: self.pem"
     exit 2
 }
 
 NAME="$(basename $0)"
 HERE="$(cd "$(dirname "$0")" && pwd)"
-WEB_PORT="6080"
-PROXY_PORT="6081"
+PORT="6080"
 VNC_DEST="localhost:5900"
-web_pid=""
+CERT=""
 proxy_pid=""
 
 die() {
@@ -36,10 +35,6 @@ cleanup() {
     trap - TERM QUIT INT EXIT
     trap "true" CHLD   # Ignore cleanup messages
     echo
-    if [ -n "${web_pid}" ]; then
-        echo "Terminating webserver (${web_pid})"
-        kill ${web_pid}
-    fi
     if [ -n "${proxy_pid}" ]; then
         echo "Terminating WebSockets proxy (${proxy_pid})"
         kill ${proxy_pid}
@@ -52,12 +47,12 @@ cleanup() {
 while [ "$*" ]; do
     param=$1; shift; OPTARG=$1
     case $param in
-    --web)   WEB_PORT="${OPTARG}"; shift          ;;
-    --proxy) PROXY_PORT="${OPTARG}"; shift        ;;
-    --vnc)   VNC_DEST="${OPTARG}"; shift          ;;
-    -h|--help) usage ;;
+    --listen)  PORT="${OPTARG}"; shift            ;;
+    --vnc)     VNC_DEST="${OPTARG}"; shift        ;;
+    --cert)    CERT="${OPTARG}"; shift            ;;
+    -h|--help) usage                              ;;
     -*) usage "Unknown chrooter option: ${param}" ;;
-    *) break ;;
+    *) break                                      ;;
     esac
 done
 
@@ -65,40 +60,39 @@ done
 which netstat >/dev/null 2>&1 \
     || die "Must have netstat installed"
 
-netstat -ltn | grep -qs "${WEB_PORT}.*LISTEN" \
-    && die "Port ${WEB_PORT} in use. Try --web WEB_PORT"
-
-netstat -ltn | grep -qs "${PROXY_PORT}.*LISTEN" \
-    && die "Port ${PROXY_PORT} in use. Try --proxy PROXY_PORT"
+netstat -ltn | grep -qs "${PORT}.*LISTEN" \
+    && die "Port ${PORT} in use. Try --listen PORT"
 
 trap "cleanup" TERM QUIT INT EXIT
 
 # Find vnc.html
 if [ -e "$(pwd)/vnc.html" ]; then
-    TOP=$(pwd)
+    WEB=$(pwd)
 elif [ -e "${HERE}/../vnc.html" ]; then
-    TOP=${HERE}/../
+    WEB=${HERE}/../
 elif [ -e "${HERE}/vnc.html" ]; then
-    TOP=${HERE}
+    WEB=${HERE}
 else
     die "Could not find vnc.html"
 fi
-cd ${TOP}
 
-echo "Starting webserver on port ${WEB_PORT}"
-${HERE}/web.py ${WEB_PORT} >/dev/null &
-web_pid="$!"
-sleep 1
-if ps -p ${web_pid} >/dev/null; then
-    echo "Started webserver (pid: ${web_pid})"
+# Find self.pem
+if [ -n "${CERT}" ]; then
+    if [ ! -e "${CERT}" ]; then
+        die "Could not find ${CERT}"
+    fi
+elif [ -e "$(pwd)/self.pem" ]; then
+    CERT="$(pwd)/self.pem"
+elif [ -e "${HERE}/../self.pem" ]; then
+    CERT="${HERE}/../self.pem"
+elif [ -e "${HERE}/self.pem" ]; then
+    CERT="${HERE}/self.pem"
 else
-    web_pid=
-    echo "Failed to start webserver"
-    exit 1
+    echo "Warning: could not find self.pem"
 fi
 
-echo "Starting WebSockets proxy on port ${PROXY_PORT}"
-${HERE}/wsproxy.py -f ${PROXY_PORT} ${VNC_DEST} &
+echo "Starting webserver and WebSockets proxy on port ${PORT}"
+${HERE}/wsproxy.py -f --web ${WEB} ${CERT:+--cert ${CERT}} ${PORT} ${VNC_DEST} &
 proxy_pid="$!"
 sleep 1
 if ps -p ${proxy_pid} >/dev/null; then
@@ -110,8 +104,7 @@ else
 fi
 
 echo -e "\n\nNavigate to to this URL:\n"
-echo -e "    http://$(hostname):${WEB_PORT}/vnc.html?host=$(hostname)&port=${PROXY_PORT}\n"
+echo -e "    http://$(hostname):${PORT}/vnc.html?host=$(hostname)&port=${PORT}\n"
 echo -e "Press Ctrl-C to exit\n\n"
 
-wait ${web_pid}
-
+wait ${proxy_pid}
index 70748c1de910c56e9572c23b32fa257a7bef51a8..7efd01a3e7d32c477d3846d629146158701e417e 100755 (executable)
@@ -13,6 +13,8 @@ as taken from http://docs.python.org/dev/library/ssl.html#certificates
 
 import sys, socket, ssl, struct, traceback
 import os, resource, errno, signal # daemonizing
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+from cStringIO import StringIO
 from base64 import b64encode, b64decode
 try:
     from hashlib import md5
@@ -31,7 +33,8 @@ settings = {
     'key'         : None,
     'ssl_only'    : False,
     'daemon'      : True,
-    'record'      : None, }
+    'record'      : None,
+    'web'         : False, }
 
 server_handshake = """HTTP/1.1 101 Web Socket Protocol Handshake\r
 Upgrade: WebSocket\r
@@ -47,6 +50,29 @@ policy_response = """<cross-domain-policy><allow-access-from domain="*" to-ports
 class EClose(Exception):
     pass
 
+# HTTP handler with request from a string and response to a socket
+class SplitHTTPHandler(SimpleHTTPRequestHandler):
+    def __init__(self, req, resp, addr):
+        # Save the response socket
+        self.response = resp
+        SimpleHTTPRequestHandler.__init__(self, req, addr, object())
+
+    def setup(self):
+        self.connection = self.response
+        # Duck type request string to file object
+        self.rfile = StringIO(self.request)
+        self.wfile = self.connection.makefile('wb', self.wbufsize)
+
+    def send_response(self, code, message=None):
+        # Save the status code
+        self.last_code = code
+        SimpleHTTPRequestHandler.send_response(self, code, message)
+
+    def log_message(self, f, *args):
+        # Save instead of printing
+        self.last_message = f % args
+
+
 def traffic(token="."):
     if settings['verbose'] and not settings['daemon']:
         sys.stdout.write(token)
@@ -54,7 +80,10 @@ def traffic(token="."):
 
 def handler_msg(msg):
     if not settings['daemon']:
-        print "  %d: %s" % (settings['handler_id'], msg)
+        print "% 3d: %s" % (settings['handler_id'], msg)
+
+def handler_vmsg(msg):
+    if settings['verbose']: handler_msg(msg)
 
 def encode(buf):
     buf = b64encode(buf)
@@ -96,56 +125,77 @@ def gen_md5(keys):
     return md5(struct.pack('>II8s', num1, num2, key3)).digest()
 
 
-def do_handshake(sock):
+def do_handshake(sock, address):
+    stype = ""
 
     # Peek, but don't read the data
     handshake = sock.recv(1024, socket.MSG_PEEK)
     #handler_msg("Handshake [%s]" % repr(handshake))
     if handshake == "":
-        handler_msg("ignoring empty handshake")
-        sock.close()
-        return False
+        raise EClose("ignoring empty handshake")
     elif handshake.startswith("<policy-file-request/>"):
         handshake = sock.recv(1024)
-        handler_msg("Sending flash policy response")
         sock.send(policy_response)
-        sock.close()
-        return False
+        raise EClose("Sending flash policy response")
     elif handshake[0] in ("\x16", "\x80"):
         if not os.path.exists(settings['cert']):
-            handler_msg("SSL connection but '%s' not found"
-                    % settings['cert'])
-            sock.close()
-            return False
-        retsock = ssl.wrap_socket(
-                sock,
-                server_side=True,
-                certfile=settings['cert'],
-                keyfile=settings['key'])
+            raise EClose("SSL connection but '%s' not found"
+                         % settings['cert'])
+        try:
+            retsock = ssl.wrap_socket(
+                    sock,
+                    server_side=True,
+                    certfile=settings['cert'],
+                    keyfile=settings['key'])
+        except ssl.SSLError, x:
+            if x.args[0] == ssl.SSL_ERROR_EOF:
+                raise EClose("")
+            else:
+                raise
+
         scheme = "wss"
-        handler_msg("using SSL/TLS")
+        stype = "SSL/TLS (wss://)"
     elif settings['ssl_only']:
-        handler_msg("non-SSL connection disallowed")
-        sock.close()
-        return False
+        raise EClose("non-SSL connection received but disallowed")
     else:
         retsock = sock
         scheme = "ws"
-        handler_msg("using plain (not SSL) socket")
+        stype = "Plain non-SSL (ws://)"
+
+    # Now get the data from the socket
     handshake = retsock.recv(4096)
     #handler_msg("handshake: " + repr(handshake))
+
     if len(handshake) == 0:
         raise EClose("Client closed during handshake")
+
+    # Handle normal web requests
+    if handshake.startswith('GET ') and \
+        handshake.find('Upgrade: WebSocket\r\n') == -1:
+        if not settings['web']:
+            raise EClose("Normal web request received but disallowed")
+        sh = SplitHTTPHandler(handshake, retsock, address)
+        if sh.last_code < 200 or sh.last_code >= 300:
+            raise EClose(sh.last_message)
+        elif settings['verbose']:
+            raise EClose(sh.last_message)
+        else:
+            raise EClose("")
+
+    # Do WebSockets handshake and return the socket
     h = parse_handshake(handshake)
 
     if h.get('key3'):
         trailer = gen_md5(h)
         pre = "Sec-"
-        handler_msg("using protocol version 76")
+        ver = 76
     else:
         trailer = ""
         pre = ""
-        handler_msg("using protocol version 75")
+        ver = 75
+
+    handler_msg("%s WebSocket connection (version %s) from %s"
+                % (stype, ver, address[0]))
 
     response = server_handshake % (pre, h['Origin'], pre, scheme,
             h['Host'], h['path'], pre, trailer)
@@ -177,8 +227,8 @@ def daemonize(keepfd=None):
         try:
             if fd != keepfd:
                 os.close(fd)
-            elif settings['verbose']:
-                print "Keeping fd: %d" % fd
+            else:
+                handler_vmsg("Keeping fd: %d" % fd)
         except OSError, exc:
             if exc.errno != errno.EBADF: raise
 
@@ -209,25 +259,25 @@ def start_server():
             csock = startsock = None
             pid = 0
             startsock, address = lsock.accept()
-            handler_msg('got client connection from %s' % address[0])
-
-            handler_msg("forking handler process")
+            handler_vmsg('%s: forking handler' % address[0])
             pid = os.fork()
 
             if pid == 0:  # handler process
-                csock = do_handshake(startsock)
-                if not csock:
-                    handler_msg("No connection after handshake");
-                    break
+                csock = do_handshake(startsock, address)
                 settings['handler'](csock)
             else:         # parent process
                 settings['handler_id'] += 1
 
         except EClose, exc:
-            handler_msg("handler exit: %s" % exc.args)
+            if csock and csock != startsock:
+                csock.close()
+            startsock.close()
+            if exc.args[0]:
+                handler_msg("%s: %s" % (address[0], exc.args[0]))
         except Exception, exc:
             handler_msg("handler exception: %s" % str(exc))
-            #handler_msg(traceback.format_exc())
+            if settings['verbose']:
+                handler_msg(traceback.format_exc())
 
         if pid == 0:
             if csock: csock.close()
index 378e47436bf8e81f1e4d6aaec0a2ff068c4be08b..32a4021e073d92afb6824b84d922192a3e67a979 100755 (executable)
@@ -143,6 +143,8 @@ if __name__ == '__main__':
             help="SSL key file (if separate from cert)")
     parser.add_option("--ssl-only", action="store_true",
             help="disallow non-encrypted connections")
+    parser.add_option("--web", default=None, metavar="DIR",
+            help="run webserver on same port. Serve files from DIR.")
     (options, args) = parser.parse_args()
 
     if len(args) > 2: parser.error("Too many arguments")
@@ -176,4 +178,7 @@ if __name__ == '__main__':
     settings['daemon'] = options.daemon
     if options.record:
         settings['record'] = os.path.abspath(options.record)
+    if options.web:
+        os.chdir = options.web
+        settings['web'] = options.web
     start_server()