From afff64dbcde9cf45956f5557a8270b21c0e93dd5 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Sun, 18 Dec 2016 12:51:48 +0100 Subject: curl_easy_recv: Improve documentation and example program Follow-up to 82245ea: Fix the example program sendrecv.c (handle CURLE_AGAIN, handle incomplete send). Improve the documentation for curl_easy_recv() and curl_easy_send(). Reviewed-by: Frank Meier Assisted-by: Jay Satiro See https://github.com/curl/curl/pull/1134 --- docs/examples/sendrecv.c | 91 ++++++++++++++++++++------------ docs/libcurl/curl_easy_recv.3 | 29 ++++++---- docs/libcurl/curl_easy_send.3 | 23 +++++--- docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3 | 1 + tests/data/test556 | 2 +- tests/libtest/lib556.c | 9 ++-- 6 files changed, 97 insertions(+), 58 deletions(-) diff --git a/docs/examples/sendrecv.c b/docs/examples/sendrecv.c index 41e283cdc..662323487 100644 --- a/docs/examples/sendrecv.c +++ b/docs/examples/sendrecv.c @@ -62,10 +62,9 @@ int main(void) CURLcode res; /* Minimalistic http request */ const char *request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"; - curl_socket_t sockfd; /* socket */ - long sockextr; - size_t iolen; - curl_off_t nread; + size_t request_len = strlen(request); + curl_socket_t sockfd; + size_t nsent_total = 0; /* A general note of caution here: if you're using curl_easy_recv() or curl_easy_send() to implement HTTP or _any_ other protocol libcurl @@ -82,54 +81,76 @@ int main(void) curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); res = curl_easy_perform(curl); - if(CURLE_OK != res) { - printf("Error: %s\n", strerror(res)); + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); return 1; } - /* Extract the socket from the curl handle - we'll need it for waiting. - * Note that this API takes a pointer to a 'long' while we use - * curl_socket_t for sockets otherwise. - */ - res = curl_easy_getinfo(curl, CURLINFO_LASTSOCKET, &sockextr); + /* Extract the socket from the curl handle - we'll need it for waiting. */ + res = curl_easy_getinfo(curl, CURLINFO_ACTIVESOCKET, &sockfd); - if(CURLE_OK != res) { + if(res != CURLE_OK) { printf("Error: %s\n", curl_easy_strerror(res)); return 1; } - sockfd = (curl_socket_t)sockextr; + printf("Sending request.\n"); - /* wait for the socket to become ready for sending */ - if(!wait_on_socket(sockfd, 0, 60000L)) { - printf("Error: timeout.\n"); - return 1; - } + do { + /* Warning: This example program may loop indefinitely. + * A production-quality program must define a timeout and exit this loop + * as soon as the timeout has expired. */ + size_t nsent; + do { + nsent = 0; + res = curl_easy_send(curl, request + nsent_total, + request_len - nsent_total, &nsent); + nsent_total += nsent; - puts("Sending request."); - /* Send the request. Real applications should check the iolen - * to see if all the request has been sent */ - res = curl_easy_send(curl, request, strlen(request), &iolen); + if(res == CURLE_AGAIN && !wait_on_socket(sockfd, 0, 60000L)) { + printf("Error: timeout.\n"); + return 1; + } + } while(res == CURLE_AGAIN); - if(CURLE_OK != res) { - printf("Error: %s\n", curl_easy_strerror(res)); - return 1; - } - puts("Reading response."); + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); + return 1; + } - /* read the response */ - for(;;) { - char buf[1024]; + printf("Sent %" CURL_FORMAT_CURL_OFF_T " bytes.\n", + (curl_off_t)nsent); + + } while(nsent_total < request_len); - wait_on_socket(sockfd, 1, 60000L); - res = curl_easy_recv(curl, buf, 1024, &iolen); + printf("Reading response.\n"); - if(CURLE_OK != res) + for(;;) { + /* Warning: This example program may loop indefinitely (see above). */ + char buf[1024]; + size_t nread; + do { + nread = 0; + res = curl_easy_recv(curl, buf, sizeof(buf), &nread); + + if(res == CURLE_AGAIN && !wait_on_socket(sockfd, 1, 60000L)) { + printf("Error: timeout.\n"); + return 1; + } + } while(res == CURLE_AGAIN); + + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); break; + } - nread = (curl_off_t)iolen; + if(nread == 0) { + /* end of the response */ + break; + } - printf("Received %" CURL_FORMAT_CURL_OFF_T " bytes.\n", nread); + printf("Received %" CURL_FORMAT_CURL_OFF_T " bytes.\n", + (curl_off_t)nread); } /* always cleanup */ diff --git a/docs/libcurl/curl_easy_recv.3 b/docs/libcurl/curl_easy_recv.3 index 3560cc511..7a812dd00 100644 --- a/docs/libcurl/curl_easy_recv.3 +++ b/docs/libcurl/curl_easy_recv.3 @@ -46,12 +46,21 @@ calling \fIcurl_easy_perform(3)\fP or \fIcurl_multi_perform(3)\fP. Note that \fIcurl_easy_recv(3)\fP does not work on connections that were created without this option. -You must ensure that the socket has data to read before calling -\fIcurl_easy_recv(3)\fP, otherwise the call will return \fBCURLE_AGAIN\fP - -the socket is used in non-blocking mode internally. Use -\fIcurl_easy_getinfo(3)\fP with \fICURLINFO_ACTIVESOCKET(3)\fP to obtain the -socket; use your operating system facilities like \fIselect(2)\fP to check if -it has any data you can read. +The call will return \fBCURLE_AGAIN\fP if there is no data to read - the +socket is used in non-blocking mode internally. When \fBCURLE_AGAIN\fP is +returned, use your operating system facilities like \fIselect(2)\fP to wait +for data. The socket may be obtained using \fIcurl_easy_getinfo(3)\fP with +\fICURLINFO_ACTIVESOCKET(3)\fP. + +Wait on the socket only if \fIcurl_easy_recv(3)\fP returns \fBCURLE_AGAIN\fP. +The reason for this is libcurl or the SSL library may internally cache some +data, therefore you should call \fIcurl_easy_recv(3)\fP until all data is +read which would include any cached data. + +Furthermore if you wait on the socket and it tells you there is data to read, +\fIcurl_easy_recv(3)\fP may return \fBCURLE_AGAIN\fP if the only data that was +read was for internal SSL processing, and no other data is available. + .SH AVAILABILITY Added in 7.18.2. .SH RETURN VALUE @@ -60,13 +69,13 @@ On success, returns \fBCURLE_OK\fP, stores the received data into On failure, returns the appropriate error code. -If there is no data to read, the function returns \fBCURLE_AGAIN\fP. Use your -operating system facilities to wait until the data is ready, and retry. +The function may return \fBCURLE_AGAIN\fP. In this case, use your operating +system facilities to wait until data can be read, and retry. -Reading exactly 0 bytes would indicate a closed connection. +Reading exactly 0 bytes indicates a closed connection. If there's no socket available to use from the previous transfer, this function -returns CURLE_UNSUPPORTED_PROTOCOL. +returns \fBCURLE_UNSUPPORTED_PROTOCOL\fP. .SH EXAMPLE See \fBsendrecv.c\fP in \fBdocs/examples\fP directory for usage example. .SH "SEE ALSO" diff --git a/docs/libcurl/curl_easy_send.3 b/docs/libcurl/curl_easy_send.3 index 2a7443985..47976f391 100644 --- a/docs/libcurl/curl_easy_send.3 +++ b/docs/libcurl/curl_easy_send.3 @@ -40,16 +40,20 @@ connection set-up. The variable \fBn\fP points to will receive the number of sent bytes. To establish the connection, set \fICURLOPT_CONNECT_ONLY(3)\fP option before -calling \fIcurl_easy_perform(3)\fP or \fIcurl_multi_perform()\fP. Note that +calling \fIcurl_easy_perform(3)\fP or \fIcurl_multi_perform(3)\fP. Note that \fIcurl_easy_send(3)\fP will not work on connections that were created without this option. -You must ensure that the socket is writable before calling -\fIcurl_easy_send(3)\fP, otherwise the call will return \fBCURLE_AGAIN\fP - -the socket is used in non-blocking mode internally. Use -\fIcurl_easy_getinfo(3)\fP with \fICURLINFO_ACTIVESOCKET(3)\fP to obtain the -socket; use your operating system facilities like \fIselect(2)\fP to check if -it can be written to. +The call will return \fBCURLE_AGAIN\fP if it's not possible to send data right +now - the socket is used in non-blocking mode internally. When +\fBCURLE_AGAIN\fP is returned, use your operating system facilities like +\fIselect(2)\fP to wait until the socket is writable. The socket may be +obtained using \fIcurl_easy_getinfo(3)\fP with \fICURLINFO_ACTIVESOCKET(3)\fP. + +Furthermore if you wait on the socket and it tells you it's writable, +\fIcurl_easy_send(3)\fP may return \fBCURLE_AGAIN\fP if the only data that was +sent was for internal SSL processing, and no other data could be sent. + .SH AVAILABILITY Added in 7.18.2. .SH RETURN VALUE @@ -59,8 +63,11 @@ wanted to send. On failure, returns the appropriate error code. +This function may return \fBCURLE_AGAIN\fP. In this case, use your operating +system facilities to wait until the socket is writable, and retry. + If there's no socket available to use from the previous transfer, this function -returns CURLE_UNSUPPORTED_PROTOCOL. +returns \fBCURLE_UNSUPPORTED_PROTOCOL\fP. .SH EXAMPLE See \fBsendrecv.c\fP in \fBdocs/examples\fP directory for usage example. .SH "SEE ALSO" diff --git a/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3 b/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3 index 61289e8d2..973c2aedc 100644 --- a/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3 +++ b/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3 @@ -49,3 +49,4 @@ Added in 7.15.2 Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. .SH "SEE ALSO" .BR CURLOPT_VERBOSE "(3), " CURLOPT_HTTPPROXYTUNNEL "(3), " +.BR curl_easy_recv "(3), " curl_easy_send "(3) " diff --git a/tests/data/test556 b/tests/data/test556 index 55c7c91c3..549b9a4a2 100644 --- a/tests/data/test556 +++ b/tests/data/test556 @@ -8,7 +8,7 @@ HTTP GET -HTTP/1.1 200 OK +HTTP/1.1 200 OK swsclose Server: test-server/fake Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT Content-Length: 6 diff --git a/tests/libtest/lib556.c b/tests/libtest/lib556.c index a26101940..f06c5299c 100644 --- a/tests/libtest/lib556.c +++ b/tests/libtest/lib556.c @@ -77,11 +77,10 @@ int test(char *URL) if(!res) { /* we assume that sending always work */ - size_t total=0; do { /* busy-read like crazy */ - res = curl_easy_recv(curl, buf, 1024, &iolen); + res = curl_easy_recv(curl, buf, sizeof(buf), &iolen); #ifdef TPF sleep(1); /* avoid ctl-10 dump */ @@ -92,10 +91,12 @@ int test(char *URL) if(!write(STDOUT_FILENO, buf, iolen)) break; } - total += iolen; - } while(((res == CURLE_OK) || (res == CURLE_AGAIN)) && (total < 129)); + } while((res == CURLE_OK && iolen != 0) || (res == CURLE_AGAIN)); } + + if(res != CURLE_OK || iolen != 0) + return TEST_ERR_FAILURE; } test_cleanup: -- cgit v1.2.3