You've already forked ArduinoHttpClient
mirror of
https://github.com/arduino-libraries/ArduinoHttpClient.git
synced 2025-07-03 16:42:31 +03:00
Create src folder
This commit is contained in:
11
src/ArduinoHttpClient.h
Normal file
11
src/ArduinoHttpClient.h
Normal file
@ -0,0 +1,11 @@
|
||||
// Library to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright Arduino. 2016
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef ArduinoHttpClient_h
|
||||
#define ArduinoHttpClient_h
|
||||
|
||||
#include "HttpClient.h"
|
||||
#include "WebSocketClient.h"
|
||||
|
||||
#endif
|
715
src/HttpClient.cpp
Normal file
715
src/HttpClient.cpp
Normal file
@ -0,0 +1,715 @@
|
||||
// Class to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright 2010-2011 MCQN Ltd
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "HttpClient.h"
|
||||
#include "b64.h"
|
||||
|
||||
// Initialize constants
|
||||
const char* HttpClient::kUserAgent = "Arduino/2.2.0";
|
||||
const char* HttpClient::kContentLengthPrefix = HTTP_HEADER_CONTENT_LENGTH ": ";
|
||||
|
||||
HttpClient::HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort)
|
||||
: iClient(&aClient), iServerName(aServerName), iServerAddress(), iServerPort(aServerPort),
|
||||
iConnectionClose(true), iSendDefaultRequestHeaders(true)
|
||||
{
|
||||
resetState();
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort)
|
||||
: HttpClient(aClient, aServerName.c_str(), aServerPort)
|
||||
{
|
||||
}
|
||||
|
||||
HttpClient::HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort)
|
||||
: iClient(&aClient), iServerName(NULL), iServerAddress(aServerAddress), iServerPort(aServerPort),
|
||||
iConnectionClose(true), iSendDefaultRequestHeaders(true)
|
||||
{
|
||||
resetState();
|
||||
}
|
||||
|
||||
void HttpClient::resetState()
|
||||
{
|
||||
iState = eIdle;
|
||||
iStatusCode = 0;
|
||||
iContentLength = 0;
|
||||
iBodyLengthConsumed = 0;
|
||||
iContentLengthPtr = kContentLengthPrefix;
|
||||
iHttpResponseTimeout = kHttpResponseTimeout;
|
||||
}
|
||||
|
||||
void HttpClient::stop()
|
||||
{
|
||||
iClient->stop();
|
||||
resetState();
|
||||
}
|
||||
|
||||
void HttpClient::connectionKeepAlive()
|
||||
{
|
||||
iConnectionClose = false;
|
||||
}
|
||||
|
||||
void HttpClient::noDefaultRequestHeaders()
|
||||
{
|
||||
iSendDefaultRequestHeaders = false;
|
||||
}
|
||||
|
||||
void HttpClient::beginRequest()
|
||||
{
|
||||
iState = eRequestStarted;
|
||||
}
|
||||
|
||||
int HttpClient::startRequest(const char* aURLPath, const char* aHttpMethod,
|
||||
const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
if (iState == eReadingBody)
|
||||
{
|
||||
flushClientRx();
|
||||
|
||||
resetState();
|
||||
}
|
||||
|
||||
tHttpState initialState = iState;
|
||||
|
||||
if ((eIdle != iState) && (eRequestStarted != iState))
|
||||
{
|
||||
return HTTP_ERROR_API;
|
||||
}
|
||||
|
||||
if (iConnectionClose || !iClient->connected())
|
||||
{
|
||||
if (iServerName)
|
||||
{
|
||||
if (!iClient->connect(iServerName, iServerPort) > 0)
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connection failed");
|
||||
#endif
|
||||
return HTTP_ERROR_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!iClient->connect(iServerAddress, iServerPort) > 0)
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connection failed");
|
||||
#endif
|
||||
return HTTP_ERROR_CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connection already open");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Now we're connected, send the first part of the request
|
||||
int ret = sendInitialHeaders(aURLPath, aHttpMethod);
|
||||
|
||||
if (HTTP_SUCCESS == ret)
|
||||
{
|
||||
if (aContentType)
|
||||
{
|
||||
sendHeader(HTTP_HEADER_CONTENT_TYPE, aContentType);
|
||||
}
|
||||
|
||||
if (aContentLength > 0)
|
||||
{
|
||||
sendHeader(HTTP_HEADER_CONTENT_LENGTH, aContentLength);
|
||||
}
|
||||
|
||||
bool hasBody = (aBody && aContentLength > 0);
|
||||
|
||||
if (initialState == eIdle || hasBody)
|
||||
{
|
||||
// This was a simple version of the API, so terminate the headers now
|
||||
finishHeaders();
|
||||
}
|
||||
// else we'll call it in endRequest or in the first call to print, etc.
|
||||
|
||||
if (hasBody)
|
||||
{
|
||||
write(aBody, aContentLength);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int HttpClient::sendInitialHeaders(const char* aURLPath, const char* aHttpMethod)
|
||||
{
|
||||
#ifdef LOGGING
|
||||
Serial.println("Connected");
|
||||
#endif
|
||||
// Send the HTTP command, i.e. "GET /somepath/ HTTP/1.0"
|
||||
iClient->print(aHttpMethod);
|
||||
iClient->print(" ");
|
||||
|
||||
iClient->print(aURLPath);
|
||||
iClient->println(" HTTP/1.1");
|
||||
if (iSendDefaultRequestHeaders)
|
||||
{
|
||||
// The host header, if required
|
||||
if (iServerName)
|
||||
{
|
||||
iClient->print("Host: ");
|
||||
iClient->print(iServerName);
|
||||
if (iServerPort != kHttpPort)
|
||||
{
|
||||
iClient->print(":");
|
||||
iClient->print(iServerPort);
|
||||
}
|
||||
iClient->println();
|
||||
}
|
||||
// And user-agent string
|
||||
sendHeader(HTTP_HEADER_USER_AGENT, kUserAgent);
|
||||
}
|
||||
|
||||
if (iConnectionClose)
|
||||
{
|
||||
// Tell the server to
|
||||
// close this connection after we're done
|
||||
sendHeader(HTTP_HEADER_CONNECTION, "close");
|
||||
}
|
||||
|
||||
// Everything has gone well
|
||||
iState = eRequestStarted;
|
||||
return HTTP_SUCCESS;
|
||||
}
|
||||
|
||||
void HttpClient::sendHeader(const char* aHeader)
|
||||
{
|
||||
iClient->println(aHeader);
|
||||
}
|
||||
|
||||
void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue)
|
||||
{
|
||||
iClient->print(aHeaderName);
|
||||
iClient->print(": ");
|
||||
iClient->println(aHeaderValue);
|
||||
}
|
||||
|
||||
void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue)
|
||||
{
|
||||
iClient->print(aHeaderName);
|
||||
iClient->print(": ");
|
||||
iClient->println(aHeaderValue);
|
||||
}
|
||||
|
||||
void HttpClient::sendBasicAuth(const char* aUser, const char* aPassword)
|
||||
{
|
||||
// Send the initial part of this header line
|
||||
iClient->print("Authorization: Basic ");
|
||||
// Now Base64 encode "aUser:aPassword" and send that
|
||||
// This seems trickier than it should be but it's mostly to avoid either
|
||||
// (a) some arbitrarily sized buffer which hopes to be big enough, or
|
||||
// (b) allocating and freeing memory
|
||||
// ...so we'll loop through 3 bytes at a time, outputting the results as we
|
||||
// go.
|
||||
// In Base64, each 3 bytes of unencoded data become 4 bytes of encoded data
|
||||
unsigned char input[3];
|
||||
unsigned char output[5]; // Leave space for a '\0' terminator so we can easily print
|
||||
int userLen = strlen(aUser);
|
||||
int passwordLen = strlen(aPassword);
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < (userLen+1+passwordLen); i++)
|
||||
{
|
||||
// Copy the relevant input byte into the input
|
||||
if (i < userLen)
|
||||
{
|
||||
input[inputOffset++] = aUser[i];
|
||||
}
|
||||
else if (i == userLen)
|
||||
{
|
||||
input[inputOffset++] = ':';
|
||||
}
|
||||
else
|
||||
{
|
||||
input[inputOffset++] = aPassword[i-(userLen+1)];
|
||||
}
|
||||
// See if we've got a chunk to encode
|
||||
if ( (inputOffset == 3) || (i == userLen+passwordLen) )
|
||||
{
|
||||
// We've either got to a 3-byte boundary, or we've reached then end
|
||||
b64_encode(input, inputOffset, output, 4);
|
||||
// NUL-terminate the output string
|
||||
output[4] = '\0';
|
||||
// And write it out
|
||||
iClient->print((char*)output);
|
||||
// FIXME We might want to fill output with '=' characters if b64_encode doesn't
|
||||
// FIXME do it for us when we're encoding the final chunk
|
||||
inputOffset = 0;
|
||||
}
|
||||
}
|
||||
// And end the header we've sent
|
||||
iClient->println();
|
||||
}
|
||||
|
||||
void HttpClient::finishHeaders()
|
||||
{
|
||||
iClient->println();
|
||||
iState = eRequestSent;
|
||||
}
|
||||
|
||||
void HttpClient::flushClientRx()
|
||||
{
|
||||
while (iClient->available())
|
||||
{
|
||||
iClient->read();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpClient::endRequest()
|
||||
{
|
||||
if (iState < eRequestSent)
|
||||
{
|
||||
// We still need to finish off the headers
|
||||
finishHeaders();
|
||||
}
|
||||
// else the end of headers has already been sent, so nothing to do here
|
||||
}
|
||||
|
||||
int HttpClient::get(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_GET);
|
||||
}
|
||||
|
||||
int HttpClient::get(const String& aURLPath)
|
||||
{
|
||||
return get(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::post(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_POST);
|
||||
}
|
||||
|
||||
int HttpClient::post(const String& aURLPath)
|
||||
{
|
||||
return post(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::post(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return post(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::post(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return post(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_POST, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::put(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_PUT);
|
||||
}
|
||||
|
||||
int HttpClient::put(const String& aURLPath)
|
||||
{
|
||||
return put(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::put(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return put(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::put(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return put(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_PUT, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::del(const char* aURLPath)
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_DELETE);
|
||||
}
|
||||
|
||||
int HttpClient::del(const String& aURLPath)
|
||||
{
|
||||
return del(aURLPath.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::del(const char* aURLPath, const char* aContentType, const char* aBody)
|
||||
{
|
||||
return del(aURLPath, aContentType, strlen(aBody), (const byte*)aBody);
|
||||
}
|
||||
|
||||
int HttpClient::del(const String& aURLPath, const String& aContentType, const String& aBody)
|
||||
{
|
||||
return del(aURLPath.c_str(), aContentType.c_str(), aBody.length(), (const byte*)aBody.c_str());
|
||||
}
|
||||
|
||||
int HttpClient::del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[])
|
||||
{
|
||||
return startRequest(aURLPath, HTTP_METHOD_DELETE, aContentType, aContentLength, aBody);
|
||||
}
|
||||
|
||||
int HttpClient::responseStatusCode()
|
||||
{
|
||||
if (iState < eRequestSent)
|
||||
{
|
||||
return HTTP_ERROR_API;
|
||||
}
|
||||
// The first line will be of the form Status-Line:
|
||||
// HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
||||
// Where HTTP-Version is of the form:
|
||||
// HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT
|
||||
|
||||
int c = '\0';
|
||||
do
|
||||
{
|
||||
// Make sure the status code is reset, and likewise the state. This
|
||||
// lets us easily cope with 1xx informational responses by just
|
||||
// ignoring them really, and reading the next line for a proper response
|
||||
iStatusCode = 0;
|
||||
iState = eRequestSent;
|
||||
|
||||
unsigned long timeoutStart = millis();
|
||||
// Psuedo-regexp we're expecting before the status-code
|
||||
const char* statusPrefix = "HTTP/*.* ";
|
||||
const char* statusPtr = statusPrefix;
|
||||
// Whilst we haven't timed out & haven't reached the end of the headers
|
||||
while ((c != '\n') &&
|
||||
( (millis() - timeoutStart) < iHttpResponseTimeout ))
|
||||
{
|
||||
if (available())
|
||||
{
|
||||
c = read();
|
||||
if (c != -1)
|
||||
{
|
||||
switch(iState)
|
||||
{
|
||||
case eRequestSent:
|
||||
// We haven't reached the status code yet
|
||||
if ( (*statusPtr == '*') || (*statusPtr == c) )
|
||||
{
|
||||
// This character matches, just move along
|
||||
statusPtr++;
|
||||
if (*statusPtr == '\0')
|
||||
{
|
||||
// We've reached the end of the prefix
|
||||
iState = eReadingStatusCode;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return HTTP_ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
break;
|
||||
case eReadingStatusCode:
|
||||
if (isdigit(c))
|
||||
{
|
||||
// This assumes we won't get more than the 3 digits we
|
||||
// want
|
||||
iStatusCode = iStatusCode*10 + (c - '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've reached the end of the status code
|
||||
// We could sanity check it here or double-check for ' '
|
||||
// rather than anything else, but let's be lenient
|
||||
iState = eStatusCodeRead;
|
||||
}
|
||||
break;
|
||||
case eStatusCodeRead:
|
||||
// We're just waiting for the end of the line now
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
};
|
||||
// We read something, reset the timeout counter
|
||||
timeoutStart = millis();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We haven't got any data, so let's pause to allow some to
|
||||
// arrive
|
||||
delay(kHttpWaitForDataDelay);
|
||||
}
|
||||
}
|
||||
if ( (c == '\n') && (iStatusCode < 200 && iStatusCode != 101) )
|
||||
{
|
||||
// We've reached the end of an informational status line
|
||||
c = '\0'; // Clear c so we'll go back into the data reading loop
|
||||
}
|
||||
}
|
||||
// If we've read a status code successfully but it's informational (1xx)
|
||||
// loop back to the start
|
||||
while ( (iState == eStatusCodeRead) && (iStatusCode < 200 && iStatusCode != 101) );
|
||||
|
||||
if ( (c == '\n') && (iState == eStatusCodeRead) )
|
||||
{
|
||||
// We've read the status-line successfully
|
||||
return iStatusCode;
|
||||
}
|
||||
else if (c != '\n')
|
||||
{
|
||||
// We must've timed out before we reached the end of the line
|
||||
return HTTP_ERROR_TIMED_OUT;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This wasn't a properly formed status line, or at least not one we
|
||||
// could understand
|
||||
return HTTP_ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
}
|
||||
|
||||
int HttpClient::skipResponseHeaders()
|
||||
{
|
||||
// Just keep reading until we finish reading the headers or time out
|
||||
unsigned long timeoutStart = millis();
|
||||
// Whilst we haven't timed out & haven't reached the end of the headers
|
||||
while ((!endOfHeadersReached()) &&
|
||||
( (millis() - timeoutStart) < iHttpResponseTimeout ))
|
||||
{
|
||||
if (available())
|
||||
{
|
||||
(void)readHeader();
|
||||
// We read something, reset the timeout counter
|
||||
timeoutStart = millis();
|
||||
}
|
||||
else
|
||||
{
|
||||
// We haven't got any data, so let's pause to allow some to
|
||||
// arrive
|
||||
delay(kHttpWaitForDataDelay);
|
||||
}
|
||||
}
|
||||
if (endOfHeadersReached())
|
||||
{
|
||||
// Success
|
||||
return HTTP_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We must've timed out
|
||||
return HTTP_ERROR_TIMED_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
int HttpClient::contentLength()
|
||||
{
|
||||
// skip the response headers, if they haven't been read already
|
||||
if (!endOfHeadersReached())
|
||||
{
|
||||
skipResponseHeaders();
|
||||
}
|
||||
|
||||
return iContentLength;
|
||||
}
|
||||
|
||||
String HttpClient::responseBody()
|
||||
{
|
||||
int bodyLength = contentLength();
|
||||
String response;
|
||||
|
||||
if (bodyLength > 0)
|
||||
{
|
||||
response.reserve(bodyLength);
|
||||
}
|
||||
|
||||
while (available())
|
||||
{
|
||||
response += (char)read();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
bool HttpClient::endOfBodyReached()
|
||||
{
|
||||
if (endOfHeadersReached() && (contentLength() != kNoContentLengthHeader))
|
||||
{
|
||||
// We've got to the body and we know how long it will be
|
||||
return (iBodyLengthConsumed >= contentLength());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int HttpClient::read()
|
||||
{
|
||||
int ret = iClient->read();
|
||||
if (ret >= 0)
|
||||
{
|
||||
if (endOfHeadersReached() && iContentLength > 0)
|
||||
{
|
||||
// We're outputting the body now and we've seen a Content-Length header
|
||||
// So keep track of how many bytes are left
|
||||
iBodyLengthConsumed++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HttpClient::headerAvailable()
|
||||
{
|
||||
// clear the currently store header line
|
||||
iHeaderLine = "";
|
||||
|
||||
while (!endOfHeadersReached())
|
||||
{
|
||||
// read a byte from the header
|
||||
int c = readHeader();
|
||||
|
||||
if (c == '\r' || c == '\n')
|
||||
{
|
||||
if (iHeaderLine.length())
|
||||
{
|
||||
// end of the line, all done
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ignore any CR or LF characters
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// append byte to header line
|
||||
iHeaderLine += (char)c;
|
||||
}
|
||||
|
||||
return (iHeaderLine.length() > 0);
|
||||
}
|
||||
|
||||
String HttpClient::readHeaderName()
|
||||
{
|
||||
int colonIndex = iHeaderLine.indexOf(':');
|
||||
|
||||
if (colonIndex == -1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return iHeaderLine.substring(0, colonIndex);
|
||||
}
|
||||
|
||||
String HttpClient::readHeaderValue()
|
||||
{
|
||||
int colonIndex = iHeaderLine.indexOf(':');
|
||||
int startIndex = colonIndex + 1;
|
||||
|
||||
if (colonIndex == -1)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// trim any leading whitespace
|
||||
while (startIndex < (int)iHeaderLine.length() && isSpace(iHeaderLine[startIndex]))
|
||||
{
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
return iHeaderLine.substring(startIndex);
|
||||
}
|
||||
|
||||
int HttpClient::read(uint8_t *buf, size_t size)
|
||||
{
|
||||
int ret =iClient->read(buf, size);
|
||||
if (endOfHeadersReached() && iContentLength > 0)
|
||||
{
|
||||
// We're outputting the body now and we've seen a Content-Length header
|
||||
// So keep track of how many bytes are left
|
||||
if (ret >= 0)
|
||||
{
|
||||
iBodyLengthConsumed += ret;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int HttpClient::readHeader()
|
||||
{
|
||||
char c = read();
|
||||
|
||||
if (endOfHeadersReached())
|
||||
{
|
||||
// We've passed the headers, but rather than return an error, we'll just
|
||||
// act as a slightly less efficient version of read()
|
||||
return c;
|
||||
}
|
||||
|
||||
// Whilst reading out the headers to whoever wants them, we'll keep an
|
||||
// eye out for the "Content-Length" header
|
||||
switch(iState)
|
||||
{
|
||||
case eStatusCodeRead:
|
||||
// We're at the start of a line, or somewhere in the middle of reading
|
||||
// the Content-Length prefix
|
||||
if (*iContentLengthPtr == c)
|
||||
{
|
||||
// This character matches, just move along
|
||||
iContentLengthPtr++;
|
||||
if (*iContentLengthPtr == '\0')
|
||||
{
|
||||
// We've reached the end of the prefix
|
||||
iState = eReadingContentLength;
|
||||
// Just in case we get multiple Content-Length headers, this
|
||||
// will ensure we just get the value of the last one
|
||||
iContentLength = 0;
|
||||
}
|
||||
}
|
||||
else if ((iContentLengthPtr == kContentLengthPrefix) && (c == '\r'))
|
||||
{
|
||||
// We've found a '\r' at the start of a line, so this is probably
|
||||
// the end of the headers
|
||||
iState = eLineStartingCRFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This isn't the Content-Length header, skip to the end of the line
|
||||
iState = eSkipToEndOfHeader;
|
||||
}
|
||||
break;
|
||||
case eReadingContentLength:
|
||||
if (isdigit(c))
|
||||
{
|
||||
iContentLength = iContentLength*10 + (c - '0');
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've reached the end of the content length
|
||||
// We could sanity check it here or double-check for "\r\n"
|
||||
// rather than anything else, but let's be lenient
|
||||
iState = eSkipToEndOfHeader;
|
||||
}
|
||||
break;
|
||||
case eLineStartingCRFound:
|
||||
if (c == '\n')
|
||||
{
|
||||
iState = eReadingBody;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// We're just waiting for the end of the line now
|
||||
break;
|
||||
};
|
||||
|
||||
if ( (c == '\n') && !endOfHeadersReached() )
|
||||
{
|
||||
// We've got to the end of this line, start processing again
|
||||
iState = eStatusCodeRead;
|
||||
iContentLengthPtr = kContentLengthPrefix;
|
||||
}
|
||||
// And return the character read to whoever wants it
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
|
350
src/HttpClient.h
Normal file
350
src/HttpClient.h
Normal file
@ -0,0 +1,350 @@
|
||||
// Class to simplify HTTP fetching on Arduino
|
||||
// (c) Copyright MCQN Ltd. 2010-2012
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#ifndef HttpClient_h
|
||||
#define HttpClient_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <IPAddress.h>
|
||||
#include "Client.h"
|
||||
|
||||
static const int HTTP_SUCCESS =0;
|
||||
// The end of the headers has been reached. This consumes the '\n'
|
||||
// Could not connect to the server
|
||||
static const int HTTP_ERROR_CONNECTION_FAILED =-1;
|
||||
// This call was made when the HttpClient class wasn't expecting it
|
||||
// to be called. Usually indicates your code is using the class
|
||||
// incorrectly
|
||||
static const int HTTP_ERROR_API =-2;
|
||||
// Spent too long waiting for a reply
|
||||
static const int HTTP_ERROR_TIMED_OUT =-3;
|
||||
// The response from the server is invalid, is it definitely an HTTP
|
||||
// server?
|
||||
static const int HTTP_ERROR_INVALID_RESPONSE =-4;
|
||||
|
||||
// Define some of the common methods and headers here
|
||||
// That lets other code reuse them without having to declare another copy
|
||||
// of them, so saves code space and RAM
|
||||
#define HTTP_METHOD_GET "GET"
|
||||
#define HTTP_METHOD_POST "POST"
|
||||
#define HTTP_METHOD_PUT "PUT"
|
||||
#define HTTP_METHOD_DELETE "DELETE"
|
||||
#define HTTP_HEADER_CONTENT_LENGTH "Content-Length"
|
||||
#define HTTP_HEADER_CONTENT_TYPE "Content-Type"
|
||||
#define HTTP_HEADER_CONNECTION "Connection"
|
||||
#define HTTP_HEADER_USER_AGENT "User-Agent"
|
||||
|
||||
class HttpClient : public Client
|
||||
{
|
||||
public:
|
||||
static const int kNoContentLengthHeader =-1;
|
||||
static const int kHttpPort =80;
|
||||
static const char* kUserAgent;
|
||||
|
||||
// FIXME Write longer API request, using port and user-agent, example
|
||||
// FIXME Update tempToPachube example to calculate Content-Length correctly
|
||||
|
||||
HttpClient(Client& aClient, const char* aServerName, uint16_t aServerPort = kHttpPort);
|
||||
HttpClient(Client& aClient, const String& aServerName, uint16_t aServerPort = kHttpPort);
|
||||
HttpClient(Client& aClient, const IPAddress& aServerAddress, uint16_t aServerPort = kHttpPort);
|
||||
|
||||
/** Start a more complex request.
|
||||
Use this when you need to send additional headers in the request,
|
||||
but you will also need to call endRequest() when you are finished.
|
||||
*/
|
||||
void beginRequest();
|
||||
|
||||
/** End a more complex request.
|
||||
Use this when you need to have sent additional headers in the request,
|
||||
but you will also need to call beginRequest() at the start.
|
||||
*/
|
||||
void endRequest();
|
||||
|
||||
/** Connect to the server and start to send a GET request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int get(const char* aURLPath);
|
||||
int get(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and start to send a POST request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int post(const char* aURLPath);
|
||||
int post(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a POST request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int post(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int post(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int post(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send a PUT request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int put(const char* aURLPath);
|
||||
int put(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a PUT request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int put(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int put(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int put(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send a DELETE request.
|
||||
@param aURLPath Url to request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int del(const char* aURLPath);
|
||||
int del(const String& aURLPath);
|
||||
|
||||
/** Connect to the server and send a DELETE request
|
||||
with body and content type
|
||||
@param aURLPath Url to request
|
||||
@param aContentType Content type of request body
|
||||
@param aBody Body of the request
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int del(const char* aURLPath, const char* aContentType, const char* aBody);
|
||||
int del(const String& aURLPath, const String& aContentType, const String& aBody);
|
||||
int del(const char* aURLPath, const char* aContentType, int aContentLength, const byte aBody[]);
|
||||
|
||||
/** Connect to the server and start to send the request.
|
||||
If a body is provided, the entire request (including headers and body) will be sent
|
||||
@param aURLPath Url to request
|
||||
@param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
|
||||
@param aContentType Content type of request body (optional)
|
||||
@param aContentLength Length of request body (optional)
|
||||
@param aBody Body of request (optional)
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int startRequest(const char* aURLPath,
|
||||
const char* aHttpMethod,
|
||||
const char* aContentType = NULL,
|
||||
int aContentLength = -1,
|
||||
const byte aBody[] = NULL);
|
||||
|
||||
/** Send an additional header line. This can only be called in between the
|
||||
calls to startRequest and finishRequest.
|
||||
@param aHeader Header line to send, in its entirety (but without the
|
||||
trailing CRLF. E.g. "Authorization: Basic YQDDCAIGES"
|
||||
*/
|
||||
void sendHeader(const char* aHeader);
|
||||
|
||||
void sendHeader(const String& aHeader)
|
||||
{ sendHeader(aHeader.c_str()); }
|
||||
|
||||
/** Send an additional header line. This is an alternate form of
|
||||
sendHeader() which takes the header name and content as separate strings.
|
||||
The call will add the ": " to separate the header, so for example, to
|
||||
send a XXXXXX header call sendHeader("XXXXX", "Something")
|
||||
@param aHeaderName Type of header being sent
|
||||
@param aHeaderValue Value for that header
|
||||
*/
|
||||
void sendHeader(const char* aHeaderName, const char* aHeaderValue);
|
||||
|
||||
void sendHeader(const String& aHeaderName, const String& aHeaderValue)
|
||||
{ sendHeader(aHeaderName.c_str(), aHeaderValue.c_str()); }
|
||||
|
||||
/** Send an additional header line. This is an alternate form of
|
||||
sendHeader() which takes the header name and content separately but where
|
||||
the value is provided as an integer.
|
||||
The call will add the ": " to separate the header, so for example, to
|
||||
send a XXXXXX header call sendHeader("XXXXX", 123)
|
||||
@param aHeaderName Type of header being sent
|
||||
@param aHeaderValue Value for that header
|
||||
*/
|
||||
void sendHeader(const char* aHeaderName, const int aHeaderValue);
|
||||
|
||||
void sendHeader(const String& aHeaderName, const int aHeaderValue)
|
||||
{ sendHeader(aHeaderName.c_str(), aHeaderValue); }
|
||||
|
||||
/** Send a basic authentication header. This will encode the given username
|
||||
and password, and send them in suitable header line for doing Basic
|
||||
Authentication.
|
||||
@param aUser Username for the authorization
|
||||
@param aPassword Password for the user aUser
|
||||
*/
|
||||
void sendBasicAuth(const char* aUser, const char* aPassword);
|
||||
|
||||
void sendBasicAuth(const String& aUser, const String& aPassword)
|
||||
{ sendBasicAuth(aUser.c_str(), aPassword.c_str()); }
|
||||
|
||||
/** Get the HTTP status code contained in the response.
|
||||
For example, 200 for successful request, 404 for file not found, etc.
|
||||
*/
|
||||
int responseStatusCode();
|
||||
|
||||
/** Check if a header is available to be read.
|
||||
Use readHeaderName() to read header name, and readHeaderValue() to
|
||||
read the header value
|
||||
MUST be called after responseStatusCode() and before contentLength()
|
||||
*/
|
||||
bool headerAvailable();
|
||||
|
||||
/** Read the name of the current response header.
|
||||
Returns empty string if a header is not available.
|
||||
*/
|
||||
String readHeaderName();
|
||||
|
||||
/** Read the vallue of the current response header.
|
||||
Returns empty string if a header is not available.
|
||||
*/
|
||||
String readHeaderValue();
|
||||
|
||||
/** Read the next character of the response headers.
|
||||
This functions in the same way as read() but to be used when reading
|
||||
through the headers. Check whether or not the end of the headers has
|
||||
been reached by calling endOfHeadersReached(), although after that point
|
||||
this will still return data as read() would, but slightly less efficiently
|
||||
MUST be called after responseStatusCode() and before contentLength()
|
||||
@return The next character of the response headers
|
||||
*/
|
||||
int readHeader();
|
||||
|
||||
/** Skip any response headers to get to the body.
|
||||
Use this if you don't want to do any special processing of the headers
|
||||
returned in the response. You can also use it after you've found all of
|
||||
the headers you're interested in, and just want to get on with processing
|
||||
the body.
|
||||
MUST be called after responseStatusCode()
|
||||
@return HTTP_SUCCESS if successful, else an error code
|
||||
*/
|
||||
int skipResponseHeaders();
|
||||
|
||||
/** Test whether all of the response headers have been consumed.
|
||||
@return true if we are now processing the response body, else false
|
||||
*/
|
||||
bool endOfHeadersReached() { return (iState == eReadingBody); };
|
||||
|
||||
/** Test whether the end of the body has been reached.
|
||||
Only works if the Content-Length header was returned by the server
|
||||
@return true if we are now at the end of the body, else false
|
||||
*/
|
||||
bool endOfBodyReached();
|
||||
virtual bool endOfStream() { return endOfBodyReached(); };
|
||||
virtual bool completed() { return endOfBodyReached(); };
|
||||
|
||||
/** Return the length of the body.
|
||||
Also skips response headers if they have not been read already
|
||||
MUST be called after responseStatusCode()
|
||||
@return Length of the body, in bytes, or kNoContentLengthHeader if no
|
||||
Content-Length header was returned by the server
|
||||
*/
|
||||
int contentLength();
|
||||
|
||||
/** Return the response body as a String
|
||||
Also skips response headers if they have not been read already
|
||||
MUST be called after responseStatusCode()
|
||||
@return response body of request as a String
|
||||
*/
|
||||
String responseBody();
|
||||
|
||||
/** Enables connection keep-alive mode
|
||||
*/
|
||||
void connectionKeepAlive();
|
||||
|
||||
/** Disables sending the default request headers (Host and User Agent)
|
||||
*/
|
||||
void noDefaultRequestHeaders();
|
||||
|
||||
// Inherited from Print
|
||||
// Note: 1st call to these indicates the user is sending the body, so if need
|
||||
// Note: be we should finish the header first
|
||||
virtual size_t write(uint8_t aByte) { if (iState < eRequestSent) { finishHeaders(); }; return iClient-> write(aByte); };
|
||||
virtual size_t write(const uint8_t *aBuffer, size_t aSize) { if (iState < eRequestSent) { finishHeaders(); }; return iClient->write(aBuffer, aSize); };
|
||||
// Inherited from Stream
|
||||
virtual int available() { return iClient->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() { return iClient->peek(); };
|
||||
virtual void flush() { return iClient->flush(); };
|
||||
|
||||
// Inherited from Client
|
||||
virtual int connect(IPAddress ip, uint16_t port) { return iClient->connect(ip, port); };
|
||||
virtual int connect(const char *host, uint16_t port) { return iClient->connect(host, port); };
|
||||
virtual void stop();
|
||||
virtual uint8_t connected() { return iClient->connected(); };
|
||||
virtual operator bool() { return bool(iClient); };
|
||||
virtual uint32_t httpResponseTimeout() { return iHttpResponseTimeout; };
|
||||
virtual void setHttpResponseTimeout(uint32_t timeout) { iHttpResponseTimeout = timeout; };
|
||||
protected:
|
||||
/** Reset internal state data back to the "just initialised" state
|
||||
*/
|
||||
void resetState();
|
||||
|
||||
/** Send the first part of the request and the initial headers.
|
||||
@param aURLPath Url to request
|
||||
@param aHttpMethod Type of HTTP request to make, e.g. "GET", "POST", etc.
|
||||
@return 0 if successful, else error
|
||||
*/
|
||||
int sendInitialHeaders(const char* aURLPath,
|
||||
const char* aHttpMethod);
|
||||
|
||||
/* Let the server know that we've reached the end of the headers
|
||||
*/
|
||||
void finishHeaders();
|
||||
|
||||
/** Reading any pending data from the client (used in connection keep alive mode)
|
||||
*/
|
||||
void flushClientRx();
|
||||
|
||||
// Number of milliseconds that we wait each time there isn't any data
|
||||
// available to be read (during status code and header processing)
|
||||
static const int kHttpWaitForDataDelay = 1000;
|
||||
// Number of milliseconds that we'll wait in total without receiveing any
|
||||
// data before returning HTTP_ERROR_TIMED_OUT (during status code and header
|
||||
// processing)
|
||||
static const int kHttpResponseTimeout = 30*1000;
|
||||
static const char* kContentLengthPrefix;
|
||||
typedef enum {
|
||||
eIdle,
|
||||
eRequestStarted,
|
||||
eRequestSent,
|
||||
eReadingStatusCode,
|
||||
eStatusCodeRead,
|
||||
eReadingContentLength,
|
||||
eSkipToEndOfHeader,
|
||||
eLineStartingCRFound,
|
||||
eReadingBody
|
||||
} tHttpState;
|
||||
// Client we're using
|
||||
Client* iClient;
|
||||
// Server we are connecting to
|
||||
const char* iServerName;
|
||||
IPAddress iServerAddress;
|
||||
// Port of server we are connecting to
|
||||
uint16_t iServerPort;
|
||||
// Current state of the finite-state-machine
|
||||
tHttpState iState;
|
||||
// Stores the status code for the response, once known
|
||||
int iStatusCode;
|
||||
// Stores the value of the Content-Length header, if present
|
||||
int iContentLength;
|
||||
// How many bytes of the response body have been read by the user
|
||||
int iBodyLengthConsumed;
|
||||
// How far through a Content-Length header prefix we are
|
||||
const char* iContentLengthPtr;
|
||||
uint32_t iHttpResponseTimeout;
|
||||
bool iConnectionClose;
|
||||
bool iSendDefaultRequestHeaders;
|
||||
String iHeaderLine;
|
||||
};
|
||||
|
||||
#endif
|
372
src/WebSocketClient.cpp
Normal file
372
src/WebSocketClient.cpp
Normal file
@ -0,0 +1,372 @@
|
||||
// (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
src/WebSocketClient.h
Normal file
99
src/WebSocketClient.h
Normal 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
|
72
src/b64.cpp
Normal file
72
src/b64.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
// Simple Base64 code
|
||||
// (c) Copyright 2010 MCQN Ltd.
|
||||
// Released under Apache License, version 2.0
|
||||
|
||||
#include "b64.h"
|
||||
|
||||
/* Simple test program
|
||||
#include <stdio.h>
|
||||
void main()
|
||||
{
|
||||
char* in = "amcewen";
|
||||
char out[22];
|
||||
|
||||
b64_encode(in, 15, out, 22);
|
||||
out[21] = '\0';
|
||||
|
||||
printf(out);
|
||||
}
|
||||
*/
|
||||
|
||||
int b64_encode(const unsigned char* aInput, int aInputLen, unsigned char* aOutput, int aOutputLen)
|
||||
{
|
||||
// Work out if we've got enough space to encode the input
|
||||
// Every 6 bits of input becomes a byte of output
|
||||
if (aOutputLen < (aInputLen*8)/6)
|
||||
{
|
||||
// FIXME Should we return an error here, or just the length
|
||||
return (aInputLen*8)/6;
|
||||
}
|
||||
|
||||
// If we get here we've got enough space to do the encoding
|
||||
|
||||
const char* b64_dictionary = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
if (aInputLen == 3)
|
||||
{
|
||||
aOutput[0] = b64_dictionary[aInput[0] >> 2];
|
||||
aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)];
|
||||
aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2|(aInput[2]>>6)];
|
||||
aOutput[3] = b64_dictionary[aInput[2]&0x3F];
|
||||
}
|
||||
else if (aInputLen == 2)
|
||||
{
|
||||
aOutput[0] = b64_dictionary[aInput[0] >> 2];
|
||||
aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4|(aInput[1]>>4)];
|
||||
aOutput[2] = b64_dictionary[(aInput[1]&0x0F)<<2];
|
||||
aOutput[3] = '=';
|
||||
}
|
||||
else if (aInputLen == 1)
|
||||
{
|
||||
aOutput[0] = b64_dictionary[aInput[0] >> 2];
|
||||
aOutput[1] = b64_dictionary[(aInput[0] & 0x3)<<4];
|
||||
aOutput[2] = '=';
|
||||
aOutput[3] = '=';
|
||||
}
|
||||
else
|
||||
{
|
||||
// Break the input into 3-byte chunks and process each of them
|
||||
int i;
|
||||
for (i = 0; i < aInputLen/3; i++)
|
||||
{
|
||||
b64_encode(&aInput[i*3], 3, &aOutput[i*4], 4);
|
||||
}
|
||||
if (aInputLen % 3 > 0)
|
||||
{
|
||||
// It doesn't fit neatly into a 3-byte chunk, so process what's left
|
||||
b64_encode(&aInput[i*3], aInputLen % 3, &aOutput[i*4], aOutputLen - (i*4));
|
||||
}
|
||||
}
|
||||
|
||||
return ((aInputLen+2)/3)*4;
|
||||
}
|
||||
|
Reference in New Issue
Block a user