diff options
Diffstat (limited to 'tests/http/clients')
-rw-r--r-- | tests/http/clients/.gitignore | 8 | ||||
-rw-r--r-- | tests/http/clients/Makefile.am | 71 | ||||
-rw-r--r-- | tests/http/clients/Makefile.inc | 30 | ||||
-rw-r--r-- | tests/http/clients/h2-download.c | 275 | ||||
-rw-r--r-- | tests/http/clients/h2-serverpush.c | 271 | ||||
-rw-r--r-- | tests/http/clients/ws-data.c | 263 | ||||
-rw-r--r-- | tests/http/clients/ws-pingpong.c | 158 |
7 files changed, 1076 insertions, 0 deletions
diff --git a/tests/http/clients/.gitignore b/tests/http/clients/.gitignore new file mode 100644 index 000000000..02084e18a --- /dev/null +++ b/tests/http/clients/.gitignore @@ -0,0 +1,8 @@ +# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. +# +# SPDX-License-Identifier: curl + +h2-serverpush +h2-download +ws-data +ws-pingpong
\ No newline at end of file diff --git a/tests/http/clients/Makefile.am b/tests/http/clients/Makefile.am new file mode 100644 index 000000000..386da5a22 --- /dev/null +++ b/tests/http/clients/Makefile.am @@ -0,0 +1,71 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### + +AUTOMAKE_OPTIONS = foreign nostdinc + + +# Specify our include paths here, and do it relative to $(top_srcdir) and +# $(top_builddir), to ensure that these paths which belong to the library +# being currently built and tested are searched before the library which +# might possibly already be installed in the system. +# +# $(top_srcdir)/include is for libcurl's external include files + +AM_CPPFLAGS = -I$(top_srcdir)/include \ + -DCURL_DISABLE_DEPRECATION + +LIBDIR = $(top_builddir)/lib + +# Avoid libcurl obsolete stuff +AM_CPPFLAGS += -DCURL_NO_OLDIES + +if USE_CPPFLAG_CURL_STATICLIB +AM_CPPFLAGS += -DCURL_STATICLIB +endif + +# Prevent LIBS from being used for all link targets +LIBS = $(BLANK_AT_MAKETIME) + +# Dependencies +if USE_EXPLICIT_LIB_DEPS +LDADD = $(LIBDIR)/libcurl.la @LIBCURL_LIBS@ +else +LDADD = $(LIBDIR)/libcurl.la +endif + +# This might hold -Werror +CFLAGS += @CURL_CFLAG_EXTRAS@ + +# Makefile.inc provides the check_PROGRAMS and COMPLICATED_EXAMPLES defines +include Makefile.inc + +all: $(check_PROGRAMS) + +CHECKSRC = $(CS_$(V)) +CS_0 = @echo " RUN " $@; +CS_1 = +CS_ = $(CS_0) + +checksrc: + $(CHECKSRC)(@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(srcdir) $(srcdir)/*.c) diff --git a/tests/http/clients/Makefile.inc b/tests/http/clients/Makefile.inc new file mode 100644 index 000000000..e0abf0a33 --- /dev/null +++ b/tests/http/clients/Makefile.inc @@ -0,0 +1,30 @@ +#*************************************************************************** +# _ _ ____ _ +# Project ___| | | | _ \| | +# / __| | | | |_) | | +# | (__| |_| | _ <| |___ +# \___|\___/|_| \_\_____| +# +# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at https://curl.se/docs/copyright.html. +# +# You may opt to use, copy, modify, merge, publish, distribute and/or sell +# copies of the Software, and permit persons to whom the Software is +# furnished to do so, under the terms of the COPYING file. +# +# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +# KIND, either express or implied. +# +# SPDX-License-Identifier: curl +# +########################################################################### + +# These are all libcurl example programs to be test compiled +check_PROGRAMS = \ + h2-serverpush \ + h2-download \ + ws-data \ + ws-pingpong diff --git a/tests/http/clients/h2-download.c b/tests/http/clients/h2-download.c new file mode 100644 index 000000000..d87ee01e1 --- /dev/null +++ b/tests/http/clients/h2-download.c @@ -0,0 +1,275 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* <DESC> + * HTTP/2 server push + * </DESC> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* somewhat unix-specific */ +#include <sys/time.h> +#include <unistd.h> + +/* curl stuff */ +#include <curl/curl.h> +#include <curl/mprintf.h> + +#ifndef CURLPIPE_MULTIPLEX +#error "too old libcurl, cannot do HTTP/2 server push!" +#endif + +static int verbose = 1; + +static +int my_trace(CURL *handle, curl_infotype type, + char *data, size_t size, + void *userp) +{ + const char *text; + (void)handle; /* prevent compiler warning */ + (void)userp; + + switch(type) { + case CURLINFO_TEXT: + fprintf(stderr, "== Info: %s", data); + /* FALLTHROUGH */ + default: /* in case a new one is introduced to shock us */ + return 0; + + case CURLINFO_HEADER_OUT: + text = "=> Send header"; + break; + case CURLINFO_DATA_OUT: + if(verbose <= 1) + return 0; + text = "=> Send data"; + break; + case CURLINFO_HEADER_IN: + text = "<= Recv header"; + break; + case CURLINFO_DATA_IN: + if(verbose <= 1) + return 0; + text = "<= Recv data"; + break; + } + + fprintf(stderr, "%s, %lu bytes (0x%lx)\n", + text, (unsigned long)size, (unsigned long)size); + return 0; +} + +struct transfer { + int idx; + CURL *easy; + char filename[128]; + FILE *out; + curl_off_t recv_size; + curl_off_t pause_at; + int paused; + int resumed; + int done; +}; + +static size_t transfer_count; +static struct transfer *transfers; + +static struct transfer *get_transfer_for_easy(CURL *easy) +{ + size_t i; + for(i = 0; i < transfer_count; ++i) { + if(easy == transfers[i].easy) + return &transfers[i]; + } + return NULL; +} + +static size_t my_write_cb(char *buf, size_t nitems, size_t buflen, + void *userdata) +{ + struct transfer *t = userdata; + ssize_t nwritten; + + if(!t->resumed && + t->recv_size < t->pause_at && + ((curl_off_t)(t->recv_size + (nitems * buflen)) >= t->pause_at)) { + fprintf(stderr, "transfer %d: PAUSE\n", t->idx); + t->paused = 1; + return CURL_WRITEFUNC_PAUSE; + } + + if(!t->out) { + curl_msnprintf(t->filename, sizeof(t->filename)-1, "download_%u.data", + t->idx); + t->out = fopen(t->filename, "wb"); + if(!t->out) + return 0; + } + + nwritten = fwrite(buf, nitems, buflen, t->out); + if(nwritten < 0) { + fprintf(stderr, "transfer %d: write failure\n", t->idx); + return 0; + } + t->recv_size += nwritten; + return (size_t)nwritten; +} + +static int setup(CURL *hnd, const char *url, struct transfer *t) +{ + curl_easy_setopt(hnd, CURLOPT_URL, url); + curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); + + curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb); + curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t); + + /* please be verbose */ + if(verbose) { + curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace); + } + +#if (CURLPIPE_MULTIPLEX > 0) + /* wait for pipe connection to confirm */ + curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); +#endif + return 0; /* all is good */ +} + +/* + * Download a file over HTTP/2, take care of server push. + */ +int main(int argc, char *argv[]) +{ + CURLM *multi_handle; + int active_transfers; + struct CURLMsg *m; + const char *url; + size_t i; + long pause_offset; + struct transfer *t; + + if(argc != 4) { + fprintf(stderr, "usage: h2-download count pause-offset url\n"); + return 2; + } + + transfer_count = (size_t)strtol(argv[1], NULL, 10); + pause_offset = strtol(argv[2], NULL, 10); + url = argv[3]; + + transfers = calloc(transfer_count, sizeof(*transfers)); + if(!transfers) { + fprintf(stderr, "error allocating transfer structs\n"); + return 1; + } + + multi_handle = curl_multi_init(); + curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); + + active_transfers = 0; + for(i = 0; i < transfer_count; ++i) { + t = &transfers[i]; + t->idx = (int)i; + t->pause_at = (curl_off_t)pause_offset * i; + t->easy = curl_easy_init(); + if(!t->easy || setup(t->easy, url, t)) { + fprintf(stderr, "setup of transfer #%d failed\n", (int)i); + return 1; + } + curl_multi_add_handle(multi_handle, t->easy); + ++active_transfers; + } + + do { + int still_running; /* keep number of running handles */ + CURLMcode mc = curl_multi_perform(multi_handle, &still_running); + + if(still_running) { + /* wait for activity, timeout or "nothing" */ + mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); + fprintf(stderr, "curl_multi_poll() -> %d\n", mc); + } + + if(mc) + break; + + /* + * A little caution when doing server push is that libcurl itself has + * created and added one or more easy handles but we need to clean them up + * when we are done. + */ + do { + int msgq = 0; + m = curl_multi_info_read(multi_handle, &msgq); + if(m && (m->msg == CURLMSG_DONE)) { + CURL *e = m->easy_handle; + active_transfers--; + curl_multi_remove_handle(multi_handle, e); + t = get_transfer_for_easy(e); + if(t) { + t->done = 1; + } + else + curl_easy_cleanup(e); + } + else { + /* nothing happending, resume one paused transfer if there is one */ + for(i = 0; i < transfer_count; ++i) { + t = &transfers[i]; + if(!t->done && t->paused) { + t->resumed = 1; + t->paused = 0; + curl_easy_pause(t->easy, CURLPAUSE_CONT); + fprintf(stderr, "transfer %d: RESUME\n", t->idx); + break; + } + } + + } + } while(m); + + } while(active_transfers); /* as long as we have transfers going */ + + for(i = 0; i < transfer_count; ++i) { + t = &transfers[i]; + if(t->out) { + fclose(t->out); + t->out = NULL; + } + if(t->easy) { + curl_easy_cleanup(t->easy); + t->easy = NULL; + } + } + free(transfers); + + curl_multi_cleanup(multi_handle); + + return 0; +} diff --git a/tests/http/clients/h2-serverpush.c b/tests/http/clients/h2-serverpush.c new file mode 100644 index 000000000..5a1eb3c91 --- /dev/null +++ b/tests/http/clients/h2-serverpush.c @@ -0,0 +1,271 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* <DESC> + * HTTP/2 server push + * </DESC> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* somewhat unix-specific */ +#include <sys/time.h> +#include <unistd.h> + +/* curl stuff */ +#include <curl/curl.h> +#include <curl/mprintf.h> + +#ifndef CURLPIPE_MULTIPLEX +#error "too old libcurl, cannot do HTTP/2 server push!" +#endif + +static +void dump(const char *text, unsigned char *ptr, size_t size, + char nohex) +{ + size_t i; + size_t c; + + unsigned int width = 0x10; + + if(nohex) + /* without the hex output, we can fit more on screen */ + width = 0x40; + + fprintf(stderr, "%s, %lu bytes (0x%lx)\n", + text, (unsigned long)size, (unsigned long)size); + + for(i = 0; i<size; i += width) { + + fprintf(stderr, "%4.4lx: ", (unsigned long)i); + + if(!nohex) { + /* hex not disabled, show it */ + for(c = 0; c < width; c++) + if(i + c < size) + fprintf(stderr, "%02x ", ptr[i + c]); + else + fputs(" ", stderr); + } + + for(c = 0; (c < width) && (i + c < size); c++) { + /* check for 0D0A; if found, skip past and start a new line of output */ + if(nohex && (i + c + 1 < size) && ptr[i + c] == 0x0D && + ptr[i + c + 1] == 0x0A) { + i += (c + 2 - width); + break; + } + fprintf(stderr, "%c", + (ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.'); + /* check again for 0D0A, to avoid an extra \n if it's at width */ + if(nohex && (i + c + 2 < size) && ptr[i + c + 1] == 0x0D && + ptr[i + c + 2] == 0x0A) { + i += (c + 3 - width); + break; + } + } + fputc('\n', stderr); /* newline */ + } +} + +static +int my_trace(CURL *handle, curl_infotype type, + char *data, size_t size, + void *userp) +{ + const char *text; + (void)handle; /* prevent compiler warning */ + (void)userp; + switch(type) { + case CURLINFO_TEXT: + fprintf(stderr, "== Info: %s", data); + /* FALLTHROUGH */ + default: /* in case a new one is introduced to shock us */ + return 0; + + case CURLINFO_HEADER_OUT: + text = "=> Send header"; + break; + case CURLINFO_DATA_OUT: + text = "=> Send data"; + break; + case CURLINFO_SSL_DATA_OUT: + text = "=> Send SSL data"; + break; + case CURLINFO_HEADER_IN: + text = "<= Recv header"; + break; + case CURLINFO_DATA_IN: + text = "<= Recv data"; + break; + case CURLINFO_SSL_DATA_IN: + text = "<= Recv SSL data"; + break; + } + + dump(text, (unsigned char *)data, size, 1); + return 0; +} + +#define OUTPUTFILE "download_0.data" + +static int setup(CURL *hnd, const char *url) +{ + FILE *out = fopen(OUTPUTFILE, "wb"); + if(!out) + /* failed */ + return 1; + + curl_easy_setopt(hnd, CURLOPT_URL, url); + curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L); + curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYHOST, 0L); + + curl_easy_setopt(hnd, CURLOPT_WRITEDATA, out); + + /* please be verbose */ + curl_easy_setopt(hnd, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(hnd, CURLOPT_DEBUGFUNCTION, my_trace); + +#if (CURLPIPE_MULTIPLEX > 0) + /* wait for pipe connection to confirm */ + curl_easy_setopt(hnd, CURLOPT_PIPEWAIT, 1L); +#endif + return 0; /* all is good */ +} + +/* called when there's an incoming push */ +static int server_push_callback(CURL *parent, + CURL *easy, + size_t num_headers, + struct curl_pushheaders *headers, + void *userp) +{ + char *headp; + size_t i; + int *transfers = (int *)userp; + char filename[128]; + FILE *out; + static unsigned int count = 0; + int rv; + + (void)parent; /* we have no use for this */ + curl_msnprintf(filename, sizeof(filename)-1, "push%u", count++); + + /* here's a new stream, save it in a new file for each new push */ + out = fopen(filename, "wb"); + if(!out) { + /* if we cannot save it, deny it */ + fprintf(stderr, "Failed to create output file for push\n"); + rv = CURL_PUSH_DENY; + goto out; + } + + /* write to this file */ + curl_easy_setopt(easy, CURLOPT_WRITEDATA, out); + + fprintf(stderr, "**** push callback approves stream %u, got %lu headers!\n", + count, (unsigned long)num_headers); + + for(i = 0; i<num_headers; i++) { + headp = curl_pushheader_bynum(headers, i); + fprintf(stderr, "**** header %lu: %s\n", (unsigned long)i, headp); + } + + headp = curl_pushheader_byname(headers, ":path"); + if(headp) { + fprintf(stderr, "**** The PATH is %s\n", headp /* skip :path + colon */); + } + + (*transfers)++; /* one more */ + rv = CURL_PUSH_OK; + +out: + return rv; +} + + +/* + * Download a file over HTTP/2, take care of server push. + */ +int main(int argc, char *argv[]) +{ + CURL *easy; + CURLM *multi_handle; + int transfers = 1; /* we start with one */ + struct CURLMsg *m; + const char *url; + + if(argc != 2) { + fprintf(stderr, "need URL as argument\n"); + return 2; + } + url = argv[1]; + + multi_handle = curl_multi_init(); + curl_multi_setopt(multi_handle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX); + curl_multi_setopt(multi_handle, CURLMOPT_PUSHFUNCTION, server_push_callback); + curl_multi_setopt(multi_handle, CURLMOPT_PUSHDATA, &transfers); + + easy = curl_easy_init(); + if(setup(easy, url)) { + fprintf(stderr, "failed\n"); + return 1; + } + + curl_multi_add_handle(multi_handle, easy); + do { + int still_running; /* keep number of running handles */ + CURLMcode mc = curl_multi_perform(multi_handle, &still_running); + + if(still_running) + /* wait for activity, timeout or "nothing" */ + mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); + + if(mc) + break; + + /* + * A little caution when doing server push is that libcurl itself has + * created and added one or more easy handles but we need to clean them up + * when we are done. + */ + do { + int msgq = 0; + m = curl_multi_info_read(multi_handle, &msgq); + if(m && (m->msg == CURLMSG_DONE)) { + CURL *e = m->easy_handle; + transfers--; + curl_multi_remove_handle(multi_handle, e); + curl_easy_cleanup(e); + } + } while(m); + + } while(transfers); /* as long as we have transfers going */ + + curl_multi_cleanup(multi_handle); + + return 0; +} diff --git a/tests/http/clients/ws-data.c b/tests/http/clients/ws-data.c new file mode 100644 index 000000000..1c61d59f7 --- /dev/null +++ b/tests/http/clients/ws-data.c @@ -0,0 +1,263 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* <DESC> + * Websockets data echos + * </DESC> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* somewhat unix-specific */ +#include <sys/time.h> +#include <unistd.h> + + +/* curl stuff */ +#include <curl/curl.h> +#include "../../../lib/curl_setup.h" + +#ifdef USE_WEBSOCKETS + +static +void dump(const char *text, unsigned char *ptr, size_t size, + char nohex) +{ + size_t i; + size_t c; + + unsigned int width = 0x10; + + if(nohex) + /* without the hex output, we can fit more on screen */ + width = 0x40; + + fprintf(stderr, "%s, %lu bytes (0x%lx)\n", + text, (unsigned long)size, (unsigned long)size); + + for(i = 0; i<size; i += width) { + + fprintf(stderr, "%4.4lx: ", (unsigned long)i); + + if(!nohex) { + /* hex not disabled, show it */ + for(c = 0; c < width; c++) + if(i + c < size) + fprintf(stderr, "%02x ", ptr[i + c]); + else + fputs(" ", stderr); + } + + for(c = 0; (c < width) && (i + c < size); c++) { + /* check for 0D0A; if found, skip past and start a new line of output */ + if(nohex && (i + c + 1 < size) && ptr[i + c] == 0x0D && + ptr[i + c + 1] == 0x0A) { + i += (c + 2 - width); + break; + } + fprintf(stderr, "%c", + (ptr[i + c] >= 0x20) && (ptr[i + c]<0x80)?ptr[i + c]:'.'); + /* check again for 0D0A, to avoid an extra \n if it's at width */ + if(nohex && (i + c + 2 < size) && ptr[i + c + 1] == 0x0D && + ptr[i + c + 2] == 0x0A) { + i += (c + 3 - width); + break; + } + } + fputc('\n', stderr); /* newline */ + } +} + +static CURLcode send_binary(CURL *curl, char *buf, size_t buflen) +{ + size_t nwritten; + CURLcode result = + curl_ws_send(curl, buf, buflen, &nwritten, 0, CURLWS_BINARY); + fprintf(stderr, "ws: send_binary(len=%ld) -> %d, %ld\n", + (long)buflen, result, (long)nwritten); + return result; +} + +static CURLcode recv_binary(CURL *curl, char *exp_data, size_t exp_len) +{ + struct curl_ws_frame *frame; + char recvbuf[256]; + size_t r_offset, nread; + CURLcode result; + + fprintf(stderr, "recv_binary: expected payload %ld bytes\n", (long)exp_len); + r_offset = 0; + while(1) { + result = curl_ws_recv(curl, recvbuf, sizeof(recvbuf), &nread, &frame); + if(result == CURLE_AGAIN) { + fprintf(stderr, "EAGAIN, sleep, try again\n"); + usleep(100*1000); + continue; + } + fprintf(stderr, "ws: curl_ws_recv(offset=%ld, len=%ld) -> %d, %ld\n", + (long)r_offset, (long)sizeof(recvbuf), result, (long)nread); + if(result) { + return result; + } + if(!(frame->flags & CURLWS_BINARY)) { + fprintf(stderr, "recv_data: wrong frame, got %ld bytes rflags %x\n", + (long)nread, frame->flags); + return CURLE_RECV_ERROR; + } + if(frame->offset != (curl_off_t)r_offset) { + fprintf(stderr, "recv_data: frame offset, expected %ld, got %ld\n", + (long)r_offset, (long)frame->offset); + return CURLE_RECV_ERROR; + } + if(frame->bytesleft != (curl_off_t)(exp_len - r_offset - nread)) { + fprintf(stderr, "recv_data: frame bytesleft, expected %ld, got %ld\n", + (long)(exp_len - r_offset - nread), (long)frame->bytesleft); + return CURLE_RECV_ERROR; + } + if(r_offset + nread > exp_len) { + fprintf(stderr, "recv_data: data length, expected %ld, now at %ld\n", + (long)exp_len, (long)(r_offset + nread)); + return CURLE_RECV_ERROR; + } + if(memcmp(exp_data + r_offset, recvbuf, nread)) { + fprintf(stderr, "recv_data: data differs, offset=%ld, len=%ld\n", + (long)r_offset, (long)nread); + dump("expected:", (unsigned char *)exp_data + r_offset, nread, 0); + dump("received:", (unsigned char *)recvbuf, nread, 0); + return CURLE_RECV_ERROR; + } + r_offset += nread; + if(r_offset >= exp_len) { + fprintf(stderr, "recv_data: frame complete\n"); + break; + } + } + return CURLE_OK; +} + +/* just close the connection */ +static void websocket_close(CURL *curl) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); +} + +static CURLcode data_echo(CURL *curl, size_t plen_min, size_t plen_max) +{ + CURLcode res; + size_t len; + char *send_buf; + size_t i; + + send_buf = calloc(1, plen_max); + if(!send_buf) + return CURLE_OUT_OF_MEMORY; + for(i = 0; i < plen_max; ++i) { + send_buf[i] = (char)('0' + ((int)i % 10)); + } + + for(len = plen_min; len <= plen_max; ++len) { + res = send_binary(curl, send_buf, len); + if(res) + goto out; + res = recv_binary(curl, send_buf, len); + if(res) { + fprintf(stderr, "recv_data(len=%ld) -> %d\n", (long)len, res); + goto out; + } + } + +out: + if(!res) + websocket_close(curl); + free(send_buf); + return res; +} + +#endif + +int main(int argc, char *argv[]) +{ +#ifdef USE_WEBSOCKETS + CURL *curl; + CURLcode res = CURLE_OK; + const char *url; + curl_off_t l1, l2; + size_t plen_min, plen_max; + + + if(argc != 4) { + fprintf(stderr, "usage: ws-data url minlen maxlen\n"); + return 2; + } + url = argv[1]; + l1 = strtol(argv[2], NULL, 10); + if(l1 < 0) { + fprintf(stderr, "minlen must be >= 0, got %ld\n", (long)l1); + return 2; + } + l2 = strtol(argv[3], NULL, 10); + if(l2 < 0) { + fprintf(stderr, "maxlen must be >= 0, got %ld\n", (long)l2); + return 2; + } + plen_min = l1; + plen_max = l2; + if(plen_max < plen_min) { + fprintf(stderr, "maxlen must be >= minlen, got %ld-%ld\n", + (long)plen_min, (long)plen_max); + return 2; + } + + curl_global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, url); + + /* use the callback style */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "ws-data"); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ + res = curl_easy_perform(curl); + fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res); + if(res == CURLE_OK) + res = data_echo(curl, plen_min, plen_max); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return (int)res; + +#else /* USE_WEBSOCKETS */ + (void)argc; + (void)argv; + fprintf(stderr, "websockets not enabled in libcurl\n"); + return 1; +#endif /* !USE_WEBSOCKETS */ +} diff --git a/tests/http/clients/ws-pingpong.c b/tests/http/clients/ws-pingpong.c new file mode 100644 index 000000000..4307dea73 --- /dev/null +++ b/tests/http/clients/ws-pingpong.c @@ -0,0 +1,158 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +/* <DESC> + * Websockets pingpong + * </DESC> + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* somewhat unix-specific */ +#include <sys/time.h> +#include <unistd.h> + + +/* curl stuff */ +#include <curl/curl.h> +#include "../../../lib/curl_setup.h" + +#ifdef USE_WEBSOCKETS + +static CURLcode ping(CURL *curl, const char *send_payload) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, send_payload, strlen(send_payload), &sent, 0, + CURLWS_PING); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); + + return result; +} + +static CURLcode recv_pong(CURL *curl, const char *exected_payload) +{ + size_t rlen; + struct curl_ws_frame *meta; + char buffer[256]; + CURLcode result = curl_ws_recv(curl, buffer, sizeof(buffer), &rlen, &meta); + if(result) { + fprintf(stderr, "ws: curl_ws_recv returned %u, received %ld\n", + (int)result, (long)rlen); + return result; + } + + if(!(meta->flags & CURLWS_PONG)) { + fprintf(stderr, "recv_pong: wrong frame, got %d bytes rflags %x\n", + (int)rlen, meta->flags); + return CURLE_RECV_ERROR; + } + + fprintf(stderr, "ws: got PONG back\n"); + if(rlen == strlen(exected_payload) && + !memcmp(exected_payload, buffer, rlen)) { + fprintf(stderr, "ws: got the same payload back\n"); + return CURLE_OK; + } + fprintf(stderr, "ws: did NOT get the same payload back\n"); + return CURLE_RECV_ERROR; +} + +/* just close the connection */ +static void websocket_close(CURL *curl) +{ + size_t sent; + CURLcode result = + curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE); + fprintf(stderr, + "ws: curl_ws_send returned %u, sent %u\n", (int)result, (int)sent); +} + +static CURLcode pingpong(CURL *curl, const char *payload) +{ + CURLcode res; + int i; + + res = ping(curl, payload); + if(res) + return res; + for(i = 0; i < 10; ++i) { + fprintf(stderr, "Receive pong\n"); + res = recv_pong(curl, payload); + if(res == CURLE_AGAIN) { + usleep(100*1000); + continue; + } + websocket_close(curl); + return res; + } + websocket_close(curl); + return CURLE_RECV_ERROR; +} + +#endif + +int main(int argc, char *argv[]) +{ +#ifdef USE_WEBSOCKETS + CURL *curl; + CURLcode res = CURLE_OK; + const char *url, *payload; + + if(argc != 3) { + fprintf(stderr, "usage: ws-pingpong url payload\n"); + return 2; + } + url = argv[1]; + payload = argv[2]; + + curl_global_init(CURL_GLOBAL_ALL); + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, url); + + /* use the callback style */ + curl_easy_setopt(curl, CURLOPT_USERAGENT, "ws-pingpong"); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */ + res = curl_easy_perform(curl); + fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res); + if(res == CURLE_OK) + res = pingpong(curl, payload); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return (int)res; + +#else /* USE_WEBSOCKETS */ + (void)argc; + (void)argv; + fprintf(stderr, "websockets not enabled in libcurl\n"); + return 1; +#endif /* !USE_WEBSOCKETS */ +} |