efl_net_dialer_websocket: EFL now does WebSocket!

The Efl.Net.Dialer.Websocket is just like other Efl.Net.Dialers: you
can dial, you can close, monitor connected/address resolved and so
on. And you can use WebSocket primitives and events such as
text_send(), binary_send(), ping() and close_request() (since
WebSockets use a close process where you should state a close
reason). See efl_net_dialer_websocket_example.c

Even if WebSocket is a message-based protocol (like "packets" from
UDP), you can use efl_net_dialer_websocket_streaming_mode_set() to
tell it to handle text or binary messages as a stream. Then all the
Efl.Io.Reader and Efl.Io.Writer APIs work as expected, see
efl_io_copier_example.c updates.
This commit is contained in:
Gustavo Sverzut Barbieri 2016-08-30 00:28:00 -03:00
parent 57e765a758
commit e12afd772c
10 changed files with 3136 additions and 5 deletions

View File

@ -12,6 +12,7 @@ ecore_con_eolian_files = \
lib/ecore_con/efl_net_dialer.eo \
lib/ecore_con/efl_net_dialer_tcp.eo \
lib/ecore_con/efl_net_dialer_http.eo \
lib/ecore_con/efl_net_dialer_websocket.eo \
lib/ecore_con/efl_net_server.eo \
lib/ecore_con/efl_net_server_fd.eo \
lib/ecore_con/efl_net_server_tcp.eo \
@ -75,6 +76,7 @@ lib/ecore_con/efl_net_socket_tcp.c \
lib/ecore_con/efl_net_dialer.c \
lib/ecore_con/efl_net_dialer_tcp.c \
lib/ecore_con/efl_net_dialer_http.c \
lib/ecore_con/efl_net_dialer_websocket.c \
lib/ecore_con/efl_net_server.c \
lib/ecore_con/efl_net_server_fd.c \
lib/ecore_con/efl_net_server_tcp.c

View File

@ -51,3 +51,5 @@
/efl_io_queue_example
/efl_net_server_example
/efl_net_dialer_http_example
/efl_net_dialer_websocket_example
/efl_net_dialer_websocket_autobahntestee

View File

@ -81,7 +81,9 @@ efl_io_copier_example \
efl_io_copier_simple_example \
efl_io_queue_example \
efl_net_server_example \
efl_net_dialer_http_example
efl_net_dialer_http_example \
efl_net_dialer_websocket_example \
efl_net_dialer_websocket_autobahntestee
ECORE_COMMON_LDADD = \
$(top_builddir)/src/lib/ecore/libecore.la \
@ -297,6 +299,12 @@ efl_net_server_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_dialer_http_example_SOURCES = efl_net_dialer_http_example.c
efl_net_dialer_http_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_dialer_websocket_example_SOURCES = efl_net_dialer_websocket_example.c
efl_net_dialer_websocket_example_LDADD = $(ECORE_CON_COMMON_LDADD)
efl_net_dialer_websocket_autobahntestee_SOURCES = efl_net_dialer_websocket_autobahntestee.c
efl_net_dialer_websocket_autobahntestee_LDADD = $(ECORE_CON_COMMON_LDADD)
SRCS = \
ecore_animator_example.c \
ecore_buffer_example.c \
@ -347,7 +355,9 @@ efl_io_copier_example.c \
efl_io_copier_simple_example.c \
efl_io_queue_example.c \
efl_net_server_example.c \
efl_net_dialer_http_example.c
efl_net_dialer_http_example.c \
efl_net_dialer_websocket_example.c \
efl_net_dialer_websocket_autobahntestee.c
DATA_FILES = red.png Makefile.examples

View File

