Samuel Mokrani / WebSocketClient

Simple websocket client

Dependents:   Websocket_Ethernet_HelloWorld Websocket_Wifly_HelloWorld RPC_Wifly_HelloWorld RPC_Ethernet_HelloWorld ... more

Committer:
samux
Date:
Fri Feb 08 12:33:04 2013 +0000
Revision:
7:4567996414a5
Parent:
6:86e89a0369b9
reduce timeout

Who changed what in which revision?

UserRevisionLine numberNew contents of line
samux3:9589afa4712e 1#include "Websocket.h"
samux3:9589afa4712e 2
samux7:4567996414a5 3#define MAX_TRY_WRITE 20
samux7:4567996414a5 4#define MAX_TRY_READ 10
samux3:9589afa4712e 5
samux3:9589afa4712e 6//Debug is disabled by default
samux3:9589afa4712e 7#if 0
samux3:9589afa4712e 8#define DBG(x, ...) std::printf("[WebSocket : DBG]"x"\r\n", ##__VA_ARGS__);
samux3:9589afa4712e 9#define WARN(x, ...) std::printf("[WebSocket : WARN]"x"\r\n", ##__VA_ARGS__);
samux3:9589afa4712e 10#define ERR(x, ...) std::printf("[WebSocket : ERR]"x"\r\n", ##__VA_ARGS__);
samux3:9589afa4712e 11#else
samux3:9589afa4712e 12#define DBG(x, ...)
samux3:9589afa4712e 13#define WARN(x, ...)
samux3:9589afa4712e 14#define ERR(x, ...)
samux3:9589afa4712e 15#endif
samux3:9589afa4712e 16
samux3:9589afa4712e 17#define INFO(x, ...) printf("[WebSocket : INFO]"x"\r\n", ##__VA_ARGS__);
samux3:9589afa4712e 18
samux3:9589afa4712e 19Websocket::Websocket(char * url) {
samux3:9589afa4712e 20 fillFields(url);
samux3:9589afa4712e 21 socket.set_blocking(false, 400);
samux3:9589afa4712e 22}
samux3:9589afa4712e 23
samux3:9589afa4712e 24void Websocket::fillFields(char * url) {
donatien6:86e89a0369b9 25 int ret = parseURL(url, scheme, sizeof(scheme), host, sizeof(host), &port, path, sizeof(path));
donatien6:86e89a0369b9 26 if(ret)
donatien6:86e89a0369b9 27 {
donatien6:86e89a0369b9 28 ERR("URL parsing failed; please use: \"ws://ip-or-domain[:port]/path\"");
donatien6:86e89a0369b9 29 return;
donatien6:86e89a0369b9 30 }
samux3:9589afa4712e 31
donatien6:86e89a0369b9 32 if(port == 0) //TODO do handle WSS->443
donatien6:86e89a0369b9 33 {
donatien6:86e89a0369b9 34 port = 80;
donatien6:86e89a0369b9 35 }
donatien6:86e89a0369b9 36
donatien6:86e89a0369b9 37 if(strcmp(scheme, "ws"))
donatien6:86e89a0369b9 38 {
donatien6:86e89a0369b9 39 ERR("Wrong scheme, please use \"ws\" instead");
donatien6:86e89a0369b9 40 }
donatien6:86e89a0369b9 41}
samux3:9589afa4712e 42
donatien6:86e89a0369b9 43int Websocket::parseURL(const char* url, char* scheme, size_t maxSchemeLen, char* host, size_t maxHostLen, uint16_t* port, char* path, size_t maxPathLen) //Parse URL
donatien6:86e89a0369b9 44{
donatien6:86e89a0369b9 45 char* schemePtr = (char*) url;
donatien6:86e89a0369b9 46 char* hostPtr = (char*) strstr(url, "://");
donatien6:86e89a0369b9 47 if(hostPtr == NULL)
donatien6:86e89a0369b9 48 {
donatien6:86e89a0369b9 49 WARN("Could not find host");
donatien6:86e89a0369b9 50 return -1; //URL is invalid
donatien6:86e89a0369b9 51 }
samux3:9589afa4712e 52
donatien6:86e89a0369b9 53 if( maxSchemeLen < hostPtr - schemePtr + 1 ) //including NULL-terminating char
donatien6:86e89a0369b9 54 {
donatien6:86e89a0369b9 55 WARN("Scheme str is too small (%d >= %d)", maxSchemeLen, hostPtr - schemePtr + 1);
donatien6:86e89a0369b9 56 return -1;
donatien6:86e89a0369b9 57 }
donatien6:86e89a0369b9 58 memcpy(scheme, schemePtr, hostPtr - schemePtr);
donatien6:86e89a0369b9 59 scheme[hostPtr - schemePtr] = '\0';
samux3:9589afa4712e 60
donatien6:86e89a0369b9 61 hostPtr+=3;
samux3:9589afa4712e 62
donatien6:86e89a0369b9 63 size_t hostLen = 0;
samux3:9589afa4712e 64
donatien6:86e89a0369b9 65 char* portPtr = strchr(hostPtr, ':');
donatien6:86e89a0369b9 66 if( portPtr != NULL )
donatien6:86e89a0369b9 67 {
donatien6:86e89a0369b9 68 hostLen = portPtr - hostPtr;
donatien6:86e89a0369b9 69 portPtr++;
donatien6:86e89a0369b9 70 if( sscanf(portPtr, "%hu", port) != 1)
donatien6:86e89a0369b9 71 {
donatien6:86e89a0369b9 72 WARN("Could not find port");
donatien6:86e89a0369b9 73 return -1;
samux3:9589afa4712e 74 }
donatien6:86e89a0369b9 75 }
donatien6:86e89a0369b9 76 else
donatien6:86e89a0369b9 77 {
donatien6:86e89a0369b9 78 *port=0;
donatien6:86e89a0369b9 79 }
donatien6:86e89a0369b9 80 char* pathPtr = strchr(hostPtr, '/');
donatien6:86e89a0369b9 81 if( hostLen == 0 )
donatien6:86e89a0369b9 82 {
donatien6:86e89a0369b9 83 hostLen = pathPtr - hostPtr;
donatien6:86e89a0369b9 84 }
donatien6:86e89a0369b9 85
donatien6:86e89a0369b9 86 if( maxHostLen < hostLen + 1 ) //including NULL-terminating char
donatien6:86e89a0369b9 87 {
donatien6:86e89a0369b9 88 WARN("Host str is too small (%d >= %d)", maxHostLen, hostLen + 1);
donatien6:86e89a0369b9 89 return -1;
donatien6:86e89a0369b9 90 }
donatien6:86e89a0369b9 91 memcpy(host, hostPtr, hostLen);
donatien6:86e89a0369b9 92 host[hostLen] = '\0';
donatien6:86e89a0369b9 93
donatien6:86e89a0369b9 94 size_t pathLen;
donatien6:86e89a0369b9 95 char* fragmentPtr = strchr(hostPtr, '#');
donatien6:86e89a0369b9 96 if(fragmentPtr != NULL)
donatien6:86e89a0369b9 97 {
donatien6:86e89a0369b9 98 pathLen = fragmentPtr - pathPtr;
donatien6:86e89a0369b9 99 }
donatien6:86e89a0369b9 100 else
donatien6:86e89a0369b9 101 {
donatien6:86e89a0369b9 102 pathLen = strlen(pathPtr);
donatien6:86e89a0369b9 103 }
donatien6:86e89a0369b9 104
donatien6:86e89a0369b9 105 if( maxPathLen < pathLen + 1 ) //including NULL-terminating char
donatien6:86e89a0369b9 106 {
donatien6:86e89a0369b9 107 WARN("Path str is too small (%d >= %d)", maxPathLen, pathLen + 1);
donatien6:86e89a0369b9 108 return -1;
donatien6:86e89a0369b9 109 }
donatien6:86e89a0369b9 110 memcpy(path, pathPtr, pathLen);
donatien6:86e89a0369b9 111 path[pathLen] = '\0';
donatien6:86e89a0369b9 112
donatien6:86e89a0369b9 113 return 0;
samux3:9589afa4712e 114}
samux3:9589afa4712e 115
samux3:9589afa4712e 116
samux3:9589afa4712e 117bool Websocket::connect() {
samux3:9589afa4712e 118 char cmd[200];
samux3:9589afa4712e 119
donatien6:86e89a0369b9 120 while (socket.connect(host, port) < 0) {
donatien6:86e89a0369b9 121 ERR("Unable to connect to (%s) on port (%d)", host, port);
samux3:9589afa4712e 122 wait(0.2);
donatien6:86e89a0369b9 123 return false;
samux3:9589afa4712e 124 }
samux3:9589afa4712e 125
samux3:9589afa4712e 126 // sent http header to upgrade to the ws protocol
donatien6:86e89a0369b9 127 sprintf(cmd, "GET %s HTTP/1.1\r\n", path);
samux3:9589afa4712e 128 write(cmd, strlen(cmd));
donatien6:86e89a0369b9 129
donatien6:86e89a0369b9 130 sprintf(cmd, "Host: %s:%d\r\n", host, port);
samux3:9589afa4712e 131 write(cmd, strlen(cmd));
samux3:9589afa4712e 132
samux3:9589afa4712e 133 sprintf(cmd, "Upgrade: WebSocket\r\n");
samux3:9589afa4712e 134 write(cmd, strlen(cmd));
samux3:9589afa4712e 135
samux3:9589afa4712e 136 sprintf(cmd, "Connection: Upgrade\r\n");
samux3:9589afa4712e 137 write(cmd, strlen(cmd));
samux3:9589afa4712e 138
samux3:9589afa4712e 139 sprintf(cmd, "Sec-WebSocket-Key: L159VM0TWUzyDxwJEIEzjw==\r\n");
samux3:9589afa4712e 140 write(cmd, strlen(cmd));
samux3:9589afa4712e 141
samux3:9589afa4712e 142 sprintf(cmd, "Sec-WebSocket-Version: 13\r\n\r\n");
samux3:9589afa4712e 143 int ret = write(cmd, strlen(cmd));
samux3:9589afa4712e 144 if (ret != strlen(cmd)) {
samux3:9589afa4712e 145 close();
samux3:9589afa4712e 146 ERR("Could not send request");
samux3:9589afa4712e 147 return false;
samux3:9589afa4712e 148 }
samux3:9589afa4712e 149
samux3:9589afa4712e 150 ret = read(cmd, 200, 100);
samux3:9589afa4712e 151 if (ret < 0) {
samux3:9589afa4712e 152 close();
samux3:9589afa4712e 153 ERR("Could not receive answer\r\n");
samux3:9589afa4712e 154 return false;
samux3:9589afa4712e 155 }
samux3:9589afa4712e 156
samux3:9589afa4712e 157 cmd[ret] = '\0';
samux3:9589afa4712e 158 DBG("recv: %s\r\n", cmd);
samux3:9589afa4712e 159
samux3:9589afa4712e 160 if ( strstr(cmd, "DdLWT/1JcX+nQFHebYP+rqEx5xI=") == NULL ) {
samux3:9589afa4712e 161 ERR("Wrong answer from server, got \"%s\" instead\r\n", cmd);
samux3:9589afa4712e 162 do {
samux3:9589afa4712e 163 ret = read(cmd, 200, 100);
samux3:9589afa4712e 164 if (ret < 0) {
samux3:9589afa4712e 165 ERR("Could not receive answer\r\n");
samux3:9589afa4712e 166 return false;
samux3:9589afa4712e 167 }
samux3:9589afa4712e 168 cmd[ret] = '\0';
samux3:9589afa4712e 169 printf("%s",cmd);
samux3:9589afa4712e 170 } while (ret > 0);
samux3:9589afa4712e 171 close();
samux3:9589afa4712e 172 return false;
samux3:9589afa4712e 173 }
samux3:9589afa4712e 174
donatien6:86e89a0369b9 175 INFO("\r\nhost: %s\r\npath: %s\r\nport: %d\r\n\r\n", host, path, port);
samux3:9589afa4712e 176 return true;
samux3:9589afa4712e 177}
samux3:9589afa4712e 178
samux4:466f90b7849a 179int Websocket::sendLength(uint32_t len, char * msg) {
samux3:9589afa4712e 180
samux3:9589afa4712e 181 if (len < 126) {
samux4:466f90b7849a 182 msg[0] = len | (1<<7);
samux3:9589afa4712e 183 return 1;
samux3:9589afa4712e 184 } else if (len < 65535) {
samux4:466f90b7849a 185 msg[0] = 126 | (1<<7);
samux4:466f90b7849a 186 msg[1] = (len >> 8) & 0xff;
samux4:466f90b7849a 187 msg[2] = len & 0xff;
samux3:9589afa4712e 188 return 3;
samux3:9589afa4712e 189 } else {
samux4:466f90b7849a 190 msg[0] = 127 | (1<<7);
samux3:9589afa4712e 191 for (int i = 0; i < 8; i++) {
samux4:466f90b7849a 192 msg[i+1] = (len >> i*8) & 0xff;
samux3:9589afa4712e 193 }
samux3:9589afa4712e 194 return 9;
samux3:9589afa4712e 195 }
samux3:9589afa4712e 196}
samux3:9589afa4712e 197
samux3:9589afa4712e 198int Websocket::readChar(char * pC, bool block) {
samux3:9589afa4712e 199 return read(pC, 1, 1);
samux3:9589afa4712e 200}
samux3:9589afa4712e 201
samux4:466f90b7849a 202int Websocket::sendOpcode(uint8_t opcode, char * msg) {
samux4:466f90b7849a 203 msg[0] = 0x80 | (opcode & 0x0f);
samux4:466f90b7849a 204 return 1;
samux3:9589afa4712e 205}
samux3:9589afa4712e 206
samux4:466f90b7849a 207int Websocket::sendMask(char * msg) {
samux3:9589afa4712e 208 for (int i = 0; i < 4; i++) {
samux4:466f90b7849a 209 msg[i] = 0;
samux3:9589afa4712e 210 }
samux3:9589afa4712e 211 return 4;
samux3:9589afa4712e 212}
samux3:9589afa4712e 213
samux3:9589afa4712e 214int Websocket::send(char * str) {
samux4:466f90b7849a 215 char msg[strlen(str) + 15];
samux4:466f90b7849a 216 int idx = 0;
samux4:466f90b7849a 217 idx = sendOpcode(0x01, msg);
samux4:466f90b7849a 218 idx += sendLength(strlen(str), msg + idx);
samux4:466f90b7849a 219 idx += sendMask(msg + idx);
samux4:466f90b7849a 220 memcpy(msg+idx, str, strlen(str));
samux4:466f90b7849a 221 int res = write(msg, idx + strlen(str));
samux3:9589afa4712e 222 return res;
samux3:9589afa4712e 223}
samux3:9589afa4712e 224
samux3:9589afa4712e 225
samux3:9589afa4712e 226bool Websocket::read(char * message) {
samux3:9589afa4712e 227 int i = 0;
samux3:9589afa4712e 228 uint32_t len_msg;
samux3:9589afa4712e 229 char opcode = 0;
samux3:9589afa4712e 230 char c;
samux3:9589afa4712e 231 char mask[4] = {0, 0, 0, 0};
samux3:9589afa4712e 232 bool is_masked = false;
samux3:9589afa4712e 233 Timer tmr;
samux3:9589afa4712e 234
samux3:9589afa4712e 235 // read the opcode
samux3:9589afa4712e 236 tmr.start();
samux3:9589afa4712e 237 while (true) {
samux3:9589afa4712e 238 if (tmr.read() > 3) {
samux3:9589afa4712e 239 DBG("timeout ws\r\n");
samux3:9589afa4712e 240 return false;
samux3:9589afa4712e 241 }
donatien5:bb09d7a6c92f 242
donatien5:bb09d7a6c92f 243 if(!socket.is_connected())
donatien5:bb09d7a6c92f 244 {
donatien5:bb09d7a6c92f 245 WARN("Connection was closed by server");
donatien5:bb09d7a6c92f 246 return false;
donatien5:bb09d7a6c92f 247 }
samux3:9589afa4712e 248
samux3:9589afa4712e 249 socket.set_blocking(false, 1);
samux3:9589afa4712e 250 if (socket.receive(&opcode, 1) != 1) {
samux3:9589afa4712e 251 socket.set_blocking(false, 2000);
samux3:9589afa4712e 252 return false;
samux3:9589afa4712e 253 }
samux3:9589afa4712e 254
samux3:9589afa4712e 255 socket.set_blocking(false, 2000);
samux3:9589afa4712e 256
samux3:9589afa4712e 257 if (opcode == 0x81)
samux3:9589afa4712e 258 break;
samux3:9589afa4712e 259 }
samux3:9589afa4712e 260 DBG("opcode: 0x%X\r\n", opcode);
samux3:9589afa4712e 261
samux3:9589afa4712e 262 readChar(&c);
samux3:9589afa4712e 263 len_msg = c & 0x7f;
samux3:9589afa4712e 264 is_masked = c & 0x80;
samux3:9589afa4712e 265 if (len_msg == 126) {
samux3:9589afa4712e 266 readChar(&c);
samux3:9589afa4712e 267 len_msg = c << 8;
samux3:9589afa4712e 268 readChar(&c);
samux3:9589afa4712e 269 len_msg += c;
samux3:9589afa4712e 270 } else if (len_msg == 127) {
samux3:9589afa4712e 271 len_msg = 0;
samux3:9589afa4712e 272 for (int i = 0; i < 8; i++) {
samux3:9589afa4712e 273 readChar(&c);
samux3:9589afa4712e 274 len_msg += (c << (7-i)*8);
samux3:9589afa4712e 275 }
samux3:9589afa4712e 276 }
samux3:9589afa4712e 277
samux3:9589afa4712e 278 if (len_msg == 0) {
samux3:9589afa4712e 279 return false;
samux3:9589afa4712e 280 }
samux3:9589afa4712e 281 DBG("length: %d\r\n", len_msg);
samux3:9589afa4712e 282
samux3:9589afa4712e 283 if (is_masked) {
samux3:9589afa4712e 284 for (i = 0; i < 4; i++)
samux3:9589afa4712e 285 readChar(&c);
samux3:9589afa4712e 286 mask[i] = c;
samux3:9589afa4712e 287 }
samux3:9589afa4712e 288
samux3:9589afa4712e 289 int nb = read(message, len_msg, len_msg);
samux3:9589afa4712e 290 if (nb != len_msg)
samux3:9589afa4712e 291 return false;
samux3:9589afa4712e 292
samux3:9589afa4712e 293 for (i = 0; i < len_msg; i++) {
samux3:9589afa4712e 294 message[i] = message[i] ^ mask[i % 4];
samux3:9589afa4712e 295 }
samux3:9589afa4712e 296
samux3:9589afa4712e 297 message[len_msg] = '\0';
samux3:9589afa4712e 298
samux3:9589afa4712e 299 return true;
samux3:9589afa4712e 300}
samux3:9589afa4712e 301
samux3:9589afa4712e 302bool Websocket::close() {
samux3:9589afa4712e 303 if (!is_connected())
samux3:9589afa4712e 304 return false;
samux3:9589afa4712e 305
samux3:9589afa4712e 306 int ret = socket.close();
samux3:9589afa4712e 307 if (ret < 0) {
samux3:9589afa4712e 308 ERR("Could not disconnect");
samux3:9589afa4712e 309 return false;
samux3:9589afa4712e 310 }
samux3:9589afa4712e 311 return true;
samux3:9589afa4712e 312}
samux3:9589afa4712e 313
samux3:9589afa4712e 314bool Websocket::is_connected() {
samux3:9589afa4712e 315 return socket.is_connected();
samux3:9589afa4712e 316}
samux3:9589afa4712e 317
donatien6:86e89a0369b9 318char* Websocket::getPath() {
samux3:9589afa4712e 319 return path;
samux3:9589afa4712e 320}
samux3:9589afa4712e 321
samux3:9589afa4712e 322int Websocket::write(char * str, int len) {
samux3:9589afa4712e 323 int res = 0, idx = 0;
samux3:9589afa4712e 324
samux3:9589afa4712e 325 for (int j = 0; j < MAX_TRY_WRITE; j++) {
donatien5:bb09d7a6c92f 326
donatien5:bb09d7a6c92f 327 if(!socket.is_connected())
donatien5:bb09d7a6c92f 328 {
donatien5:bb09d7a6c92f 329 WARN("Connection was closed by server");
donatien5:bb09d7a6c92f 330 break;
donatien5:bb09d7a6c92f 331 }
samux3:9589afa4712e 332
samux3:9589afa4712e 333 if ((res = socket.send_all(str + idx, len - idx)) == -1)
samux3:9589afa4712e 334 continue;
samux3:9589afa4712e 335
samux3:9589afa4712e 336 idx += res;
samux3:9589afa4712e 337
samux3:9589afa4712e 338 if (idx == len)
samux3:9589afa4712e 339 return len;
samux3:9589afa4712e 340 }
samux3:9589afa4712e 341
samux3:9589afa4712e 342 return (idx == 0) ? -1 : idx;
samux3:9589afa4712e 343}
samux3:9589afa4712e 344
samux3:9589afa4712e 345int Websocket::read(char * str, int len, int min_len) {
samux3:9589afa4712e 346 int res = 0, idx = 0;
samux3:9589afa4712e 347
samux3:9589afa4712e 348 for (int j = 0; j < MAX_TRY_WRITE; j++) {
samux3:9589afa4712e 349
samux3:9589afa4712e 350 if ((res = socket.receive_all(str + idx, len - idx)) == -1)
samux3:9589afa4712e 351 continue;
samux3:9589afa4712e 352
samux3:9589afa4712e 353 idx += res;
samux3:9589afa4712e 354
samux3:9589afa4712e 355 if (idx == len || (min_len != -1 && idx > min_len))
samux3:9589afa4712e 356 return idx;
samux3:9589afa4712e 357 }
samux3:9589afa4712e 358
samux3:9589afa4712e 359 return (idx == 0) ? -1 : idx;
samux3:9589afa4712e 360}