Отправить сообщение WebSockets на сервер - PullRequest
0 голосов
/ 03 января 2019

Я пытаюсь работать с API одного устройства, но он использует интерфейс WS с принудительным заголовком Origin, что доставляет мне проблемы.

В Chrome я могу открыть консоль, пока страницас правильным источником Происхождения загружено, создайте соединение WS и отправьте / получите сообщения без затруднений: WS connection created, messages sent and received Обратите внимание, что отправленные сообщения (выделены зеленым цветом) всегда подтверждаются сервером.

Для справки, это то, что происходит, если я создаю соединение на другой странице, что приводит к несоответствию заголовка источника, сообщаемому как 404: WS connection created with wrong Origin header

Чтобы обойти эту проблему, яобратился к C, потому что остальная часть моей программы написана в этом так или иначе.Это код, который у меня есть сейчас, основанный главным образом на этом ответе :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <libwebsockets.h>

#define KGRN "\033[0;32;32m"
#define KCYN "\033[0;36m"
#define KRED "\033[0;32;31m"
#define KYEL "\033[1;33m"
#define KBLU "\033[0;32;34m"
#define KCYN_L "\033[1;36m"
#define KBRN "\033[0;33m"
#define RESET "\033[0m"

static int destroy_flag = 0;
static int connection_flag = 0;
static int writeable_flag = 0;

static void INT_HANDLER(int signo) {
    destroy_flag = 1;
}

struct session_data {
    int fd;
};

struct pthread_routine_tool {
    struct lws_context *context;
    struct lws *wsi;
};

static int websocket_write_back(struct lws *wsi_in, char *str, int str_size_in) 
{
    if (str == NULL || wsi_in == NULL)
        return -1;

    int n;
    int len;
    char *out = NULL;

    if (str_size_in < 1) 
        len = strlen(str);
    else
        len = str_size_in;

    out = (char *)malloc(sizeof(char)*(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING));
    //* setup the buffer*/
    memcpy (out + LWS_SEND_BUFFER_PRE_PADDING, str, len );
    //* write out*/
    n = lws_write(wsi_in, out + LWS_SEND_BUFFER_PRE_PADDING, len, LWS_WRITE_TEXT);

    printf(KBLU"[websocket_write_back] %s\n"RESET, str);
    //* free the buffer*/
    free(out);

    return n;
}


static int ws_service_callback(
                         struct lws *wsi,
                         enum lws_callback_reasons reason, void *user,
                         void *in, size_t len)
{

    switch (reason) {

        case LWS_CALLBACK_CLIENT_ESTABLISHED:
            printf(KYEL"[Main Service] Connect with server success.\n"RESET);
            connection_flag = 1;
            break;

        case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
            printf(KRED"[Main Service] Connect with server error.\n"RESET);
            destroy_flag = 1;
            connection_flag = 0;
            break;

        case LWS_CALLBACK_CLOSED:
            printf(KYEL"[Main Service] LWS_CALLBACK_CLOSED\n"RESET);
            destroy_flag = 1;
            connection_flag = 0;
            break;

        case LWS_CALLBACK_CLIENT_RECEIVE:
            printf(KCYN_L"[Main Service] Client recvived:%s\n"RESET, (char *)in);

            if (writeable_flag)
                destroy_flag = 1;

            break;
        case LWS_CALLBACK_CLIENT_WRITEABLE :
            printf(KYEL"[Main Service] On writeable is called. send byebye message\n"RESET);
            websocket_write_back(wsi, "{\"command\":\"subscribe\",\"identifier\":\"{\\\"channel\\\":\\\"DevicesChannel\\\",\\\"share_token\\\":\\\"D0E91\\\"}\"}", -1);
            websocket_write_back(wsi, "{\"command\":\"message\",\"identifier\":\"{\\\"channel\\\":\\\"DevicesChannel\\\",\\\"share_token\\\":\\\"D0E91\\\"}\",\"data\":\"{\\\"value\\\":100,\\\"action\\\":\\\"set_buzz\\\"}\"}", -1);
            writeable_flag = 1;
            break;

        default:
            break;
    }

    return 0;
}

static void *pthread_routine(void *tool_in)
{
    struct pthread_routine_tool *tool = tool_in;

    printf(KBRN"[pthread_routine] Good day. This is pthread_routine.\n"RESET);

    //* waiting for connection with server done.*/
    while(!connection_flag)
        usleep(1000*20);

    //*Send greeting to server*/ 
    lws_callback_on_writable(tool->wsi);

}

