break client connect into states and apply timeout
authorAndy Green <andy@warmcat.com>
Mon, 14 Feb 2011 20:25:43 +0000 (20:25 +0000)
committerAndy Green <andy@warmcat.com>
Mon, 14 Feb 2011 20:25:43 +0000 (20:25 +0000)
Doing a client connect was atomic until now, blocking
all the other service while it waited for proxy and / or
server response.

This patch uses the new timeout system and breaks the
client connect sequence into three states handled by
the normal poll() processing.  It means that there are
now no blocking network delays and it's all handled
by the main state machine.

Signed-off-by: Andy Green <andy@warmcat.com>
lib/client-handshake.c
lib/libwebsockets.c
lib/private-libwebsockets.h
libwebsockets-api-doc.html

index c6f8ba3..56c4e94 100644 (file)
@@ -2,20 +2,6 @@
 #include <netdb.h>
 
 
-/*
- * In-place str to lower case
- */
-
-void
-strtolower(char *s)
-{
-       while (*s) {
-               *s = tolower(*s);
-               s++;
-       }
-}
-
-
 /**
  * libwebsocket_client_connect() - Connect to another websocket server
  * @this:      Websocket context
@@ -48,25 +34,12 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
 {
        struct hostent *server_hostent;
        struct sockaddr_in server_addr;
-       char buf[150];
-       char key_b64[150];
-       char hash[20];
+       char pkt[512];
        struct pollfd pfd;
-       static const char magic_websocket_guid[] =
-                                        "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-       static const char magic_websocket_04_masking_guid[] =
-                                        "61AC5F19-FBBA-4540-B96F-6561F1AB40A8";
-       char pkt[1024];
-       char *p = &pkt[0];
-       const char *pc;
-       int len;
-       int okay = 0;
        struct libwebsocket *wsi;
        int n;
        int plen = 0;
-#ifdef LWS_OPENSSL_SUPPORT
-       char ssl_err_buf[512];
-#else
+#ifndef LWS_OPENSSL_SUPPORT
        if (ssl_connection) {
                fprintf(stderr, "libwebsockets not configured for ssl\n");
                return NULL;
@@ -74,10 +47,8 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
 #endif
 
        wsi = malloc(sizeof(struct libwebsocket));
-       if (wsi == NULL) {
-               fprintf(stderr, "Out of memory allocing new connection\n");
-               return NULL;
-       }
+       if (wsi == NULL)
+               goto bail1;
 
        memset(wsi, 0, sizeof *wsi);
 
@@ -93,6 +64,32 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
        wsi->pings_vs_pongs = 0;
        wsi->protocol = NULL;
        wsi->pending_timeout = NO_PENDING_TIMEOUT;
+#ifdef LWS_OPENSSL_SUPPORT
+       wsi->use_ssl = ssl_connection;
+#endif
+
+       /* copy parameters over so state machine has access */
+
+       wsi->c_path = malloc(strlen(path) + 1);
+       if (wsi->c_path == NULL)
+               goto bail1;
+       strcpy(wsi->c_path, path);
+       wsi->c_host = malloc(strlen(host) + 1);
+       if (wsi->c_host == NULL)
+               goto oom1;
+       strcpy(wsi->c_host, host);
+       wsi->c_origin = malloc(strlen(origin) + 1);
+       if (wsi->c_origin == NULL)
+               goto oom2;
+       strcpy(wsi->c_origin, origin);
+       if (protocol) {
+               wsi->c_protocol = malloc(strlen(protocol) + 1);
+               if (wsi->c_protocol == NULL)
+                       goto oom3;
+               strcpy(wsi->c_protocol, protocol);
+       } else
+               wsi->c_protocol = NULL;
+
 
        /* set up appropriate masking */
 
@@ -109,7 +106,7 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
                fprintf(stderr,
                        "Client ietf version %d not supported\n",
                                                       wsi->ietf_spec_revision);
-               return NULL;
+               goto oom4;
        }
 
        /* force no mask if he asks for that though */
@@ -145,18 +142,16 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
        server_hostent = gethostbyname(address);
        if (server_hostent == NULL) {
                fprintf(stderr, "Unable to get host name from %s\n", address);
-               goto bail1;
+               goto oom4;
        }
 
        wsi->sock = socket(AF_INET, SOCK_STREAM, 0);
 
        if (wsi->sock < 0) {
                fprintf(stderr, "Unable to open socket\n");
-               goto bail1;
+               goto oom4;
        }
 
