Files
esp-weather-station/HttpsRedirect.cpp
Daniel Eichhorn eb560a2797 Cleaning up code
2017-06-25 08:30:19 +02:00

487 lines
12 KiB
C++

/* HTTPS on ESP8266 with follow redirects, chunked encoding support
* Version 2.1
* Author: Sujay Phadke
* Github: @electronicsguy
* Copyright (C) 2017 Sujay Phadke <electronicsguy123@gmail.com>
* All rights reserved.
*
*/
#include "HTTPSRedirect.h"
HTTPSRedirect::HTTPSRedirect(void) : _httpsPort(443){
Init();
}
HTTPSRedirect::HTTPSRedirect(const int p) : _httpsPort(p){
Init();
}
HTTPSRedirect::~HTTPSRedirect(){
}
void HTTPSRedirect::Init(void){
_keepAlive = true;
_printResponseBody = false;
_maxRedirects = 10;
_contentTypeHeader = "application/x-www-form-urlencoded";
}
// This is the main function which is similar to the method
// print() from WifiClient or WifiClientSecure
bool HTTPSRedirect::printRedir(void){
unsigned int httpStatus;
// Check if connection to host is alive
if (!connected()){
Serial.println("Error! Not connected to host.");
return false;
}
// Clear the input stream of any junk data before making the request
while(available())
read();
// Create HTTP/1.1 compliant request string
// HTTP/1.1 complaint request packet must exist
DPRINTLN(_Request);
// Make the actual HTTPS request using the method
// print() from the WifiClientSecure class
// Make sure the input stream is cleared (as above) before making the call
print(_Request);
// Read HTTP Response Status lines
while (connected()) {
httpStatus = getResponseStatus();
// Only some HTTP response codes are checked for
// http://www.restapitutorial.com/httpstatuscodes.html
switch (httpStatus){
// Success. Fetch final response body
case 200:
case 201:
{
// final header is discarded
fetchHeader();
#ifdef EXTRA_FNS
printHeaderFields();
#endif
if (_hF.transferEncoding == "chunked")
fetchBodyChunked();
else
fetchBodyUnChunked(_hF.contentLength);
return true;
}
break;
case 301:
case 302:
{
// Get re-direction URL from the 'Location' field in the header
if (getLocationURL()){
//stop(); // may not be required
_myResponse.redirected = true;
// Make a new connection to the re-direction server
if (!connect(_redirHost.c_str(), _httpsPort)) {
Serial.println("Connection to re-directed URL failed!");
return false;
}
// Recursive call to the requested URL on the server
return printRedir();
}
else{
Serial.println("Unable to retrieve redirection URL!");
return false;
}
}
break;
default:
Serial.print("Error with request. Response status code: ");
Serial.println(httpStatus);
return false;
break;
} // end of switch
} // end of while
return false;
}
// Create a HTTP GET request packet
// GET headers must be terminated with a "\r\n\r\n"
// http://stackoverflow.com/questions/6686261/what-at-the-bare-minimum-is-required-for-an-http-request
void HTTPSRedirect::createGetRequest(const String& url, const char* host){
_Request = String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266\r\n" +
(_keepAlive ? "" : "Connection: close\r\n") +
"\r\n\r\n";
return;
}
// Create a HTTP POST request packet
// POST headers must be terminated with a "\r\n\r\n"
// POST requests have 1 single blank like between the end of the header fields and the body payload
void HTTPSRedirect::createPostRequest(const String& url, const char* host, const String& payload){
// Content-Length is mandatory in POST requests
// Body content will include payload and a newline character
unsigned int len = payload.length() + 1;
_Request = String("POST ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: ESP8266\r\n" +
(_keepAlive ? "" : "Connection: close\r\n") +
"Content-Type: " + _contentTypeHeader + "\r\n" +
"Content-Length: " + len + "\r\n" +
"\r\n" +
payload +
"\r\n\r\n";
return;
}
bool HTTPSRedirect::getLocationURL(void){
bool flag;
// Keep reading from the input stream till we get to
// the location field in the header
flag = find("Location: ");
if (flag){
// Skip URI protocol (http, https, etc. till '//')
// This assumes that the location field will be containing
// a URL of the form: http<s>://<hostname>/<url>
readStringUntil('/');
readStringUntil('/');
// get hostname
_redirHost = readStringUntil('/');
// get remaining url
_redirUrl = String('/') + readStringUntil('\n');
}
else{
DPRINT("No valid 'Location' field found in header!");
}
// Create a GET request for the new location
createGetRequest(_redirUrl, _redirHost.c_str());
DPRINT("_redirHost: ");
DPRINTLN(_redirHost);
DPRINT("_redirUrl: ");
DPRINTLN(_redirUrl);
return flag;
}
void HTTPSRedirect::fetchHeader(void){
String line = "";
int pos = -1;
int pos2 = -1;
int pos3 = -1;
_hF.transferEncoding = "";
_hF.contentLength = 0;
#ifdef EXTRA_FNS
_hF.contentType = "";
#endif
while (connected()) {
line = readStringUntil('\n');
DPRINTLN(line);
// HTTP headers are terminated by a CRLF ('\r\n')
// Hence the final line will contain only '\r'
// since we have already till the end ('\n')
if (line == "\r")
break;
if (pos < 0){
pos = line.indexOf("Transfer-Encoding: ");
if (!pos)
// get string & remove trailing '\r' character to facilitate string comparisons
_hF.transferEncoding = line.substring(19, line.length()-1);
}
if (pos2 < 0){
pos2 = line.indexOf("Content-Length: ");
if (!pos2)
_hF.contentLength = line.substring(16).toInt();
}
#ifdef EXTRA_FNS
if (pos3 < 0){
pos3 = line.indexOf("Content-Type: ");
if (!pos3)
// get string & remove trailing '\r' character to facilitate string comparisons
_hF.contentType = line.substring(14, line.length()-1);
}
#endif
}
return;
}
void HTTPSRedirect::fetchBodyUnChunked(unsigned len){
String line;
DPRINTLN("Body:");
while ((connected()) && (len > 0)) {
line = readStringUntil('\n');
len -= line.length();
// Content length will include all '\n' terminating characters
// Decrement once more to account for the '\n' line ending character
--len;
if (_printResponseBody)
Serial.println(line);
_myResponse.body += line;
_myResponse.body += '\n';
}
}
// Ref: http://mihai.ibanescu.net/chunked-encoding-and-python-requests
// http://fssnip.net/2t
void HTTPSRedirect::fetchBodyChunked(void){
String line;
int chunkSize;
while (connected()){
line = readStringUntil('\n');
// Skip any empty lines
if (line == "\r")
continue;
// Chunk sizes are in hexadecimal so convert to integer
chunkSize = (uint32_t) strtol((const char *) line.c_str(), NULL, 16);
DPRINT("Chunk Size: ");
DPRINTLN(chunkSize);
// Terminating chunk is of size 0
if (chunkSize == 0)
break;
while (chunkSize > 0){
line = readStringUntil('\n');
if (_printResponseBody)
Serial.println(line);
_myResponse.body += line;
_myResponse.body += '\n';
chunkSize -= line.length();
// The line above includes the '\r' character
// which is not part of chunk size, so account for it
--chunkSize;
}
// Skip over chunk trailer
}
return;
}
unsigned int HTTPSRedirect::getResponseStatus(void){
// Read response status line
// ref: https://www.tutorialspoint.com/http/http_responses.htm
unsigned int statusCode;
String reasonPhrase;
String line;
unsigned int pos = -1;
unsigned int pos2 = -1;
// Skip any empty lines
do{
line = readStringUntil('\n');
}while(line.length() == 0);
pos = line.indexOf("HTTP/1.1 ");
pos2 = line.indexOf(" ", 9);
if (!pos){
statusCode = line.substring(9, pos2).toInt();
reasonPhrase = line.substring(pos2+1, line.length()-1);
}
else{
DPRINTLN("Error! No valid Status Code found in HTTP Response.");
statusCode = 0;
reasonPhrase = "";
}
_myResponse.statusCode = statusCode;
_myResponse.reasonPhrase = reasonPhrase;
DPRINT("Status code: ");
DPRINTLN(statusCode);
DPRINT("Reason phrase: ");
DPRINTLN(reasonPhrase);
return statusCode;
}
bool HTTPSRedirect::GET(const String& url, const char* host){
return GET(url, host, _printResponseBody);
}
bool HTTPSRedirect::GET(const String& url, const char* host, const bool& disp){
bool retval;
bool oldval;
// set _printResponseBody temporarily to argument passed
oldval = _printResponseBody;
_printResponseBody = disp;
// redirected Host and Url need to be initialized in case a
// reConnectFinalEndpoint() request is made after an initial request
// which did not have redirection
_redirHost = host;
_redirUrl = url;
InitResponse();
// Create request packet
createGetRequest(url, host);
// Calll request handler
retval = printRedir();
_printResponseBody = oldval;
return retval;
}
bool HTTPSRedirect::POST(const String& url, const char* host, const String& payload){
return POST(url, host, payload, _printResponseBody);
}
bool HTTPSRedirect::POST(const String& url, const char* host, const String& payload, const bool& disp){
bool retval;
bool oldval;
// set _printResponseBody temporarily to argument passed
oldval = _printResponseBody;
_printResponseBody = disp;
// redirected Host and Url need to be initialized in case a
// reConnectFinalEndpoint() request is made after an initial request
// which did not have redirection
_redirHost = host;
_redirUrl = url;
InitResponse();
// Create request packet
createPostRequest(url, host, payload);
// Call request handler
retval = printRedir();
_printResponseBody = oldval;
return retval;
}
void HTTPSRedirect::InitResponse(void){
// Init response data
_myResponse.body = "";
_myResponse.statusCode = 0;
_myResponse.reasonPhrase = "";
_myResponse.redirected = false;
}
int HTTPSRedirect::getStatusCode(void){
return _myResponse.statusCode;
}
String HTTPSRedirect::getReasonPhrase(void){
return _myResponse.reasonPhrase;
}
String HTTPSRedirect::getResponseBody(void){
return _myResponse.body;
}
void HTTPSRedirect::setPrintResponseBody(bool disp){
_printResponseBody = disp;
}
void HTTPSRedirect::setMaxRedirects(const unsigned int n){
_maxRedirects = n; // to-do: use this in code above
}
void HTTPSRedirect::setContentTypeHeader(const char *type){
_contentTypeHeader = type;
}
#ifdef OPTIMIZE_SPEED
bool HTTPSRedirect::reConnectFinalEndpoint(void){
// disconnect if connection already exists
if (connected())
stop();
DPRINT("_redirHost: ");
DPRINTLN(_redirHost);
DPRINT("_redirUrl: ");
DPRINTLN(_redirUrl);
// Connect to stored final endpoint
if (!connect(_redirHost.c_str(), _httpsPort)) {
DPRINTLN("Connection to final URL failed!");
return false;
}
// Valid request packed must already be
// present at this point in the member variable _Request
// from the previous GET() or POST() request
// Make call to final endpoint
return printRedir();
}
#endif
#ifdef EXTRA_FNS
void HTTPSRedirect::fetchBodyRaw(void){
String line;
while (connected()){
line = readStringUntil('\n');
if (_printResponseBody)
Serial.println(line);
_myResponse.body += line;
_myResponse.body += '\n';
}
}
void HTTPSRedirect::printHeaderFields(void){
DPRINT("Transfer Encoding: ");
DPRINTLN(_hF.transferEncoding);
DPRINT("Content Length: ");
DPRINTLN(_hF.contentLength);
DPRINT("Content Type: ");
DPRINTLN(_hF.contentType);
}
#endif