int main(void)
{
    //* register the signal SIGINT handler */
    struct sigaction act;
    act.sa_handler = INT_HANDLER;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction( SIGINT, &act, 0);


    struct lws_context *context = NULL;
    struct lws_context_creation_info info;
    struct lws *wsi = NULL;
    struct lws_protocols protocol;

    memset(&info, 0, sizeof info);
    info.port = CONTEXT_PORT_NO_LISTEN;
    info.iface = NULL;
    info.protocols = &protocol;
    info.ssl_cert_filepath = NULL;
    info.ssl_private_key_filepath = NULL;
    info.extensions = lws_get_internal_extensions();
    info.gid = -1;
    info.uid = -1;
    info.options = 0;

    protocol.name  = "websockets";
    protocol.callback = &ws_service_callback;
    protocol.per_session_data_size = sizeof(struct session_data);
    protocol.rx_buffer_size = 0;
    protocol.id = 0;
    protocol.user = NULL;

    context = lws_create_context(&info);
    printf(KRED"[Main] context created.\n"RESET);

    if (context == NULL) {
        printf(KRED"[Main] context is NULL.\n"RESET);
        return -1;
    }


    wsi = lws_client_connect(context, "mobu1.herokuapp.com", 443, 1,
        "/cable", "mobu1.herokuapp.com", "link.motorbunny.com",
    if (wsi == NULL) {
        printf(KRED"[Main] wsi create error.\n"RESET);
        return -1;
    }

    printf(KGRN"[Main] wsi create success.\n"RESET);

    struct pthread_routine_tool tool;
    tool.wsi = wsi;
    tool.context = context;

    pthread_t pid;
    pthread_create(&pid, NULL, pthread_routine, &tool);
    pthread_detach(pid);

    while(!destroy_flag)
    {
        lws_service(context, 50);
    }

    lws_context_destroy(context);

    return 0;
}

Результат запуска вышеуказанной программы следующий: WS messages from server

Как вы можете видеть, периодические эхо-запросы от сервера к моему клиенту перехватываются, но lws_callback_on_writable(wsi);, похоже, не действует, так как обратный вызов LWS_CALLBACK_CLIENT_WRITEABLE никогда не вызывается.Кроме того, если я звоню websocket_write_back() напрямую куда-либо еще, кажется, что он ничего не отправляет на сервер, и подтверждения также нет.

Есть ли что-то очевидное, что я делаю неправильно?

РЕДАКТИРОВАТЬ 1: Я нашел это аккуратное wscat, где я могу повторить результаты из Chrome: wscat works Теперь вопрос, как я могу связать это с моимC программа таким образом, что он может ждать приветственное сообщение от сервера, а затем отправлять два сообщения?И еще лучше, как оставаться на связи, чтобы моя программа могла отправлять несколько команд в разные моменты времени, не выполняя рукопожатие все время?

Ответы [ 2 ]

0 голосов
/ 09 января 2019

Причина, по которой обратный вызов LWS_CALLBACK_CLIENT_WRITEABLE никогда не вызывался, заключалась в том, что этот конкретный сервер использует нестандартное рукопожатие.Итак, чтобы обойти это, я разветвил вилку libwsclient и изменил функцию проверки рукопожатия, чтобы она не вызывала сбоев при несовпадении.Я также добавил необязательный заголовок Origin.

Теперь все, что мне нужно сделать в моей исходной программе, это

wsclient *client;
char sync_str[6];
void mb_send(int power, char* type)
{
  char cmd[2048];
  sprintf (cmd, "{\"command\":\"message\",\"identifier\":\"{\\\"channel\\\":\\\"DevicesChannel\\\",\\\"share_token\\\":\\\"%s\\\"}\",\"data\":\"{\\\"value\\\":%d,\\\"action\\\":\\\"set_%s\\\"}\"}",sync_str,power,type);
  libwsclient_send(client,cmd);
}
void mb_connect()
{
  char cmd[2048];
  sprintf (cmd, "{\"command\":\"subscribe\",\"identifier\":\"{\\\"channel\\\":\\\"DevicesChannel\\\",\\\"share_token\\\":\\\"%s\\\"}\"}",sync_str);
  libwsclient_send(client,cmd);
  mb_send(0,"buzz");
}
int nop()
{
  return 0;
}
int main()
{
  client = libwsclient_new_extra("wss://mobu1.herokuapp.com/cable","https://link.motorbunny.com");
  if(!client) {
    fprintf(stderr, "Unable to initialize new WS client.\n");
    exit(1);
  }

  libwsclient_onopen(client, &nop);
  libwsclient_onmessage(client, &nop);
  libwsclient_onerror(client, &nop);
  libwsclient_onclose(client, &nop);

  libwsclient_run(client);

  ...
  mb_connect();
  ...  
  mb_send(200,"buzz");
  mb_send(40,"twirl");
  ...
  mb_send(0,"buzz");
  mb_send(0,"twirl");
}
0 голосов
/ 04 января 2019

Я нашел уродливый хак, заставляющий мою программу на C отправлять сообщения WebSocket на сервер через программу wsta .

Для этого требуется текстовый файл, в который моя программа будет добавляться всякий раз, когда онахочет отправить сообщение на сервер.Затем новые строки выбираются в фоновом режиме tail -f и передаются в wsta, что поддерживает соединение.Вывод может быть перенаправлен на /dev/null, чтобы вывод wsta не загрязнял вывод моей программы, или отправлялся в файл, если необходимо проанализировать ответы с сервера.Весь сценарий для выполнения этой работы будет выглядеть следующим образом (или вы можете использовать канал FIFO с cat вместо файла с tail):

#!/bin/bash
touch commands.txt
tail commands.txt -f -n 0 | wsta --header "Origin: https://link.motorbunny.com" "wss://mobu1.herokuapp.com/cable" &> /dev/null &
./program

В программе на C мне просто нужнозаписать в commands.txt файл:

FILE* cmd;
char sync_str[6];
void mb_connect()
{
  fprintf (cmd, "{\"command\":\"subscribe\",\"identifier\":\"{\\\"channel\\\":\\\"DevicesChannel\\\",\\\"share_token\\\":\\\"%s\\\"}\"}\n",sync_str);
  fflush(cmd);
}
void mb_send(int power, char* type)
{
  fprintf (cmd, "{\"command\":\"message\",\"identifier\":\"{\\\"channel\\\":\\\"DevicesChannel\\\",\\\"share_token\\\":\\\"%s\\\"}\",\"data\":\"{\\\"value\\\":%d,\\\"action\\\":\\\"set_%s\\\"}\"}\n",sync_str,power,type);
  fflush(cmd);
}

int main()
{
  cmd = fopen ("commands.txt","w");
  ...
  mb_connect();
  ...
  mb_send(200,"buzz");
  ...
  mb_send(0,"buzz");
}
...