-       insert_wsi(this, wsi);
-
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port);
        server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr);
@@ -165,9 +160,24 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
        if (connect(wsi->sock, (struct sockaddr *)&server_addr,
                                              sizeof(struct sockaddr)) == -1)  {
                fprintf(stderr, "Connect failed\n");
-               goto bail1;
+               goto oom4;
        }
 
+       /* into fd -> wsi hashtable */
+
+       insert_wsi(this, wsi);
+
+       /* into internal poll list */
+
+       this->fds[this->fds_count].fd = wsi->sock;
+       this->fds[this->fds_count].revents = 0;
+       this->fds[this->fds_count++].events = POLLIN;
+
+       /* external POLL support via protocol 0 */
+       this->protocols[0].callback(this, wsi,
+               LWS_CALLBACK_ADD_POLL_FD,
+               (void *)(long)wsi->sock, NULL, POLLIN);
+
        /* we are connected to server, or proxy */
 
        if (this->http_proxy_port) {
@@ -179,349 +189,41 @@ libwebsocket_client_connect(struct libwebsocket_context *this,
                        goto bail1;
                }
 
-               pfd.fd = wsi->sock;
-               pfd.events = POLLIN;
-               pfd.revents = 0;
+               libwebsocket_set_timeout(wsi,
+                       PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, 5);
 
-               n = poll(&pfd, 1, 5000);
-               if (n <= 0) {
-                       close(wsi->sock);
-                       fprintf(stderr, "libwebsocket_client_handshake "
-                                       "timeout on proxy response");
-                       goto bail1;
-               }
-
-               n = recv(wsi->sock, pkt, sizeof pkt, 0);
-               if (n < 0) {
-                       close(wsi->sock);
-                       fprintf(stderr, "ERROR reading from proxy socket\n");
-                       goto bail1;
-               }
-
-               pkt[13] = '\0';
-               if (strcmp(pkt, "HTTP/1.0 200 ") != 0) {
-                       close(wsi->sock);
-                       fprintf(stderr, "ERROR from proxy: %s\n", pkt);
-                       goto bail1;
-               }
-
-               /* we can just start sending to proxy */
-       }
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if (ssl_connection) {
-
-               wsi->ssl = SSL_new(this->ssl_client_ctx);
-               wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE);
-               SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
-
-               if (SSL_connect(wsi->ssl) <= 0) {
-                       fprintf(stderr, "SSL connect error %s\n",
-                               ERR_error_string(ERR_get_error(), ssl_err_buf));
-                       goto bail1;
-               }
+               wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY;
 
-               n = SSL_get_verify_result(wsi->ssl);
-               if (n != X509_V_OK) {
-                       if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
-                                                         ssl_connection != 2) {
-
-                               fprintf(stderr, "server's cert didn't "
-                                                          "look good %d\n", n);
-                               goto bail2;
-                       }
-               }
-       } else {
-               wsi->ssl = NULL;
-#endif
-
-
-#ifdef LWS_OPENSSL_SUPPORT
+               return wsi;
        }
-#endif
 
        /*
-        * create the random key
+        * provoke service to issue the handshake directly
+        * we need to do it this way because in the proxy case, this is the
+        * next state and executed only if and when we get a good proxy
+        * response inside the state machine
         */
 
