On 25/07/15 01:03, Daniel Stenberg wrote:
If you insist on using libcurl for this, then I think that is at least a way you _can_ do it.
Yes, the benefit of using curl in this case, only to establish the connection, is really small. That's why I tried to find/implement a solution were the functionality of libcurl could be used for more than that.
I haven't fully looked through the code path for this approach but it seems reasonable. And even if you made it work, it's not really a documented way to do it and we don't test for it so it would be a rather fragile method that could break in a future release.
I agree, this would be more a hack than a nice solution. If we go this direction I think there should be a documented way to detach the easy-handle from the multi-handle, without severing the underling connection. After that the easy-handle could be freely used with curl_easy_send() und _recv(). An Idea that comes to my mind would be to add a curl_easy_option that let curl do the HTTP header send/receive part and then yields the control to the application, similarly to CURLOPT_CONNECT_ONLY.
It'd need to be more than that (like for example we always need to wait for the socket to be BOTH readable and writable since we don't know the protocol and that seems like a hard nut to crack or you need to signal that somehow)
I don't understand. Isn't it good enough to have the the KEEP_RECV/SEND flags set? The sending is controlled by the READFUNCTION that could return READFUNC_PAUSE if no data has to be sent. And the WRITEFUNCTION is just called if there is data available from the server. This is really low level, all the websocket protocol and framing stuff has to be implemented in the application itself.
and I'm also interested in how you imagine the API for enabling/controlling this. I'm slightly scared that allowing this is the first step on a slippery slope and that we will soon find out that it isn't enough to do good websockets anyway unless we also do something else that is more intrusive.
In this first approach I thought about it to be controlled by the availability of the "Upgrade: websocket" header in the request (see new patch). But maybe it would be better to enable it explicitly with a config option. So far I also didn't consider yet to check the correctness of the "Sec-WebSocket-Accept" header. But of course there will be a minimum of checks that have to be performed before libcurl switches to "websocket-mode".

After some more tinkering I also think after the protocol switch is performed, the "conn->handler" should be switched to something simpler that only receives and sends data back and forth on the socket (similiar to telnet). This way the impact on existing code should be very limited.

I'm standing at crossroads. I'd be willing to put effort into this, but I need guidance to know what could be done, and what would be accepted. We are talking about quite different approaches. The first is more a workaround to takeover the connection, whereas the other aims at a "naturally" integrated solution. Also my knowhow of the internals of libcurl might not be sufficient to work out the solution in the needed quality by myself.
And I'll again mention that we need test cases if we are to consider doing this.
I completely agree. I'd be willing to provide those too as far I can manage, but might need some help with this too.

cheers, Frank


diff --git a/lib/http.c b/lib/http.c
index a1eef81..db2d91c 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -2350,6 +2350,11 @@ CURLcode Curl_http(struct connectdata *conn, bool *done)
     if(result)
       return result;
   }
