Skip to content

Commit 29203af

Browse files
committed
Added basic support for Middleware/Proxy functions and Basic Auth. Close fhessel#10
1 parent c5d8a1e commit 29203af

8 files changed

+335
-8
lines changed

https/HTTPConnection.cpp

+18-4
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ void HTTPConnection::loop() {
425425
}
426426

427427
// Create request context
428-
HTTPRequest req = HTTPRequest(this, _httpHeaders, resolvedResource.getParams());
428+
HTTPRequest req = HTTPRequest(this, _httpHeaders, resolvedResource.getParams(), _httpResource, _httpMethod);
429429
HTTPResponse res = HTTPResponse(this);
430430

431431
// Add default headers to the response
@@ -434,9 +434,23 @@ void HTTPConnection::loop() {
434434
res.setHeader((*header)->_name, (*header)->_value);
435435
}
436436

437-
// Call the callback
438-
HTTPS_DLOG("[ ] Calling handler function");
439-
resolvedResource.getMatchingNode()->_callback(&req, &res);
437+
// Callback for the actual resource
438+
HTTPSCallbackFunction * resourceCallback = resolvedResource.getMatchingNode()->_callback;
439+
440+
// Get the current middleware chain
441+
auto vecMw = _resResolver->getMiddleware();
442+
443+
// Anchor of the chain is the actual resource. The call to the handler is bound here
444+
std::function<void()> next = std::function<void()>(std::bind(resourceCallback, &req, &res));
445+
446+
// Go back in the middleware chain and glue everything together
447+
auto itMw = vecMw.rbegin();
448+
while(itMw != vecMw.rend()) {
449+
next = std::function<void()>(std::bind((*itMw), &req, &res, next));
450+
itMw++;
451+
}
452+
// Call the whole chain
453+
next();
440454

441455
// The callback-function should have read all of the request body.
442456
// However, if it does not, we need to clear the request body now,

https/HTTPConnection.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <Arduino.h>
55

66
#include <string>
7+
#include <functional>
78

89
// Required for sockets
910
#include "lwip/netdb.h"

https/HTTPMiddlewareFunction.hpp

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef HTTPS_HTTPMIDDLEWAREFUNCTION_HPP_
2+
#define HTTPS_HTTPMIDDLEWAREFUNCTION_HPP_
3+
4+
#include <functional>
5+
6+
#include "HTTPRequest.hpp"
7+
#include "HTTPResponse.hpp"
8+
#include "HTTPSCallbackFunction.hpp"
9+
10+
namespace httpsserver {
11+
/**
12+
* A middleware function that can be registered at the server.
13+
*
14+
* It will be called before an incoming request is passed to any HTTPSCallbackFunction and may perform
15+
* operations like redirects or authentication.
16+
*
17+
* It receives the request and response object as well as a function pointer ("next") to pass on processing.
18+
* This allows chaining those functions. If next() is not called, the HTTPSCallbackFunction that
19+
* would match the request url will not be invoked. This might become handy if you want to intercept request
20+
* handling in case of missing authentication. Don't forget to call next in case you want to access your
21+
* resources, though.
22+
*/
23+
typedef void (HTTPSMiddlewareFunction)(HTTPRequest * req, HTTPResponse * res, std::function<void()> next);
24+
}
25+
#endif /* HTTPS_HTTPMIDDLEWAREFUNCTION_HPP_ */

https/HTTPRequest.cpp