-       n = read(this->fd_random, hash, 16);
-       if (n != 16) {
-               fprintf(stderr, "Unable to read from random device %s\n",
-                                                       SYSTEM_RANDOM_FILEPATH);
-               goto bail2;
-       }
-
-       lws_b64_encode_string(hash, 16, key_b64, sizeof key_b64);
-
-       /*
-        * 04 example client handshake
-        *
-        * GET /chat HTTP/1.1
-        * Host: server.example.com
-        * Upgrade: websocket
-        * Connection: Upgrade
-        * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
-        * Sec-WebSocket-Origin: http://example.com
-        * Sec-WebSocket-Protocol: chat, superchat
-        * Sec-WebSocket-Version: 4
-        */
-
-        p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", path);
-        p += sprintf(p, "Host: %s\x0d\x0a", host);
-        p += sprintf(p, "Upgrade: websocket\x0d\x0a");
-        p += sprintf(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Key: ");
-        strcpy(p, key_b64);
-        p += strlen(key_b64);
-        p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", origin);
-        if (protocol != NULL)
-               p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", protocol);
-        p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a",
-                                                      wsi->ietf_spec_revision);
-
-
-       /* prepare the expected server accept response */
-
-       strcpy(buf, key_b64);
-       strcpy(&buf[strlen(buf)], magic_websocket_guid);
-
-       SHA1((unsigned char *)buf, strlen(buf), (unsigned char *)hash);
-
-       lws_b64_encode_string(hash, 20, wsi->initial_handshake_hash_base64,
-                                 sizeof wsi->initial_handshake_hash_base64);
-
-       /* send our request to the server */
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if (ssl_connection)
-               n = SSL_write(wsi->ssl, pkt, p - pkt);
-       else
-#endif
-               n = send(wsi->sock, pkt, p - pkt, 0);
-
-       if (n < 0) {
-               fprintf(stderr, "ERROR writing to client socket\n");
-               goto bail2;
-       }
-
-       wsi->parser_state = WSI_TOKEN_NAME_PART;
-
+       wsi->mode = LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE;
        pfd.fd = wsi->sock;
