From b819c72700d5f01cab5848f1cd3c880205d01c81 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 25 Feb 2007 11:38:13 +0000 Subject: - Adam D. Moss made the HTTP CONNECT procedure less blocking when used from the multi interface. Note that it still does a part of the connection in a blocking manner. --- lib/http.c | 491 ++++++++++++++++++++++++++++++++-------------------------- lib/multi.c | 32 +++- lib/urldata.h | 2 + 3 files changed, 301 insertions(+), 224 deletions(-) (limited to 'lib') diff --git a/lib/http.c b/lib/http.c index 522bc00f4..16c59fb87 100644 --- a/lib/http.c +++ b/lib/http.c @@ -1115,259 +1115,309 @@ CURLcode Curl_proxyCONNECT(struct connectdata *conn, struct Curl_transfer_keeper *k = &data->reqdata.keep; CURLcode result; int res; - size_t nread; /* total size read */ - int perline; /* count bytes per line */ - int keepon=TRUE; - ssize_t gotbytes; - char *ptr; long timeout = data->set.timeout?data->set.timeout:3600000; /* in milliseconds */ - char *line_start; - char *host_port; curl_socket_t tunnelsocket = conn->sock[sockindex]; - send_buffer *req_buffer; curl_off_t cl=0; bool closeConnection = FALSE; + long check; #define SELECT_OK 0 #define SELECT_ERROR 1 #define SELECT_TIMEOUT 2 int error = SELECT_OK; - infof(data, "Establish HTTP proxy tunnel to %s:%d\n", hostname, remote_port); conn->bits.proxy_connect_closed = FALSE; do { - if(data->reqdata.newurl) { - /* This only happens if we've looped here due to authentication reasons, - and we don't really use the newly cloned URL here then. Just free() - it. */ - free(data->reqdata.newurl); - data->reqdata.newurl = NULL; - } - - /* initialize a dynamic send-buffer */ - req_buffer = add_buffer_init(); + if (!conn->bits.tunnel_connecting) { /* BEGIN CONNECT PHASE */ + char *host_port; + send_buffer *req_buffer; + + infof(data, "Establish HTTP proxy tunnel to %s:%d\n", + hostname, remote_port); + + if(data->reqdata.newurl) { + /* This only happens if we've looped here due to authentication + reasons, and we don't really use the newly cloned URL here + then. Just free() it. */ + free(data->reqdata.newurl); + data->reqdata.newurl = NULL; + } - if(!req_buffer) - return CURLE_OUT_OF_MEMORY; + /* initialize a dynamic send-buffer */ + req_buffer = add_buffer_init(); - host_port = aprintf("%s:%d", hostname, remote_port); - if(!host_port) - return CURLE_OUT_OF_MEMORY; + if(!req_buffer) + return CURLE_OUT_OF_MEMORY; - /* Setup the proxy-authorization header, if any */ - result = Curl_http_output_auth(conn, (char *)"CONNECT", host_port, TRUE); + host_port = aprintf("%s:%d", hostname, remote_port); + if(!host_port) + return CURLE_OUT_OF_MEMORY; - if(CURLE_OK == result) { - char *host=(char *)""; - const char *proxyconn=""; - const char *useragent=""; + /* Setup the proxy-authorization header, if any */ + result = Curl_http_output_auth(conn, (char *)"CONNECT", host_port, TRUE); - if(!checkheaders(data, "Host:")) { - host = aprintf("Host: %s\r\n", host_port); - if(!host) - result = CURLE_OUT_OF_MEMORY; - } - if(!checkheaders(data, "Proxy-Connection:")) - proxyconn = "Proxy-Connection: Keep-Alive\r\n"; + if(CURLE_OK == result) { + char *host=(char *)""; + const char *proxyconn=""; + const char *useragent=""; + + if(!checkheaders(data, "Host:")) { + host = aprintf("Host: %s\r\n", host_port); + if(!host) + result = CURLE_OUT_OF_MEMORY; + } + if(!checkheaders(data, "Proxy-Connection:")) + proxyconn = "Proxy-Connection: Keep-Alive\r\n"; - if(!checkheaders(data, "User-Agent:") && data->set.useragent) - useragent = conn->allocptr.uagent; + if(!checkheaders(data, "User-Agent:") && data->set.useragent) + useragent = conn->allocptr.uagent; - if(CURLE_OK == result) { - /* Send the connect request to the proxy */ - /* BLOCKING */ - result = - add_bufferf(req_buffer, - "CONNECT %s:%d HTTP/1.0\r\n" - "%s" /* Host: */ - "%s" /* Proxy-Authorization */ - "%s" /* User-Agent */ - "%s", /* Proxy-Connection */ - hostname, remote_port, - host, - conn->allocptr.proxyuserpwd? - conn->allocptr.proxyuserpwd:"", - useragent, - proxyconn); - - if(CURLE_OK == result) - result = add_custom_headers(conn, req_buffer); - - if(host && *host) - free(host); - - if(CURLE_OK == result) - /* CRLF terminate the request */ - result = add_bufferf(req_buffer, "\r\n"); - - if(CURLE_OK == result) - /* Now send off the request */ - result = add_buffer_send(req_buffer, conn, - &data->info.request_size, 0, sockindex); + if(CURLE_OK == result) { + /* Send the connect request to the proxy */ + /* BLOCKING */ + result = + add_bufferf(req_buffer, + "CONNECT %s:%d HTTP/1.0\r\n" + "%s" /* Host: */ + "%s" /* Proxy-Authorization */ + "%s" /* User-Agent */ + "%s", /* Proxy-Connection */ + hostname, remote_port, + host, + conn->allocptr.proxyuserpwd? + conn->allocptr.proxyuserpwd:"", + useragent, + proxyconn); + + if(CURLE_OK == result) + result = add_custom_headers(conn, req_buffer); + + if(host && *host) + free(host); + + if(CURLE_OK == result) + /* CRLF terminate the request */ + result = add_bufferf(req_buffer, "\r\n"); + + if(CURLE_OK == result) + /* Now send off the request */ + result = add_buffer_send(req_buffer, conn, + &data->info.request_size, 0, sockindex); + } + if(result) + failf(data, "Failed sending CONNECT to proxy"); } + free(host_port); if(result) - failf(data, "Failed sending CONNECT to proxy"); - } - free(host_port); - if(result) - return result; + return result; - ptr=data->state.buffer; - line_start = ptr; + conn->bits.tunnel_connecting = TRUE; + } /* END CONNECT PHASE */ - nread=0; - perline=0; - keepon=TRUE; + /* now we've issued the CONNECT and we're waiting to hear back - + we try not to block here in multi-mode because that might be a LONG + wait if the proxy cannot connect-through to the remote host. */ - while((nreadnow); /* spent time */ + if(check <=0 ) { + failf(data, "Proxy CONNECT aborted due to timeout"); + error = SELECT_TIMEOUT; /* already too little time */ + break; + } - /* if timeout is requested, find out how much remaining time we have */ - long check = timeout - /* timeout time */ - Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */ - if(check <= 0) { - failf(data, "Proxy CONNECT aborted due to timeout"); - error = SELECT_TIMEOUT; /* already too little time */ - break; + /* if we're in multi-mode and we would block, return instead for a retry */ + if (Curl_if_multi == data->state.used_interface) { + if (0 == Curl_select(tunnelsocket, CURL_SOCKET_BAD, 0)) + /* return so we'll be called again polling-style */ + return CURLE_OK; + else { + DEBUGF(infof(data, + "Multi mode finished polling for response from " + "proxy CONNECT.")); } + } + else { + DEBUGF(infof(data, "Easy mode waiting for response from proxy CONNECT.")); + } - /* loop every second at least, less if the timeout is near */ - switch (Curl_select(tunnelsocket, CURL_SOCKET_BAD, - check<1000L?(int)check:1000)) { - case -1: /* select() error, stop reading */ - error = SELECT_ERROR; - failf(data, "Proxy CONNECT aborted due to select() error"); - break; - case 0: /* timeout */ - break; - default: - res = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, &gotbytes); - if(res< 0) - /* EWOULDBLOCK */ - continue; /* go loop yourself */ - else if(res) - keepon = FALSE; - else if(gotbytes <= 0) { - keepon = FALSE; - error = SELECT_ERROR; - failf(data, "Proxy CONNECT aborted"); + /* at this point, either: + 1) we're in easy-mode and so it's okay to block waiting for a CONNECT + response + 2) we're in multi-mode and we didn't block - it's either an error or we + now have some data waiting. + In any case, the tunnel_connecting phase is over. */ + conn->bits.tunnel_connecting = FALSE; + + { /* BEGIN NEGOTIATION PHASE */ + size_t nread; /* total size read */ + int perline; /* count bytes per line */ + int keepon=TRUE; + ssize_t gotbytes; + char *ptr; + char *line_start; + + ptr=data->state.buffer; + line_start = ptr; + + nread=0; + perline=0; + keepon=TRUE; + + while((nreadnow); /* spent time */ + if(check <= 0) { + failf(data, "Proxy CONNECT aborted due to timeout"); + error = SELECT_TIMEOUT; /* already too little time */ + break; } - else { - /* - * We got a whole chunk of data, which can be anything from one byte - * to a set of lines and possibly just a piece of the last line. - */ - int i; - - nread += gotbytes; - - if(keepon > TRUE) { - /* This means we are currently ignoring a response-body, so we - simply count down our counter and make sure to break out of the - loop when we're done! */ - cl -= gotbytes; - if(cl<=0) { - keepon = FALSE; - break; - } - } - else - for(i = 0; i < gotbytes; ptr++, i++) { - perline++; /* amount of bytes in this line so far */ - if(*ptr=='\n') { - char letter; - int writetype; - - /* output debug if that is requested */ - if(data->set.verbose) - Curl_debug(data, CURLINFO_HEADER_IN, - line_start, (size_t)perline, conn); - - /* send the header to the callback */ - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - result = Curl_client_write(conn, writetype, line_start, perline); - if(result) - return result; - - /* Newlines are CRLF, so the CR is ignored as the line isn't - really terminated until the LF comes. Treat a following CR - as end-of-headers as well.*/ - - if(('\r' == line_start[0]) || - ('\n' == line_start[0])) { - /* end of response-headers from the proxy */ - if(cl && (407 == k->httpcode) && !data->state.authproblem) { - /* If we get a 407 response code with content length when we - * have no auth problem, we must ignore the whole - * response-body */ - keepon = 2; - infof(data, "Ignore %" FORMAT_OFF_T - " bytes of response-body\n", cl); - cl -= (gotbytes - i);/* remove the remaining chunk of what - we already read */ - if(cl<=0) - /* if the whole thing was already read, we are done! */ - keepon=FALSE; - } - else - keepon = FALSE; - break; /* breaks out of for-loop, not switch() */ - } - /* keep a backup of the position we are about to blank */ - letter = line_start[perline]; - line_start[perline]=0; /* zero terminate the buffer */ - if((checkprefix("WWW-Authenticate:", line_start) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", line_start) && - (407 == k->httpcode))) { - result = Curl_http_input_auth(conn, k->httpcode, line_start); - if(result) - return result; - } - else if(checkprefix("Content-Length:", line_start)) { - cl = curlx_strtoofft(line_start + strlen("Content-Length:"), - NULL, 10); - } - else if(Curl_compareheader(line_start, - "Connection:", "close")) - closeConnection = TRUE; - else if(2 == sscanf(line_start, "HTTP/1.%d %d", - &subversion, - &k->httpcode)) { - /* store the HTTP code from the proxy */ - data->info.httpproxycode = k->httpcode; + /* loop every second at least, less if the timeout is near */ + switch (Curl_select(tunnelsocket, CURL_SOCKET_BAD, + check<1000L?(int)check:1000)) { + case -1: /* select() error, stop reading */ + error = SELECT_ERROR; + failf(data, "Proxy CONNECT aborted due to select() error"); + break; + case 0: /* timeout */ + break; + default: + res = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread, &gotbytes); + if(res< 0) + /* EWOULDBLOCK */ + continue; /* go loop yourself */ + else if(res) + keepon = FALSE; + else if(gotbytes <= 0) { + keepon = FALSE; + error = SELECT_ERROR; + failf(data, "Proxy CONNECT aborted"); + } + else { + /* + * We got a whole chunk of data, which can be anything from one + * byte to a set of lines and possibly just a piece of the last + * line. + */ + int i; + + nread += gotbytes; + + if(keepon > TRUE) { + /* This means we are currently ignoring a response-body, so we + simply count down our counter and make sure to break out of + the loop when we're done! */ + cl -= gotbytes; + if(cl<=0) { + keepon = FALSE; + break; } - /* put back the letter we blanked out before */ - line_start[perline]= letter; - - perline=0; /* line starts over here */ - line_start = ptr+1; /* this skips the zero byte we wrote */ } + else + for(i = 0; i < gotbytes; ptr++, i++) { + perline++; /* amount of bytes in this line so far */ + if(*ptr=='\n') { + char letter; + int writetype; + + /* output debug if that is requested */ + if(data->set.verbose) + Curl_debug(data, CURLINFO_HEADER_IN, + line_start, (size_t)perline, conn); + + /* send the header to the callback */ + writetype = CLIENTWRITE_HEADER; + if(data->set.include_header) + writetype |= CLIENTWRITE_BODY; + + result = Curl_client_write(conn, writetype, line_start, perline); + if(result) + return result; + + /* Newlines are CRLF, so the CR is ignored as the line isn't + really terminated until the LF comes. Treat a following CR + as end-of-headers as well.*/ + + if(('\r' == line_start[0]) || + ('\n' == line_start[0])) { + /* end of response-headers from the proxy */ + if(cl && (407 == k->httpcode) && + !data->state.authproblem) { + /* If we get a 407 response code with content length + * when we have no auth problem, we must ignore the + * whole response-body */ + keepon = 2; + infof(data, "Ignore %" FORMAT_OFF_T + " bytes of response-body\n", cl); + cl -= (gotbytes - i);/* remove the remaining chunk of + what we already read */ + if(cl<=0) + /* if the whole thing was already read, we are done! */ + keepon=FALSE; + } + else + keepon = FALSE; + break; /* breaks out of for-loop, not switch() */ + } + + /* keep a backup of the position we are about to blank */ + letter = line_start[perline]; + line_start[perline]=0; /* zero terminate the buffer */ + if((checkprefix("WWW-Authenticate:", line_start) && + (401 == k->httpcode)) || + (checkprefix("Proxy-authenticate:", line_start) && + (407 == k->httpcode))) { + result = Curl_http_input_auth(conn, k->httpcode, + line_start); + if(result) + return result; + } + else if(checkprefix("Content-Length:", line_start)) { + cl = curlx_strtoofft(line_start + strlen("Content-Length:"), + NULL, 10); + } + else if(Curl_compareheader(line_start, + "Connection:", "close")) + closeConnection = TRUE; + else if(2 == sscanf(line_start, "HTTP/1.%d %d", + &subversion, + &k->httpcode)) { + /* store the HTTP code from the proxy */ + data->info.httpproxycode = k->httpcode; + } + /* put back the letter we blanked out before */ + line_start[perline]= letter; + + perline=0; /* line starts over here */ + line_start = ptr+1; /* this skips the zero byte we wrote */ + } + } } - } + break; + } /* switch */ + } /* while there's buffer left and loop is requested */ + + if(error) + return CURLE_RECV_ERROR; + + if(data->info.httpproxycode != 200) + /* Deal with the possibly already received authenticate + headers. 'newurl' is set to a new URL if we must loop. */ + Curl_http_auth_act(conn); + + if (closeConnection && data->reqdata.newurl) { + /* Connection closed by server. Don't use it anymore */ + sclose(conn->sock[sockindex]); + conn->sock[sockindex] = CURL_SOCKET_BAD; break; - } /* switch */ - } /* while there's buffer left and loop is requested */ - - if(error) - return CURLE_RECV_ERROR; - - if(data->info.httpproxycode != 200) - /* Deal with the possibly already received authenticate - headers. 'newurl' is set to a new URL if we must loop. */ - Curl_http_auth_act(conn); - - if (closeConnection && data->reqdata.newurl) { - /* Connection closed by server. Don't use it anymore */ - sclose(conn->sock[sockindex]); - conn->sock[sockindex] = CURL_SOCKET_BAD; - break; - } + } + } /* END NEGOTIATION PHASE */ } while(data->reqdata.newurl); if(200 != k->httpcode) { @@ -1423,6 +1473,11 @@ CURLcode Curl_http_connect(struct connectdata *conn, bool *done) return result; } + if (conn->bits.tunnel_connecting) { + /* nothing else to do except wait right now - we're not done here. */ + return CURLE_OK; + } + if(!data->state.this_is_a_follow) { /* this is not a followed location, get the original host name */ if (data->state.first_host) diff --git a/lib/multi.c b/lib/multi.c index 2a7f50baa..e55cb6994 100644 --- a/lib/multi.c +++ b/lib/multi.c @@ -47,6 +47,7 @@ #include "multiif.h" #include "sendf.h" #include "timeval.h" +#include "http.h" /* The last #include file should be: */ #include "memdebug.h" @@ -62,6 +63,7 @@ typedef enum { CURLM_STATE_CONNECT, /* resolve/connect has been sent off */ CURLM_STATE_WAITRESOLVE, /* awaiting the resolve to finalize */ CURLM_STATE_WAITCONNECT, /* awaiting the connect to finalize */ + CURLM_STATE_WAITPROXYCONNECT, /* awaiting proxy CONNECT to finalize */ CURLM_STATE_PROTOCONNECT, /* completing the protocol-specific connect phase */ CURLM_STATE_WAITDO, /* wait for our turn to send the request */ @@ -791,7 +793,8 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, multistate(easy, CURLM_STATE_CONNECT); result = CURLM_CALL_MULTI_PERFORM; easy->result = CURLE_OK; - } else { + } + else { easy->result = CURLE_COULDNT_CONNECT; multistate(easy, CURLM_STATE_COMPLETED); } @@ -871,10 +874,13 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, WAITDO! */ result = CURLM_CALL_MULTI_PERFORM; - if(protocol_connect) { + if(protocol_connect) multistate(easy, CURLM_STATE_WAITDO); - } else { - multistate(easy, CURLM_STATE_WAITCONNECT); + else { + if (easy->easy_conn->bits.tunnel_connecting) + multistate(easy, CURLM_STATE_WAITPROXYCONNECT); + else + multistate(easy, CURLM_STATE_WAITCONNECT); } } } @@ -903,8 +909,12 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, result = CURLM_CALL_MULTI_PERFORM; if(protocol_connect) multistate(easy, CURLM_STATE_DO); - else - multistate(easy, CURLM_STATE_WAITCONNECT); + else { + if (easy->easy_conn->bits.tunnel_connecting) + multistate(easy, CURLM_STATE_WAITPROXYCONNECT); + else + multistate(easy, CURLM_STATE_WAITCONNECT); + } } } @@ -917,6 +927,16 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi, } break; + case CURLM_STATE_WAITPROXYCONNECT: + /* this is HTTP-specific, but sending CONNECT to a proxy is HTTP... */ + easy->result = Curl_http_connect(easy->easy_conn, &protocol_connect); + + if(CURLE_OK == easy->result) { + if (!easy->easy_conn->bits.tunnel_connecting) + multistate(easy, CURLM_STATE_WAITCONNECT); + } + break; + case CURLM_STATE_WAITCONNECT: /* awaiting a completion of an asynch connect */ easy->result = Curl_is_connected(easy->easy_conn, diff --git a/lib/urldata.h b/lib/urldata.h index 6a1f7045e..2e13f6046 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -470,6 +470,8 @@ struct ConnectBits { This is implicit when SSL-protocols are used through proxies, but can also be enabled explicitly by apps */ + bool tunnel_connecting; /* TRUE while we're still waiting for a proxy CONNECT + */ bool authneg; /* TRUE when the auth phase has started, which means that we are creating a request with an auth header, but it is not the final request in the auth -- cgit v1.2.3