diff --git a/Sming/SmingCore/Network/Http/HttpClientConnection.cpp b/Sming/SmingCore/Network/Http/HttpClientConnection.cpp index 18b51c3c52..6e74f812ae 100644 --- a/Sming/SmingCore/Network/Http/HttpClientConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpClientConnection.cpp @@ -6,24 +6,42 @@ * * HttpClientConnection.cpp * + * @author: 2017 - Slavey Karadzhov + * ****/ #include "HttpClientConnection.h" +#include "Data/Stream/FileStream.h" +#include "Data/Stream/MemoryDataStream.h" +#include "Data/Stream/LimitedMemoryStream.h" +#include "Data/Stream/ChunkedStream.h" +#include "Data/Stream/UrlencodedOutputStream.h" -HttpClientConnection::~HttpClientConnection() +bool HttpClientConnection::connect(const String& host, int port, bool useSsl, uint32_t sslOptions) { - // ObjectQueue doesn't own its objects - while(requestQueue.count() != 0) { - delete requestQueue.dequeue(); + debug_d("HttpClientConnection::connect: TCP state: %d, isStarted: %d, isActive: %d", + (tcp != nullptr ? tcp->state : -1), (int)(getConnectionState() != eTCS_Ready), (int)isActive()); + + if(isProcessing()) { + return true; } -#ifdef ENABLE_SSL - delete sslSessionId; -#endif + + if(getConnectionState() != eTCS_Ready && isActive()) { + debug_d("HttpClientConnection::reusing TCP connection "); + + // we might have still alive connection + onConnected(ERR_OK); + return true; + } + + debug_d("HttpClientConnection::connecting ..."); + + return TcpClient::connect(host, port, useSsl, sslOptions); } bool HttpClientConnection::send(HttpRequest* request) { - if(!requestQueue.enqueue(request)) { + if(!waitingQueue.enqueue(request)) { // the queue is full and we cannot add more requests at the time. debug_e("The request queue is full at the moment"); delete request; @@ -46,3 +64,324 @@ bool HttpClientConnection::send(HttpRequest* request) return connect(request->uri.Host, request->uri.Port, useSsl); } + +void HttpClientConnection::reset() +{ + delete incomingRequest; + incomingRequest = nullptr; + + response.reset(); + + HttpConnection::reset(); +} + +int HttpClientConnection::onMessageBegin(http_parser* parser) +{ + incomingRequest = executionQueue.dequeue(); + if(incomingRequest == nullptr) { + return 1; // there are no requests in the queue + } + + return 0; +} + +HttpPartResult HttpClientConnection::multipartProducer() +{ + HttpPartResult result; + + if(outgoingRequest->files.count()) { + const String& name = outgoingRequest->files.keyAt(0); + auto file = outgoingRequest->files.extractAt(0); + result.stream = file; + + auto headers = new HttpHeaders(); + (*headers)[HTTP_HEADER_CONTENT_DISPOSITION] = + F("form-data; name=\"") + name + F("\"; filename=\"") + file->getName() + '"'; + (*headers)[HTTP_HEADER_CONTENT_TYPE] = ContentType::fromFullFileName(file->getName()); + result.headers = headers; + + return result; + } + + if(outgoingRequest->postParams.count()) { + const String& name = outgoingRequest->postParams.keyAt(0); + const String& value = outgoingRequest->postParams.valueAt(0); + + auto mStream = new MemoryDataStream(); + mStream->print(value); + result.stream = mStream; + + auto headers = new HttpHeaders(); + (*headers)[HTTP_HEADER_CONTENT_DISPOSITION] = F("form-data; name=\"") + name + '"'; + result.headers = headers; + + outgoingRequest->postParams.removeAt(0); + return result; + } + + return result; +} + +int HttpClientConnection::onMessageComplete(http_parser* parser) +{ + if(!incomingRequest) { + return -2; // no current request... + } + + debug_d("staticOnMessageComplete: Execution queue: %d, %s", executionQueue.count(), + incomingRequest->uri.toString().c_str()); + + // we are finished with this request + int hasError = 0; + if(incomingRequest->requestCompletedDelegate) { + bool success = (HTTP_PARSER_ERRNO(parser) == HPE_OK) && // false when the parsing has failed + (response.code >= 200 && response.code <= 399); // false when the HTTP status code is not ok + hasError = incomingRequest->requestCompletedDelegate(*this, success); + } + + if(incomingRequest->retries > 0) { + incomingRequest->retries--; + return (executionQueue.enqueue(incomingRequest) ? 0 : -1); + } + + delete incomingRequest; + incomingRequest = nullptr; + + if(!executionQueue.count()) { + onConnected(ERR_OK); + } + + return hasError; +} + +int HttpClientConnection::onHeadersComplete(const HttpHeaders& headers) +{ + /* Callbacks should return non-zero to indicate an error. The parser will + * then halt execution. + * + * The one exception is on_headers_complete. In a HTTP_RESPONSE parser + * returning '1' from on_headers_complete will tell the parser that it + * should not expect a body. This is used when receiving a response to a + * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: + * chunked' headers that indicate the presence of a body. + * + * Returning `2` from on_headers_complete will tell parser that it should not + * expect neither a body nor any further responses on this connection. This is + * useful for handling responses to a CONNECT request which may not contain + * `Upgrade` or `Connection: upgrade` headers. + */ + + if(incomingRequest == nullptr) { + // nothing to process right now... + return 1; + } + + response.headers.setMultiple(headers); + response.code = parser.status_code; + + if(incomingRequest->auth != nullptr) { + incomingRequest->auth->setResponse(getResponse()); + } + + int error = 0; + if(incomingRequest->headersCompletedDelegate) { + error = incomingRequest->headersCompletedDelegate(*this, response); + } + + if(!error && incomingRequest->method == HTTP_HEAD) { + error = 1; + } + + if(!error) { + // set the response stream + if(incomingRequest->responseStream != nullptr) { + response.setBuffer(incomingRequest->responseStream); + incomingRequest->responseStream = nullptr; // the response object will release that stream + } else { + response.setBuffer(new LimitedMemoryStream(NETWORK_SEND_BUFFER_SIZE)); + } + } + + return error; +} + +int HttpClientConnection::onBody(const char* at, size_t length) +{ + if(incomingRequest == nullptr) { + // nothing to process right now... + return 1; + } + + if(incomingRequest->requestBodyDelegate) { + return incomingRequest->requestBodyDelegate(*this, at, length); + } + + if(response.buffer != nullptr) { + auto res = response.buffer->write((const uint8_t*)at, length); + if(res != length) { + // unable to write the requested bytes - stop here... + response.freeStreams(); + return 1; + } + } + + return 0; +} + +void HttpClientConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) +{ + debug_d("HttpClientConnection::onReadyToSendData: waitingQueue.count: %d", waitingQueue.count()); + +REENTER: + switch(state) { + case eHCS_Ready: { + HttpRequest* request = waitingQueue.peek(); + if(request == nullptr) { + debug_d("Nothing in the waiting queue"); + outgoingRequest = nullptr; + break; + } + + // if the executionQueue is not empty then we have to check if we can pipeline that request + if(executionQueue.count()) { + if(!(request->method == HTTP_GET || request->method == HTTP_HEAD)) { + // if the current request cannot be pipelined -> break; + break; + } + + // if we have previous request + if(outgoingRequest != nullptr) { + if(!(outgoingRequest->method == HTTP_GET || outgoingRequest->method == HTTP_HEAD)) { + // the outgoing request does not allow pipelining + break; + } + } + } // executionQueue.count() + + if(!executionQueue.enqueue(request)) { + debug_e("The working queue is full at the moment"); + break; + } + + waitingQueue.dequeue(); + + outgoingRequest = request; + sendRequestHeaders(request); + + state = eHCS_SendingHeaders; + } + + case eHCS_SendingHeaders: { + if(stream != nullptr && !stream->isFinished()) { + break; + } + + state = eHCS_StartBody; + } + + case eHCS_StartBody: + case eHCS_SendingBody: { + if(sendRequestBody(outgoingRequest)) { + state = eHCS_Ready; + delete stream; + stream = nullptr; + goto REENTER; + } + } + + default:; // Do nothing + } // switch(state) + + TcpClient::onReadyToSendData(sourceEvent); +} + +void HttpClientConnection::sendRequestHeaders(HttpRequest* request) +{ + sendString(String(http_method_str(request->method)) + ' ' + request->uri.getPathWithQuery() + _F(" HTTP/1.1\r\n")); + + if(!request->headers.contains(HTTP_HEADER_HOST)) { + request->headers[HTTP_HEADER_HOST] = request->uri.Host; + } + + request->headers[HTTP_HEADER_CONTENT_LENGTH] = "0"; + if(request->files.count()) { + MultipartStream* mStream = + new MultipartStream(HttpPartProducerDelegate(&HttpClientConnection::multipartProducer, this)); + request->headers[HTTP_HEADER_CONTENT_TYPE] = + ContentType::toString(MIME_FORM_MULTIPART) + _F("; boundary=") + mStream->getBoundary(); + if(request->bodyStream) { + debug_e("HttpClientConnection: existing stream is discarded due to POST params"); + delete request->bodyStream; + } + request->bodyStream = mStream; + } else if(request->postParams.count()) { + UrlencodedOutputStream* uStream = new UrlencodedOutputStream(request->postParams); + request->headers[HTTP_HEADER_CONTENT_TYPE] = ContentType::toString(MIME_FORM_URL_ENCODED); + if(request->bodyStream) { + debug_e("HttpClientConnection: existing stream is discarded due to POST params"); + delete request->bodyStream; + } + request->bodyStream = uStream; + } /* if (request->postParams.count()) */ + + if(request->bodyStream != nullptr) { + if(request->bodyStream->available() > -1) { + request->headers[HTTP_HEADER_CONTENT_LENGTH] = String(request->bodyStream->available()); + } else { + request->headers.remove(HTTP_HEADER_CONTENT_LENGTH); + } + } + + if(!request->headers.contains(HTTP_HEADER_CONTENT_LENGTH)) { + request->headers[HTTP_HEADER_TRANSFER_ENCODING] = _F("chunked"); + } + + for(unsigned i = 0; i < request->headers.count(); i++) { + // TODO: add name and/or value escaping (implement in HttpHeaders) + sendString(request->headers[i]); + } + sendString("\r\n"); +} + +bool HttpClientConnection::sendRequestBody(HttpRequest* request) +{ + if(state == eHCS_StartBody) { + state = eHCS_SendingBody; + + if(request->bodyStream == nullptr) { + return true; + } + + delete stream; + if(request->headers[HTTP_HEADER_TRANSFER_ENCODING] == _F("chunked")) { + stream = new ChunkedStream(request->bodyStream); + } else { + stream = request->bodyStream; // avoid intermediate buffers + } + request->bodyStream = nullptr; + return false; + } + + if(stream == nullptr) { + // we are done for now + return true; + } + + if(request->bodyStream == nullptr && !stream->isFinished()) { + return false; + } + + return true; +} + +// end of public methods for HttpClientConnection + +void HttpClientConnection::cleanup() +{ + reset(); + + // if there are requests in the executionQueue -> move them back to the waiting queue + while(executionQueue.count() != 0) { + waitingQueue.enqueue(executionQueue.dequeue()); + } +} diff --git a/Sming/SmingCore/Network/Http/HttpClientConnection.h b/Sming/SmingCore/Network/Http/HttpClientConnection.h index db334c3657..07ec924651 100644 --- a/Sming/SmingCore/Network/Http/HttpClientConnection.h +++ b/Sming/SmingCore/Network/Http/HttpClientConnection.h @@ -6,38 +6,82 @@ * * HttpClientConnection.h * + * @author: 2017 - Slavey Karadzhov + * ****/ +#ifndef _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_H_ +#define _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_H_ + +#include "HttpConnection.h" +#include "DateTime.h" +#include "Data/ObjectQueue.h" + /** @defgroup HTTP client connection - * @brief Provides HTTP/S client connection + * @brief Provides http client connection * @ingroup http * @{ */ -#ifndef _SMING_CORE_NETWORK_HTTP_HTTP_CLIENT_CONNECTION_H_ -#define _SMING_CORE_NETWORK_HTTP_HTTP_CLIENT_CONNECTION_H_ - -#include "HttpConnection.h" +typedef ObjectQueue RequestQueue; class HttpClientConnection : public HttpConnection { public: - HttpClientConnection() : HttpConnection(&requestQueue) + HttpClientConnection() : HttpConnection(HTTP_RESPONSE) + { + } + + ~HttpClientConnection() + { + cleanup(); + + // Free any outstanding queued requests + while(waitingQueue.count() != 0) { + delete waitingQueue.dequeue(); + } + +#ifdef ENABLE_SSL + delete sslSessionId; +#endif + } + + bool connect(const String& host, int port, bool useSsl = false, uint32_t sslOptions = 0) override; + + bool send(HttpRequest* request) override; + + HttpRequest* getRequest() override { + return incomingRequest; } - ~HttpClientConnection(); + void reset() override; + +protected: + // HTTP parser methods - /** @brief Queue a request - * @param request - * @retval bool true on success - * @note we take ownership of the request. On error, it is destroyed before returning. - */ - bool send(HttpRequest* request); + int onMessageBegin(http_parser* parser) override; + int onHeadersComplete(const HttpHeaders& headers) override; + int onBody(const char* at, size_t length) override; + int onMessageComplete(http_parser* parser) override; + + // TCP methods + void onReadyToSendData(TcpConnectionEvent sourceEvent) override; + + void cleanup() override; private: - RequestQueue requestQueue; + void sendRequestHeaders(HttpRequest* request); + bool sendRequestBody(HttpRequest* request); + HttpPartResult multipartProducer(); + +private: + RequestQueue waitingQueue; ///< Requests waiting to be started + RequestQueue executionQueue; ///< Requests being executed in a pipeline + + HttpRequest* incomingRequest = nullptr; + HttpRequest* outgoingRequest = nullptr; }; /** @} */ -#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_CLIENT_CONNECTION_H_ */ +#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpConnection.cpp b/Sming/SmingCore/Network/Http/HttpConnection.cpp index 793c14cb40..24772e2985 100644 --- a/Sming/SmingCore/Network/Http/HttpConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpConnection.cpp @@ -6,16 +6,11 @@ * * HttpConnection.cpp * - * @author: 2017 - Slavey Karadzhov + * @author: 2018 - Slavey Karadzhov * ****/ #include "HttpConnection.h" -#include "Data/Stream/FileStream.h" -#include "Data/Stream/MemoryDataStream.h" -#include "Data/Stream/LimitedMemoryStream.h" -#include "Data/Stream/ChunkedStream.h" -#include "Data/Stream/UrlencodedOutputStream.h" #ifdef __linux__ #include "lwip/priv/tcp_priv.h" @@ -23,163 +18,118 @@ #include "lwip/tcp_impl.h" #endif -bool HttpConnection::connect(const String& host, int port, bool useSsl, uint32_t sslOptions) -{ - debug_d("HttpConnection::connect: TCP state: %d, isStarted: %d, isActive: %d", (tcp != nullptr ? tcp->state : -1), - (int)(getConnectionState() != eTCS_Ready), (int)isActive()); - - if(isProcessing()) { - return true; - } - - if(getConnectionState() != eTCS_Ready && isActive()) { - debug_d("HttpConnection::reusing TCP connection "); +/** @brief http_parser function table + * @note stored in flash memory; as it is word-aligned it can be accessed directly + * Notification callbacks: on_message_begin, on_headers_complete, on_message_complete + * Data callbacks: on_url, (common) on_header_field, on_header_value, on_body + */ +const http_parser_settings HttpConnection::parserSettings PROGMEM = { + .on_message_begin = staticOnMessageBegin, + .on_url = staticOnPath, +#ifdef COMPACT_MODE + .on_status = nullptr, +#else + .on_status = staticOnStatus, +#endif + .on_header_field = staticOnHeaderField, + .on_header_value = staticOnHeaderValue, + .on_headers_complete = staticOnHeadersComplete, + .on_body = staticOnBody, + .on_message_complete = staticOnMessageComplete, +#ifdef COMPACT_MODE + .on_chunk_header = nullptr, + .on_chunk_complete = nullptr, +#else + .on_chunk_header = staticOnChunkHeader, + .on_chunk_complete = staticOnChunkComplete +#endif +}; - // we might have still alive connection - onConnected(ERR_OK); - return true; +/** @brief Boilerplate code for http_parser callbacks + * @note Obtain connection object and check it + */ +#define GET_CONNECTION() \ + auto connection = static_cast(parser->data); \ + if(connection == nullptr) { \ + return -1; \ } - debug_d("HttpConnection::connecting ..."); - - return TcpClient::connect(host, port, useSsl, sslOptions); -} - -bool HttpConnection::isActive() +void HttpConnection::init(http_parser_type type) { - if(tcp == nullptr) { - return false; - } - - struct tcp_pcb* pcb; - for(pcb = tcp_active_pcbs; pcb != nullptr; pcb = pcb->next) { - if(tcp == pcb) { - return true; - } - } - - return false; + http_parser_init(&parser, type); + parser.data = this; + setDefaultParser(); } -String HttpConnection::getResponseHeader(String headerName, String defaultValue) +void HttpConnection::setDefaultParser() { - if(response.headers.contains(headerName)) - return response.headers[headerName]; - - return defaultValue; + TcpClient::setReceiveDelegate(TcpClientDataDelegate(&HttpConnection::onTcpReceive, this)); } -DateTime HttpConnection::getLastModifiedDate() +void HttpConnection::resetHeaders() { - DateTime res; - String strLM = response.headers[HTTP_HEADER_LAST_MODIFIED]; - if(res.fromHttpDate(strLM)) - return res; - else - return DateTime(); + header.reset(); + incomingHeaders.clear(); } -DateTime HttpConnection::getServerDate() +int HttpConnection::staticOnMessageBegin(http_parser* parser) { - DateTime res; - String strSD = response.headers[HTTP_HEADER_DATE]; - if(res.fromHttpDate(strSD)) - return res; - else - return DateTime(); + GET_CONNECTION() + + connection->reset(); + return connection->onMessageBegin(parser); } -void HttpConnection::reset() +int HttpConnection::staticOnPath(http_parser* parser, const char* at, size_t length) { - delete incomingRequest; - incomingRequest = nullptr; + GET_CONNECTION() - response.reset(); - - HttpConnectionBase::reset(); + return connection->onPath(URL(String(at, length))); } -int HttpConnection::onMessageBegin(http_parser* parser) +#ifndef COMPACT_MODE +int HttpConnection::staticOnStatus(http_parser* parser, const char* at, size_t length) { - incomingRequest = executionQueue.dequeue(); - if(incomingRequest == nullptr) { - return 1; // there are no requests in the queue - } + GET_CONNECTION() - return 0; + return connection->onStatus(parser); } -HttpPartResult HttpConnection::multipartProducer() +int HttpConnection::staticOnChunkHeader(http_parser* parser) { - HttpPartResult result; - - if(outgoingRequest->files.count()) { - String name = outgoingRequest->files.keyAt(0); - auto file = outgoingRequest->files.extractAt(0); - result.stream = file; + GET_CONNECTION() - HttpHeaders* headers = new HttpHeaders(); - (*headers)[HTTP_HEADER_CONTENT_DISPOSITION] = - F("form-data; name=\"") + name + F("\"; filename=\"") + file->getName() + '"'; - (*headers)[HTTP_HEADER_CONTENT_TYPE] = ContentType::fromFullFileName(file->getName()); - result.headers = headers; - - return result; - } - - if(outgoingRequest->postParams.count()) { - String name = outgoingRequest->postParams.keyAt(0); - String value = outgoingRequest->postParams[name]; - - MemoryDataStream* mStream = new MemoryDataStream(); - mStream->write((uint8_t*)value.c_str(), value.length()); - result.stream = mStream; - - HttpHeaders* headers = new HttpHeaders(); - (*headers)[HTTP_HEADER_CONTENT_DISPOSITION] = F("form-data; name=\"") + name + '"'; - result.headers = headers; - - outgoingRequest->postParams.remove(name); - return result; - } - - return result; + return connection->onChunkHeader(parser); } -int HttpConnection::onMessageComplete(http_parser* parser) +int HttpConnection::staticOnChunkComplete(http_parser* parser) { - if(!incomingRequest) { - return -2; // no current request... - } + GET_CONNECTION() - debug_d("staticOnMessageComplete: Execution queue: %d, %s", executionQueue.count(), - incomingRequest->uri.toString().c_str()); - - // we are finished with this request - int hasError = 0; - if(incomingRequest->requestCompletedDelegate) { - bool success = (HTTP_PARSER_ERRNO(parser) == HPE_OK) && // false when the parsing has failed - (response.code >= 200 && response.code <= 399); // false when the HTTP status code is not ok - hasError = incomingRequest->requestCompletedDelegate(*this, success); - } + return connection->onChunkComplete(parser); +} +#endif - if(incomingRequest->retries > 0) { - incomingRequest->retries--; - return (executionQueue.enqueue(incomingRequest) ? 0 : -1); - } +int HttpConnection::staticOnHeaderField(http_parser* parser, const char* at, size_t length) +{ + GET_CONNECTION() - delete incomingRequest; - incomingRequest = nullptr; + return connection->header.onHeaderField(at, length); +} - if(!executionQueue.count()) { - onConnected(ERR_OK); - } +int HttpConnection::staticOnHeaderValue(http_parser* parser, const char* at, size_t length) +{ + GET_CONNECTION() - return hasError; + return connection->header.onHeaderValue(connection->incomingHeaders, at, length); } -int HttpConnection::onHeadersComplete(const HttpHeaders& headers) +int HttpConnection::staticOnHeadersComplete(http_parser* parser) { + GET_CONNECTION() + + debug_d("The headers are complete"); + /* Callbacks should return non-zero to indicate an error. The parser will * then halt execution. * @@ -194,218 +144,70 @@ int HttpConnection::onHeadersComplete(const HttpHeaders& headers) * useful for handling responses to a CONNECT request which may not contain * `Upgrade` or `Connection: upgrade` headers. */ - - if(incomingRequest == nullptr) { - // nothing to process right now... - return 1; - } - - response.headers.setMultiple(headers); - response.code = parser.status_code; - - if(incomingRequest->auth != nullptr) { - incomingRequest->auth->setResponse(getResponse()); - } - - int error = 0; - if(incomingRequest->headersCompletedDelegate) { - error = incomingRequest->headersCompletedDelegate(*this, response); - } - - if(!error && incomingRequest->method == HTTP_HEAD) { - error = 1; - } - - if(!error) { - // set the response stream - if(incomingRequest->responseStream != nullptr) { - response.setBuffer(incomingRequest->responseStream); - incomingRequest->responseStream = nullptr; // the response object will release that stream - } else { - response.setBuffer(new LimitedMemoryStream(NETWORK_SEND_BUFFER_SIZE)); - } - } + int error = connection->onHeadersComplete(connection->incomingHeaders); + connection->resetHeaders(); return error; } -int HttpConnection::onBody(const char* at, size_t length) +int HttpConnection::staticOnBody(http_parser* parser, const char* at, size_t length) { - if(incomingRequest == nullptr) { - // nothing to process right now... - return 1; - } - - if(incomingRequest->requestBodyDelegate) { - return incomingRequest->requestBodyDelegate(*this, at, length); - } + GET_CONNECTION() - if(response.buffer != nullptr) { - auto res = response.buffer->write((const uint8_t*)at, length); - if(res != length) { - // unable to write the requested bytes - stop here... - response.freeStreams(); - return 1; - } - } - - return 0; + return connection->onBody(at, length); } -void HttpConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) +int HttpConnection::staticOnMessageComplete(http_parser* parser) { - debug_d("HttpConnection::onReadyToSendData: waitingQueue.count: %d", waitingQueue->count()); - -REENTER: - switch(state) { - case eHCS_Ready: { - HttpRequest* request = waitingQueue->peek(); - if(request == nullptr) { - debug_d("Nothing in the waiting queue"); - outgoingRequest = nullptr; - break; - } - - // if the executionQueue is not empty then we have to check if we can pipeline that request - if(executionQueue.count()) { - if(!(request->method == HTTP_GET || request->method == HTTP_HEAD)) { - // if the current request cannot be pipelined -> break; - break; - } - - // if we have previous request - if(outgoingRequest != nullptr) { - if(!(outgoingRequest->method == HTTP_GET || outgoingRequest->method == HTTP_HEAD)) { - // the outgoing request does not allow pipelining - break; - } - } - } // executionQueue.count() - - if(!executionQueue.enqueue(request)) { - debug_e("The working queue is full at the moment"); - break; - } - - waitingQueue->dequeue(); + GET_CONNECTION() - outgoingRequest = request; - sendRequestHeaders(request); + int error = connection->onMessageComplete(parser); + connection->reset(); - state = eHCS_SendingHeaders; - } - - case eHCS_SendingHeaders: { - if(stream != nullptr && !stream->isFinished()) { - break; - } - - state = eHCS_StartBody; - } - - case eHCS_StartBody: - case eHCS_SendingBody: { - if(sendRequestBody(outgoingRequest)) { - state = eHCS_Ready; - delete stream; - stream = nullptr; - goto REENTER; - } - } - - default:; // Do nothing - } // switch(state) - - TcpClient::onReadyToSendData(sourceEvent); + return error; } -void HttpConnection::sendRequestHeaders(HttpRequest* request) +void HttpConnection::onHttpError(http_errno error) { - sendString(String(http_method_str(request->method)) + ' ' + request->uri.getPathWithQuery() + _F(" HTTP/1.1\r\n")); - - if(!request->headers.contains(HTTP_HEADER_HOST)) { - request->headers[HTTP_HEADER_HOST] = request->uri.Host; - } - - request->headers[HTTP_HEADER_CONTENT_LENGTH] = "0"; - if(request->files.count()) { - MultipartStream* mStream = - new MultipartStream(HttpPartProducerDelegate(&HttpConnection::multipartProducer, this)); - request->headers[HTTP_HEADER_CONTENT_TYPE] = - ContentType::toString(MIME_FORM_MULTIPART) + _F("; boundary=") + mStream->getBoundary(); - if(request->bodyStream) { - debug_e("HttpConnection: existing stream is discarded due to POST params"); - delete request->bodyStream; - } - request->bodyStream = mStream; - } else if(request->postParams.count()) { - UrlencodedOutputStream* uStream = new UrlencodedOutputStream(request->postParams); - request->headers[HTTP_HEADER_CONTENT_TYPE] = ContentType::toString(MIME_FORM_URL_ENCODED); - if(request->bodyStream) { - debug_e("HttpConnection: existing stream is discarded due to POST params"); - delete request->bodyStream; - } - request->bodyStream = uStream; - } /* if (request->postParams.count()) */ - - if(request->bodyStream != nullptr) { - if(request->bodyStream->available() > -1) { - request->headers[HTTP_HEADER_CONTENT_LENGTH] = String(request->bodyStream->available()); - } else { - request->headers.remove(HTTP_HEADER_CONTENT_LENGTH); - } - } - - if(!request->headers.contains(HTTP_HEADER_CONTENT_LENGTH)) { - request->headers[HTTP_HEADER_TRANSFER_ENCODING] = _F("chunked"); - } - - for(unsigned i = 0; i < request->headers.count(); i++) { - // TODO: add name and/or value escaping (implement in HttpHeaders) - sendString(request->headers[i]); - } - sendString("\r\n"); + debug_e("HTTP parser error: %s", httpGetErrorName(error).c_str()); } -bool HttpConnection::sendRequestBody(HttpRequest* request) +bool HttpConnection::onTcpReceive(TcpClient& client, char* data, int size) { - if(state == eHCS_StartBody) { - state = eHCS_SendingBody; - - if(request->bodyStream == nullptr) { - return true; - } - - delete stream; - if(request->headers[HTTP_HEADER_TRANSFER_ENCODING] == _F("chunked")) { - stream = new ChunkedStream(request->bodyStream); - } else { - stream = request->bodyStream; // avoid intermediate buffers - } - request->bodyStream = nullptr; + int parsedBytes = http_parser_execute(&parser, &parserSettings, data, size); + if(HTTP_PARSER_ERRNO(&parser) != HPE_OK) { + // we ran into trouble - abort the connection + onHttpError(HTTP_PARSER_ERRNO(&parser)); return false; } - if(stream == nullptr) { - // we are done for now - return true; - } - - if(request->bodyStream == nullptr && !stream->isFinished()) { + if(parser.upgrade) { + return onProtocolUpgrade(&parser); + } else if(parsedBytes != size) { return false; } return true; } -// end of public methods for HttpConnection +void HttpConnection::onError(err_t err) +{ + cleanup(); + TcpClient::onError(err); +} -void HttpConnection::cleanup() +bool HttpConnection::isActive() { - reset(); + if(tcp == nullptr) { + return false; + } - // if there are requests in the executionQueue -> move them back to the waiting queue - for(unsigned i = 0; i < executionQueue.count(); i++) { - waitingQueue->enqueue(executionQueue.dequeue()); + struct tcp_pcb* pcb; + for(pcb = tcp_active_pcbs; pcb != nullptr; pcb = pcb->next) { + if(tcp == pcb) { + return true; + } } + + return false; } diff --git a/Sming/SmingCore/Network/Http/HttpConnection.h b/Sming/SmingCore/Network/Http/HttpConnection.h index 70e391d566..6dee6dd9d9 100644 --- a/Sming/SmingCore/Network/Http/HttpConnection.h +++ b/Sming/SmingCore/Network/Http/HttpConnection.h @@ -6,85 +6,97 @@ * * HttpConnection.h * - * @author: 2017 - Slavey Karadzhov + * @author: 2018 - Slavey Karadzhov * ****/ -#ifndef _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_H_ -#define _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_H_ +#ifndef _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_BASE_H_ +#define _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_BASE_H_ -#include "HttpConnectionBase.h" -#include "DateTime.h" -#include "Data/ObjectQueue.h" +#include "../TcpClient.h" +#include "WString.h" +#include "HttpCommon.h" +#include "HttpResponse.h" +#include "HttpRequest.h" +#include "HttpHeaderBuilder.h" -/** @defgroup HTTP client connection - * @brief Provides http client connection +/** @defgroup HTTP base connection + * @brief Provides http base used for client and server connections * @ingroup http * @{ */ -typedef ObjectQueue RequestQueue; - -class HttpConnection : public HttpConnectionBase +class HttpConnection : public TcpClient { public: - HttpConnection(RequestQueue* queue) : HttpConnectionBase(HTTP_RESPONSE) + HttpConnection(http_parser_type type, bool autoDestruct = false) : TcpClient(autoDestruct) { - this->waitingQueue = queue; + init(type); } - ~HttpConnection() + HttpConnection(tcp_pcb* connection, http_parser_type type) : TcpClient(connection, nullptr, nullptr) { - cleanup(); + init(type); } - bool connect(const String& host, int port, bool useSsl = false, uint32_t sslOptions = 0) override; + virtual void reset() + { + resetHeaders(); + } - bool send(HttpRequest* request) + virtual void cleanup() { - return waitingQueue->enqueue(request); + reset(); + } + + virtual void setDefaultParser(); + + using TcpConnection::getRemoteIp; + using TcpConnection::getRemotePort; + + using TcpClient::send; + + /* Overridden by HttpClientConnection */ + virtual bool send(HttpRequest* request) + { + delete request; + return false; } bool isActive(); /** * @brief Returns pointer to the current request - * @return HttpRequest* + * @retval HttpRequest* */ - HttpRequest* getRequest() - { - return incomingRequest; - } + virtual HttpRequest* getRequest() = 0; /** * @brief Returns pointer to the current response - * @return HttpResponse* + * @retval HttpResponse* */ HttpResponse* getResponse() { return &response; } - using TcpClient::close; - -#ifdef ENABLE_SSL - using TcpClient::getSsl; -#endif - // Backported for compatibility reasons /** * @deprecated Use `getResponse()->code` instead */ - int getResponseCode() SMING_DEPRECATED + int getResponseCode() const SMING_DEPRECATED { return response.code; } /** - * @deprecated Use `getResponse()->headers[headerName]` instead + * @deprecated Use `getResponse()->headers[]` instead */ - String getResponseHeader(String headerName, String defaultValue = nullptr) SMING_DEPRECATED; + String getResponseHeader(const String& headerName, const String& defaultValue = nullptr) const SMING_DEPRECATED + { + return response.headers[headerName] ?: defaultValue; + } /** * @deprecated Use `getResponse()->headers` instead @@ -95,14 +107,20 @@ class HttpConnection : public HttpConnectionBase } /** - * @todo deprecate: Use `getResponse()->headers[HTTP_HEADER_LAST_MODIFIED]` instead + * @deprecated Use `getResponse()->headers.getLastModifiedDate()` instead */ - DateTime getLastModifiedDate(); + DateTime getLastModifiedDate() const SMING_DEPRECATED + { + return response.headers.getLastModifiedDate(); + } /** - * @todo deprecate: Use `getResponse()->headers[HTTP_HEADER_DATE]` instead + * @deprecated Use `getResponse()->headers.getServerDate()` instead */ - DateTime getServerDate(); + DateTime getServerDate() const SMING_DEPRECATED + { + return response.headers.getServerDate(); + } /** * @deprecated Use `getResponse()->getBody()` instead @@ -112,58 +130,111 @@ class HttpConnection : public HttpConnectionBase return response.getBody(); } - void reset() override; - protected: + /** @brief Called after all headers have been received and processed */ + void resetHeaders(); + + /** @brief Initializes the http parser for a specific type of HTTP message + * @param type + */ + virtual void init(http_parser_type type); + // HTTP parser methods - /** - * Called when a new incoming data is beginning to come - * @paran http_parser* parser - * @return 0 on success, non-0 on error + /** @brief Called when a new incoming data is beginning to come + * @param parser + * @retval int 0 on success, non-0 on error */ - int onMessageBegin(http_parser* parser) override; + virtual int onMessageBegin(http_parser* parser) = 0; - /** - * Called when all headers are received - * @param HttpHeaders headers - the processed headers - * @return 0 on success, non-0 on error + /** @brief Called when the URL path is known + * @param uri + * @retval int 0 on success, non-0 on error */ - int onHeadersComplete(const HttpHeaders& headers) override; + virtual int onPath(const URL& uri) + { + return 0; + } - /** - * Called when a piece of body data is received - * @param const char* at - the data - * @paran size_t length - * @return 0 on success, non-0 on error + /** @brief Called when all headers are received + * @param headers The processed headers + * @retval int 0 on success, non-0 on error */ - int onBody(const char* at, size_t length) override; + virtual int onHeadersComplete(const HttpHeaders& headers) = 0; - /** - * Called when the incoming data is complete - * @paran http_parser* parser - * @return 0 on success, non-0 on error +#ifndef COMPACT_MODE + virtual int onStatus(http_parser* parser) + { + return 0; + } + + virtual int onChunkHeader(http_parser* parser) + { + return 0; + } + + virtual int onChunkComplete(http_parser* parser) + { + return 0; + } + +#endif /* COMPACT MODE */ + + /** @brief Called when a piece of body data is received + * @param at the data + * @param length + * @retval int 0 on success, non-0 on error + */ + virtual int onBody(const char* at, size_t length) = 0; + + /** @brief Called when the incoming data is complete + * @param parser + * @retval int 0 on success, non-0 on error + */ + virtual int onMessageComplete(http_parser* parser) = 0; + + /** @brief Called when the HTTP protocol should be upgraded + * @param parser + * @retval bool true on success */ - int onMessageComplete(http_parser* parser) override; + virtual bool onProtocolUpgrade(http_parser* parser) + { + return true; + } + + virtual void onHttpError(http_errno error); // TCP methods - void onReadyToSendData(TcpConnectionEvent sourceEvent) override; + virtual bool onTcpReceive(TcpClient& client, char* data, int size); - void cleanup() override; + void onError(err_t err) override; private: - void sendRequestHeaders(HttpRequest* request); - bool sendRequestBody(HttpRequest* request); - HttpPartResult multipartProducer(); + // http_parser callback functions + static int staticOnMessageBegin(http_parser* parser); + static int staticOnPath(http_parser* parser, const char* at, size_t length); +#ifndef COMPACT_MODE + static int staticOnStatus(http_parser* parser, const char* at, size_t length); +#endif + static int staticOnHeadersComplete(http_parser* parser); + static int staticOnHeaderField(http_parser* parser, const char* at, size_t length); + static int staticOnHeaderValue(http_parser* parser, const char* at, size_t length); + static int staticOnBody(http_parser* parser, const char* at, size_t length); +#ifndef COMPACT_MODE + static int staticOnChunkHeader(http_parser* parser); + static int staticOnChunkComplete(http_parser* parser); +#endif + static int staticOnMessageComplete(http_parser* parser); protected: - RequestQueue* waitingQueue = nullptr; ///< Requests waiting to be started - we do not own this queue - RequestQueue executionQueue; ///< Requests being executed in a pipeline + http_parser parser; + static const http_parser_settings parserSettings; ///< Callback table for parser + HttpHeaderBuilder header; ///< Header construction + HttpHeaders incomingHeaders; ///< Full set of incoming headers + HttpConnectionState state = eHCS_Ready; - HttpRequest* incomingRequest = nullptr; - HttpRequest* outgoingRequest = nullptr; HttpResponse response; }; /** @} */ -#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_H_ */ +#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_BASE_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpConnectionBase.cpp b/Sming/SmingCore/Network/Http/HttpConnectionBase.cpp deleted file mode 100644 index ba57fd46eb..0000000000 --- a/Sming/SmingCore/Network/Http/HttpConnectionBase.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * HttpConnectionBase.cpp - * - * @author: 2018 - Slavey Karadzhov - * - ****/ - -#include "HttpConnectionBase.h" - -/** @brief http_parser function table - * @note stored in flash memory; as it is word-aligned it can be accessed directly - * Notification callbacks: on_message_begin, on_headers_complete, on_message_complete - * Data callbacks: on_url, (common) on_header_field, on_header_value, on_body - */ -const http_parser_settings HttpConnectionBase::parserSettings PROGMEM = { - .on_message_begin = staticOnMessageBegin, - .on_url = staticOnPath, -#ifdef COMPACT_MODE - .on_status = nullptr, -#else - .on_status = staticOnStatus, -#endif - .on_header_field = staticOnHeaderField, - .on_header_value = staticOnHeaderValue, - .on_headers_complete = staticOnHeadersComplete, - .on_body = staticOnBody, - .on_message_complete = staticOnMessageComplete, -#ifdef COMPACT_MODE - .on_chunk_header = nullptr, - .on_chunk_complete = nullptr, -#else - .on_chunk_header = staticOnChunkHeader, - .on_chunk_complete = staticOnChunkComplete -#endif -}; - -/** @brief Boilerplate code for http_parser callbacks - * @note Obtain connection object and check it - */ -#define GET_CONNECTION() \ - auto connection = static_cast(parser->data); \ - if(connection == nullptr) { \ - return -1; \ - } - -void HttpConnectionBase::init(http_parser_type type) -{ - http_parser_init(&parser, type); - parser.data = this; - setDefaultParser(); -} - -void HttpConnectionBase::setDefaultParser() -{ - TcpClient::setReceiveDelegate(TcpClientDataDelegate(&HttpConnectionBase::onTcpReceive, this)); -} - -void HttpConnectionBase::resetHeaders() -{ - header.reset(); - incomingHeaders.clear(); -} - -int HttpConnectionBase::staticOnMessageBegin(http_parser* parser) -{ - GET_CONNECTION() - - connection->reset(); - return connection->onMessageBegin(parser); -} - -int HttpConnectionBase::staticOnPath(http_parser* parser, const char* at, size_t length) -{ - GET_CONNECTION() - - return connection->onPath(URL(String(at, length))); -} - -#ifndef COMPACT_MODE -int HttpConnectionBase::staticOnStatus(http_parser* parser, const char* at, size_t length) -{ - GET_CONNECTION() - - return connection->onStatus(parser); -} - -int HttpConnectionBase::staticOnChunkHeader(http_parser* parser) -{ - GET_CONNECTION() - - return connection->onChunkHeader(parser); -} - -int HttpConnectionBase::staticOnChunkComplete(http_parser* parser) -{ - GET_CONNECTION() - - return connection->onChunkComplete(parser); -} -#endif - -int HttpConnectionBase::staticOnHeaderField(http_parser* parser, const char* at, size_t length) -{ - GET_CONNECTION() - - return connection->header.onHeaderField(at, length); -} - -int HttpConnectionBase::staticOnHeaderValue(http_parser* parser, const char* at, size_t length) -{ - GET_CONNECTION() - - return connection->header.onHeaderValue(connection->incomingHeaders, at, length); -} - -int HttpConnectionBase::staticOnHeadersComplete(http_parser* parser) -{ - GET_CONNECTION() - - debug_d("The headers are complete"); - - /* Callbacks should return non-zero to indicate an error. The parser will - * then halt execution. - * - * The one exception is on_headers_complete. In a HTTP_RESPONSE parser - * returning '1' from on_headers_complete will tell the parser that it - * should not expect a body. This is used when receiving a response to a - * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: - * chunked' headers that indicate the presence of a body. - * - * Returning `2` from on_headers_complete will tell parser that it should not - * expect neither a body nor any further responses on this connection. This is - * useful for handling responses to a CONNECT request which may not contain - * `Upgrade` or `Connection: upgrade` headers. - */ - int error = connection->onHeadersComplete(connection->incomingHeaders); - connection->resetHeaders(); - - return error; -} - -int HttpConnectionBase::staticOnBody(http_parser* parser, const char* at, size_t length) -{ - GET_CONNECTION() - - return connection->onBody(at, length); -} - -int HttpConnectionBase::staticOnMessageComplete(http_parser* parser) -{ - GET_CONNECTION() - - int error = connection->onMessageComplete(parser); - connection->reset(); - - return error; -} - -void HttpConnectionBase::onHttpError(http_errno error) -{ - debug_e("HTTP parser error: %s", httpGetErrorName(error).c_str()); -} - -bool HttpConnectionBase::onTcpReceive(TcpClient& client, char* data, int size) -{ - int parsedBytes = http_parser_execute(&parser, &parserSettings, data, size); - if(HTTP_PARSER_ERRNO(&parser) != HPE_OK) { - // we ran into trouble - abort the connection - onHttpError(HTTP_PARSER_ERRNO(&parser)); - return false; - } - - if(parser.upgrade) { - return onProtocolUpgrade(&parser); - } else if(parsedBytes != size) { - return false; - } - - return true; -} - -void HttpConnectionBase::onError(err_t err) -{ - cleanup(); - TcpClient::onError(err); -} diff --git a/Sming/SmingCore/Network/Http/HttpConnectionBase.h b/Sming/SmingCore/Network/Http/HttpConnectionBase.h deleted file mode 100644 index b2a5ebe095..0000000000 --- a/Sming/SmingCore/Network/Http/HttpConnectionBase.h +++ /dev/null @@ -1,200 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/anakod/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * HttpConnectionBase.h - * - * @author: 2018 - Slavey Karadzhov - * - ****/ - -#ifndef _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_BASE_H_ -#define _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_BASE_H_ - -#include "../TcpClient.h" -#include "WString.h" -#include "HttpCommon.h" -#include "HttpResponse.h" -#include "HttpRequest.h" - -/** @defgroup HTTP base connection - * @brief Provides http base used for client and server connections - * @ingroup http - * @{ - */ - -/** @brief Re-assembles headers from fragments via onHeaderField / onHeaderValue callbacks */ -class HttpHeaderBuilder -{ -public: - int onHeaderField(const char* at, size_t length) - { - if(lastWasValue) { - // we are starting to process new header - setLength keeps allocated memory - lastData.setLength(0); - lastWasValue = false; - } - lastData.concat(at, length); - - return 0; - } - - int onHeaderValue(HttpHeaders& headers, const char* at, size_t length) - { - if(!lastWasValue) { - currentField = lastData; - headers[currentField] = nullptr; - lastWasValue = true; - } - headers[currentField].concat(at, length); - return 0; - } - - void reset() - { - lastWasValue = true; - lastData = nullptr; - currentField = nullptr; - } - -private: - bool lastWasValue = true; ///< Indicates whether last callback was Field or Value - String lastData; ///< Content of field or value, may be constructed over several callbacks - String currentField; ///< Header field name -}; - -class HttpConnectionBase : public TcpClient -{ -public: - HttpConnectionBase(http_parser_type type, bool autoDestruct = false) : TcpClient(autoDestruct) - { - init(type); - } - - HttpConnectionBase(tcp_pcb* connection, http_parser_type type) : TcpClient(connection, nullptr, nullptr) - { - init(type); - } - - virtual void reset() - { - resetHeaders(); - } - - virtual void cleanup() - { - reset(); - } - - virtual void setDefaultParser(); - - using TcpConnection::getRemoteIp; - using TcpConnection::getRemotePort; - -protected: - /** @brief Called after all headers have been received and processed */ - void resetHeaders(); - - /** @brief Initializes the http parser for a specific type of HTTP message - * @param http_parser_type - */ - virtual void init(http_parser_type type); - - // HTTP parser methods - /** @brief Called when a new incoming data is beginning to come - * @paran http_parser* parser - * @return 0 on success, non-0 on error - */ - virtual int onMessageBegin(http_parser* parser) = 0; - - /** @brief Called when the URL path is known - * @param String path - * @return 0 on success, non-0 on error - */ - virtual int onPath(const URL& uri) - { - return 0; - } - - /** @brief Called when all headers are received - * @param HttpHeaders headers - the processed headers - * @return 0 on success, non-0 on error - */ - virtual int onHeadersComplete(const HttpHeaders& headers) = 0; - -#ifndef COMPACT_MODE - virtual int onStatus(http_parser* parser) - { - return 0; - } - - virtual int onChunkHeader(http_parser* parser) - { - return 0; - } - - virtual int onChunkComplete(http_parser* parser) - { - return 0; - } - -#endif /* COMPACT MODE */ - - /** @brief Called when a piece of body data is received - * @param at the data - * @param length - * @retval int 0 on success, non-0 on error - */ - virtual int onBody(const char* at, size_t length) = 0; - - /** @brief Called when the incoming data is complete - * @param parser - * @retval int 0 on success, non-0 on error - */ - virtual int onMessageComplete(http_parser* parser) = 0; - - /** @brief Called when the HTTP protocol should be upgraded - * @param parser - * @retval bool true on success - */ - virtual bool onProtocolUpgrade(http_parser* parser) - { - return true; - } - - virtual void onHttpError(http_errno error); - - // TCP methods - virtual bool onTcpReceive(TcpClient& client, char* data, int size); - - void onError(err_t err) override; - -private: - // http_parser callback functions - static int staticOnMessageBegin(http_parser* parser); - static int staticOnPath(http_parser* parser, const char* at, size_t length); -#ifndef COMPACT_MODE - static int staticOnStatus(http_parser* parser, const char* at, size_t length); -#endif - static int staticOnHeadersComplete(http_parser* parser); - static int staticOnHeaderField(http_parser* parser, const char* at, size_t length); - static int staticOnHeaderValue(http_parser* parser, const char* at, size_t length); - static int staticOnBody(http_parser* parser, const char* at, size_t length); -#ifndef COMPACT_MODE - static int staticOnChunkHeader(http_parser* parser); - static int staticOnChunkComplete(http_parser* parser); -#endif - static int staticOnMessageComplete(http_parser* parser); - -protected: - http_parser parser; - static const http_parser_settings parserSettings; ///< Callback table for parser - HttpHeaderBuilder header; ///< Header construction - HttpHeaders incomingHeaders; ///< Full set of incoming headers - HttpConnectionState state = eHCS_Ready; -}; - -/** @} */ -#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_CONNECTION_BASE_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpHeaderBuilder.h b/Sming/SmingCore/Network/Http/HttpHeaderBuilder.h new file mode 100644 index 0000000000..4352f6fa95 --- /dev/null +++ b/Sming/SmingCore/Network/Http/HttpHeaderBuilder.h @@ -0,0 +1,56 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/anakod/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * HttpHeaderBuilder.h + * + ****/ + +#ifndef _SMING_CORE_NETWORK_HTTP_HTTP_HEADER_BUILDER_H_ +#define _SMING_CORE_NETWORK_HTTP_HTTP_HEADER_BUILDER_H_ + +#include "HttpHeaders.h" + +/** @brief Re-assembles headers from fragments via onHeaderField / onHeaderValue callbacks */ +class HttpHeaderBuilder +{ +public: + int onHeaderField(const char* at, size_t length) + { + if(lastWasValue) { + // we are starting to process new header - setLength keeps allocated memory + lastData.setLength(0); + lastWasValue = false; + } + lastData.concat(at, length); + + return 0; + } + + int onHeaderValue(HttpHeaders& headers, const char* at, size_t length) + { + if(!lastWasValue) { + currentField = lastData; + headers[currentField] = nullptr; + lastWasValue = true; + } + headers[currentField].concat(at, length); + return 0; + } + + void reset() + { + lastWasValue = true; + lastData = nullptr; + currentField = nullptr; + } + +private: + bool lastWasValue = true; ///< Indicates whether last callback was Field or Value + String lastData; ///< Content of field or value, may be constructed over several callbacks + String currentField; ///< Header field name +}; + +#endif /* _SMING_CORE_NETWORK_HTTP_HTTP_HEADER_BUILDER_H_ */ diff --git a/Sming/SmingCore/Network/Http/HttpHeaders.h b/Sming/SmingCore/Network/Http/HttpHeaders.h index 8a411b3d81..87868aa975 100644 --- a/Sming/SmingCore/Network/Http/HttpHeaders.h +++ b/Sming/SmingCore/Network/Http/HttpHeaders.h @@ -23,6 +23,7 @@ #include "Data/CStringArray.h" #include "WString.h" #include "WHashMap.h" +#include "DateTime.h" /* * Common HTTP header field names. Enumerating these simplifies matching @@ -159,7 +160,7 @@ class HttpHeaders : private HashMap using HashMap::contains; - bool contains(const String& name) + bool contains(const String& name) const { return contains(fromString(name)); } @@ -195,6 +196,20 @@ class HttpHeaders : private HashMap using HashMap::count; + DateTime getLastModifiedDate() const + { + DateTime dt; + String strLM = operator[](HTTP_HEADER_LAST_MODIFIED); + return dt.fromHttpDate(strLM) ? dt : DateTime(); + } + + DateTime getServerDate() const + { + DateTime dt; + String strSD = operator[](HTTP_HEADER_DATE); + return dt.fromHttpDate(strSD) ? dt : DateTime(); + } + private: /** @brief Try to match a string against the list of custom field names * @param name diff --git a/Sming/SmingCore/Network/Http/HttpRequest.h b/Sming/SmingCore/Network/Http/HttpRequest.h index 88a7c2356e..d5e61afc49 100644 --- a/Sming/SmingCore/Network/Http/HttpRequest.h +++ b/Sming/SmingCore/Network/Http/HttpRequest.h @@ -24,8 +24,6 @@ #include "HttpParams.h" #include "Data/ObjectMap.h" -class HttpClient; -class HttpServerConnection; class HttpConnection; typedef Delegate RequestHeadersCompletedDelegate; @@ -37,7 +35,6 @@ typedef Delegate RequestCompletedD */ class HttpRequest { - friend class HttpConnection; friend class HttpClientConnection; friend class HttpServerConnection; @@ -108,7 +105,7 @@ class HttpRequest * @param const String& formElementName the name of the element in the form * @param IDataSourceStream* stream - pointer to the stream (doesn't have to be a FileStream) * - * @return HttpRequest* + * @retval HttpRequest* */ HttpRequest* setFile(const String& formElementName, IDataSourceStream* stream) { @@ -230,7 +227,7 @@ class HttpRequest * Check if SHA256 hash of Subject Public Key Info matches the one given. * @param fingerprints - passes the certificate fingerprints by reference. * - * @return bool true of success, false or failure + * @retval bool true of success, false or failure */ HttpRequest* pinCertificate(SslFingerprints& fingerprints) { @@ -243,7 +240,7 @@ class HttpRequest * @param SSLKeyCertPair * @param bool freeAfterHandshake * - * @return HttpRequest pointer + * @retval HttpRequest pointer */ HttpRequest* setSslKeyCert(const SslKeyCertPair& keyCertPair) { @@ -255,7 +252,7 @@ class HttpRequest #ifndef SMING_RELEASE /** * @brief Tries to present a readable version of the current request values - * @return String + * @retval String */ String toString(); #endif diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp index 6b48fb97ce..05d42914b1 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.cpp +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.cpp @@ -157,7 +157,7 @@ int HttpServerConnection::onBody(const char* at, size_t length) void HttpServerConnection::onHttpError(http_errno error) { sendError(httpGetErrorName(error)); - HttpConnectionBase::onHttpError(error); + HttpConnection::onHttpError(error); } void HttpServerConnection::onReadyToSendData(TcpConnectionEvent sourceEvent) diff --git a/Sming/SmingCore/Network/Http/HttpServerConnection.h b/Sming/SmingCore/Network/Http/HttpServerConnection.h index 1197d71989..0187728a49 100644 --- a/Sming/SmingCore/Network/Http/HttpServerConnection.h +++ b/Sming/SmingCore/Network/Http/HttpServerConnection.h @@ -13,7 +13,7 @@ #ifndef _SMING_CORE_NETWORK_HTTP_HTTP_SERVER_CONNECTION_H_ #define _SMING_CORE_NETWORK_HTTP_HTTP_SERVER_CONNECTION_H_ -#include "HttpConnectionBase.h" +#include "HttpConnection.h" #include "HttpResource.h" #include "HttpBodyParser.h" @@ -40,10 +40,10 @@ typedef Delegate HttpServerConnectionDel typedef std::function HttpServerProtocolUpgradeCallback; -class HttpServerConnection : public HttpConnectionBase +class HttpServerConnection : public HttpConnection { public: - HttpServerConnection(tcp_pcb* clientTcp) : HttpConnectionBase(clientTcp, HTTP_REQUEST) + HttpServerConnection(tcp_pcb* clientTcp) : HttpConnection(clientTcp, HTTP_REQUEST) { } @@ -77,42 +77,18 @@ class HttpServerConnection : public HttpConnectionBase upgradeCallback = callback; } + HttpRequest* getRequest() override + { + return &request; + } + protected: // HTTP parser methods - /** - * Called when a new incoming data is beginning to come - * @paran http_parser* parser - * @return 0 on success, non-0 on error - */ - int onMessageBegin(http_parser* parser) override; - /** - * Called when the URL path is known - * @param String path - * @return 0 on success, non-0 on error - */ + int onMessageBegin(http_parser* parser) override; int onPath(const URL& path) override; - - /** - * Called when all headers are received - * @param HttpHeaders headers - the processed headers - * @return 0 on success, non-0 on error - */ int onHeadersComplete(const HttpHeaders& headers) override; - - /** - * Called when a piece of body data is received - * @param const char* at - the data - * @paran size_t length - * @return 0 on success, non-0 on error - */ int onBody(const char* at, size_t length) override; - - /** - * Called when the incoming data is complete - * @paran http_parser* parser - * @return 0 on success, non-0 on error - */ int onMessageComplete(http_parser* parser) override; bool onProtocolUpgrade(http_parser* parser) override @@ -142,7 +118,6 @@ class HttpServerConnection : public HttpConnectionBase HttpResource* resource = nullptr; ///< Resource for currently executing path HttpRequest request; - HttpResponse response; HttpResourceDelegate headersCompleteDelegate = nullptr; HttpResourceDelegate requestCompletedDelegate = nullptr; diff --git a/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.cpp b/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.cpp index b9cb5ae0c4..34480d0d6e 100644 --- a/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.cpp +++ b/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.cpp @@ -44,7 +44,7 @@ const ws_parser_callbacks_t WebsocketConnection::parserSettings PROGMEM = {.on_d return -1; \ } -WebsocketConnection::WebsocketConnection(HttpConnectionBase* connection, bool isClientConnection) : userData(this) +WebsocketConnection::WebsocketConnection(HttpConnection* connection, bool isClientConnection) { setConnection(connection, isClientConnection); diff --git a/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.h b/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.h index 0eb26f264d..fc7109a115 100644 --- a/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.h +++ b/Sming/SmingCore/Network/Http/Websocket/WebsocketConnection.h @@ -12,7 +12,7 @@ #define _SMING_CORE_NETWORK_HTTP_WEBSOCKET_WEBSOCKET_CONNECTION_H_ #include "Network/TcpServer.h" -#include "../HttpConnectionBase.h" +#include "../HttpConnection.h" extern "C" { #include "../ws_parser/ws_parser.h" } @@ -66,7 +66,7 @@ class WebsocketConnection * @param connection the transport connection * @param isClientConnection true when the passed connection is an http client conneciton */ - WebsocketConnection(HttpConnectionBase* connection, bool isClientConnection = true); + WebsocketConnection(HttpConnection* connection, bool isClientConnection = true); virtual ~WebsocketConnection() { @@ -175,8 +175,12 @@ class WebsocketConnection return (this == &rhs); } - /** @deprecated Will be removed */ - WebsocketList& getActiveWebsockets() SMING_DEPRECATED + /** + * @brief Obtain the list of active websockets + * @retval const WebsocketList& + * @note Return value is const as only restricted operations should be carried out on the list. + */ + static const WebsocketList& getActiveWebsockets() { return websocketList; } @@ -231,9 +235,9 @@ class WebsocketConnection /** * @brief Gets the underlying HTTP connection - * @retval HttpConnectionBase* + * @retval HttpConnection* */ - HttpConnectionBase* getConnection() + HttpConnection* getConnection() { return connection; } @@ -243,7 +247,7 @@ class WebsocketConnection * @param connection the transport connection * @param isClientConnection true when the passed connection is an http client conneciton */ - void setConnection(HttpConnectionBase* connection, bool isClientConnection = true) + void setConnection(HttpConnection* connection, bool isClientConnection = true) { this->connection = connection; this->isClientConnection = isClientConnection; @@ -308,7 +312,7 @@ class WebsocketConnection bool isClientConnection = true; - HttpConnectionBase* connection = nullptr; + HttpConnection* connection = nullptr; bool activated = false; }; diff --git a/Sming/SmingCore/Network/Ssl/SslFingerprints.h b/Sming/SmingCore/Network/Ssl/SslFingerprints.h index 706bfe3b17..03a5fa0ea5 100644 --- a/Sming/SmingCore/Network/Ssl/SslFingerprints.h +++ b/Sming/SmingCore/Network/Ssl/SslFingerprints.h @@ -38,7 +38,7 @@ enum SslFingerprintType { * @note Lifetime as follows: * - Constructed by application, using appropriate setXXX method; * - Passed into HttpRequest by application, using pinCertificate method - request is then queued; - * - Passed into HttpConnection (TcpClient descendant) by HttpClient, using pinCertificate method + * - Passed into HttpClientConnection (TcpClient descendant) by HttpClient, using pinCertificate method * - When certificate validated, memory is released * */ diff --git a/Sming/SmingCore/Network/WebsocketClient.cpp b/Sming/SmingCore/Network/WebsocketClient.cpp index 1cf4cb98e2..c8972b7f2c 100644 --- a/Sming/SmingCore/Network/WebsocketClient.cpp +++ b/Sming/SmingCore/Network/WebsocketClient.cpp @@ -20,9 +20,9 @@ HttpConnection* WebsocketClient::getHttpConnection() { - HttpConnection* connection = static_cast(WebsocketConnection::getConnection()); - if(!connection && state == eWSCS_Closed) { - connection = new HttpConnection(new RequestQueue()); + auto connection = WebsocketConnection::getConnection(); + if(connection == nullptr && state == eWSCS_Closed) { + connection = new HttpClientConnection(); setConnection(connection); } diff --git a/Sming/SmingCore/Network/WebsocketClient.h b/Sming/SmingCore/Network/WebsocketClient.h index 01a04009ed..d0d063b200 100644 --- a/Sming/SmingCore/Network/WebsocketClient.h +++ b/Sming/SmingCore/Network/WebsocketClient.h @@ -18,7 +18,7 @@ #ifndef _SMING_CORE_NETWORK_WEBSOCKET_CLIENT_H_ #define _SMING_CORE_NETWORK_WEBSOCKET_CLIENT_H_ -#include "Http/HttpConnection.h" +#include "Http/HttpClientConnection.h" #include "Http/Websocket/WebsocketConnection.h" /** @defgroup wsclient Websocket client @@ -32,11 +32,11 @@ class WebsocketClient : protected WebsocketConnection { public: - WebsocketClient() : WebsocketConnection(new HttpConnection(new RequestQueue())) + WebsocketClient() : WebsocketConnection(new HttpClientConnection) { } - WebsocketClient(HttpConnectionBase* connection) : WebsocketConnection(connection) + WebsocketClient(HttpConnection* connection) : WebsocketConnection(connection) { } diff --git a/samples/HttpServer_ConfigNetwork/app/application.cpp b/samples/HttpServer_ConfigNetwork/app/application.cpp index 2d968db3d5..69c076d015 100644 --- a/samples/HttpServer_ConfigNetwork/app/application.cpp +++ b/samples/HttpServer_ConfigNetwork/app/application.cpp @@ -216,7 +216,6 @@ void init() lastModified.trim(); } - Serial.setPort(UART_ID_1); Serial.begin(SERIAL_BAUD_RATE); // 115200 by default Serial.systemDebugOutput(true); // Enable debug output to serial AppSettings.load(); diff --git a/samples/MeteoControl/app/application.cpp b/samples/MeteoControl/app/application.cpp index ee96f410f9..3f5bf22d47 100644 --- a/samples/MeteoControl/app/application.cpp +++ b/samples/MeteoControl/app/application.cpp @@ -168,9 +168,9 @@ int onClockUpdating(HttpConnection& client, bool successful) } // Extract date header from response - clockValue = client.getServerDate(); + clockValue = client.getResponse()->headers.getServerDate(); if(clockValue.isNull()) - clockValue = client.getLastModifiedDate(); + clockValue = client.getResponse()->headers.getLastModifiedDate(); if(!clockValue.isNull()) clockValue.addMilliseconds(ActiveConfig.AddTZ * 1000 * 60 * 60);