-       pfd.events = POLLIN;
-       pfd.revents = 0;
-
-       n = poll(&pfd, 1, 5000);
-       if (n < 0) {
-               fprintf(stderr, "libwebsocket_client_handshake socket error "
-                               "while waiting for handshake response");
-               goto bail2;
-       }
-       if (n == 0) {
-               fprintf(stderr, "libwebsocket_client_handshake timeout "
-                               "while waiting for handshake response");
-               goto bail2;
-       }
-
-       /* interpret the server response */
-
-       /*
-        *  HTTP/1.1 101 Switching Protocols
-        *  Upgrade: websocket
-        *  Connection: Upgrade
-        *  Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
-        *  Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
-        *  Sec-WebSocket-Protocol: chat
-        */
-
-#ifdef LWS_OPENSSL_SUPPORT
-       if (ssl_connection)
-               len = SSL_read(wsi->ssl, pkt, sizeof pkt);
-       else
-#endif
-               len = recv(wsi->sock, pkt, sizeof pkt, 0);
-
-       if (len < 0) {
-               fprintf(stderr, "libwebsocket_client_handshake read error\n");
-               goto bail2;
-       }
-
-       p = pkt;
-       for (n = 0; n < len; n++)
-               libwebsocket_parse(wsi, *p++);
-
-       if (wsi->parser_state != WSI_PARSING_COMPLETE) {
-               fprintf(stderr, "libwebsocket_client_handshake server response"
-                               " failed parsing\n");
-               goto bail2;
-       }
-
-       /*
-        * well, what the server sent looked reasonable for syntax.
-        * Now let's confirm it sent all the necessary headers
-        */
-
-        if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len ||
-                       !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
-                       !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len ||
-                       !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len ||
-                       !wsi->utf8_token[WSI_TOKEN_NONCE].token_len ||
-                       (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len &&
-                                                           protocol != NULL)) {
-               fprintf(stderr, "libwebsocket_client_handshake "
-                                               "missing required header(s)\n");
-               pkt[len] = '\0';
-               fprintf(stderr, "%s", pkt);
-               goto bail2;
-       }
-
-       /*
-        * Everything seems to be there, now take a closer look at what is in
-        * each header
-        */
-
-       strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token);
-       if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token,
-                                                  "101 switching protocols")) {
-               fprintf(stderr, "libwebsocket_client_handshake server sent bad"
-                               " HTTP response '%s'\n",
-                                        wsi->utf8_token[WSI_TOKEN_HTTP].token);
-               goto bail2;
-       }
-
-       strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
-       if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, "websocket")) {
-               fprintf(stderr, "libwebsocket_client_handshake server sent bad"
-                               " Upgrade header '%s'\n",
-                                     wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
-               goto bail2;
-       }
-
-       strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
-       if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, "upgrade")) {
-               fprintf(stderr, "libwebsocket_client_handshake server sent bad"
-                               " Connection hdr '%s'\n",
-                                  wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
-               goto bail2;
-       }
-       /*
-        * confirm the protocol the server wants to talk was in the list of
-        * protocols we offered
-        */
-
-       if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) {
+       pfd.revents = POLLIN;
+       libwebsocket_service_fd(this, &pfd);
 
-               /* no protocol name to work from, default to first protocol */
-               wsi->protocol = &this->protocols[0];
-
-               goto check_accept;
-       }
-
-       pc = protocol;
-       while (*pc && !okay) {
-               if ((!strncmp(pc, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
-                       wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) &&
-               (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' ||
-                  pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) {
-                       okay = 1;
-                       continue;
-               }
-               while (*pc && *pc != ',')
-                       pc++;
-               while (*pc && *pc != ' ')
-                       pc++;
-       }
-       if (!okay) {
-               fprintf(stderr, "libwebsocket_client_handshake server "
-                                       "sent bad protocol '%s'\n",
-                            wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
-               goto bail2;
-       }
-
-       /*
-        * identify the selected protocol struct and set it
-        */
-       n = 0;
-       wsi->protocol = NULL;
-       while (this->protocols[n].callback) {
-               if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
-                                      this->protocols[n].name) == 0)
-                       wsi->protocol = &this->protocols[n];
-               n++;
-       }
-
-       if (wsi->protocol == NULL) {
-               fprintf(stderr, "libwebsocket_client_handshake server "
-                               "requested protocol '%s', which we "
-                               "said we supported but we don't!\n",
-                            wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
-               goto bail2;
-       }
-
-check_accept:
-       /*
-        * Confirm his accept token is the same as the one we precomputed
-        */
-
-       if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
-                                         wsi->initial_handshake_hash_base64)) {
-               fprintf(stderr, "libwebsocket_client_handshake server sent "
-                               "bad ACCEPT '%s' vs computed '%s'\n",
-                               wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
-                                           wsi->initial_handshake_hash_base64);
-               goto bail2;
-       }
-
-       /*
-        * Calculate the masking key to use when sending data to server
-        */
-
-       strcpy(buf, key_b64);
-       p = buf + strlen(key_b64);
-       strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token);
-       p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len;
-       strcpy(p, magic_websocket_04_masking_guid);
-       SHA1((unsigned char *)buf, strlen(buf), wsi->masking_key_04);
-
-       /* allocate the per-connection user memory (if any) */
-
-       if (wsi->protocol->per_session_data_size) {
-               wsi->user_space = malloc(
-                                 wsi->protocol->per_session_data_size);
-               if (wsi->user_space  == NULL) {
-                       fprintf(stderr, "Out of memory for "
-                                                  "conn user space\n");
-                       goto bail2;
-               }
-       } else
-               wsi->user_space = NULL;
-
-       /* okay he is good to go */
-
-       this->fds[this->fds_count].fd = wsi->sock;
-       this->fds[this->fds_count].revents = 0;
-       this->fds[this->fds_count++].events = POLLIN;
-
-       /* external POLL support via protocol 0 */
-       this->protocols[0].callback(this, wsi,
-               LWS_CALLBACK_ADD_POLL_FD,
-               (void *)(long)wsi->sock, NULL, POLLIN);
-
-
-       wsi->state = WSI_STATE_ESTABLISHED;
-       wsi->mode = LWS_CONNMODE_WS_CLIENT;
+       return wsi;
 
-       fprintf(stderr, "handshake OK for protocol %s\n", wsi->protocol->name);
+oom4:
+       if (wsi->c_protocol)
+               free(wsi->c_protocol);
 
-       /* call him back to inform him he is up */
+oom3:
+       free(wsi->c_origin);
 
-       wsi->protocol->callback(this, wsi,
-                        LWS_CALLBACK_CLIENT_ESTABLISHED,
-                        wsi->user_space,
-                        NULL, 0);
-       return wsi;
+oom2:
+       free(wsi->c_host);
 
+oom1:
+       free(wsi->c_path);
 
-bail2:
-       libwebsocket_close_and_free_session(this, wsi);
 bail1:
        free(wsi);
 
index a35193d..0e64817 100644 (file)
 
 #include "private-libwebsockets.h"
 
+/*
+ * In-place str to lower case
+ */
+
+static void
+strtolower(char *s)
+{
+       while (*s) {
+               *s = tolower(*s);
+               s++;
+       }
+}
+
 /* file descriptor hash management */
 
 struct libwebsocket *
@@ -285,7 +298,18 @@ libwebsocket_service_fd(struct libwebsocket_context *this,
        unsigned int clilen;
        struct sockaddr_in cli_addr;
        struct timeval tv;
-
+       static const char magic_websocket_guid[] =
+                                        "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+       static const char magic_websocket_04_masking_guid[] =
+                                        "61AC5F19-FBBA-4540-B96F-6561F1AB40A8";
+       char hash[20];
+       char pkt[1024];
+       char *p = &pkt[0];
+       const char *pc;
+       int okay = 0;
+#ifdef LWS_OPENSSL_SUPPORT
+       char ssl_err_buf[512];
+#endif
        /*
         * you can call us with pollfd = NULL to just allow the once-per-second
         * global timeout checks; if less than a second since the last check
@@ -601,6 +625,397 @@ libwebsocket_service_fd(struct libwebsocket_context *this,
                }
                break;
 
+       case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY:
+
+               /* handle proxy hung up on us */
+
+               if (pollfd->revents & (POLLERR | POLLHUP)) {
+
+                       fprintf(stderr, "Proxy connection %p (fd=%d) dead\n",
+                               (void *)wsi, pollfd->fd);
+
+                       libwebsocket_close_and_free_session(this, wsi);
+                       return 1;
+               }
+
+               n = recv(wsi->sock, pkt, sizeof pkt, 0);
+               if (n < 0) {
+                       libwebsocket_close_and_free_session(this, wsi);
+                       fprintf(stderr, "ERROR reading from proxy socket\n");
+                       return 1;
+               }
+
+               pkt[13] = '\0';
+               if (strcmp(pkt, "HTTP/1.0 200 ") != 0) {
+                       libwebsocket_close_and_free_session(this, wsi);
+                       fprintf(stderr, "ERROR from proxy: %s\n", pkt);
+                       return 1;
+               }
+
+               /* clear his proxy connection timeout */
+
+               libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+               /* fallthru */
+
+       case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE:
+
+       #ifdef LWS_OPENSSL_SUPPORT
+               if (wsi->use_ssl) {
+
+                       wsi->ssl = SSL_new(this->ssl_client_ctx);
+                       wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE);
+                       SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
+
+                       if (SSL_connect(wsi->ssl) <= 0) {
+                               fprintf(stderr, "SSL connect error %s\n",
+                                       ERR_error_string(ERR_get_error(), ssl_err_buf));
+                               libwebsocket_close_and_free_session(this, wsi);
+                               return 1;
+                       }
+
+                       n = SSL_get_verify_result(wsi->ssl);
+                       if (n != X509_V_OK) {
+                               if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+                                                           wsi->use_ssl != 2) {
+
+                                       fprintf(stderr, "server's cert didn't "
+                                                                  "look good %d\n", n);
+                                       libwebsocket_close_and_free_session(this, wsi);
+                                       return 1;
+                               }
+                       }
+               } else {
+                       wsi->ssl = NULL;
+       #endif
+
+
+       #ifdef LWS_OPENSSL_SUPPORT
+               }
+       #endif
+
+               /*
+                * create the random key
+                */
+
+               n = read(this->fd_random, hash, 16);
+               if (n != 16) {
+                       fprintf(stderr, "Unable to read from random dev %s\n",
+                                                       SYSTEM_RANDOM_FILEPATH);
+                       free(wsi->c_path);
+                       free(wsi->c_host);
+                       free(wsi->c_origin);
+                       if (wsi->c_protocol)
+                               free(wsi->c_protocol);
+                       libwebsocket_close_and_free_session(this, wsi);
+                       return 1;
+               }
+
+               lws_b64_encode_string(hash, 16, wsi->key_b64,
+                                                          sizeof wsi->key_b64);
+
+               /*
+                * 04 example client handshake
+                *
+                * GET /chat HTTP/1.1
+                * Host: server.example.com
+                * Upgrade: websocket
+                * Connection: Upgrade
+                * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+                * Sec-WebSocket-Origin: http://example.com
+                * Sec-WebSocket-Protocol: chat, superchat
+                * Sec-WebSocket-Version: 4
+                */
+
+                p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", wsi->c_path);
+                p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host);
+                p += sprintf(p, "Upgrade: websocket\x0d\x0a");
+                p += sprintf(p, "Connection: Upgrade\x0d\x0a"
+                                       "Sec-WebSocket-Key: ");
+                strcpy(p, wsi->key_b64);
+                p += strlen(wsi->key_b64);
+                p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a",
+                                                                wsi->c_origin);
+                if (wsi->c_protocol != NULL)
+                       p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a",
+                                                              wsi->c_protocol);
+                p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a",
+                                                      wsi->ietf_spec_revision);
+
+               /* done with these now */
+
+               free(wsi->c_path);
+               free(wsi->c_host);
+               free(wsi->c_origin);
+
+               /* prepare the expected server accept response */
+
+               strcpy((char *)buf, wsi->key_b64);
+               strcpy((char *)&buf[strlen((char *)buf)], magic_websocket_guid);
+
+               SHA1(buf, strlen((char *)buf), (unsigned char *)hash);
+
+               lws_b64_encode_string(hash, 20,
+                               wsi->initial_handshake_hash_base64,
+                                    sizeof wsi->initial_handshake_hash_base64);
+
+               /* send our request to the server */
+
+       #ifdef LWS_OPENSSL_SUPPORT
+               if (wsi->use_ssl)
+                       n = SSL_write(wsi->ssl, pkt, p - pkt);
+               else
+       #endif
+                       n = send(wsi->sock, pkt, p - pkt, 0);
+
+               if (n < 0) {
+                       fprintf(stderr, "ERROR writing to client socket\n");
+                       libwebsocket_close_and_free_session(this, wsi);
+                       return 1;
+               }
+
+               wsi->parser_state = WSI_TOKEN_NAME_PART;
+               wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY;
+               libwebsocket_set_timeout(wsi,
+                               PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, 5);
+
+               break;
+
+       case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY:
+
+               /* handle server hung up on us */
+
+               if (pollfd->revents & (POLLERR | POLLHUP)) {
+
+                       fprintf(stderr, "Server connection %p (fd=%d) dead\n",
+                               (void *)wsi, pollfd->fd);
+
+                       goto bail3;
+               }
+
+
+               /* interpret the server response */
+
+               /*
+                *  HTTP/1.1 101 Switching Protocols
+                *  Upgrade: websocket
+                *  Connection: Upgrade
+                *  Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
+                *  Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
+                *  Sec-WebSocket-Protocol: chat
+                */
+
+       #ifdef LWS_OPENSSL_SUPPORT
+               if (wsi->use_ssl)
+                       len = SSL_read(wsi->ssl, pkt, sizeof pkt);
+               else
+       #endif
+                       len = recv(wsi->sock, pkt, sizeof pkt, 0);
+
+               if (len < 0) {
+                       fprintf(stderr,
+                                 "libwebsocket_client_handshake read error\n");
+                       goto bail3;
+               }
+
+               p = pkt;
+               for (n = 0; n < len; n++)
+                       libwebsocket_parse(wsi, *p++);
+
+               if (wsi->parser_state != WSI_PARSING_COMPLETE) {
+                       fprintf(stderr, "libwebsocket_client_handshake "
+                                       "server response ailed parsing\n");
+                       goto bail3;
+               }
+
+               /*
+                * well, what the server sent looked reasonable for syntax.
+                * Now let's confirm it sent all the necessary headers
+                */
+
+                if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len ||
+                       !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
+                       !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len ||
+                       !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len ||
+                       !wsi->utf8_token[WSI_TOKEN_NONCE].token_len ||
+                       (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len &&
+                                                    wsi->c_protocol != NULL)) {
+                       fprintf(stderr, "libwebsocket_client_handshake "
+                                                       "missing required header(s)\n");
+                       pkt[len] = '\0';
+                       fprintf(stderr, "%s", pkt);
+                       goto bail3;
+               }
+
+               /*
+                * Everything seems to be there, now take a closer look at what
+                * is in each header
+                */
+
+               strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token);
+               if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token,
+                                                  "101 switching protocols")) {
+                       fprintf(stderr, "libwebsocket_client_handshake "
+                                       "server sent bad HTTP response '%s'\n",
+                                        wsi->utf8_token[WSI_TOKEN_HTTP].token);
+                       goto bail3;
+               }
+
+               strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
+               if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token,
+                                                                "websocket")) {
+                       fprintf(stderr, "libwebsocket_client_handshake server "
+                                       "sent bad Upgrade header '%s'\n",
+                                     wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
+                       goto bail3;
+               }
+
+               strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
+               if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token,
+                                                                  "upgrade")) {
+                       fprintf(stderr, "libwebsocket_client_handshake server "
+                                       "sent bad Connection hdr '%s'\n",
+                                  wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
+                       goto bail3;
+               }
+
+
+               pc = wsi->c_protocol;
+
+               /*
+                * confirm the protocol the server wants to talk was in the list
+                * of protocols we offered
+                */
+
+               if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) {
+
+                       /*
+                        * no protocol name to work from,
+                        * default to first protocol
+                        */
+                       wsi->protocol = &this->protocols[0];
+
+                       free(wsi->c_protocol);
+
+                       goto check_accept;
+               }
+
+               while (*pc && !okay) {
+                       if ((!strncmp(pc,
+                               wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
+                          wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) &&
+                (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' ||
+                  pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) {
+                               okay = 1;
+                               continue;
+                       }
+                       while (*pc && *pc != ',')
+                               pc++;
+                       while (*pc && *pc != ' ')
+                               pc++;
+               }
+
+               /* done with him now */
+
+               if (wsi->c_protocol)
+                       free(wsi->c_protocol);
+
+
+               if (!okay) {
+                       fprintf(stderr, "libwebsocket_client_handshake server "
+                                               "sent bad protocol '%s'\n",
+                                    wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
+                       goto bail2;
+               }
+
+               /*
+                * identify the selected protocol struct and set it
+                */
+               n = 0;
+               wsi->protocol = NULL;
+               while (this->protocols[n].callback) {
+                       if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
+                                              this->protocols[n].name) == 0)
+                               wsi->protocol = &this->protocols[n];
+                       n++;
+               }
+
+               if (wsi->protocol == NULL) {
+                       fprintf(stderr, "libwebsocket_client_handshake server "
+                                       "requested protocol '%s', which we "
+                                       "said we supported but we don't!\n",
+                                    wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
+                       goto bail2;
+               }
+
+       check_accept:
+               /*
+                * Confirm his accept token is the one we precomputed
+                */
+
+               if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
+                                         wsi->initial_handshake_hash_base64)) {
+                       fprintf(stderr, "libwebsocket_client_handshake server "
+                               "sent bad ACCEPT '%s' vs computed '%s'\n",
+                               wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
+                                           wsi->initial_handshake_hash_base64);
+                       goto bail2;
+               }
+
+               /*
+                * Calculate the masking key to use when sending data to server
+                */
+
+               strcpy((char *)buf, wsi->key_b64);
+               p = (char *)buf + strlen(wsi->key_b64);
+               strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token);
+               p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len;
+               strcpy(p, magic_websocket_04_masking_guid);
+               SHA1(buf, strlen((char *)buf), wsi->masking_key_04);
+
+               /* allocate the per-connection user memory (if any) */
+
+               if (wsi->protocol->per_session_data_size) {
+                       wsi->user_space = malloc(
+                                         wsi->protocol->per_session_data_size);
+                       if (wsi->user_space  == NULL) {
+                               fprintf(stderr, "Out of memory for "
+                                                          "conn user space\n");
+                               goto bail2;
+                       }
+               } else
+                       wsi->user_space = NULL;
+
+               /* clear his proxy connection timeout */
+
+               libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+               /* mark him as being alive */
+
+               wsi->state = WSI_STATE_ESTABLISHED;
+               wsi->mode = LWS_CONNMODE_WS_CLIENT;
+
+               fprintf(stderr, "handshake OK for protocol %s\n",
+                                                          wsi->protocol->name);
+
+               /* call him back to inform him he is up */
+
+               wsi->protocol->callback(this, wsi,
+                                LWS_CALLBACK_CLIENT_ESTABLISHED,
+                                wsi->user_space,
+                                NULL, 0);
+
+               break;
+
+bail3:
+               if (wsi->c_protocol)
+                       free(wsi->c_protocol);
+
+bail2:
+               libwebsocket_close_and_free_session(this, wsi);
+               return 1;
+               
+
        case LWS_CONNMODE_WS_SERVING:
        case LWS_CONNMODE_WS_CLIENT:
 
@@ -829,6 +1244,28 @@ libwebsocket_callback_on_writable_all_protocol(
        return 0;
 }
 
+/**
+ * libwebsocket_set_timeout() - marks the wsi as subject to a timeout
+ *
+ * You will not need this unless you are doing something special
+ *
+ * @wsi:       Websocket connection instance
+ * @reason:    timeout reason
+ * @secs:      how many seconds
+ */
+
+void
+libwebsocket_set_timeout(struct libwebsocket *wsi,
+                                         enum pending_timeout reason, int secs)
+{
+       struct timeval tv;
+
+       gettimeofday(&tv, NULL);
+
+       wsi->pending_timeout_limit = tv.tv_sec + secs;
+       wsi->pending_timeout = reason;
+}
+
 
 /**
  * libwebsocket_get_socket_fd() - returns the socket file descriptor
index d588541..5fbbcef 100644 (file)
@@ -106,7 +106,7 @@ enum lws_connection_states {
        WSI_STATE_DEAD_SOCKET,
        WSI_STATE_ESTABLISHED,
        WSI_STATE_CLIENT_UNCONNECTED,
-       WSI_STATE_RETURNED_CLOSE_ALREADY
+       WSI_STATE_RETURNED_CLOSE_ALREADY,
 };
 
 enum lws_rx_parse_state {
@@ -141,6 +141,11 @@ enum connection_mode {
        LWS_CONNMODE_WS_SERVING,
        LWS_CONNMODE_WS_CLIENT,
 
+       /* transient modes */
+       LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY,
+       LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE,
+       LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY,
+
        /* special internal types */
        LWS_CONNMODE_SERVER_LISTENER,
        LWS_CONNMODE_BROADCAST_PROXY_LISTENER,
