| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -17,7 +17,7 @@ HTTPConnection::HTTPConnection(ResourceResolver * resResolver): | |||
| 17 | 17 | _isKeepAlive = false; | |
| 18 | 18 | _lastTransmissionTS = millis(); | |
| 19 | 19 | _shutdownTS = 0; | |
| 20 | - _websocket = nullptr; | ||
| 20 | + _wsHandler = nullptr; | ||
| 21 | 21 | } | |
| 22 | 22 | ||
| 23 | 23 | HTTPConnection::~HTTPConnection() { | |
@@ -125,6 +125,11 @@ void HTTPConnection::closeConnection() { | |||
| 125 | 125 | delete _httpHeaders; | |
| 126 | 126 | _httpHeaders = NULL; | |
| 127 | 127 | } | |
| 128 | + | ||
| 129 | + if (_wsHandler != nullptr) { | ||
| 130 | + HTTPS_DLOG("[ ] Freeing WS Handler"); | ||
| 131 | + delete _wsHandler; | ||
| 132 | + } | ||
| 128 | 133 | } | |
| 129 | 134 | ||
| 130 | 135 | /** | |
@@ -417,48 +422,29 @@ void HTTPConnection::loop() { | |||
| 417 | 422 | { | |
| 418 | 423 | HTTPS_DLOG("[ ] Resolving resource..."); | |
| 419 | 424 | ResolvedResource resolvedResource; | |
| 420 | - _resResolver->resolveNode(_httpMethod, _httpResource, resolvedResource); | ||
| 425 | + | ||
| 426 | + // Check which kind of node we need (Websocket or regular) | ||
| 427 | + bool websocketRequested = checkWebsocket(); | ||
| 428 | + | ||
| 429 | + _resResolver->resolveNode(_httpMethod, _httpResource, resolvedResource, websocketRequested ? WEBSOCKET : HANDLER_CALLBACK); | ||
| 430 | + | ||
| 431 | + // Is there any match (may be the defaultNode, if it is configured) | ||
| 421 | 432 | if (resolvedResource.didMatch()) { | |
| 422 | - // Did the client request connection:keep-alive? | ||
| 423 | - HTTPHeader * connectionHeader = _httpHeaders->get("Connection"); | ||
| 424 | - if (connectionHeader != NULL && std::string("keep-alive").compare(connectionHeader->_value)==0) { | ||
| 425 | - HTTPS_DLOGHEX("[ ] Keep-Alive activated. fid=", _socket); | ||
| 426 | - _isKeepAlive = true; | ||
| 433 | + // Check for client's request to keep-alive if we have a handler function. | ||
| 434 | + if (resolvedResource.getMatchingNode()->_nodeType == HANDLER_CALLBACK) { | ||
| 435 | + // Did the client set connection:keep-alive? | ||
| 436 | + HTTPHeader * connectionHeader = _httpHeaders->get("Connection"); | ||
| 437 | + if (connectionHeader != NULL && std::string("keep-alive").compare(connectionHeader->_value)==0) { | ||
| 438 | + HTTPS_DLOGHEX("[ ] Keep-Alive activated. fid=", _socket); | ||
| 439 | + _isKeepAlive = true; | ||
| 440 | + } else { | ||
| 441 | + HTTPS_DLOGHEX("[ ] Keep-Alive disabled. fid=", _socket); | ||
| 442 | + _isKeepAlive = false; | ||
| 443 | + } | ||
| 427 | 444 | } else { | |
| 428 | - HTTPS_DLOGHEX("[ ] Keep-Alive disabled. fid=", _socket); | ||
| 429 | 445 | _isKeepAlive = false; | |
| 430 | 446 | } | |
| 431 | 447 | ||
| 432 | - // do we have a websocket connection? | ||
| 433 | - if(checkWebsocket()) { | ||
| 434 | - // Create response | ||
| 435 | - HTTPResponse res = HTTPResponse(this); | ||
| 436 | - // Add default headers to the response | ||
| 437 | - auto allDefaultHeaders = _defaultHeaders->getAll(); | ||
| 438 | - for(std::vector<HTTPHeader*>::iterator header = allDefaultHeaders->begin(); header != allDefaultHeaders->end(); ++header) { | ||
| 439 | - res.setHeader((*header)->_name, (*header)->_value); | ||
| 440 | - } | ||
| 441 | - // Set the response status | ||
| 442 | - res.setStatusCode(101); | ||
| 443 | - res.setStatusText("Switching Protocols"); | ||
| 444 | - res.setHeader("Upgrade", "websocket"); | ||
| 445 | - res.setHeader("Connection", "Upgrade"); | ||
| 446 | - res.setHeader("Sec-WebSocket-Accept", websocketKeyResponseHash(_httpHeaders->getValue("Sec-WebSocket-Key"))); | ||
| 447 | - res.print(""); | ||
| 448 | - | ||
| 449 | - // Callback for the actual resource | ||
| 450 | - HTTPSCallbackFunction * resourceCallback = resolvedResource.getMatchingNode()->_callback; | ||
| 451 | - // persistent request for websocket. To be destroyed when connection closes. | ||
| 452 | - HTTPRequest *req = new HTTPRequest(this, _httpHeaders, resolvedResource.getParams(), _httpResource, _httpMethod, ""); | ||
| 453 | - // bind function call to the actual resource | ||
| 454 | - std::function<void()> next = std::function<void()>(std::bind(resourceCallback, req, nullptr)); | ||
| 455 | - _websocket = new Websocket(this); // make websocket with this connection | ||
| 456 | - next(); // call callback | ||
| 457 | - delete req; | ||
| 458 | - _connectionState = STATE_WEBSOCKET; | ||
| 459 | - break; | ||
| 460 | - } | ||
| 461 | - | ||
| 462 | 448 | // Create request context | |
| 463 | 449 | HTTPRequest req = HTTPRequest(this, _httpHeaders, resolvedResource.getParams(), _httpResource, _httpMethod, resolvedResource.getMatchingNode()->_tag); | |
| 464 | 450 | HTTPResponse res = HTTPResponse(this); | |
@@ -469,8 +455,15 @@ void HTTPConnection::loop() { | |||
| 469 | 455 | res.setHeader((*header)->_name, (*header)->_value); | |
| 470 | 456 | } | |
| 471 | 457 | ||
| 472 | - // Callback for the actual resource | ||
| 473 | - HTTPSCallbackFunction * resourceCallback = resolvedResource.getMatchingNode()->_callback; | ||
| 458 | + // Find the request handler callback | ||
| 459 | + HTTPSCallbackFunction * resourceCallback; | ||
| 460 | + if (websocketRequested) { | ||
| 461 | + // For the websocket, we use the handshake callback defined below | ||
| 462 | + resourceCallback = &handleWebsocketHandshake; | ||
| 463 | + } else { | ||
| 464 | + // For resource nodes, we use the callback defined by the node itself | ||
| 465 | + resourceCallback = ((ResourceNode*)resolvedResource.getMatchingNode())->_callback; | ||
| 466 | + } | ||
| 474 | 467 | ||
| 475 | 468 | // Get the current middleware chain | |
| 476 | 469 | auto vecMw = _resResolver->getMiddleware(); | |
@@ -495,37 +488,45 @@ void HTTPConnection::loop() { | |||
| 495 | 488 | req.discardRequestBody(); | |
| 496 | 489 | } | |
| 497 | 490 | ||
| 498 | - // Handling the request is done | ||
| 499 | - HTTPS_DLOG("[ ] Handler function done, request complete"); | ||
| 500 | - | ||
| 501 | - // Now we need to check if we can use keep-alive to reuse the SSL connection | ||
| 502 | - // However, if the client did not set content-size or defined connection: close, | ||
| 503 | - // we have no chance to do so. | ||
| 504 | - if (!_isKeepAlive) { | ||
| 505 | - // No KeepAlive -> We are done. Transition to next state. | ||
| 506 | - if (!isClosed()) { | ||
| 507 | - _connectionState = STATE_BODY_FINISHED; | ||
| 508 | - } | ||
| 491 | + // Finally, after the handshake is done, we create the WebsocketHandler and change the internal state. | ||
| 492 | + if(websocketRequested) { | ||
| 493 | + _wsHandler = ((WebsocketNode*)resolvedResource.getMatchingNode())->newHandler(); | ||
| 494 | + _wsHandler->initialize(this); // make websocket with this connection | ||
| 495 | + _connectionState = STATE_WEBSOCKET; | ||
| 509 | 496 | } else { | |
| 510 | - if (res.isResponseBuffered()) { | ||
| 511 | - // If the response could be buffered: | ||
| 512 | - res.setHeader("Connection", "keep-alive"); | ||
| 513 | - res.finalize(); | ||
| 514 | - if (_clientState != CSTATE_CLOSED) { | ||
| 515 | - // Refresh the timeout for the new request | ||
| 516 | - refreshTimeout(); | ||
| 517 | - // Reset headers for the new connection | ||
| 518 | - _httpHeaders->clearAll(); | ||
| 519 | - // Go back to initial state | ||
| 520 | - _connectionState = STATE_INITIAL; | ||
| 497 | + // Handling the request is done | ||
| 498 | + HTTPS_DLOG("[ ] Handler function done, request complete"); | ||
| 499 | + | ||
| 500 | + // Now we need to check if we can use keep-alive to reuse the SSL connection | ||
| 501 | + // However, if the client did not set content-size or defined connection: close, | ||
| 502 | + // we have no chance to do so. | ||
| 503 | + if (!_isKeepAlive) { | ||
| 504 | + // No KeepAlive -> We are done. Transition to next state. | ||
| 505 | + if (!isClosed()) { | ||
| 506 | + _connectionState = STATE_BODY_FINISHED; | ||
| 507 | + } | ||
| 508 | + } else { | ||
| 509 | + if (res.isResponseBuffered()) { | ||
| 510 | + // If the response could be buffered: | ||
| 511 | + res.setHeader("Connection", "keep-alive"); | ||
| 512 | + res.finalize(); | ||
| 513 | + if (_clientState != CSTATE_CLOSED) { | ||
| 514 | + // Refresh the timeout for the new request | ||
| 515 | + refreshTimeout(); | ||
| 516 | + // Reset headers for the new connection | ||
| 517 | + _httpHeaders->clearAll(); | ||
| 518 | + // Go back to initial state | ||
| 519 | + _connectionState = STATE_INITIAL; | ||
| 520 | + } | ||
| 521 | + } | ||
| 522 | + // The response could not be buffered or the client has closed: | ||
| 523 | + if (!isClosed() && _connectionState!=STATE_INITIAL) { | ||
| 524 | + _connectionState = STATE_BODY_FINISHED; | ||
| 521 | 525 | } | |
| 522 | - } | ||
| 523 | - // The response could not be buffered or the client has closed: | ||
| 524 | - if (!isClosed() && _connectionState!=STATE_INITIAL) { | ||
| 525 | - _connectionState = STATE_BODY_FINISHED; | ||
| 526 | 526 | } | |
| 527 | 527 | } | |
| 528 | 528 | } else { | |
| 529 | + // No match (no default route configured, nothing does match) | ||
| 529 | 530 | HTTPS_DLOG("[ERR] Could not find a matching resource. Server error."); | |
| 530 | 531 | serverError(); | |
| 531 | 532 | } | |
@@ -542,14 +543,15 @@ void HTTPConnection::loop() { | |||
| 542 | 543 | refreshTimeout(); // don't timeout websocket connection | |
| 543 | 544 | if(pendingBufferSize() > 0) { | |
| 544 | 545 | HTTPS_DLOG("[ ] websocket handler"); | |
| 545 | - if(_websocket->read() < 0) { | ||
| 546 | - _websocket->close(); | ||
| 547 | - delete _websocket; | ||
| 548 | - _httpHeaders->clearAll(); | ||
| 549 | - _connectionState = STATE_CLOSING; | ||
| 550 | - } | ||
| 546 | + _wsHandler->loop(); | ||
| 547 | + } | ||
| 548 | + // If the handler has terminated the connection, clean up and close the socket too | ||
| 549 | + if (_wsHandler->closed()) { | ||
| 550 | + HTTPS_DLOG("[ ] WS Connection closed. Freeing WS Handler"); | ||
| 551 | + delete _wsHandler; | ||
| 552 | + _wsHandler = nullptr; | ||
| 553 | + _connectionState = STATE_CLOSING; | ||
| 551 | 554 | } | |
| 552 | - //readBuffer(); | ||
| 553 | 555 | break; | |
| 554 | 556 | default:; | |
| 555 | 557 | } | |
@@ -579,7 +581,22 @@ bool HTTPConnection::checkWebsocket() { | |||
| 579 | 581 | return false; | |
| 580 | 582 | } | |
| 581 | 583 | ||
| 582 | - std::string HTTPConnection::websocketKeyResponseHash(std::string key) { | ||
| 584 | + /** | ||
| 585 | + * Handler function for the websocket handshake. Will be used by HTTPConnection if a websocket is detected | ||
| 586 | + */ | ||
| 587 | + void handleWebsocketHandshake(HTTPRequest * req, HTTPResponse * res) { | ||
| 588 | + res->setStatusCode(101); | ||
| 589 | + res->setStatusText("Switching Protocols"); | ||
| 590 | + res->setHeader("Upgrade", "websocket"); | ||
| 591 | + res->setHeader("Connection", "Upgrade"); | ||
| 592 | + res->setHeader("Sec-WebSocket-Accept", websocketKeyResponseHash(req->getHeader("Sec-WebSocket-Key"))); | ||
| 593 | + res->print(""); | ||
| 594 | + } | ||
| 595 | + | ||
| 596 | + /** | ||
| 597 | + * Function used to compute the value of the Sec-WebSocket-Accept during Websocket handshake | ||
| 598 | + */ | ||
| 599 | + std::string websocketKeyResponseHash(std::string key) { | ||
| 583 | 600 | std::string newKey = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | |
| 584 | 601 | uint8_t shaData[HTTPS_SHA1_LENGTH]; | |
| 585 | 602 | esp_sha(SHA1, (uint8_t*)newKey.data(), newKey.length(), shaData); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -14,15 +14,20 @@ | |||
| 14 | 14 | #include "lwip/sockets.h" | |
| 15 | 15 | ||
| 16 | 16 | #include "HTTPSServerConstants.hpp" | |
| 17 | + #include "ConnectionContext.hpp" | ||
| 18 | + | ||
| 17 | 19 | #include "HTTPHeaders.hpp" | |
| 18 | 20 | #include "HTTPHeader.hpp" | |
| 21 | + | ||
| 19 | 22 | #include "ResourceResolver.hpp" | |
| 20 | 23 | #include "ResolvedResource.hpp" | |
| 24 | + | ||
| 21 | 25 | #include "ResourceNode.hpp" | |
| 22 | 26 | #include "HTTPRequest.hpp" | |
| 23 | 27 | #include "HTTPResponse.hpp" | |
| 24 | - #include "ConnectionContext.hpp" | ||
| 25 | - #include "Websocket.hpp" | ||
| 28 | + | ||
| 29 | + #include "WebsocketHandler.hpp" | ||
| 30 | + #include "WebsocketNode.hpp" | ||
| 26 | 31 | ||
| 27 | 32 | namespace httpsserver { | |
| 28 | 33 | ||
@@ -150,10 +155,14 @@ class HTTPConnection : private ConnectionContext { | |||
| 150 | 155 | bool _isKeepAlive; | |
| 151 | 156 | ||
| 152 | 157 | //Websocket connection | |
| 153 | - Websocket * _websocket; | ||
| 158 | + WebsocketHandler * _wsHandler; | ||
| 154 | 159 | ||
| 155 | 160 | }; | |
| 156 | 161 | ||
| 162 | + void handleWebsocketHandshake(HTTPRequest * req, HTTPResponse * res); | ||
| 163 | + | ||
| 164 | + std::string websocketKeyResponseHash(std::string key); | ||
| 165 | + | ||
| 157 | 166 | } /* namespace httpsserver */ | |
| 158 | 167 | ||
| 159 | 168 | #endif /* SRC_HTTPCONNECTION_HPP_ */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,52 @@ | |||
| 1 | + #include "HTTPNode.hpp" | ||
| 2 | + | ||
| 3 | + namespace httpsserver { | ||
| 4 | + | ||
| 5 | + HTTPNode::HTTPNode(const std::string path, const HTTPNodeType nodeType, const std::string tag): | ||
| 6 | + _path(std::move(path)), | ||
| 7 | + _tag(std::move(tag)), | ||
| 8 | + _nodeType(nodeType) { | ||
| 9 | + | ||
| 10 | + // Count the parameters | ||
| 11 | + _urlParamCount = 0; | ||
| 12 | + size_t idx = 0; | ||
| 13 | + while((idx = path.find("/*", idx)) != std::string::npos) { | ||
| 14 | + _urlParamCount+=1; | ||
| 15 | + // If we don't do this, the same index will be found again... and again... and again... | ||
| 16 | + idx+=1; | ||
| 17 | + }; | ||
| 18 | + | ||
| 19 | + // Check if there are parameters | ||
| 20 | + if (_urlParamCount > 0) { | ||
| 21 | + // If there are parameters, store their indices | ||
| 22 | + _urlParamIdx = new size_t[_urlParamCount]; | ||
| 23 | + for(int i = 0; i < _urlParamCount; i++) { | ||
| 24 | + _urlParamIdx[i] = path.find("/*", i==0 ? 0 : _urlParamIdx[i-1])+1; | ||
| 25 | + } | ||
| 26 | + } else { | ||
| 27 | + _urlParamIdx = NULL; | ||
| 28 | + } | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + HTTPNode::~HTTPNode() { | ||
| 32 | + if (_urlParamIdx != NULL) { | ||
| 33 | + delete[] _urlParamIdx; | ||
| 34 | + } | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + bool HTTPNode::hasUrlParameter() { | ||
| 38 | + return _urlParamCount > 0; | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + size_t HTTPNode::getParamIdx(uint8_t idx) { | ||
| 42 | + if (idx<_urlParamCount) { | ||
| 43 | + return _urlParamIdx[idx]; | ||
| 44 | + } else { | ||
| 45 | + return -1; | ||
| 46 | + } | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + uint8_t HTTPNode::getUrlParamCount() { | ||
| 50 | + return _urlParamCount; | ||
| 51 | + } | ||
| 52 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,47 @@ | |||
| 1 | + #ifndef SRC_HTTPNODE_HPP_ | ||
| 2 | + #define SRC_HTTPNODE_HPP_ | ||
| 3 | + | ||
| 4 | + #include <Arduino.h> | ||
| 5 | + #include <string> | ||
| 6 | + | ||
| 7 | + namespace httpsserver { | ||
| 8 | + | ||
| 9 | + enum HTTPNodeType { | ||
| 10 | + /** Node with a handler callback function (class ResourceNode) */ | ||
| 11 | + HANDLER_CALLBACK, | ||
| 12 | + /** Node with a websocket handler (class WebsocketNode) */ | ||
| 13 | + WEBSOCKET | ||
| 14 | + }; | ||
| 15 | + | ||
| 16 | + class HTTPNode { | ||
| 17 | + public: | ||
| 18 | + HTTPNode(const std::string path, const HTTPNodeType nodeType, const std::string tag = ""); | ||
| 19 | + virtual ~HTTPNode(); | ||
| 20 | + | ||
| 21 | + /** | ||
| 22 | + * The path under which this node will be available. Should start with a slash. Example: | ||
| 23 | + * "/myResource" | ||
| 24 | + */ | ||
| 25 | + const std::string _path; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * Stores a tag that can be used in middleware and handler functions to identify this | ||
| 29 | + * specific node, tag the node with a required permission, ... | ||
| 30 | + */ | ||
| 31 | + const std::string _tag; | ||
| 32 | + | ||
| 33 | + /** Stores the type of the node (as we have not runtime type information by default) */ | ||
| 34 | + const HTTPNodeType _nodeType; | ||
| 35 | + | ||
| 36 | + bool hasUrlParameter(); | ||
| 37 | + uint8_t getUrlParamCount(); | ||
| 38 | + size_t getParamIdx(uint8_t); | ||
| 39 | + | ||
| 40 | + private: | ||
| 41 | + uint8_t _urlParamCount; | ||
| 42 | + size_t * _urlParamIdx; | ||
| 43 | + }; | ||
| 44 | + | ||
| 45 | + } // namespace httpserver | ||
| 46 | + | ||
| 47 | + #endif | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,11 +18,11 @@ bool ResolvedResource::didMatch() { | |||
| 18 | 18 | return _matchingNode != NULL; | |
| 19 | 19 | } | |
| 20 | 20 | ||
| 21 | - ResourceNode * ResolvedResource::getMatchingNode() { | ||
| 21 | + HTTPNode * ResolvedResource::getMatchingNode() { | ||
| 22 | 22 | return _matchingNode; | |
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | - void ResolvedResource::setMatchingNode(ResourceNode * node) { | ||
| 25 | + void ResolvedResource::setMatchingNode(HTTPNode * node) { | ||
| 26 | 26 | _matchingNode = node; | |
| 27 | 27 | } | |
| 28 | 28 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,14 +11,14 @@ class ResolvedResource { | |||
| 11 | 11 | ResolvedResource(); | |
| 12 | 12 | ~ResolvedResource(); | |
| 13 | 13 | ||
| 14 | - void setMatchingNode(ResourceNode * node); | ||
| 15 | - ResourceNode * getMatchingNode(); | ||
| 14 | + void setMatchingNode(HTTPNode * node); | ||
| 15 | + HTTPNode * getMatchingNode(); | ||
| 16 | 16 | bool didMatch(); | |
| 17 | 17 | ResourceParameters * getParams(); | |
| 18 | 18 | void setParams(ResourceParameters * params); | |
| 19 | 19 | ||
| 20 | 20 | private: | |
| 21 | - ResourceNode * _matchingNode; | ||
| 21 | + HTTPNode * _matchingNode; | ||
| 22 | 22 | ResourceParameters * _params; | |
| 23 | 23 | }; | |
| 24 | 24 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments