1
0
mirror of https://github.com/lammertb/libhttp.git synced 2025-07-29 21:01:13 +03:00

Initial version of websocket client. SSL tested working. websocket_client example included.

This commit is contained in:
Jordan
2014-08-21 08:53:00 -04:00
parent 3533484987
commit 9b8cda8d95
10 changed files with 397 additions and 0 deletions

View File

@ -0,0 +1,36 @@
#
# Copyright (c) 2013 No Face Press, LLC
# License http://opensource.org/licenses/mit-license.php MIT License
#
#This makefile is used to test the other Makefiles
PROG = websocket_client
SRC = websocket_client.c
TOP = ../..
CIVETWEB_LIB = libcivetweb.a
CFLAGS = -I$(TOP)/include $(COPT)
LIBS = -lpthread
include $(TOP)/resources/Makefile.in-os
ifeq ($(TARGET_OS),LINUX)
LIBS += -ldl
endif
all: $(PROG)
$(PROG): $(CIVETWEB_LIB) $(SRC)
$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $(SRC) $(CIVETWEB_LIB) $(LIBS)
$(CIVETWEB_LIB):
$(MAKE) -C $(TOP) clean lib
cp $(TOP)/$(CIVETWEB_LIB) .
clean:
rm -f $(CIVETWEB_LIB) $(PROG)
.PHONY: all clean

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIICATCCAWoCCQC2BCIqIvgSUTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE0MDgyMTEyMzAwMVoXDTI0MDgxODEyMzAwMVowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA9k9s
gH22BCo9neTeB/YnilK7n0sMe0+pjS9KSWhU59Q4w8hqPrW0tuYikIDd0wVggkJF
BZNg4WPoulTdwXsgNBeG88q2wnNtUosXTS+KQTQBSiQof9Ay9GHQtgxnogI1zIXb
HOppyyG5zre8a/X6fzDOnFc4iJMBwxTAnjCqObkCAwEAATANBgkqhkiG9w0BAQUF
AAOBgQBX9V46VUVsB9P9fb8sFuMx2ezFE42ynEeJPrKRrof+dFYbjvR1OUZFSLCy
aZKwVH7iCnVBJiU12JxO7PR3L6ob3FYPyNHQWYq1/IFUvqBRagehldj5H8iFeEDz
Wtz2+p1rUyVxcSUqTjobaji0aC8lzPZio0nd1KKM6A92/adHyQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE REQUEST-----
MIIBhDCB7gIBADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh
MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQD2T2yAfbYEKj2d5N4H9ieKUrufSwx7T6mNL0pJaFTn1DjD
yGo+tbS25iKQgN3TBWCCQkUFk2DhY+i6VN3BeyA0F4bzyrbCc21SixdNL4pBNAFK
JCh/0DL0YdC2DGeiAjXMhdsc6mnLIbnOt7xr9fp/MM6cVziIkwHDFMCeMKo5uQID
AQABoAAwDQYJKoZIhvcNAQEFBQADgYEA1EOFwyFJ2NAnRNktZCy5yVcLx9C78HoC
oHPPCOElu0VDIqe6ZecYdaqWbYlhGE0+isbOQn2CwHOeBGN8mIDsNUYzVEpsEfgg
9OK873LpE5pf4mdjSiRBXkk/h8BxuqkcKi+Qx+qEE7+dH2nK5aKeIHVvbLyfGOch
9I85q+msBNE=
-----END CERTIFICATE REQUEST-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQD2T2yAfbYEKj2d5N4H9ieKUrufSwx7T6mNL0pJaFTn1DjDyGo+
tbS25iKQgN3TBWCCQkUFk2DhY+i6VN3BeyA0F4bzyrbCc21SixdNL4pBNAFKJCh/
0DL0YdC2DGeiAjXMhdsc6mnLIbnOt7xr9fp/MM6cVziIkwHDFMCeMKo5uQIDAQAB
AoGAYwospr3lomcZv5N3c9wWqhf6OWMD8dFma87IIBxDh7Rd3tuHXQ/TSnffDhvD
FkbjN31OI5/PJNH3knTtdg78MywPloE4jYsbt4+fEaW7Fzww2nU61N1p+mYk5d/b
SCTAHhGzF9g9ZMw25CCUFGjDU2z+Ty6my22Euxhk2Qq8tMECQQD9ZYIxWkPhywDW
pX3v70dqIv7411hEYpuL/ZJl26UCmQsj4HPtXQCraQksVPs74WY5aTtd6MAV9V3M
UnErHO5/AkEA+NdG2MmfBOBPusDB/WwQaUPiCWGITS9llGTR2JXbvDqmKgL1+UTG
o27sLNIFCrF1wejpyRGqwjcObFYR0yKrxwJBAOB2uPuK4DL1psp9Uq/mIDbOxVod
OF1rlCpP9w0vol5Iv+uJ+mc7SUqOAsg4h0yl/+2/YA1yDiXlcq96IDF2sXUCQGAv
Nh9Nr72+xpK1N0axopZNuu1NWdYb3/PAFKzXIBxdvyS2CEXVo8JAeeHJPFGpzo6p
bNRfk9WGWnjdu/4UhLkCQQCekR9zpIpzdJiPYCd6XMya+TPCDYlOQL1jlnJIRa2V
BEOz0rSpzXAGh0PyCB/kMneyVk87LWn8joE6179RoUfv
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,89778A6427F05D4A
4aXqO/8oCHVfMLB+a1DfjbXyEddjbd7nB+YVFLPKy68Tam9PRTvC1zRHBet59ll0
1w7R8tXR6/xH7HRhBeqDHCcuvBhtw3xGxtXWv54WBFhzuq7TvKOAaCFl++cw/JHq
PCS0rAaYnqF2MAgMi7QBjZKmHFHL43Gy60VfOrB0mmOdxqqXA0NBFC2uEd7Z/MAx
S2A85bNJJKQaWEeDThP1u0OOlNCq99lkLJ31jiOH7ntdL0/vqcbZ+PUtdPLwAG4L
1GUHuiC2v5FvDlPiejMk2dvrxCNpcu2e3tQKHpg2KcsTVrpB7EVzRSazln4HywUZ
EJfBvxqqrS7plImZgj4LXSnln0JPuBb+aHnhKIFvisjYSwqDGJnnp/OaD7YdRhYh
UCcL011Ge+yUbRipeAmHdtJlSUSdB14KWq+WdIX/KgCRGx06QZm9s1PBLH+fww+I
EL3A/LFX0a5KUHkCp29akYYv9bUYaQ79Nt7BlaEON+/SW3pJMbGr+nx8aqogr0Yo
SJ/Zz5TSDBhecRjbCDGkT6DizVZ8cbm2xl8QLBd0H+ZA6uYMgfpAOJGrJx3Nm4Lv
prEApgFtjSrsQDGYHAcmDMW1UWOVHuNp7BSvwUze9Ftnzr/jlpdzES2rhgMyGhg1
0Szbsfs3vgw4iM83LFJXza07GQJzF8gRF79dY5JiQX/sOKUprA6Lofk631jE0G8r
3z59cxblaq9y7EgFsE944Gk7/HIEimBRiqIZzGVJVukD0itynQ+XmYTdbyH1lpvi
c0ZheZPUoGwUW9RYy+nle5gEDFyZWXcCAuJasQvDBXt//r/bso3ZpA==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIICATCCAWoCCQC2BCIqIvgSUTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJB
VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
cyBQdHkgTHRkMB4XDTE0MDgyMTEyMzAwMVoXDTI0MDgxODEyMzAwMVowRTELMAkG
A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0
IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA9k9s
gH22BCo9neTeB/YnilK7n0sMe0+pjS9KSWhU59Q4w8hqPrW0tuYikIDd0wVggkJF
BZNg4WPoulTdwXsgNBeG88q2wnNtUosXTS+KQTQBSiQof9Ay9GHQtgxnogI1zIXb
HOppyyG5zre8a/X6fzDOnFc4iJMBwxTAnjCqObkCAwEAATANBgkqhkiG9w0BAQUF
AAOBgQBX9V46VUVsB9P9fb8sFuMx2ezFE42ynEeJPrKRrof+dFYbjvR1OUZFSLCy
aZKwVH7iCnVBJiU12JxO7PR3L6ob3FYPyNHQWYq1/IFUvqBRagehldj5H8iFeEDz
Wtz2+p1rUyVxcSUqTjobaji0aC8lzPZio0nd1KKM6A92/adHyQ==
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQD2T2yAfbYEKj2d5N4H9ieKUrufSwx7T6mNL0pJaFTn1DjDyGo+
tbS25iKQgN3TBWCCQkUFk2DhY+i6VN3BeyA0F4bzyrbCc21SixdNL4pBNAFKJCh/
0DL0YdC2DGeiAjXMhdsc6mnLIbnOt7xr9fp/MM6cVziIkwHDFMCeMKo5uQIDAQAB
AoGAYwospr3lomcZv5N3c9wWqhf6OWMD8dFma87IIBxDh7Rd3tuHXQ/TSnffDhvD
FkbjN31OI5/PJNH3knTtdg78MywPloE4jYsbt4+fEaW7Fzww2nU61N1p+mYk5d/b
SCTAHhGzF9g9ZMw25CCUFGjDU2z+Ty6my22Euxhk2Qq8tMECQQD9ZYIxWkPhywDW
pX3v70dqIv7411hEYpuL/ZJl26UCmQsj4HPtXQCraQksVPs74WY5aTtd6MAV9V3M
UnErHO5/AkEA+NdG2MmfBOBPusDB/WwQaUPiCWGITS9llGTR2JXbvDqmKgL1+UTG
o27sLNIFCrF1wejpyRGqwjcObFYR0yKrxwJBAOB2uPuK4DL1psp9Uq/mIDbOxVod
OF1rlCpP9w0vol5Iv+uJ+mc7SUqOAsg4h0yl/+2/YA1yDiXlcq96IDF2sXUCQGAv
Nh9Nr72+xpK1N0axopZNuu1NWdYb3/PAFKzXIBxdvyS2CEXVo8JAeeHJPFGpzo6p
bNRfk9WGWnjdu/4UhLkCQQCekR9zpIpzdJiPYCd6XMya+TPCDYlOQL1jlnJIRa2V
BEOz0rSpzXAGh0PyCB/kMneyVk87LWn8joE6179RoUfv
-----END RSA PRIVATE KEY-----

Binary file not shown.

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2013 No Face Press, LLC
* License http://opensource.org/licenses/mit-license.php MIT License
*/
// Simple example program on how to use Embedded C interface.
#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif
#include <string.h>
#include "civetweb.h"
#define DOCUMENT_ROOT "."
#define PORT "8888"
#define SSL_CERT "./ssl/server.pem"
int websocket_data_handler(struct mg_connection *conn, int flags, char *data, size_t data_len)
{
printf("From server: %s\r\n", data);
return 1;
}
int main(int argc, char *argv[])
{
const char * options[] = { "document_root", DOCUMENT_ROOT,
"ssl_certificate", SSL_CERT,
"listening_ports", PORT, 0
};
struct mg_callbacks callbacks;
struct mg_context *ctx;
memset(&callbacks, 0, sizeof(callbacks));
ctx = mg_start(&callbacks, 0, options);
char ebuf[100];
struct mg_connection* newconn = mg_client_websocket_connect("echo.websocket.org", 443, 1,
ebuf, sizeof(ebuf),
"/", "http://websocket.org",websocket_data_handler);
if(newconn == NULL)
{
printf("Error: %s", ebuf);
return 1;
}
mg_websocket_write(newconn, WEBSOCKET_OPCODE_TEXT, "data1", 5);
sleep(5);
mg_websocket_write(newconn, WEBSOCKET_OPCODE_TEXT, "data2", 5);
sleep(5);
mg_websocket_write(newconn, WEBSOCKET_OPCODE_TEXT, "data3", 5);
sleep(5);
mg_websocket_write(newconn, WEBSOCKET_OPCODE_TEXT, "data4", 5);
sleep(5);
mg_close_connection(newconn);
printf("Bye!\n");
return 0;
}

