Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IChunkStream interface and post from stream #36

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions firmware/HttpClient.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "HttpClient.h"
#include "IChunkStream.h"

static const uint16_t DEFAULT_TIMEOUT = 5000; // Allow maximum 5s between data packets.

Expand Down Expand Up @@ -284,3 +285,231 @@ void HttpClient::request(http_request_t &aRequest, http_response_t &aResponse, h
// Return the entire message body from bodyPos+4 till end.
aResponse.body = buffer;
}

/**
* Method to send an HTTP Request. Allocate variables in your application code
* in the aResponse struct and set the headers and the options in the aRequest
* struct.
*/
void HttpClient::request(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[], const char* aHttpMethod, IChunkStream* stream)
{
// If a proper response code isn't received it will be set to -1.
aResponse.status = -1;

// NOTE: The default port tertiary statement is unpredictable if the request structure is not initialised
// http_request_t request = {0} or memset(&request, 0, sizeof(http_request_t)) should be used
// to ensure all fields are zero
bool connected = false;
if(aRequest.hostname!=NULL) {
connected = client.connect(aRequest.hostname.c_str(), (aRequest.port) ? aRequest.port : 80 );
} else {
connected = client.connect(aRequest.ip, aRequest.port);
}

#ifdef LOGGING
if (connected) {
if(aRequest.hostname!=NULL) {
Serial.print("HttpClient>\tConnecting to: ");
Serial.print(aRequest.hostname);
} else {
Serial.print("HttpClient>\tConnecting to IP: ");
Serial.print(aRequest.ip);
}
Serial.print(":");
Serial.println(aRequest.port);
} else {
Serial.println("HttpClient>\tConnection failed.");
}
#endif

if (!connected) {
client.stop();
// If TCP Client can't connect to host, exit here.
return;
}

//
// Send HTTP Headers
//

// Send initial headers (only HTTP 1.0 is supported for now).
client.print(aHttpMethod);
client.print(" ");
client.print(aRequest.path);
client.print(" HTTP/1.0\r\n");

#ifdef LOGGING
Serial.println("HttpClient>\tStart of HTTP Request.");
Serial.print(aHttpMethod);
Serial.print(" ");
Serial.print(aRequest.path);
Serial.print(" HTTP/1.0\r\n");
#endif

// Send General and Request Headers.
sendHeader("Connection", "close"); // Not supporting keep-alive for now.
if(aRequest.hostname!=NULL) {
sendHeader("HOST", aRequest.hostname.c_str());
}

//Send Entity Headers
// TODO: Check the standard, currently sending Content-Length : 0 for empty
// POST requests, and no content-length for other types.

sendHeader("Content-Length", stream->getSize());

if (headers != NULL)
{
int i = 0;
while (headers[i].header != NULL)
{
if (headers[i].value != NULL) {
sendHeader(headers[i].header, headers[i].value);
} else {
sendHeader(headers[i].header);
}
i++;
}
}

// Empty line to finish headers
client.println();
client.flush();


//Send all chunks
while (!stream->Eof()){
client.print(stream->getNextChunk());
}

#ifdef LOGGING
Serial.println("HttpClient>\tEnd of HTTP Request.");
#endif

// clear response buffer
memset(&buffer[0], 0, sizeof(buffer));


//
// Receive HTTP Response
//
// The first value of client.available() might not represent the
// whole response, so after the first chunk of data is received instead
// of terminating the connection there is a delay and another attempt
// to read data.
// The loop exits when the connection is closed, or if there is a
// timeout or an error.

unsigned int bufferPosition = 0;
unsigned long lastRead = millis();
unsigned long firstRead = millis();
bool error = false;
bool timeout = false;
uint16_t actualTimeout = aRequest.timeout == 0 ? DEFAULT_TIMEOUT : aRequest.timeout;
char lastChar = 0;
bool inHeaders = true;

do {
#ifdef LOGGING
int bytes = client.available();
if(bytes) {
Serial.print("\r\nHttpClient>\tReceiving TCP transaction of ");
Serial.print(bytes);
Serial.println(" bytes.");
}
#endif

while (client.available()) {
char c = client.read();
#ifdef LOGGING
Serial.print(c);
#endif
lastRead = millis();

if (c == -1) {
error = true;

#ifdef LOGGING
Serial.println("HttpClient>\tError: No data available.");
#endif

break;
}

if (inHeaders) {
if ((c == '\n') && (lastChar == '\n')) {
// End of headers. Grab the status code and reset the buffer.
aResponse.status = atoi(&buffer[9]);

memset(&buffer[0], 0, sizeof(buffer));
bufferPosition = 0;
inHeaders = false;
#ifdef LOGGING
Serial.print("\r\nHttpClient>\tEnd of HTTP Headers (");
Serial.print(aResponse.status);
Serial.println(")");
#endif
continue;
} else if (c != '\r') {
lastChar = c;
}
}

// Check that received character fits in buffer before storing.
if (bufferPosition < sizeof(buffer)-1) {
buffer[bufferPosition] = c;
} else if ((bufferPosition == sizeof(buffer)-1)) {
buffer[bufferPosition] = '\0'; // Null-terminate buffer
client.stop();
error = true;

#ifdef LOGGING
Serial.println("\r\nHttpClient>\tError: Response body larger than buffer.");
#endif
break;
}
bufferPosition++;
}
// We don't need to null terminate the buffer since it was zeroed to start with, or null terminated when it reached capacity.

#ifdef LOGGING
if (bytes) {
Serial.print("\r\nHttpClient>\tEnd of TCP transaction.");
}
#endif

// Check that there hasn't been more than 5s since last read.
timeout = millis() - lastRead > actualTimeout;

// Unless there has been an error or timeout wait 200ms to allow server
// to respond or close connection.
if (!error && !timeout) {
delay(200);
}
} while (client.connected() && !timeout && !error);

#ifdef LOGGING
if (timeout) {
Serial.println("\r\nHttpClient>\tError: Timeout while reading response.");
}
Serial.print("\r\nHttpClient>\tEnd of HTTP Response (");
Serial.print(millis() - firstRead);
Serial.println("ms).");
#endif
client.stop();

#ifdef LOGGING
Serial.print("HttpClient>\tStatus Code: ");
Serial.println(aResponse.status);
#endif

if (inHeaders) {
#ifdef LOGGING
Serial.println("HttpClient>\tError: Can't find HTTP response body.");
#endif

return;
}
// Return the entire message body from bodyPos+4 till end.
aResponse.body = buffer;
}
9 changes: 8 additions & 1 deletion firmware/HttpClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "spark_wiring_string.h"
#include "spark_wiring_tcpclient.h"
#include "spark_wiring_usbserial.h"
#include "IChunkStream.h"

/**
* Defines for the HTTP methods.
Expand Down Expand Up @@ -101,7 +102,12 @@ class HttpClient {
{
request(aRequest, aResponse, headers, HTTP_METHOD_POST);
}


void post(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[], IChunkStream* stream)
{
request(aRequest, aResponse, headers, HTTP_METHOD_POST, stream);
}

void put(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[])
{
request(aRequest, aResponse, headers, HTTP_METHOD_PUT);
Expand All @@ -122,6 +128,7 @@ class HttpClient {
* Underlying HTTP methods.
*/
void request(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[], const char* aHttpMethod);
void request(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[], const char* aHttpMethod, IChunkStream* stream);
void sendHeader(const char* aHeaderName, const char* aHeaderValue);
void sendHeader(const char* aHeaderName, const int aHeaderValue);
void sendHeader(const char* aHeaderName);
Expand Down
12 changes: 12 additions & 0 deletions firmware/IChunkStream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef __I_CHUNK_STREAM_H_
#define __I_CHUNK_STREAM_H_

class IChunkStream {
public:
virtual ~IChunkStream() {}
virtual int getSize() = 0;
virtual String getNextChunk() = 0;
virtual int Eof() = 0;
};

#endif /* __I_CHUNK_STREAM_H_ */