@ -71,7 +71,7 @@ static void
_dialer_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "INFO: error: %d\n", *perr);
fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
/* no need to quit as copier will get a "eos" event and emit "done" */
}
@ -217,7 +217,7 @@ static void
_copier_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "INFO: error: %d\n", *perr);
fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
}
@ -332,6 +332,7 @@ static const Ecore_Getopt options = {
":stdin: to read from stdin.\n"
"tcp://IP:PORT to connect using TCP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n"
"http://address to do a GET request\n"
"ws://address or wss:// to do WebSocket request (must send some data once connected)\n"
"",
"input-file"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
@ -342,6 +343,7 @@ static const Ecore_Getopt options = {
":none: to not use a destination object.\n"
"tcp://IP:PORT to connect using TCP and an IPv4 (A.B.C.D:PORT) or IPv6 ([A:B:C:D::E]:PORT).\n"
"http://address to do a PUT request\n"
"ws://address or wss:// to do WebSocket request\n"
"",
"output-file"),
ECORE_GETOPT_SENTINEL
@ -472,6 +474,31 @@ main(int argc, char **argv)
goto end_input;
}
}
else if (strncmp(input_fname, "ws://", strlen("ws://")) == 0 ||
strncmp(input_fname, "wss://", strlen("wss://")) == 0)
{
Eina_Error err;
input = efl_add(EFL_NET_DIALER_WEBSOCKET_CLASS, ecore_main_loop_get(),
efl_net_dialer_websocket_streaming_mode_set(efl_self, EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_TEXT),
efl_event_callback_array_add(efl_self, input_cbs(), NULL), /* optional */
efl_event_callback_array_add(efl_self, dialer_cbs(), NULL) /* optional */
);
if (!input)
{
fprintf(stderr, "ERROR: could not create WebSocket Dialer.\n");
retval = EXIT_FAILURE;
goto end;
}
err = efl_net_dialer_dial(input, input_fname);
if (err)
{
fprintf(stderr, "ERROR: could not WebSocket dial %s: %s\n",
input_fname, eina_error_msg_get(err));
goto end_input;
}
}
else
{
/* regular file, open with flags: read-only and close-on-exec */
@ -605,6 +632,31 @@ main(int argc, char **argv)
goto end_output;
}
}
else if (strncmp(output_fname, "ws://", strlen("ws://")) == 0 ||
strncmp(output_fname, "wss://", strlen("wss://")) == 0)
{
Eina_Error err;
output = efl_add(EFL_NET_DIALER_WEBSOCKET_CLASS, ecore_main_loop_get(),
efl_net_dialer_websocket_streaming_mode_set(efl_self, EFL_NET_DIALER_WEBSOCKET_STREAMING_MODE_TEXT),
efl_event_callback_array_add(efl_self, output_cbs(), NULL), /* optional */
efl_event_callback_array_add(efl_self, dialer_cbs(), NULL) /* optional */
);
if (!output)
{
fprintf(stderr, "ERROR: could not create WebSocket Dialer.\n");
retval = EXIT_FAILURE;
goto end_input;
}
err = efl_net_dialer_dial(output, output_fname);
if (err)
{
fprintf(stderr, "ERROR: could not WebSocket dial %s: %s\n",
output_fname, eina_error_msg_get(err));
goto end_output;
}
}
else
{
/* regular file, open with flags: write-only, close-on-exec,

View File

@ -0,0 +1,693 @@
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
#include <ctype.h>
static int retval = EXIT_SUCCESS;
static char *address = NULL;
static char *agent = "efl_net_dialer_websocket";
static unsigned int start_index = 0;
static unsigned int end_index = UINT32_MAX;
static unsigned int current_index = 0;
static Eina_Bool no_report_update = EINA_FALSE;
static Eina_List *case_tuples = NULL;
static Eina_Bool verbose = 0;
static Eo *pending = NULL;
/* https://www.w3.org/International/questions/qa-forms-utf-8 */
static Eina_Bool
_utf8_check(const char *text)
{
const unsigned char * bytes = (const unsigned char *)text;
while (*bytes)
{
const unsigned char c = bytes[0];
/* ascii: [\x09\x0A\x0D\x20-\x7E] */
if (((c >= 0x20) && (c <= 0x7e)) ||
(c == 0x09) || (c == 0x0a) || (c == 0x0d))
{
bytes += 1;
continue;
}
/* autobahnsuite says 0x7f is valid */
if (c == 0x7f)
{
bytes += 1;
continue;
}
#define VALUE_BYTE_CHECK(x) ((x >= 0x80) && (x <= 0xbf))
/* non-overlong 2-byte: [\xC2-\xDF][\x80-\xBF] */
if ((c >= 0xc2) && (c <= 0xdf))
{
if (VALUE_BYTE_CHECK(bytes[1]))
{
bytes += 2;
continue;
}
}
/* excluding overlongs: \xE0[\xA0-\xBF][\x80-\xBF] */
if (c == 0xe0)
{
const unsigned char d = bytes[1];
if ((d >= 0xa0) && (d <= 0xbf))
{
if (VALUE_BYTE_CHECK(bytes[2]))
{
bytes += 3;
continue;
}
}
}
/* straight 3-byte: [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} */
if (((c >= 0xe1) && (c <= 0xec)) ||
(c == 0xee) || (c == 0xef))
{
if (VALUE_BYTE_CHECK(bytes[1]) && VALUE_BYTE_CHECK(bytes[2]))
{
bytes += 3;
continue;
}
}
/* excluding surrogates: \xED[\x80-\x9F][\x80-\xBF] */
if (c == 0xed)
{
const unsigned char d = bytes[1];
if ((d >= 0x80) && (d <= 0x9f))
{
if (VALUE_BYTE_CHECK(bytes[2]))
{
bytes += 3;
continue;
}
}
}
/* planes 1-3: \xF0[\x90-\xBF][\x80-\xBF]{2} */
if (c == 0xf0)
{
const unsigned char d = bytes[1];
if ((d >= 0x90) && (d <= 0xbf))
{
if (VALUE_BYTE_CHECK(bytes[2]) && VALUE_BYTE_CHECK(bytes[3]))
{
bytes += 4;
continue;
}
}
}
/* planes 4-15: [\xF1-\xF3][\x80-\xBF]{3} */
if ((c >= 0xf1) && (c <= 0xf3))
{
if (VALUE_BYTE_CHECK(bytes[1]) && VALUE_BYTE_CHECK(bytes[2]) && VALUE_BYTE_CHECK(bytes[3]))
{
bytes += 4;
continue;
}
}
/* plane 16: \xF4[\x80-\x8F][\x80-\xBF]{2} */
if (c == 0xf4)
{
const unsigned char d = bytes[1];
if ((d >= 0x80) && (d <= 0x8f))
{
if (VALUE_BYTE_CHECK(bytes[2]) && VALUE_BYTE_CHECK(bytes[3]))
{
bytes += 4;
continue;
}
}
}
if (verbose) fprintf(stderr, "INFO: failed unicode byte #%zd '%s'\n", (const char*)bytes - text, text);
return EINA_FALSE;
}
return EINA_TRUE;
}
static void
_ws_pong(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
const char *text = event->info;
if (!verbose) return;
fprintf(stderr, "INFO: %s got PONG: %s\n",
efl_name_get(dialer), text);
}
static void
_ws_closed_reason(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
Efl_Net_Dialer_Websocket_Closed_Reason *reason = event->info;
if (!_utf8_check(reason->message))
{
efl_net_dialer_websocket_close_request(dialer, EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_PROTOCOL_ERROR, "invalid UTF-8");
if (verbose) fprintf(stderr, "INFO: %s got CLOSE with invalid UTF-8\n", efl_name_get(dialer));
}
if (!verbose) return;
fprintf(stderr, "INFO: %s got CLOSE: %4d '%s'\n",
efl_name_get(dialer), reason->reason, reason->message);
}
static void
_ws_message_text(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
const char *text = event->info;
if (!verbose) return;
fprintf(stderr, "INFO: %s got TEXT %zd bytes:\n%s\n",
efl_name_get(dialer), strlen(text), text);
}
static void
_ws_message_binary(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
const Eina_Slice *slice = event->info;
size_t i;
if (!verbose) return;
fprintf(stderr, "INFO: %s got BINARY %zd bytes\n",
efl_name_get(dialer), slice->len);
for (i = 0; i < slice->len; i++)
{
const int c = slice->bytes[i];
if (isprint(c))
fprintf(stderr, " %#4x(%c)", c, c);
else
fprintf(stderr, " %#4x", c);
}
fprintf(stderr, "\n");
}
static void
_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
if (!verbose) return;
fprintf(stderr, "INFO: %s closed\n", efl_name_get(dialer));
}
static void
_eos(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
if (!verbose) return;
fprintf(stderr, "INFO: %s eos\n", efl_name_get(dialer));
}
static void
_connected(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
if (!verbose) return;
fprintf(stderr, "INFO: %s connected %s\n",
efl_name_get(dialer),
efl_net_dialer_address_dial_get(dialer));
}
static void
_error(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
const Eina_Error *perr = event->info;
fprintf(stderr, "ERROR: %s error: %d '%s'\n",
efl_name_get(dialer), *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
}
static void
_del(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
if (pending == dialer)
pending = NULL;
}
EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs,
{ EFL_NET_DIALER_WEBSOCKET_EVENT_PONG, _ws_pong },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_CLOSED_REASON, _ws_closed_reason },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_TEXT, _ws_message_text },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_BINARY, _ws_message_binary },
{ EFL_NET_DIALER_EVENT_CONNECTED, _connected },
{ EFL_NET_DIALER_EVENT_ERROR, _error },
{ EFL_IO_CLOSER_EVENT_CLOSED, _closed },
{ EFL_IO_READER_EVENT_EOS, _eos },
{ EFL_EVENT_DEL, _del });
static Eo *
_websocket_new(const char *name)
{
Eo *dialer;
dialer = efl_add(EFL_NET_DIALER_WEBSOCKET_CLASS, ecore_main_loop_get(),
efl_name_set(efl_self, name),
efl_event_callback_array_add(efl_self, dialer_cbs(), NULL));
if (!dialer)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not create WebSockets dialer '%s'\n", name);
return NULL;
}
pending = dialer;
return dialer;
}
static void
_closed_quit(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
efl_del(dialer);
ecore_main_loop_quit();
}
static void
_tests_finished(void)
{
Eo *dialer;
char url[4096];
int len;
Eina_Error err;
if (no_report_update)
{
if (verbose)
fprintf(stderr, "INFO: tests finished, user required to not update the reports\n");
ecore_main_loop_quit();
return;
}
case_tuples = eina_list_remove(case_tuples, case_tuples);
len = snprintf(url, sizeof(url), "%s/updateReports?agent=%s",
address, agent);
if (len < 0)
{
fprintf(stderr, "ERROR: could not create URL "
"'%s/updateReports?agent=%s': %s",
address, agent, strerror(errno));
ecore_main_loop_quit();
return;
}
else if ((size_t)len > sizeof(url))
{
fprintf(stderr, "ERROR: could not create URL "
"'%s/updateReports?agent=%s': no space.",
address, agent);
ecore_main_loop_quit();
return;
}
dialer = _websocket_new("update-reports");
if (!dialer)
{
ecore_main_loop_quit();
return;
}
efl_event_callback_add(dialer, EFL_IO_CLOSER_EVENT_CLOSED, _closed_quit, NULL);
err = efl_net_dialer_dial(dialer, url);
if (err != 0)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not dial '%s': %s",
url, eina_error_msg_get(err));
efl_del(dialer);
ecore_main_loop_quit();
return;
}
if (!verbose) return;
fprintf(stderr, "INFO: %s '%s'\n",
efl_name_get(dialer), efl_net_dialer_address_dial_get(dialer));
}
static void
_echo_text(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
const char *text = event->info;
if (!_utf8_check(text))
{
if (verbose) fprintf(stderr, "INFO: invalid UTF-8 sequence '%s'. Close the connection.\n", text);
efl_net_dialer_websocket_close_request(dialer, EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_INCONSISTENT_DATA, "invalid UTF-8");
return;
}
efl_net_dialer_websocket_text_send(dialer, text);
}
static void
_echo_binary(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
const Eina_Slice *slice = event->info;
efl_net_dialer_websocket_binary_send(dialer, *slice);
}
static Eina_Bool _websocket_test_next_case_tuple(void);
static void
_test_next_case_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
efl_del(dialer);
if (!_websocket_test_next_case_tuple())
_tests_finished();
}
EFL_CALLBACKS_ARRAY_DEFINE(_test_next_case_tuple_cbs,
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_TEXT, _echo_text },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_BINARY, _echo_binary },
{ EFL_IO_CLOSER_EVENT_CLOSED, _test_next_case_closed });
static Eina_Bool
_websocket_test_next_case_tuple(void)
{
Eo *dialer;
char url[4096];
char name[256];
char *str;
int len;
Eina_Error err;
if (!case_tuples)
return EINA_FALSE;
str = case_tuples->data;
case_tuples = eina_list_remove_list(case_tuples, case_tuples);
len = snprintf(url, sizeof(url), "%s/runCase?casetuple=%s&agent=%s",
address, str, agent);
if (len < 0)
{
fprintf(stderr, "ERROR: could not create URL "
"'%s/runCase?casetuple=%s&agent=%s': %s",
address, str, agent, strerror(errno));
free(str);
return EINA_FALSE;
}
else if ((size_t)len > sizeof(url))
{
fprintf(stderr, "ERROR: could not create URL "
"'%s/runCase?casetuple=%s&agent=%s': no space.",
address, str, agent);
free(str);
return EINA_FALSE;
}
snprintf(name, sizeof(name), "test_case=%s", str);
free(str);
dialer = _websocket_new(name);
if (!dialer) return EINA_FALSE;
efl_event_callback_array_add(dialer, _test_next_case_tuple_cbs(), NULL);
err = efl_net_dialer_dial(dialer, url);
if (err != 0)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not dial '%s': %s",
url, eina_error_msg_get(err));
efl_del(dialer);
return EINA_FALSE;
}
fprintf(stderr, "TEST: %s '%s'\n", efl_name_get(dialer), efl_net_dialer_address_dial_get(dialer));
return EINA_TRUE;
}
static Eina_Bool _websocket_test_index(unsigned int idx);
static void
_test_index_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
efl_del(dialer);
if (!_websocket_test_index(current_index + 1))
_tests_finished();
}
EFL_CALLBACKS_ARRAY_DEFINE(_test_index_cbs,
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_TEXT, _echo_text },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_BINARY, _echo_binary },
{ EFL_IO_CLOSER_EVENT_CLOSED, _test_index_closed });
static Eina_Bool
_websocket_test_index(unsigned int idx)
{
Eo *dialer;
char url[4096];
char name[64];
int len;
Eina_Error err;
if (idx > end_index)
return EINA_FALSE;
len = snprintf(url, sizeof(url), "%s/runCase?case=%u&agent=%s",
address, idx, agent);
if (len < 0)
{
fprintf(stderr, "ERROR: could not create URL "
"'%s/runCase?case=%u&agent=%s': %s",
address, idx, agent, strerror(errno));
return EINA_FALSE;
}
else if ((size_t)len > sizeof(url))
{
fprintf(stderr, "ERROR: could not create URL "
"'%s/runCase?case=%u&agent=%s': no space.",
address, idx, agent);
return EINA_FALSE;
}
snprintf(name, sizeof(name), "test_case=%u", idx);
dialer = _websocket_new(name);
if (!dialer) return EINA_FALSE;
efl_event_callback_array_add(dialer, _test_index_cbs(), NULL);
err = efl_net_dialer_dial(dialer, url);
if (err != 0)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not dial '%s': %s",
url, eina_error_msg_get(err));
efl_del(dialer);
return EINA_FALSE;
}
current_index = idx;
fprintf(stderr, "TEST: %s '%s'\n", efl_name_get(dialer), efl_net_dialer_address_dial_get(dialer));
return EINA_TRUE;
}
static void
_load_tests_text(void *data EINA_UNUSED, const Efl_Event *event)
{
const char *text = event->info;
unsigned int n = strtoul(text, NULL, 10);
if (start_index == 0)
start_index = 1;
else if (start_index > n)
start_index = n;
if (end_index == 0 || end_index > n)
end_index = n;
if (!verbose) return;
fprintf(stderr, "INFO: test count: %u, start_index=%u, end_index=%u\n",
n, start_index, end_index);
}
static void
_load_tests_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
Eo *dialer = event->object;
efl_del(dialer);
if (!_websocket_test_index(start_index))
_tests_finished();
}
EFL_CALLBACKS_ARRAY_DEFINE(_load_tests_cbs,
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_TEXT, _load_tests_text },
{ EFL_IO_CLOSER_EVENT_CLOSED, _load_tests_closed });
static Eina_Bool
_websocket_load_tests(void)
{
Eo *dialer;
char url[4096];
int len;
Eina_Error err;
len = snprintf(url, sizeof(url), "%s/getCaseCount", address);
if (len < 0)
{
fprintf(stderr, "ERROR: could not create URL '%s/getCaseCount': %s",
address, strerror(errno));
return EINA_FALSE;
}
else if ((size_t)len > sizeof(url))
{
fprintf(stderr, "ERROR: could not create URL '%s/getCaseCount': no space.",
address);
return EINA_FALSE;
}
dialer = _websocket_new("get-case-count");
if (!dialer) return EINA_FALSE;
efl_event_callback_array_add(dialer, _load_tests_cbs(), NULL);
err = efl_net_dialer_dial(dialer, url);
if (err != 0)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not dial '%s': %s",
url, eina_error_msg_get(err));
efl_del(dialer);
return EINA_FALSE;
}
if (verbose) fprintf(stderr, "INFO: %s '%s'\n", efl_name_get(dialer), efl_net_dialer_address_dial_get(dialer));
return EINA_TRUE;
}
static const Ecore_Getopt options = {
"efl_net_dialer_websocket_autobahntestee", /* program name */
NULL, /* usage line */
"1", /* version */
"(C) 2016 Enlightenment Project", /* copyright */
"BSD 2-Clause", /* license */
/* long description, may be multiline and contain \n */
"Use Efl_Net_Dialer_Websocket to implement a testee client for the Autobahn Test Suite."
"\n"
"Autobahn Test Suite http://autobahn.ws/testsuite provides a fully automated test suite to verify client and server implementations of the WebSocket Protocol for specification conformance and implementation robustness."
"\n"
"This is a client to talk to their test server, that should be executed as:\n"
" wstest -m fuzzingserver\n"
"\n",
EINA_FALSE,
{
ECORE_GETOPT_STORE_UINT('s', "start-index", "when running batch, specifies the start (first) index"),
ECORE_GETOPT_STORE_UINT('e', "end-index", "when running batch, specifies the end (last) index"),
ECORE_GETOPT_STORE_TRUE('n', "no-report-update", "do not trigger autobahn to update report"),
ECORE_GETOPT_STORE_TRUE('v', "verbose", "print messages"),
ECORE_GETOPT_APPEND('t', "test-case", "the test-case tuple such as '1.2.8'", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_STORE_STR('a', "agent", "the agent identifier"),
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The address (URL) to dial, such as ws://127.0.0.1:9001", "address"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char **argv)
{
Eina_Bool quit_option = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_UINT(start_index),
ECORE_GETOPT_VALUE_UINT(end_index),
ECORE_GETOPT_VALUE_BOOL(no_report_update),
ECORE_GETOPT_VALUE_BOOL(verbose),
ECORE_GETOPT_VALUE_LIST(case_tuples),
ECORE_GETOPT_VALUE_STR(agent),
/* standard block to provide version, copyright, license and help */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
/* positional argument */
ECORE_GETOPT_VALUE_STR(address),
ECORE_GETOPT_VALUE_NONE /* sentinel */
};
int args;
Eina_Bool r;
ecore_init();
ecore_con_init();
ecore_con_url_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
{
fputs("ERROR: Could not parse command line options.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (quit_option) goto end;
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
if (args < 0)
{
fputs("ERROR: Could not parse positional arguments.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (case_tuples)
r = _websocket_test_next_case_tuple();
else if (start_index == end_index)
r = _websocket_test_index(start_index);
else
r = _websocket_load_tests();
if (r)
{
ecore_main_loop_begin();
if (verbose) fprintf(stderr, "INFO: main loop finished. retval=%d\n", retval);
}
if (pending)
efl_del(pending);
end:
ecore_con_url_shutdown();
ecore_con_shutdown();
ecore_shutdown();
return retval;
}

View File

@ -0,0 +1,384 @@
#define EFL_BETA_API_SUPPORT 1
#define EFL_EO_API_SUPPORT 1
#include <Ecore.h>
#include <Ecore_Con.h>
#include <Ecore_Getopt.h>
#include <fcntl.h>
#include <ctype.h>
static int retval = EXIT_SUCCESS;
static int lines_text = 0;
static int lines_binary = 0;
static void
_dummy_send(Eo *dialer, Eina_Bool text, size_t lines)
{
size_t len = lines * 80;
char *buf = malloc(len + 1);
const size_t az_range = 'Z' - 'A';
size_t i;
for (i = 0; i < lines; i++) {
char *ln = buf + i * 80;
uint8_t chr;
snprintf(ln, 11, "%9zd ", i + 1);
if (text)
chr = (i % az_range) + 'A';
else
chr = i & 0xff;
memset(ln + 10, chr, 69);
ln[79] = '\n';
}
buf[len] = '\0';
if (text)
efl_net_dialer_websocket_text_send(dialer, buf);
else
{
Eina_Slice slice = {.mem = buf, .len = len};
efl_net_dialer_websocket_binary_send(dialer, slice);
}
free(buf);
}
static void
_ws_pong(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: got PONG: %s\n", (const char *)event->info);
efl_net_dialer_websocket_close_request(event->object,
EFL_NET_DIALER_WEBSOCKET_CLOSE_REASON_NORMAL,
"close it!");
}
static void
_ws_closed_reason(void *data EINA_UNUSED, const Efl_Event *event)
{
Efl_Net_Dialer_Websocket_Closed_Reason *reason = event->info;
fprintf(stderr, "INFO: got CLOSE: %4d '%s'\n",
reason->reason, reason->message);
}
static void
_ws_message_text(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: got TEXT:\n%s\n", (const char *)event->info);
if (lines_text < 5)
_dummy_send(event->object, EINA_TRUE, ++lines_text);
else
_dummy_send(event->object, EINA_FALSE, ++lines_binary);
}
static void
_ws_message_binary(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Slice *slice = event->info;
size_t i;
fprintf(stderr, "INFO: got BINARY %zd bytes\n", slice->len);
for (i = 0; i < slice->len; i++)
{
const int c = slice->bytes[i];
if (isprint(c))
fprintf(stderr, " %#4x(%c)", c, c);
else
fprintf(stderr, " %#4x", c);
}
fprintf(stderr, "\n");
if (lines_binary < 5)
_dummy_send(event->object, EINA_FALSE, ++lines_binary);
else
efl_net_dialer_websocket_ping(event->object, "will close on pong");
}
static void
_closed(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: closed %s\n",
efl_name_get(event->object));
ecore_main_loop_quit();
}
static void
_eos(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: eos %s\n",
efl_name_get(event->object));
}
static void
_connected(void *data EINA_UNUSED, const Efl_Event *event)
{
Eina_Stringshare *protocol;
Eina_Iterator *itr;
fprintf(stderr, "INFO: connected %s\n",
efl_net_dialer_address_dial_get(event->object));
itr = efl_net_dialer_websocket_response_protocols_get(event->object);
EINA_ITERATOR_FOREACH(itr, protocol)
fprintf(stderr, "INFO: server protocol: %s\n", protocol);
eina_iterator_free(itr);
_dummy_send(event->object, EINA_TRUE, ++lines_text);
}
static void
_resolved(void *data EINA_UNUSED, const Efl_Event *event)
{
fprintf(stderr, "INFO: resolved %s => %s\n",
efl_net_dialer_address_dial_get(event->object),
efl_net_socket_address_remote_get(event->object));
}
static void
_error(void *data EINA_UNUSED, const Efl_Event *event)
{
const Eina_Error *perr = event->info;
fprintf(stderr, "INFO: error: %d '%s'\n", *perr, eina_error_msg_get(*perr));
retval = EXIT_FAILURE;
ecore_main_loop_quit();
}
EFL_CALLBACKS_ARRAY_DEFINE(dialer_cbs,
{ EFL_NET_DIALER_WEBSOCKET_EVENT_PONG, _ws_pong },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_CLOSED_REASON, _ws_closed_reason },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_TEXT, _ws_message_text },
{ EFL_NET_DIALER_WEBSOCKET_EVENT_MESSAGE_BINARY, _ws_message_binary },
{ EFL_NET_DIALER_EVENT_CONNECTED, _connected },
{ EFL_NET_DIALER_EVENT_RESOLVED, _resolved },
{ EFL_NET_DIALER_EVENT_ERROR, _error },
{ EFL_IO_CLOSER_EVENT_CLOSED, _closed },
{ EFL_IO_READER_EVENT_EOS, _eos });
static const char *authentication_method_choices[] = {
"none",
"basic",
"digest",
"negotiate",
"ntlm",
"ntlm_winbind",
"any_safe",
"any",
NULL,
};
static Efl_Net_Http_Authentication_Method
_parse_authentication_method(const char *str)
{
if (strcmp(str, "basic") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_BASIC;
if (strcmp(str, "digest") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_DIGEST;
if (strcmp(str, "negotiate") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_NEGOTIATE;
if (strcmp(str, "ntlm") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_NTLM;
if (strcmp(str, "ntlm_winbind") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_NTLM_WINBIND;
if (strcmp(str, "any_safe") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_ANY_SAFE;
if (strcmp(str, "any") == 0)
return EFL_NET_HTTP_AUTHENTICATION_METHOD_ANY;
return EFL_NET_HTTP_AUTHENTICATION_METHOD_NONE;
}
static const Ecore_Getopt options = {
"efl_net_dialer_websocket_example", /* program name */
NULL, /* usage line */
"1", /* version */
"(C) 2016 Enlightenment Project", /* copyright */
"BSD 2-Clause", /* license */
/* long description, may be multiline and contain \n */
"Example of Efl_Net_Dialer_Websocket usage in message-based mode.\n"
"In this example couple of text and binary messages are sent to the server, "
"as well as a ping. On pong the websocket is closed."
"\n"
"For the EFL I/O interfaces example, see efl_io_copier_example.c"
"\n",
EINA_FALSE,
{
ECORE_GETOPT_APPEND('p', "websocket-protocol", "WebSocket protocol to request", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_STORE_STR('U', "username", "Authentication username"),
ECORE_GETOPT_STORE_STR('P', "password", "Authentication password"),
ECORE_GETOPT_CHOICE('A', "authentication-method", "Authentication method", authentication_method_choices),
ECORE_GETOPT_STORE_BOOL('R', "authentication-restricted", "Authentication method must be restricted"),
ECORE_GETOPT_STORE_BOOL('r', "allow-redirects", "allow redirections by following 'Location:' headers"),
ECORE_GETOPT_STORE_DOUBLE('t', "connect-timeout", "timeout in seconds for the connection phase"),
ECORE_GETOPT_APPEND('H', "header", "Add custom headers. Format must be 'Key: Value'", ECORE_GETOPT_TYPE_STR),
ECORE_GETOPT_STORE_STR('X', "proxy", "Set a specific proxy for the connection"),
ECORE_GETOPT_STORE_STR('c', "cookie-jar", "Set the cookie-jar file to read/save cookies from. Empty means an in-memory cookie-jar"),
ECORE_GETOPT_VERSION('V', "version"),
ECORE_GETOPT_COPYRIGHT('C', "copyright"),
ECORE_GETOPT_LICENSE('L', "license"),
ECORE_GETOPT_HELP('h', "help"),
ECORE_GETOPT_STORE_METAVAR_STR(0, NULL,
"The address (URL) to dial, such as wss://echo.websocket.org", "address"),
ECORE_GETOPT_SENTINEL
}
};
int
main(int argc, char **argv)
{
char *address = NULL;
char *username = NULL;
char *password = NULL;
char *authentication_method_str = "basic";
char *proxy = NULL;
char *cookie_jar = NULL;
Eina_Bool authentication_restricted = EINA_FALSE;
Eina_Bool allow_redirects = EINA_TRUE;
double timeout_dial = 30.0;
Eina_List *headers = NULL;
Eina_List *protocols = NULL;
Eina_Bool quit_option = EINA_FALSE;
Ecore_Getopt_Value values[] = {
ECORE_GETOPT_VALUE_LIST(protocols),
ECORE_GETOPT_VALUE_STR(username),
ECORE_GETOPT_VALUE_STR(password),
ECORE_GETOPT_VALUE_STR(authentication_method_str),
ECORE_GETOPT_VALUE_BOOL(authentication_restricted),
ECORE_GETOPT_VALUE_BOOL(allow_redirects),
ECORE_GETOPT_VALUE_DOUBLE(timeout_dial),
ECORE_GETOPT_VALUE_LIST(headers),
ECORE_GETOPT_VALUE_STR(proxy),
ECORE_GETOPT_VALUE_STR(cookie_jar),
/* standard block to provide version, copyright, license and help */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -V/--version quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -C/--copyright quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -L/--license quits */
ECORE_GETOPT_VALUE_BOOL(quit_option), /* -h/--help quits */
/* positional argument */
ECORE_GETOPT_VALUE_STR(address),
ECORE_GETOPT_VALUE_NONE /* sentinel */
};
int args;
Eo *dialer, *loop;
Efl_Net_Http_Authentication_Method authentication_method;
Efl_Net_Http_Header *header;
Eina_Iterator *itr;
Eina_Error err;
char *str;
ecore_init();
ecore_con_init();
ecore_con_url_init();
args = ecore_getopt_parse(&options, values, argc, argv);
if (args < 0)
{
fputs("ERROR: Could not parse command line options.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
if (quit_option) goto end;
loop = ecore_main_loop_get();
args = ecore_getopt_parse_positional(&options, values, argc, argv, args);
if (args < 0)
{
fputs("ERROR: Could not parse positional arguments.\n", stderr);
retval = EXIT_FAILURE;
goto end;
}
authentication_method = _parse_authentication_method(authentication_method_str);
if (cookie_jar && cookie_jar[0] == ' ')
cookie_jar = "";
dialer = efl_add(EFL_NET_DIALER_WEBSOCKET_CLASS, loop,
efl_name_set(efl_self, "dialer"),
efl_net_dialer_websocket_authentication_set(efl_self, username, password, authentication_method, authentication_restricted),
efl_net_dialer_websocket_allow_redirects_set(efl_self, allow_redirects),
efl_net_dialer_websocket_cookie_jar_set(efl_self, cookie_jar),
efl_net_dialer_proxy_set(efl_self, proxy),
efl_net_dialer_timeout_dial_set(efl_self, timeout_dial),
efl_event_callback_array_add(efl_self, dialer_cbs(), NULL));
if (!dialer)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not create WebSockets dialer\n");
goto end;
}
EINA_LIST_FREE(headers, str)
{
char *p = strchr(str, ':');
if (p)
{
p[0] = '\0';
p++;
while ((p[0] != '\0') && isspace(p[0]))
p++;
}
efl_net_dialer_websocket_request_header_add(dialer, str, p);
free(str);
}
EINA_LIST_FREE(protocols, str)
{
efl_net_dialer_websocket_request_protocol_add(dialer, str);
free(str);
}
err = efl_net_dialer_dial(dialer, address);
if (err != 0)
{
retval = EXIT_FAILURE;
fprintf(stderr, "ERROR: could not dial '%s': %s",
address, eina_error_msg_get(err));
goto no_mainloop;
}
fprintf(stderr,
"INFO: dialed %s\n"
"INFO: - allow_redirects=%d\n"
"INFO: - cookie_jar=%s\n"
"INFO: - timeout_dial=%fs\n"
"INFO: - proxy=%s\n"
"INFO: - request headers:\n",
efl_net_dialer_address_dial_get(dialer),
efl_net_dialer_websocket_allow_redirects_get(dialer),
efl_net_dialer_websocket_cookie_jar_get(dialer),
efl_net_dialer_timeout_dial_get(dialer),
efl_net_dialer_proxy_get(dialer));
itr = efl_net_dialer_websocket_request_headers_get(dialer);
EINA_ITERATOR_FOREACH(itr, header)
fprintf(stderr, "INFO: %s: %s\n", header->key, header->value);
eina_iterator_free(itr);
fprintf(stderr, "INFO: - request protocols:\n");
itr = efl_net_dialer_websocket_request_protocols_get(dialer);
EINA_ITERATOR_FOREACH(itr, str)
fprintf(stderr, "INFO: %s\n", str);
eina_iterator_free(itr);
ecore_main_loop_begin();
fprintf(stderr, "INFO: main loop finished.\n");
no_mainloop:
efl_del(dialer);
end:
ecore_con_url_shutdown();
ecore_con_shutdown();
ecore_shutdown();
return retval;
}

View File

@ -73,3 +73,4 @@ extern Eina_Error EFL_NET_HTTP_ERROR_USE_SSL_FAILED;
extern Eina_Error EFL_NET_HTTP_ERROR_WRITE_ERROR;
#include "efl_net_dialer_http.eo.h"
#include "efl_net_dialer_websocket.eo.h"

View File

@ -30,6 +30,7 @@
#include "Ecore_Con.h"
#include "ecore_con_private.h"
#include "ecore_con_url_curl.h"
#include "Emile.h"
#define MY_CLASS EFL_NETWORK_URL_CLASS
@ -114,11 +115,20 @@ EAPI int
ecore_con_url_init(void)
{
if (++_init_count > 1) return _init_count;
if (!ecore_init()) return --_init_count;
if (!ecore_init()) goto ecore_init_failed;
if (!emile_init()) goto emile_init_failed;
if (!emile_cipher_init()) goto emile_cipher_init_failed;
ECORE_CON_EVENT_URL_DATA = ecore_event_type_new();
ECORE_CON_EVENT_URL_COMPLETE = ecore_event_type_new();
ECORE_CON_EVENT_URL_PROGRESS = ecore_event_type_new();
return _init_count;
emile_cipher_init_failed:
emile_shutdown();
emile_init_failed:
ecore_shutdown();
ecore_init_failed:
return --_init_count;
}
EAPI int
@ -139,6 +149,7 @@ ecore_con_url_shutdown(void)
EINA_LIST_FREE(_fd_hd_list, fd_handler)
ecore_main_fd_handler_del(fd_handler);
_c_shutdown();
emile_shutdown(); /* no emile_cipher_shutdown(), handled here */
ecore_shutdown();
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,309 @@
import eina_types;
import efl_net_http_types;
enum Efl.Net.Dialer.Websocket.Streaming_Mode {
[[How to map WebSocket to EFL I/O Interfaces.]]
disabled, [[@Efl.Io.Writer.write and @Efl.Io.Reader.read will fail by returning ENOSTR]]
binary, [[@Efl.Io.Writer.write will result in @Efl.Net.Dialer.Websocket.binary_send]]
text, [[@Efl.Io.Writer.write will result in @Efl.Net.Dialer.Websocket.text_send]]
}
enum Efl.Net.Dialer.Websocket.Close_Reason {
[[Registered reasons for the CLOSE (opcode=0x8).
These are the well known reasons, with some ranges being defined
using "_start" and "end" suffixes.
See https://tools.ietf.org/html/rfc6455#section-7.4.1
]]
normal = 1000, [[indicates a normal closure, meaning that the purpose for which the connection was established has been fulfilled.]]
going_away = 1001, [[indicates that an endpoint is "going away", such as a server going down or a browser having navigated away from a page.]]
protocol_error = 1002, [[indicates that an endpoint is terminating the connection due to a protocol error.]]
no_reason = 1005, [[reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting a status code to indicate that no status code was actually present.]]
abruptly = 1006, [[reserved value and MUST NOT be set as a status code in a Close control frame by an endpoint. It is designated for use in applications expecting a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame.]]
unexpected_data = 1003, [[indicates that an endpoint is terminating the connection because it has received a type of data it cannot accept (e.g., an endpoint that understands only text data MAY send this if it receives a binary message).]]
inconsistent_data = 1007, [[indicates that an endpoint is terminating the connection because it has received data within a message that was not consistent with the type of the message (e.g., non-UTF-8 data within a text message).]]
policy_violation = 1008, [[indicates that an endpoint is terminating the connection because it has received a message that violates its policy. This is a generic status code that can be returned when there is no other more suitable status code (e.g., 1003 or 1009) or if there is a need to hide specific details about the policy.]]
too_big = 1009, [[indicates that an endpoint is terminating the connection because it has received a message that is too big for it to process.]]
missing_extension = 1010, [[indicates that an endpoint (client) is terminating the connection because it has expected the server to negotiate one or more extension, but the server didn't return them in the response message of the WebSocket handshake. The list of extensions that are needed SHOULD appear in the reason part of the Close frame. Note that this status code is not used by the server, because it can fail the WebSocket handshake instead.]]
server_error = 1011, [[indicates that a server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.]]
iana_registry_start = 3000, [[IANA registry starts at 3000]]
iana_registry_end = 3999, [[IANA registry ends at 3999]]
private_start = 4000, [[Applications can use range 4000-4999]]
private_end = 4999, [[Applications can use range 4000-4999]]
}
struct Efl.Net.Dialer.Websocket.Closed_Reason {
reason: Efl.Net.Dialer.Websocket.Close_Reason;
message: string;
}
class Efl.Net.Dialer.Websocket (Efl.Loop_User, Efl.Net.Dialer, Efl.Io.Reader, Efl.Io.Writer) { /* TODO: reader/writer should be from dialer->socket, but are being missed somehow... */
[[WebSocket Dialer (Client).
The WebSocket Protocol (https://tools.ietf.org/html/rfc6455) is
a message-based protocol over HTTP, this allows it to leverage
on authentication, cookies, proxies and SSL/TLS.
It's worth to note that although it uses the HTTP dialer, it's
not a subclass and thus not all HTTP features are exposed as the
WebSocket has strict requirements that must be respected.
@since 1.19
]]
methods {
ping {
[[Send a PING (opcode=0x9) to the server.
The server should reply with a PONG, that will be
emitted as "pong" event.
]]
params {
reason: string @optional;
}
}
text_send {
[[Send an UTF-8 TEXT (opcode=0x1) to the server.
The text goes in a message will be delivered as a single
entity to the remote peer.
The text is copied into a local buffer, no references
are kept after this method returns.
]]
params {
text: string;
}
}
binary_send {
[[Send a binary blob (opcode=0x2) to the server.
The slice describing the blob goes in a message will be
delivered as a single entity to the remote peer.
The memory is copied into a local buffer, no references
are kept after this method returns.
]]
params {
blob: const(Eina.Slice);
}
}
close_request {
[[Request (opcode=0x8) the server to terminate the connection.
Unlike @Efl.Io.Closer.close, this won't abruptly close
the connection, rather will queue a message requesting
the server to gracefully close it.
After this method is called you should consider the
object in "closing" state, no more messages can be sent
(@.text_send, @.binary_send and @.ping will fail).
The object will be automatically closed with
@Efl.Io.Closer.close once the serve replies with his own
close message, that will be reported as "closed,reason".
]]
params {
reason: Efl.Net.Dialer.Websocket.Close_Reason;
message: string @optional;
}
}
request_protocol_add {
[[Add a new WebSocket protocol to the request.
This should be set before dialing.
]]
params {
protocol: string;
}
}
request_protocols_get {
[[Return an iterator to the requested WebSocket protocols]]
return: free(own(iterator<string>), eina_iterator_free) @warn_unused;
}
request_protocols_clear {
[[Clear all request protocols]]
}
response_protocols_get {
[[Return an iterator to the server-replied (response) WebSocket protocols it supports]]
return: free(own(iterator<string>), eina_iterator_free) @warn_unused;
}
@property streaming_mode {
[[Configure how to map streaming APIs to WebSocket.
WebSocket is a message-based protocol with these send
via @.text_send and @.binary_send and delivered via
events such as "message,text" and "message,binary".
However this class can operate in streaming mode,
mapping each @Efl.Io.Writer.write to a @.binary_send if
streaming_mode is set to
@Efl.Net.Dialer.Websocket.Streaming_Mode.binary, of
@.text_send if
@Efl.Net.Dialer.Websocket.Streaming_Mode.text
@Efl.Io.Reader.read may consume less then the whole
received message, in this case the rest of the message
is kept for the next read call. (Note this differs from
SOCK_SEQPACKET + read(2)).
By default, streaming is disabled
(@Efl.Net.Dialer.Websocket.Streaming_Mode.disabled).
]]
get { }
set { }
values {
streaming_mode: Efl.Net.Dialer.Websocket.Streaming_Mode;
}
}
@property user_agent {
[[The User-Agent to specify.
This should be set before dialing.
]]
get { }
set { }
values {
user_agent: string;
}
}
@property authentication {
[[HTTP authentication to use.
This should be set before dialing.
]]
get { }
set { }
values {
username: string;
password: string;
method: Efl.Net.Http.Authentication_Method @optional; [[authentication method to use, defaults to @Efl.Net.Http.Authentication_Method.basic]]
restricted: bool @optional; [[restrict method]]
}
}
@property allow_redirects {
[[Allow HTTP redirects to be followed.
This should be set before dialing.
]]
get { }
set { }
values {
allow_redirects: bool;
}
}
request_header_add {
[[Add a HTTP request header 'key: value'.
See @.request_headers_clear
WebSocket won't allow the following headers to be added
as they conflict with its own operation:
- Content-Length
- Content-Type
- Transfer-Encoding
- Connection
- Upgrade
- Expect
- Sec-WebSocket-Version
- Sec-WebSocket-Key
This should be called before dialing.
]]
params {
@in key: string;
@in value: string;
}
}
request_headers_clear {
[[Clear all request headers.
See @.request_header_add
This should be called before dialing.
]]
}
request_headers_get {
[[Return an iterator to the key-value pairs for request headers]]
return: free(own(iterator<Efl.Net.Http.Header>), eina_iterator_free) @warn_unused;
}
@property cookie_jar {
[[This property sets the filename where to read and write cookies.
By setting a file to load and persist cookies to, the
internal cookie system will be activated, automatically
handling HTTP headers such as 'Set-cookie:' and sending
the appropriate cookies for a server.
If a new, empty session is to be used, start with an
empty or non-existent file such as created with
mkstemp() or tmpfile(). An alternative is to use an
empty string ("") to keep it in memory.
If it is desired to start from a pre-existent cookie jar
but do not want to modify that, first copy that file and
then pass the new, temporary file.
Likewise, if it's desired to fill some cookies to the
system, create a cookie jar and pass its path to this
property.
\@note that whenever this property is set, even if to the
same value, it will flush all cookies to the previously
set file, then erase all known cookies, then use the new
file (if any).
]]
get { }
set { }
values {
path: string;
}
}
}
events {
message,text: string; [[Received a text string message (opcode=0x1)]]
message,binary: const(Eina.Slice)*; [[Received a binary message (opcode=0x2)]]
pong: string; [[Received a pong (opcode=0xA) with optional message/reason]]
closed,reason: Efl.Net.Dialer.Websocket.Closed_Reason; [[Received a request to close the connection. It may be a reply/confirmation from a local request, see @.close_request, or some server-generated reason. After this point, no more messages are allowed to be sent and no more will be received. @Efl.Io.Closer.close will be called.]]
}
implements {
Efl.Object.constructor;
Efl.Object.destructor;
Efl.Net.Dialer.dial;
Efl.Net.Dialer.address_dial;
Efl.Net.Dialer.connected;
Efl.Net.Dialer.proxy;
Efl.Net.Dialer.timeout_dial;
Efl.Net.Socket.address_local.get;
Efl.Net.Socket.address_remote;
Efl.Io.Reader.read;
Efl.Io.Reader.can_read.get;
Efl.Io.Reader.can_read.set;
Efl.Io.Reader.eos.get;
Efl.Io.Writer.write;
Efl.Io.Writer.can_write.get;
Efl.Io.Writer.can_write.set;
Efl.Io.Closer.close;
Efl.Io.Closer.closed.get;
}
}