Created
June 23, 2014 19:17
-
-
Save dmiddlecamp/1ed052f60b7f9dc9ee6c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "application.h" | |
#include "HttpClient.h" | |
/** | |
* Declaring the variables. | |
*/ | |
unsigned int nextTime = 0; // Next time to contact the server | |
HttpClient http; | |
// Headers currently need to be set at init, useful for API keys etc. | |
http_header_t headers[] = { | |
// { "Content-Type", "application/json" }, | |
// { "Accept" , "application/json" }, | |
{ "Accept" , "*/*"}, | |
{ NULL, NULL } // NOTE: Always terminate headers will NULL | |
}; | |
http_request_t request; | |
http_response_t response; | |
void setup() { | |
Serial.begin(9600); | |
} | |
void loop() { | |
if (nextTime > millis()) { | |
return; | |
} | |
Serial.println(); | |
Serial.println("Application>\tStart of Loop."); | |
// Request path and body can be set at runtime or at setup. | |
request.port = 80; | |
request.hostname = "roman-mueller.ch"; | |
request.path = "/api/weather"; | |
// The library also supports sending a body with your request: | |
//request.body = "{\"key\":\"value\"}"; | |
// Get request | |
http.get(request, response, headers); | |
Serial.print("Application>\tResponse status: "); | |
Serial.println(response.status); | |
Serial.print("Application>\tHTTP Response Body: "); | |
Serial.println(response.body); | |
nextTime = millis() + 10000; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "HttpClient.h" | |
#define LOGGING | |
#define TIMEOUT 5000 // Allow maximum 5s between data packets. | |
/** | |
* Constructor. | |
*/ | |
HttpClient::HttpClient() | |
{ | |
} | |
/** | |
* Method to send a header, should only be called from within the class. | |
*/ | |
void HttpClient::sendHeader(const char* aHeaderName, const char* aHeaderValue) | |
{ | |
client.print(aHeaderName); | |
client.print(": "); | |
client.println(aHeaderValue); | |
#ifdef LOGGING | |
Serial.print(aHeaderName); | |
Serial.print(": "); | |
Serial.println(aHeaderValue); | |
#endif | |
} | |
void HttpClient::sendHeader(const char* aHeaderName, const int aHeaderValue) | |
{ | |
client.print(aHeaderName); | |
client.print(": "); | |
client.println(aHeaderValue); | |
#ifdef LOGGING | |
Serial.print(aHeaderName); | |
Serial.print(": "); | |
Serial.println(aHeaderValue); | |
#endif | |
} | |
void HttpClient::sendHeader(const char* aHeaderName) | |
{ | |
client.println(aHeaderName); | |
#ifdef LOGGING | |
Serial.println(aHeaderName); | |
#endif | |
} | |
/** | |
* 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) | |
{ | |
// If a proper response code isn't received it will be set to -1. | |
aResponse.status = -1; | |
bool connected = client.connect(aRequest.hostname.c_str(), aRequest.port); | |
if (!connected) { | |
client.stop(); | |
// If TCP Client can't connect to host, exit here. | |
return; | |
} | |
#ifdef LOGGING | |
if (connected) { | |
Serial.print("HttpClient>\tConnecting to: "); | |
Serial.print(aRequest.hostname); | |
Serial.print(":"); | |
Serial.println(aRequest.port); | |
} else { | |
Serial.println("HttpClient>\tConnection failed."); | |
} | |
#endif | |
// | |
// 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. | |
sendHeader("HOST", aRequest.hostname.c_str()); | |
// TODO: Support for connecting with IP address instead of URL | |
// if (aRequest.hostname == NULL) { | |
// //sendHeader("HOST", ip); | |
// } else { | |
// sendHeader("HOST", aRequest.hostname); | |
// } | |
//Send Entity Headers | |
// TODO: Check the standard, currently sending Content-Length : 0 for empty | |
// POST requests, and no content-length for other types. | |
if (aRequest.body != NULL) { | |
sendHeader("Content-Length", (aRequest.body).length()); | |
} else if (strcmp(aHttpMethod, HTTP_METHOD_POST) == 0) { //Check to see if its a Post method. | |
sendHeader("Content-Length", 0); | |
} | |
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 HTTP Request Body | |
// | |
if (aRequest.body != NULL) { | |
client.println(aRequest.body); | |
#ifdef LOGGING | |
Serial.println(aRequest.body); | |
#endif | |
} | |
#ifdef LOGGING | |
Serial.println("HttpClient>\tEnd of HTTP Request."); | |
#endif | |
// | |
// 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; | |
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; | |
} | |
// 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("HttpClient>\tError: Response body larger than buffer."); | |
#endif | |
} | |
bufferPosition++; | |
} | |
#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 > TIMEOUT; | |
// 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(); | |
String raw_response(buffer); | |
// Not super elegant way of finding the status code, but it works. | |
String statusCode = raw_response.substring(9,12); | |
#ifdef LOGGING | |
Serial.print("HttpClient>\tStatus Code: "); | |
Serial.println(statusCode); | |
#endif | |
int bodyPos = raw_response.indexOf("\r\n\r\n"); | |
if (bodyPos == -1) { | |
#ifdef LOGGING | |
Serial.println("HttpClient>\tError: Can't find HTTP response body."); | |
#endif | |
return; | |
} | |
// Return the entire message body from bodyPos+4 till end. | |
String rawSub = (String)raw_response.substring(bodyPos+4); | |
aResponse.body = rawSub; | |
aResponse.status = atoi(statusCode.c_str()); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef __HTTP_CLIENT_H_ | |
#define __HTTP_CLIENT_H_ | |
#include "application.h" | |
// #include "spark_wiring_string.h" | |
// #include "spark_wiring_tcpclient.h" | |
// #include "spark_wiring_usbserial.h" | |
/** | |
* Defines for the HTTP methods. | |
*/ | |
#define HTTP_METHOD_GET "GET" | |
#define HTTP_METHOD_POST "POST" | |
#define HTTP_METHOD_PUT "PUT" | |
#define HTTP_METHOD_DELETE "DELETE" | |
/** | |
* This struct is used to pass additional HTTP headers such as API-keys. | |
* Normally you pass this as an array. The last entry must have NULL as key. | |
*/ | |
typedef struct | |
{ | |
const char* header; | |
const char* value; | |
} http_header_t; | |
/** | |
* HTTP Request struct. | |
* hostname request host | |
* path request path | |
* port request port | |
* body request body | |
*/ | |
typedef struct | |
{ | |
String hostname; | |
String path; | |
// TODO: Look at setting the port by default. | |
//int port = 80; | |
int port; | |
String body; | |
} http_request_t; | |
/** | |
* HTTP Response struct. | |
* status response status code. | |
* body response body | |
*/ | |
typedef struct | |
{ | |
int status; | |
String body; | |
} http_response_t; | |
class HttpClient { | |
public: | |
/** | |
* Public references to variables. | |
*/ | |
TCPClient client; | |
char buffer[1024]; | |
/** | |
* Constructor. | |
*/ | |
HttpClient(void); | |
/** | |
* HTTP request methods. | |
* Can't use 'delete' as name since it's a C++ keyword. | |
*/ | |
void get(http_request_t &aRequest, http_response_t &aResponse) | |
{ | |
request(aRequest, aResponse, NULL, HTTP_METHOD_GET); | |
} | |
void post(http_request_t &aRequest, http_response_t &aResponse) | |
{ | |
request(aRequest, aResponse, NULL, HTTP_METHOD_POST); | |
} | |
void put(http_request_t &aRequest, http_response_t &aResponse) | |
{ | |
request(aRequest, aResponse, NULL, HTTP_METHOD_PUT); | |
} | |
void del(http_request_t &aRequest, http_response_t &aResponse) | |
{ | |
request(aRequest, aResponse, NULL, HTTP_METHOD_DELETE); | |
} | |
void get(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[]) | |
{ | |
request(aRequest, aResponse, headers, HTTP_METHOD_GET); | |
} | |
void post(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[]) | |
{ | |
request(aRequest, aResponse, headers, HTTP_METHOD_POST); | |
} | |
void put(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[]) | |
{ | |
request(aRequest, aResponse, headers, HTTP_METHOD_PUT); | |
} | |
void del(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[]) | |
{ | |
request(aRequest, aResponse, headers, HTTP_METHOD_DELETE); | |
} | |
private: | |
/** | |
* Underlying HTTP methods. | |
*/ | |
void request(http_request_t &aRequest, http_response_t &aResponse, http_header_t headers[], const char* aHttpMethod); | |
void sendHeader(const char* aHeaderName, const char* aHeaderValue); | |
void sendHeader(const char* aHeaderName, const int aHeaderValue); | |
void sendHeader(const char* aHeaderName); | |
}; | |
#endif /* __HTTP_CLIENT_H_ */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey man, awesome work.
This is the first really well structured clean http library i've found for Arduino.
Could you provide an MIT license with this so i could use it in my work?