View File

@ -571,6 +571,12 @@ CIVETWEB_API void mg_cry(struct mg_connection *conn,
/* utility method to compare two buffers, case incensitive. */
CIVETWEB_API int mg_strncasecmp(const char *s1, const char *s2, size_t len);
/* Connect to a websocket as a client */
typedef int (*websocket_data_func)(struct mg_connection *, int bits,
char *data, size_t data_len);
CIVETWEB_API struct mg_connection *mg_client_websocket_connect(const char *host, int port, int use_ssl,
char *error_buffer, size_t error_buffer_size,
const char *path, const char *origin, websocket_data_func data_func);
#ifdef __cplusplus
}

View File

@ -5255,6 +5255,152 @@ static void read_websocket(struct mg_connection *conn)
}
}
static void read_client_websocket(struct mg_connection *conn)
{
/* Pointer to the beginning of the portion of the incoming websocket
message queue.
The original websocket upgrade request is never removed, so the queue
begins after it. */
unsigned char *buf = (unsigned char *) conn->buf + conn->request_len;
int n, error;
/* body_len is the length of the entire queue in bytes
len is the length of the current message
data_len is the length of the current message's data payload
header_len is the length of the current message's header */
size_t i, len, mask_len, data_len, header_len, body_len;
/* "The masking key is a 32-bit value chosen at random by the client."
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5 */
unsigned char mask[4];
/* data points to the place where the message is stored when passed to the
websocket_data callback. This is either mem on the stack, or a
dynamically allocated buffer if it is too large. */
char mem[4096];
char *data = mem;
unsigned char mop; /* mask flag and opcode */
/* Loop continuously, reading messages from the socket, invoking the
callback, and waiting repeatedly until an error occurs. */
//assert(conn->content_len == 0);
for (;;) {
header_len = 0;
assert(conn->data_len >= conn->request_len);
if ((body_len = conn->data_len - conn->request_len) >= 2) {
len = buf[1] & 127;
mask_len = buf[1] & 128 ? 4 : 0;
if (len < 126 && body_len >= mask_len) {
data_len = len;
header_len = 2 + mask_len;
} else if (len == 126 && body_len >= 4 + mask_len) {
header_len = 4 + mask_len;
data_len = ((((int) buf[2]) << 8) + buf[3]);
} else if (body_len >= 10 + mask_len) {
header_len = 10 + mask_len;
data_len = (((uint64_t) ntohl(* (uint32_t *) &buf[2])) << 32) +
ntohl(* (uint32_t *) &buf[6]);
}
}
if (header_len > 0 && body_len >= header_len) {
/* Allocate space to hold websocket payload */
data = mem;
if (data_len > sizeof(mem)) {
data = (char *)mg_malloc(data_len);
if (data == NULL) {
/* Allocation failed, exit the loop and then close the
connection */
mg_cry(conn, "websocket out of memory; closing connection");
break;
}
}
/* Copy the mask before we shift the queue and destroy it */
if (mask_len > 0) {
memcpy(mask, buf + header_len - mask_len, sizeof(mask));
} else {
memset(mask, 0, sizeof(mask));
}
/* Read frame payload from the first message in the queue into
data and advance the queue by moving the memory in place. */
assert(body_len >= header_len);
if (data_len + header_len > body_len) {
mop = buf[0]; /* current mask and opcode */
/* Overflow case */
len = body_len - header_len;
memcpy(data, buf + header_len, len);
error = 0;
while (len < data_len) {
int n = pull(NULL, conn, data + len, (int)(data_len - len));
if (n <= 0) {
error = 1;
break;
}
len += n;
}
if (error) {
mg_cry(conn, "Websocket pull failed; closing connection");
break;
}
conn->data_len = conn->request_len;
} else {
mop = buf[0]; /* current mask and opcode, overwritten by memmove() */
/* Length of the message being read at the front of the
queue */
len = data_len + header_len;
/* Copy the data payload into the data pointer for the
callback */
memcpy(data, buf + header_len, data_len);
/* Move the queue forward len bytes */
memmove(buf, buf + len, body_len - len);
/* Mark the queue as advanced */
conn->data_len -= (int)len;
}
/* Apply mask if necessary */
if (mask_len > 0) {
for (i = 0; i < data_len; ++i) {
data[i] ^= mask[i & 3];
}
}
/* Exit the loop if callback signalled to exit,
or "connection close" opcode received. */
if ((conn->ctx->callbacks.websocket_data != NULL &&
#ifdef USE_LUA
(conn->lua_websocket_state == NULL) &&
#endif
!conn->ctx->callbacks.websocket_data(conn, mop, data, data_len)) ||
#ifdef USE_LUA
(conn->lua_websocket_state &&
!lua_websocket_data(conn, conn->lua_websocket_state, mop, data, data_len)) ||
#endif
(mop & 0xf) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) { /* Opcode == 8, connection close */
break;
}
if (data != mem) {
mg_free(data);
}
/* Not breaking the loop, process next websocket frame. */
} else {
/* Read from the socket into the next available location in the
message queue. */
if ((n = pull(NULL, conn, conn->buf + conn->data_len,
conn->buf_size - conn->data_len)) <= 0) {
/* Error, no bytes read */
break;
}
conn->data_len += n;
}
}
}
int mg_websocket_write(struct mg_connection* conn, int opcode, const char* data, size_t dataLen)
{
unsigned char header[10];
@ -6434,6 +6580,58 @@ struct mg_connection *mg_download(const char *host, int port, int use_ssl,
return conn;
}
static void* websocket_client_thread(void *data)
{
struct mg_connection* conn = (struct mg_connection*)data;
read_client_websocket(conn);
DEBUG_TRACE("Websocket client thread exited\n");
return NULL;
}
struct mg_connection *mg_client_websocket_connect(const char *host, int port, int use_ssl,
char *error_buffer, size_t error_buffer_size,
const char *path, const char *origin, websocket_data_func data_func)
{
static const char *magic = "x3JJHMbDL1EzLkh9GBhXDw==";
//Establish the client connection and request upgrade
struct mg_connection* conn = mg_download(host, port, use_ssl,
error_buffer, error_buffer_size,
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: %s\r\n"
"Sec-WebSocket-Version: 13\r\n"
"Origin: %s\r\n"
"\r\n", path, host, magic, origin);
//Connection object will be null if something goes wrong
if(conn == NULL)
{
DEBUG_TRACE("Websocket client connect error: %s\r\n", error_buffer);
return conn;
}
//For client connections, mg_context is fake. Set the callback for websocket
//data manually here so that read_client_websocket will automatically call it
conn->ctx->callbacks.websocket_data = data_func;
//Start a thread to read the websocket client connection
//This thread will automatically stop when mg_disconnect is
//called on the client connection
if(mg_start_thread(websocket_client_thread, (void*)conn) != 0)
{
mg_free((void*)conn);
conn = NULL;
DEBUG_TRACE("Websocket client connect thread could not be started\r\n");
}
return conn;
}
static void process_new_connection(struct mg_connection *conn)
{
struct mg_request_info *ri = &conn->request_info;