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