10000 Websocket fails to keep connected when using curl_multi API · Issue #15780 · curl/curl · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Websocket fails to keep connected when using curl_multi API #15780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
pksadiq opened this issue Dec 19, 2024 · 4 comments
Open

Websocket fails to keep connected when using curl_multi API #15780

pksadiq opened this issue Dec 19, 2024 · 4 comments
Labels
not-a-curl-bug This is not a bug in curl WebSocket

Comments

@pksadiq
Copy link
pksadiq commented Dec 19, 2024

I did this

I was trying to write a small program using libcurl websocket API. When using the curl_multi APIs, it seems to be returning after some time (instead of blocking until close/error):

C source for the test
/* curl.c
 *
 * Compile: cc -ggdb curl.c $(pkg-config --cflags --libs libcurl) -o curl
 * Run: ./curl
 *
 * Author: Mohammed Sadiq <www.sadiqpk.org>
 *
 * SPDX-License-Identifier: curl OR CC0-1.0
 */

#undef NDEBUG
#include <assert.h>
#include <curl/curl.h>
#include <string.h>
#include <unistd.h>

int
main (void)
{
  const struct curl_ws_frame *meta = NULL;
  CURLM *multi;
  CURL *easy;
  CURLMcode mc;
  CURLcode c;
  int pending, still_running;

  multi = curl_multi_init ();
  easy = curl_easy_init ();

  c = curl_easy_setopt (easy, CURLOPT_VERBOSE, 1L);
  assert (!c);

  c = curl_easy_setopt (easy, CURLOPT_URL, "wss://echo.websocket.org");
  assert (!c);
  c = curl_easy_setopt (easy, CURLOPT_CONNECT_ONLY, 2L);
  assert (!c);

  mc = curl_multi_add_handle (multi, easy);
  assert (!mc);

  do
    {
      CURLMsg *message;

      mc = curl_multi_perform (multi, &still_running);

      if (!mc)
        mc = curl_multi_poll (multi, NULL, 0, 1000, NULL);

      if (mc)
        {
          fprintf (stderr, "Error %d\n", mc);
          break;
        }

      do
        {
          message = curl_multi_info_read (multi, &pending);
          if (message && message->msg == CURLMSG_DONE)
            {
              char buffer[2048] = { 0 };
              size_t sent, rlen;

              memset (buffer, '\0', sizeof(buffer));

              /* present received data */
              c = curl_ws_recv (easy, buffer, sizeof (buffer) - 1, &rlen, &meta);
              printf ("XXXXXXXXXXXXXXXXXX receiving status: %d, data: %s\n", c, buffer);

              /* Send some data */
              c = curl_ws_send (easy, "hello", strlen ("hello"), &sent, 0, CURLWS_TEXT);
              printf ("XXXXXXXXXXXXXXXXXX sending status: %d size: %lu\n", c, sent);
            }
      } while (message);
  } while (still_running);
}

I expected the following

websocket is supposed to stay connected until the client/server initiates close request/on error. The the application should be running, and I should be receiving an echo of the message send to the websocket.org echo service.

curl/libcurl version

8.11.1-1 (as packaged in Debian) and 9fce2c5 from master (tested with jhbuild)

operating system

Debian GNU/Linux sid

@jay
Copy link
Member
jay commented Dec 20, 2024

curl_multi_info_read doesn't work like that: When you fetch a message using this function, it is removed from the internal queue so calling this function again does not return the same message again.

You've possibly received and then sent a message (there's no check for CURLE_AGAIN) and then that block is never reached again because curl_multi_info_read has already popped off that handle from its queue. IMO the way you are doing it is the wrong way to go about it. I would use normal websocket mode instead of connect-only mode. The latter is harder to use and with the former you can read messages in your writefunction and then once you have the data you need you can from that same function send messages that will be fully sent because curl_ws_send is blocking when called from a writefunction callback.

although i'm not an expert with websockets so others may want to chime in here

please see https://curl.se/libcurl/c/libcurl-ws.html and https://curl.se/libcurl/c/curl_ws_send.html

@jay jay added the not-a-curl-bug This is not a bug in curl label Dec 20, 2024
@pksadiq
Copy link
Author
pksadiq commented Dec 20, 2024

curl_multi_info_read doesn't work like that: When you fetch a message using this function, it is removed from the internal queue so calling this function again does not return the same message again.

When I connect to wss://echo.websocket.org/, I do get a message Request served by xxx without me sending anything. So the first read is supposed to get that (which occasionally errors with CURLE_AGAIN, and then the whole loop exits), after that, I'm sending the string "hello" which I'm supposed to get in subsequent request(s) - But since the loops exists once message is non-NULL nothing further happens.

I would use normal websocket mode instead of connect-only mode.

If I don't do connect-only mode. The message is always NULL, and if I run curl_ws_recv without message check, after some initial warning of CURLE_BAD_FUNCTION_ARGUMENT (until websocket is ready), I get CURLE_UNSUPPORTED_PROTOCOL with * CONNECT_ONLY is required in verbose logs

I can't do blocking mode with callbacks as I do have another loop in my program and I prefer to poll with timeout.

@jay
Copy link
Member
jay commented Dec 20, 2024

message is null because there are no more messages. The way libcurl works internally there's currently only a single message that the handle is done and that's it. Even if that wasn't so, like you add more handles or more messages types are defined in the future, then that block of code is still wrong.

The outer loop exits as well which is also wrong for this case. (edit: may be wrong for this case, depends on what his code actually does) still_running is 0 since there are no other handles running and libcurl no longer controls the transfer of your handle. However CURLOPT_CONNECT_ONLY type handles must stay in the multi to be used: If the connect only transfer is done using the multi interface, the particular easy handle must remain added to the multi handle for as long as the application wants to use it.

You have asked libcurl to "connect only" for a websocket transfer and assuming CURLMSG_DONE and message->data.result == CURLE_OK it has done that.

@pksadiq
Copy link
Author
pksadiq commented Dec 23, 2024

message is null because there are no more messages.

Well, message is always NULL, but in the verbose logs, I do see Request served by ... (I do see the message only if I do fflush() in the loop as the text has no newline), but the content is never reported with message, and it's always NULL (this is with CURLOPT_CONNECT_ONLY not set).

Anway, if curl_multi_ APIs is designed to return on message with no further wait (even with websockets), I shall stick with the easy API and handle polling socket and loop myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
not-a-curl-bug This is not a bug in curl WebSocket
Development

No branches or pull requests

3 participants
0