+69-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
namespace httpsserver {
1111

12-
HTTPRequest::HTTPRequest(ConnectionContext * con, HTTPHeaders * headers, ResourceParameters * params):
12+
HTTPRequest::HTTPRequest(ConnectionContext * con, HTTPHeaders * headers, ResourceParameters * params, std::string requestString, std::string method):
1313
_con(con),
1414
_headers(headers),
15-
_params(params) {
15+
_params(params),
16+
_requestString(requestString),
17+
_method(method) {
1618

1719
HTTPHeader * contentLength = headers->get("Content-Length");
1820
if (contentLength == NULL) {
@@ -43,6 +45,10 @@ std::string HTTPRequest::getHeader(std::string name) {
4345
}
4446
}
4547

48+
void HTTPRequest::setHeader(std::string name, std::string value) {
49+
_headers->set(new HTTPHeader(name, value));
50+
}
51+
4652
size_t HTTPRequest::readBytes(byte * buffer, size_t length) {
4753

4854
// Limit reading to content length
@@ -67,6 +73,14 @@ size_t HTTPRequest::getContentLength() {
6773
return _remainingContent;
6874
}
6975

76+
std::string HTTPRequest::getRequestString() {
77+
return _requestString;
78+
}
79+
80+
std::string HTTPRequest::getMethod() {
81+
return _method;
82+
}
83+
7084
bool HTTPRequest::requestComplete() {
7185
if (_contentLengthSet) {
7286
// If we have a content size, rely on it.
@@ -87,6 +101,59 @@ void HTTPRequest::discardRequestBody() {
87101
}
88102
}
89103

104+
std::string HTTPRequest::getBasicAuthUser() {
105+
std::string token = decodeBasicAuthToken();
106+
size_t splitpoint = token.find(":");
107+
if (splitpoint != std::string::npos && splitpoint > 0) {
108+
return token.substr(0, splitpoint);
109+
} else {
110+
return std::string();
111+
}
112+
}
113+
114+
std::string HTTPRequest::getBasicAuthPassword() {
115+
std::string token = decodeBasicAuthToken();
116+
size_t splitpoint = token.find(":");
117+
if (splitpoint != std::string::npos && splitpoint > 0) {
118+
return token.substr(splitpoint+1);
119+
} else {
120+
return std::string();
121+
}
122+
}
123+
124+
std::string HTTPRequest::decodeBasicAuthToken() {
125+
std::string basicAuthString = getHeader("Authorization");
126+
// Get the length of the token
127+
size_t sourceLength = basicAuthString.length();
128+
// Only handle basic auth tokens
129+
if (basicAuthString.substr(0, 6) != "Basic ") {
130+
return std::string();
131+
}
132+
// If the token is too long, skip
133+
if (sourceLength > 100) {
134+
return std::string();
135+
} else {
136+
// Try to decode. As we are using mbedtls anyway, we can use that function
137+
unsigned char * bufOut = new unsigned char[basicAuthString.length()];
138+
size_t outputLength = 0;
139+
int res = mbedtls_base64_decode(
140+
bufOut,
141+
sourceLength,
142+
&outputLength,
143+
((const unsigned char *)basicAuthString.substr(6).c_str()), // Strip "Basic "
144+
sourceLength - 6 // Strip "Basic "
145+
);
146+
// Failure of decoding
147+
if (res != 0) {
148+
delete[] bufOut;
149+
return std::string();
150+
}
151+
std::string tokenRes = std::string((char*)bufOut, outputLength);
152+
delete[] bufOut;
153+
return tokenRes;
154+
}
155+
}
156+
90157
bool HTTPRequest::isSecure() {
91158
return _con->isSecure();
92159
}

https/HTTPRequest.hpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <Arduino.h>
1212
#include <string>
1313

14+
#include <mbedtls/base64.h>
15+
1416
#include "util.hpp"
1517

1618
#include "ConnectionContext.hpp"
@@ -22,25 +24,35 @@ namespace httpsserver {
2224

2325
class HTTPRequest {
2426
public:
25-
HTTPRequest(ConnectionContext * con, HTTPHeaders * headers, ResourceParameters * resource);
27+
HTTPRequest(ConnectionContext * con, HTTPHeaders * headers, ResourceParameters * resource, std::string requestString, std::string method);
2628
virtual ~HTTPRequest();
2729

2830
std::string getHeader(std::string name);
31+
void setHeader(std::string name, std::string value);
32+
std::string getRequestString();
33+
std::string getMethod();
2934

3035
size_t readChars(char * buffer, size_t length);
3136
size_t readBytes(byte * buffer, size_t length);
3237
size_t getContentLength();
3338
bool requestComplete();
3439
void discardRequestBody();
3540
ResourceParameters * getParams();
41+
std::string getBasicAuthUser();
42+
std::string getBasicAuthPassword();
3643
bool isSecure();
3744
private:
45+
std::string decodeBasicAuthToken();
46+
3847
ConnectionContext * _con;
3948

4049
HTTPHeaders * _headers;
4150

4251
ResourceParameters * _params;
4352

53+
std::string _requestString;
54+
std::string _method;
55+
4456
bool _contentLengthSet;
4557
size_t _remainingContent;
4658
};

https/ResourceResolver.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,18 @@ void ResourceResolver::resolveNode(const std::string &method, const std::string
180180
}
181181
}
182182

183+
void ResourceResolver::addMiddleware(const HTTPSMiddlewareFunction * mwFunction) {
184+
_middleware.push_back(mwFunction);
185+
}
186+
187+
void ResourceResolver::removeMiddleware(const HTTPSMiddlewareFunction * mwFunction) {
188+
_middleware.erase(std::remove(_middleware.begin(), _middleware.end(), mwFunction), _middleware.end());
189+
}
190+
191+
const std::vector<HTTPSMiddlewareFunction*> ResourceResolver::getMiddleware() {
192+
return _middleware;
193+
}
194+
183195
void ResourceResolver::setDefaultNode(ResourceNode * defaultNode) {
184196
_defaultNode = defaultNode;
185197
}

https/ResourceResolver.hpp

+13
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
#undef min
1414
#undef max
1515
#include <vector>
16+
#include <algorithm>
1617

1718
#include "ResourceNode.hpp"
1819
#include "ResolvedResource.hpp"
20+
#include "HTTPMiddlewareFunction.hpp"
1921

2022
namespace httpsserver {
2123

@@ -28,11 +30,22 @@ class ResourceResolver {
2830
void unregisterNode(ResourceNode *node);
2931
void setDefaultNode(ResourceNode *node);
3032
void resolveNode(const std::string &method, const std::string &url, ResolvedResource &resolvedResource);
33+
34+
/** Add a middleware function to the end of the middleware function chain. See HTTPSMiddlewareFunction.hpp for details. */
35+
void addMiddleware(const HTTPSMiddlewareFunction * mwFunction);
36+
/** Remove a specific function from the middleware function chain. */
37+
void removeMiddleware(const HTTPSMiddlewareFunction * mwFunction);
38+
/** Get the current middleware chain with a resource function at the end */
39+
const std::vector<HTTPSMiddlewareFunction*> getMiddleware();
40+
3141
private:
3242

3343
// This vector holds all nodes (with callbacks) that are registered
3444
std::vector<ResourceNode*> * _nodes;
3545
ResourceNode * _defaultNode;
46+
47+
// Middleware functions, if any are registered. Will be called in order of the vector.
48+
std::vector<const HTTPSMiddlewareFunction*> _middleware;
3649
};
3750

3851
} /* namespace httpsserver */

0 commit comments

Comments
 (0)