@@ -182,7 +187,9 @@ struct libwebsocket_context {
 
 enum pending_timeout {
        NO_PENDING_TIMEOUT = 0,
+       PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
        PENDING_TIMEOUT_ESTABLISH_WITH_SERVER,
+       PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
        PENDING_TIMEOUT_AWAITING_PING,
 };
 
@@ -217,6 +224,7 @@ struct libwebsocket {
 
        /* 04 protocol specific */
 
+       char key_b64[150];
        unsigned char masking_key_04[20];
        unsigned char frame_masking_nonce_04[4];
        unsigned char frame_mask_04[20];
@@ -232,10 +240,15 @@ struct libwebsocket {
        /* client support */
        char initial_handshake_hash_base64[30];
        enum connection_mode mode;
+       char *c_path;
+       char *c_host;
+       char *c_origin;
+       char *c_protocol;
 
 #ifdef LWS_OPENSSL_SUPPORT
        SSL *ssl;
        BIO *client_bio;
+       int use_ssl;
 #endif
 
        void *user_space;
@@ -282,6 +295,10 @@ insert_wsi(struct libwebsocket_context *this, struct libwebsocket *wsi);
 extern int
 delete_from_fd(struct libwebsocket_context *this, int fd);
 
+extern void
+libwebsocket_set_timeout(struct libwebsocket *wsi,
+                                        enum pending_timeout reason, int secs);
+
 #ifndef LWS_OPENSSL_SUPPORT
 
 unsigned char *
index feb6638..e351ab8 100644 (file)
@@ -141,6 +141,27 @@ nothing is pending, or as soon as it services whatever was pending.
 <dd>Protocol whose connections will get callbacks
 </dl>
 <hr>
+<h2>libwebsocket_set_timeout - marks the wsi as subject to a timeout</h2>
+<i>void</i>
+<b>libwebsocket_set_timeout</b>
+(<i>struct libwebsocket *</i> <b>wsi</b>,
+<i>enum pending_timeout</i> <b>reason</b>,
+<i>int</i> <b>secs</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>wsi</b>
+<dd>Websocket connection instance
+<dt><b>reason</b>
+<dd>timeout reason
+<dt><b>secs</b>
+<dd>how many seconds
+</dl>
+<h3>Description</h3>
+<blockquote>
+<p>
+You will not need this unless you are doing something special
+</blockquote>
+<hr>
 <h2>libwebsocket_get_socket_fd - returns the socket file descriptor</h2>
 <i>int</i>
 <b>libwebsocket_get_socket_fd</b>