+  else if((ptr=Curl_checkheaders(conn, "Upgrade:"))) {
+    if(Curl_compareheader(ptr, "Upgrade:", "websocket")) {
+      data->req.upgr101 = UPGR101_WS_REQUESTED;
+    }
+  }
 
 #if !defined(CURL_DISABLE_COOKIES)
   if(data->cookies || addcookies) {
@@ -2942,6 +2947,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
 {
   CURLcode result;
   struct SingleRequest *k = &data->req;
+  const char *ptr;
 
   /* header line within buffer loop */
   do {
@@ -3035,16 +3041,6 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
 #endif /* CURL_DOES_CONVERSIONS */
 
       if(100 <= k->httpcode && 199 >= k->httpcode) {
-        /*
-         * We have made a HTTP PUT or POST and this is 1.1-lingo
-         * that tells us that the server is OK with this and ready
-         * to receive the data.
-         * However, we'll get more headers now so we must get
-         * back into the header-parsing state!
-         */
-        k->header = TRUE;
-        k->headerline = 0; /* restart the header line counter */
-
         /* "A user agent MAY ignore unexpected 1xx status responses." */
         switch(k->httpcode) {
         case 100:
@@ -3056,10 +3052,9 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
           break;
         case 101:
           /* Switching Protocols */
-          if(k->upgr101 == UPGR101_REQUESTED) {
-            infof(data, "Received 101\n");
-            k->upgr101 = UPGR101_RECEIVED;
-
+          infof(data, "Received 101\n");
+          if(k->upgr101 == UPGR101_H2_REQUESTED) {
+            k->upgr101 = UPGR101_H2_RECEIVED;
             /* switch to http2 now. The bytes after response headers
                are also processed here, otherwise they are lost. */
             result = Curl_http2_switched(conn, k->str, *nread);
@@ -3067,8 +3062,28 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
               return result;
             *nread = 0;
           }
+          else if(k->upgr101 == UPGR101_WS_REQUESTED) {
+            k->upgr101 = UPGR101_WS_RECEIVED;
+            ptr = Curl_checkheaders(conn, "Upgrade");
+            if(ptr && Curl_compareheader(ptr, "Upgrade:", "websocket")) {
+              infof(data, "Upgrade: Websocket\n");
+              k->header = FALSE;
+              k->keepon |= KEEP_SEND | KEEP_RECV;
+              conn->writesockfd = conn->sockfd;
+              connclose(conn, "close after websocket communication");
+            }
+          }
           break;
         default:
+          /*
+           * We have made a HTTP PUT or POST and this is 1.1-lingo
+           * that tells us that the server is OK with this and ready
+           * to receive the data.
+           * However, we'll get more headers now so we must get
+           * back into the header-parsing state!
+           */
+          k->header = TRUE;
+          k->headerline = 0; /* restart the header line counter */
           break;
         }
       }
@@ -3296,7 +3311,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
         if(nc==3) {
           conn->httpversion += 10 * httpversion_major;
 
-          if(k->upgr101 == UPGR101_RECEIVED) {
+          if(k->upgr101 == UPGR101_H2_RECEIVED) {
             /* supposedly upgraded to http2 now */
             if(conn->httpversion != 20)
               infof(data, "Lying server, not serving HTTP/2\n");
@@ -3379,7 +3394,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
           connclose(conn, "HTTP/1.0 close after body");
         }
         else if(conn->httpversion == 20 ||
-                (k->upgr101 == UPGR101_REQUESTED && k->httpcode == 101)) {
+                (k->upgr101 == UPGR101_H2_REQUESTED && k->httpcode == 101)) {
           DEBUGF(infof(data, "HTTP/2 found, allow multiplexing\n"));
 
           /* HTTP/2 cannot blacklist multiplexing since it is a core
@@ -3627,7 +3642,7 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
          The forth means the requested range was unsatisfied.
       */
 
-      char *ptr = k->p + 14;
+      ptr = k->p + 14;
 
       /* Move forward until first digit or asterisk */
       while(*ptr && !ISDIGIT(*ptr) && *ptr != '*')
diff --git a/lib/http2.c b/lib/http2.c
index 1a2c486..5392484 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -977,7 +977,7 @@ CURLcode Curl_http2_request_upgrade(Curl_send_buffer *req,
                             NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64);
   free(base64);
 
-  k->upgr101 = UPGR101_REQUESTED;
+  k->upgr101 = UPGR101_H2_REQUESTED;
 
   return result;
 }
@@ -1483,7 +1483,7 @@ CURLcode Curl_http2_switched(struct connectdata *conn,
   conn->recv[FIRSTSOCKET] = http2_recv;
   conn->send[FIRSTSOCKET] = http2_send;
 
-  if(conn->data->req.upgr101 == UPGR101_RECEIVED) {
+  if(conn->data->req.upgr101 == UPGR101_H2_RECEIVED) {
     /* stream 1 is opened implicitly on upgrade */
     stream->stream_id = 1;
     /* queue SETTINGS frame (again) */
diff --git a/lib/transfer.c b/lib/transfer.c
index 718139b..0b3b07d 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -802,7 +802,7 @@ static CURLcode readwrite_data(struct SessionHandle *data,
        may now close the connection. If there's now any kind of sending going
        on from our side, we need to stop that immediately. */
     infof(data, "we are done reading and this is set to close, stop send\n");
-    k->keepon &= ~KEEP_SEND; /* no writing anymore either */
+    k->keepon &= ~(KEEP_SEND|KEEP_SEND_PAUSE); /* no writing anymore either */
   }
 
   return CURLE_OK;
diff --git a/lib/urldata.h b/lib/urldata.h
index b1c2056..493d607 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -598,9 +598,10 @@ enum expect100 {
 
 enum upgrade101 {
   UPGR101_INIT,               /* default state */
-  UPGR101_REQUESTED,          /* upgrade requested */
-  UPGR101_RECEIVED,           /* response received */
-  UPGR101_WORKING             /* talking upgraded protocol */
+  UPGR101_H2_REQUESTED,       /* upgrade HTTP2 requested */
+  UPGR101_H2_RECEIVED,        /* upgrade HTTP2 received */
+  UPGR101_WS_REQUESTED,       /* upgrade WebSocket requested */
+  UPGR101_WS_RECEIVED         /* upgrade WebSocket received */
 };
 
 /*
-------------------------------------------------------------------
List admin: http://cool.haxx.se/list/listinfo/curl-library
Etiquette:  http://curl.haxx.se/mail/etiquette.html

Reply via email to