1
0
mirror of https://github.com/arduino-libraries/ArduinoHttpClient.git synced 2025-04-19 21:22:15 +03:00

Merge pull request #5 from sandeepmistry/websocket

Add initial WebSocket client
This commit is contained in:
Massimo Banzi 2016-07-01 13:25:41 +01:00 committed by GitHub
commit a19197cebe
9 changed files with 603 additions and 20 deletions

View File

@ -6,5 +6,6 @@
#define ArduinoHttpClient_h #define ArduinoHttpClient_h
#include "HttpClient.h" #include "HttpClient.h"
#include "WebSocketClient.h"
#endif #endif

View File

@ -439,7 +439,7 @@ int HttpClient::responseStatusCode()
delay(kHttpWaitForDataDelay); delay(kHttpWaitForDataDelay);
} }
} }
if ( (c == '\n') && (iStatusCode < 200) ) if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) )
{ {
// We've reached the end of an informational status line // We've reached the end of an informational status line
c = '\0'; // Clear c so we'll go back into the data reading loop c = '\0'; // Clear c so we'll go back into the data reading loop
@ -447,7 +447,7 @@ int HttpClient::responseStatusCode()
} }
// If we've read a status code successfully but it's informational (1xx) // If we've read a status code successfully but it's informational (1xx)
// loop back to the start // loop back to the start
while ( (iState == eStatusCodeRead) && (iStatusCode < 200) ); while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) );
if ( (c == '\n') && (iState == eStatusCodeRead) ) if ( (c == '\n') && (iState == eStatusCodeRead) )
{ {

371
WebSocketClient.cpp Normal file
View File

@ -0,0 +1,371 @@
// (c) Copyright Arduino. 2016
// Released under Apache License, version 2.0
#include "b64.h"
#include "WebSocketClient.h"
WebSocketClient::WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
: HttpClient(aClient, aServerName, aServerPort),
iTxStarted(false),
iRxSize(0)
{
}
WebSocketClient::WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort)
: HttpClient(aClient, aServerName, aServerPort),
iTxStarted(false),
iRxSize(0)
{
}
WebSocketClient::WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
: HttpClient(aClient, aServerAddress, aServerPort),
iTxStarted(false),
iRxSize(0)
{
}
int WebSocketClient::begin(const char* aPath)
{
// start the GET request
beginRequest();
connectionKeepAlive();
int status = get(aPath);
if (status == 0)
{
uint8_t randomKey[13];
char base64RandomKey[21];
// create a random key for the connection upgrade
for (int i = 0; i < (int)sizeof(randomKey); i++)
{
randomKey[i] = random(0x01, 0xff);
}
memset(base64RandomKey, 0x00, sizeof(base64RandomKey));
b64_encode(randomKey, sizeof(randomKey), (unsigned char*)base64RandomKey, sizeof(base64RandomKey));
// start the connection upgrade sequence
sendHeader("Upgrade", "websocket");
sendHeader("Connection", "Upgrade");
sendHeader("Sec-WebSocket-Key", base64RandomKey);
sendHeader("Sec-WebSocket-Version", "13");
endRequest();
status = responseStatusCode();
if (status > 0)
{
skipResponseHeaders();
}
}
iRxSize = 0;
// status code of 101 means success
return (status == 101) ? 0 : status;
}
int WebSocketClient::begin(const String& aPath)
{
return begin(aPath.c_str());
}
int WebSocketClient::beginMessage(int aType)
{
if (iTxStarted)
{
// fail TX already started
return 1;
}
iTxStarted = true;
iTxMessageType = (aType & 0xf);
iTxSize = 0;
return 0;
}
int WebSocketClient::endMessage()
{
if (!iTxStarted)
{
// fail TX not started
return 1;
}
// send FIN + the message type (opcode)
HttpClient::write(0x80 | iTxMessageType);
// the message is masked (0x80)
// send the length
if (iTxSize < 126)
{
HttpClient::write(0x80 | (uint8_t)iTxSize);
}
else if (iTxSize < 0xffff)
{
HttpClient::write(0x80 | 126);
HttpClient::write((iTxSize >> 8) & 0xff);
HttpClient::write((iTxSize >> 0) & 0xff);
}
else
{
HttpClient::write(0x80 | 127);
HttpClient::write((iTxSize >> 56) & 0xff);
HttpClient::write((iTxSize >> 48) & 0xff);
HttpClient::write((iTxSize >> 40) & 0xff);
HttpClient::write((iTxSize >> 32) & 0xff);
HttpClient::write((iTxSize >> 24) & 0xff);
HttpClient::write((iTxSize >> 16) & 0xff);
HttpClient::write((iTxSize >> 8) & 0xff);
HttpClient::write((iTxSize >> 0) & 0xff);
}
uint8_t maskKey[4];
// create a random mask for the data and send
for (int i = 0; i < (int)sizeof(maskKey); i++)
{
maskKey[i] = random(0xff);
}
HttpClient::write(maskKey, sizeof(maskKey));
// mask the data and send
for (int i = 0; i < (int)iTxSize; i++) {
iTxBuffer[i] ^= maskKey[i % sizeof(maskKey)];
}
size_t txSize = iTxSize;
iTxStarted = false;
iTxSize = 0;
return (HttpClient::write(iTxBuffer, txSize) == txSize) ? 0 : 1;
}
size_t WebSocketClient::write(uint8_t aByte)
{
return write(&aByte, sizeof(aByte));
}
size_t WebSocketClient::write(const uint8_t *aBuffer, size_t aSize)
{
if (iState < eReadingBody)
{
// have not upgraded the connection yet
return HttpClient::write(aBuffer, aSize);
}
if (!iTxStarted)
{
// fail TX not started
return 0;
}
// check if the write size, fits in the buffer
if ((iTxSize + aSize) > sizeof(iTxBuffer))
{
aSize = sizeof(iTxSize) - iTxSize;
}
// copy data into the buffer
memcpy(iTxBuffer + iTxSize, aBuffer, aSize);
iTxSize += aSize;
return aSize;
}
int WebSocketClient::parseMessage()
{
flushRx();
// make sure 2 bytes (opcode + length)
// are available
if (HttpClient::available() < 2)
{
return 0;
}
// read open code and length
uint8_t opcode = HttpClient::read();
int length = HttpClient::read();
if ((opcode & 0x0f) == 0)
{
// continuation, use previous opcode and update flags
iRxOpCode |= opcode;
}
else
{
iRxOpCode = opcode;
}
iRxMasked = (length & 0x80);
length &= 0x7f;
// read the RX size
if (length < 126)
{
iRxSize = length;
}
else if (length == 126)
{
iRxSize = (HttpClient::read() << 8) | HttpClient::read();
}
else
{
iRxSize = ((uint64_t)HttpClient::read() << 56) |
((uint64_t)HttpClient::read() << 48) |
((uint64_t)HttpClient::read() << 40) |
((uint64_t)HttpClient::read() << 32) |
((uint64_t)HttpClient::read() << 24) |
((uint64_t)HttpClient::read() << 16) |
((uint64_t)HttpClient::read() << 8) |
(uint64_t)HttpClient::read();
}
// read in the mask, if present
if (iRxMasked)
{
for (int i = 0; i < (int)sizeof(iRxMaskKey); i++)
{
iRxMaskKey[i] = HttpClient::read();
}
}
iRxMaskIndex = 0;
if (TYPE_CONNECTION_CLOSE == messageType())
{
flushRx();
stop();
iRxSize = 0;
}
else if (TYPE_PING == messageType())
{
beginMessage(TYPE_PONG);
while(available())
{
write(read());
}
endMessage();
iRxSize = 0;
}
else if (TYPE_PONG == messageType())
{
flushRx();
iRxSize = 0;
}
return iRxSize;
}
int WebSocketClient::messageType()
{
return (iRxOpCode & 0x0f);
}
bool WebSocketClient::isFinal()
{
return ((iRxOpCode & 0x80) != 0);
}
String WebSocketClient::readString()
{
int avail = available();
String s;
if (avail > 0)
{
s.reserve(avail);
for (int i = 0; i < avail; i++)
{
s += (char)read();
}
}
return s;
}
int WebSocketClient::ping()
{
uint8_t pingData[16];
// create random data for the ping
for (int i = 0; i < (int)sizeof(pingData); i++)
{
pingData[i] = random(0xff);
}
beginMessage(TYPE_PING);
write(pingData, sizeof(pingData));
return endMessage();
}
int WebSocketClient::available()
{
if (iState < eReadingBody)
{
return HttpClient::available();
}
return iRxSize;
}
int WebSocketClient::read()
{
byte b;
if (read(&b, sizeof(b)))
{
return b;
}
return -1;
}
int WebSocketClient::read(uint8_t *aBuffer, size_t aSize)
{
int readCount = HttpClient::read(aBuffer, aSize);
if (readCount > 0)
{
iRxSize -= readCount;
// unmask the RX data if needed
if (iRxMasked)
{
for (int i = 0; i < (int)aSize; i++, iRxMaskIndex++) {
aBuffer[i] ^= iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
}
}
}
return readCount;
}
int WebSocketClient::peek()
{
int p = HttpClient::peek();
if (p != -1 && iRxMasked)
{
// unmask the RX data if needed
p = (uint8_t)p ^ iRxMaskKey[iRxMaskIndex % sizeof(iRxMaskKey)];
}
return p;
}
void WebSocketClient::flushRx()
{
while(available())
{
read();
}
}

99
WebSocketClient.h Normal file
View File

@ -0,0 +1,99 @@
// (c) Copyright Arduino. 2016
// Released under Apache License, version 2.0
#ifndef WebSocketClient_h
#define WebSocketClient_h
#include <Arduino.h>
#include "HttpClient.h"
static const int TYPE_CONTINUATION = 0x0;
static const int TYPE_TEXT = 0x1;
static const int TYPE_BINARY = 0x2;
static const int TYPE_CONNECTION_CLOSE = 0x8;
static const int TYPE_PING = 0x9;
static const int TYPE_PONG = 0xa;
class WebSocketClient : public HttpClient
{
public:
WebSocketClient(Client& aClient, const char* aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
WebSocketClient(Client& aClient, const String& aServerName, uint16_t aServerPort = HttpClient::kHttpPort);
WebSocketClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = HttpClient::kHttpPort);
/** Start the Web Socket connection to the specified path
@param aURLPath Path to use in request (optional, "/" is used by default)
@return 0 if successful, else error
*/
int begin(const char* aPath = "/");
int begin(const String& aPath);
/** Begin to send a message of type (TYPE_TEXT or TYPE_BINARY)
Use the write or Stream API's to set message content, followed by endMessage
to complete the message.
@param aURLPath Path to use in request
@return 0 if successful, else error
*/
int beginMessage(int aType);
/** Completes sending of a message started by beginMessage
@return 0 if successful, else error
*/
int endMessage();
/** Try to parse an incoming messages
@return 0 if no message available, else size of parsed message
*/
int parseMessage();
/** Returns type of current parsed message
@return type of current parsedMessage (TYPE_TEXT or TYPE_BINARY)
*/
int messageType();
/** Returns if the current message is the final chunk of a split
message
@return true for final message, false otherwise
*/
bool isFinal();
/** Read the current messages as a string
@return current message as a string
*/
String readString();
/** Send a ping
@return 0 if successful, else error
*/
int ping();
// Inherited from Print
virtual size_t write(uint8_t aByte);
virtual size_t write(const uint8_t *aBuffer, size_t aSize);
// Inherited from Stream
virtual int available();
/** Read the next byte from the server.
@return Byte read or -1 if there are no bytes available.
*/
virtual int read();
virtual int read(uint8_t *buf, size_t size);
virtual int peek();
private:
void flushRx();
private:
bool iTxStarted;
uint8_t iTxMessageType;
uint8_t iTxBuffer[128];
uint64_t iTxSize;
uint8_t iRxOpCode;
uint64_t iRxSize;
bool iRxMasked;
int iRxMaskIndex;
uint8_t iRxMaskKey[4];
};
#endif

View File

@ -0,0 +1,80 @@
/*
Simple WebSocket client for ArduinoHttpClient library
Connects to the WebSocket server, and sends a hello
message every 5 seconds
note: WiFi SSID and password are stored in config.h file.
If it is not present, add a new tab, call it "config.h"
and add the following variables:
char ssid[] = "ssid"; // your network SSID (name)
char pass[] = "password"; // your network password
created 28 Jun 2016
by Sandeep Mistry
this example is in the public domain
*/
#include <ArduinoHttpClient.h>
#include <WiFi101.h>
#include "config.h"
char serverAddress[] = "echo.websocket.org"; // server address
int port = 80;
WiFiClient wifi;
WebSocketClient client = WebSocketClient(wifi, serverAddress, port);
int status = WL_IDLE_STATUS;
int count = 0;
void setup() {
Serial.begin(9600);
while ( status != WL_CONNECTED) {
Serial.print("Attempting to connect to Network named: ");
Serial.println(ssid); // print the network name (SSID);
// Connect to WPA/WPA2 network:
status = WiFi.begin(ssid, pass);
}
// print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// print your WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
}
void loop() {
Serial.println("starting WebSocket client");
client.begin();
while (client.connected()) {
Serial.print("Sending hello ");
Serial.println(count);
// send a hello #
client.beginMessage(TYPE_TEXT);
client.print("hello ");
client.print(count);
client.endMessage();
// increment count for next message
count++;
// check if a message is available to be received
int messageSize = client.parseMessage();
if (messageSize > 0) {
Serial.println("Received a message:");
Serial.println(client.readString());
}
// wait 5 seconds
delay(5000);
}
Serial.println("disconnected");
}

View File

@ -1,2 +1,3 @@
char ssid[] = "ssid"; // your network SSID (name) char ssid[] = "ssid"; // your network SSID (name)
char pass[] = "password"; // your network password char pass[] = "password"; // your network password

View File

@ -10,6 +10,7 @@
var express = require('express'); // include express.js var express = require('express'); // include express.js
var app = express(); // a local instance of it var app = express(); // a local instance of it
var bodyParser = require('body-parser'); // include body-parser var bodyParser = require('body-parser'); // include body-parser
var WebSocketServer = require('ws').Server // include Web Socket server
// you need a body parser: // you need a body parser:
app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded app.use(bodyParser.urlencoded({extended: false})); // for application/x-www-form-urlencoded
@ -40,3 +41,17 @@ app.all('/*', function (request, response) {
// start the server: // start the server:
var server = app.listen(8080, serverStart); var server = app.listen(8080, serverStart);
// create a WebSocket server and attach it to the server
var wss = new WebSocketServer({server: server});
wss.on('connection', function connection(ws) {
// new connection, add message listener
ws.on('message', function incoming(message) {
// received a message
console.log('received: %s', message);
// echo it back
ws.send(message);
});
});

View File

@ -2,12 +2,13 @@
"name": "node_test_server", "name": "node_test_server",
"version": "0.0.1", "version": "0.0.1",
"author": { "author": {
"name":"Tom Igoe" "name": "Tom Igoe"
}, },
"dependencies": { "dependencies": {
"body-parser": ">=1.11.0",
"express": ">=4.0.0", "express": ">=4.0.0",
"body-parser" : ">=1.11.0", "multer": "*",
"multer" : "*" "ws": "^1.1.1"
}, },
"engines": { "engines": {
"node": "0.10.x", "node": "0.10.x",

View File

@ -8,6 +8,7 @@
ArduinoHttpClient KEYWORD1 ArduinoHttpClient KEYWORD1
HttpClient KEYWORD1 HttpClient KEYWORD1
WebSocketClient KEYWORD1
####################################### #######################################
# Methods and Functions (KEYWORD2) # Methods and Functions (KEYWORD2)
@ -35,6 +36,14 @@ readHeaderName KEYWORD2
readHeaderValue KEYWORD2 readHeaderValue KEYWORD2
responseBody KEYWORD2 responseBody KEYWORD2
beginMessage KEYWORD2
endMessage KEYWORD2
parseMessage KEYWORD2
messageType KEYWORD2
isFinal KEYWORD2
readString KEYWORD2
ping KEYWORD2
####################################### #######################################
# Constants (LITERAL1) # Constants (LITERAL1)
####################################### #######################################
@ -44,3 +53,9 @@ HTTP_ERROR_API LITERAL1
HTTP_ERROR_TIMED_OUT LITERAL1 HTTP_ERROR_TIMED_OUT LITERAL1
HTTP_ERROR_INVALID_RESPONSE LITERAL1 HTTP_ERROR_INVALID_RESPONSE LITERAL1
TYPE_CONTINUATION LITERAL1
TYPE_TEXT LITERAL1
TYPE_BINARY LITERAL1
TYPE_CONNECTION_CLOSE LITERAL1
TYPE_PING LITERAL1
TYPE_PONG LITERAL1