Arduino DLNA Server
Loading...
Searching...
No Matches
HttpServer.h
Go to the documentation of this file.
1#pragma once
2
3#include <stdlib.h>
4
5#include "Client.h"
6#include "HardwareSerial.h"
7#include "HttpChunkWriter.h"
8#include "HttpHeader.h"
10#include "HttpRequestRewrite.h"
11#include "IHttpServer.h"
12#include "Server.h"
13#include "basic/IPAddressAndPort.h" // for toStr
14#include "basic/List.h"
15#include "basic/StrPrint.h"
16
17namespace tiny_dlna {
18
34template <typename ClientType, typename ServerType>
35class HttpServer : public IHttpServer {
36 public:
37 HttpServer(ServerType& server, int bufferSize = 1024) {
38 DlnaLogger.log(DlnaLogLevel::Info, "HttpServer");
39 this->server_ptr = &server;
40 this->buffer.resize(bufferSize);
41 }
42
43 ~HttpServer() override {
44 DlnaLogger.log(DlnaLogLevel::Info, "~HttpServer");
45 handler_collection.clear();
46 request_header.clear(false);
47 reply_header.clear(false);
48 rewrite_collection.clear();
49 }
50
52 IPAddress& localIP() override {
53 static IPAddress address;
54 address = WiFi.localIP();
55 return address;
56 }
57
59 bool begin() override {
60 DlnaLogger.log(DlnaLogLevel::Info, "HttpServer begin");
61 is_active = true;
62 server_ptr->begin();
63 return true;
64 }
65
67 void end() override {
68 DlnaLogger.log(DlnaLogLevel::Info, "HttpServer %s", "stop");
69 is_active = false;
70 }
71
73 void rewrite(const char* from, const char* to) override {
74 DlnaLogger.log(DlnaLogLevel::Info, "Rewriting %s to %s", from, to);
75 HttpRequestRewrite* line = new HttpRequestRewrite(from, to);
76 rewrite_collection.push_back(line);
77 }
78
80 void on(const char* url, TinyMethodID method, web_callback_fn fn,
81 void* ctx[] = nullptr, int ctxCount = 0) override {
82 DlnaLogger.log(DlnaLogLevel::Info, "Serving at %s", url);
84 hl->path = url;
85 hl->fn = fn;
86 hl->method = method;
87 // hl->context = ctx;
88 memmove(hl->context, ctx, ctxCount * sizeof(void*));
89 hl->contextCount = ctxCount;
90 addHandler(hl);
91 }
92
94 void on(const char* url, TinyMethodID method, const char* mime,
95 web_callback_fn fn) override {
96 DlnaLogger.log(DlnaLogLevel::Info, "Serving at %s", url);
98 hl->path = url;
99 hl->fn = fn;
100 hl->method = method;
101 hl->mime = mime;
102 addHandler(hl);
103 }
104
106 void on(const char* url, TinyMethodID method, const char* mime,
107 const char* result) override {
108 DlnaLogger.log(DlnaLogLevel::Info, "Serving at %s", url);
109
110 auto lambda = [](IHttpServer* server_ptr, const char* requestPath,
112 DlnaLogger.log(DlnaLogLevel::Info, "on-strings %s", "lambda");
113 if (hl->contextCount < 2) {
114 DlnaLogger.log(DlnaLogLevel::Error, "The context is not available");
115 return;
116 }
117 const char* mime = (const char*)hl->context[0];
118 const char* msg = (const char*)hl->context[1];
119 server_ptr->reply(mime, msg, 200);
120 };
122 hl->context[0] = (void*)mime;
123 hl->context[1] = (void*)result;
124 hl->path = url;
125 hl->fn = lambda;
126 hl->method = method;
127 addHandler(hl);
128 }
129
131 void on(const char* url, TinyMethodID method, const char* mime,
132 const uint8_t* data, int len) override {
133 DlnaLogger.log(DlnaLogLevel::Info, "Serving at %s", url);
134
135 auto lambda = [](IHttpServer* server_ptr, const char* requestPath,
137 DlnaLogger.log(DlnaLogLevel::Info, "on-strings %s", "lambda");
138 if (hl->contextCount < 3) {
139 DlnaLogger.log(DlnaLogLevel::Error, "The context is not available");
140 return;
141 }
142 const char* mime = static_cast<char*>(hl->context[0]);
143 const uint8_t* data = static_cast<uint8_t*>(hl->context[1]);
144 int* p_len = (int*)hl->context[2];
145 int len = *p_len;
146 DlnaLogger.log(DlnaLogLevel::Debug, "Mime %d - Len: %d", mime, len);
147 server_ptr->reply(mime, data, len, 200);
148 };
150 hl->context[0] = (void*)mime;
151 hl->context[1] = (void*)data;
152 hl->context[2] = new int(len);
153 hl->path = url;
154 hl->fn = lambda;
155 hl->method = method;
156 addHandler(hl);
157 }
158
160 void on(const char* url, TinyMethodID method, Url& redirect) override {
161 DlnaLogger.log(DlnaLogLevel::Info, "Serving at %s", url);
162 auto lambda = [](IHttpServer* server_ptr, const char* requestPath,
164 if (hl->contextCount < 1) {
165 DlnaLogger.log(DlnaLogLevel::Error, "The context is not available");
166 return;
167 }
168 DlnaLogger.log(DlnaLogLevel::Info, "on-redirect %s", "lambda");
170 Url* url = static_cast<Url*>(hl->context[0]);
171 reply_header.setValues(301, "Moved");
172 reply_header.put(LOCATION, url->url());
173 reply_header.put("X-Forwarded-Host", (const char*)hl->context[1]);
174 reply_header.write(server_ptr->client());
175 server_ptr->endClient();
176 };
177
179 const char* lh = localHost();
180 hl->context[0] = new Url(redirect);
181 hl->context[1] = (void*)lh;
182 hl->path = url;
183 hl->fn = lambda;
184 hl->method = method;
185 addHandler(hl);
186 }
187
190 bool onRequest(const char* path) override {
191 DlnaLogger.log(DlnaLogLevel::Info, "Serving at %s", path);
192
193 bool result = false;
194 // check in registered handlers
195 StrView pathStr = StrView(path);
196 pathStr.replace("//", "/"); // TODO investiage why we get //
197 for (auto handler_line_ptr : handler_collection) {
198 DlnaLogger.log(DlnaLogLevel::Debug, "onRequest: %s %s vs: %s %s %s", path,
200 nullstr(handler_line_ptr->path.c_str()),
201 methods[handler_line_ptr->method],
202 nullstr(handler_line_ptr->mime));
203
204 if (pathStr.matches(handler_line_ptr->path.c_str()) &&
205 request_header.method() == handler_line_ptr->method &&
206 matchesMime(nullstr(handler_line_ptr->mime),
208 // call registed handler function
209 DlnaLogger.log(DlnaLogLevel::Debug, "onRequest %s", "->found",
210 nullstr(handler_line_ptr->path.c_str()));
211 handler_line_ptr->fn(this, path, handler_line_ptr);
212 result = true;
213 break;
214 }
215 }
216
217 if (!result) {
218 DlnaLogger.log(DlnaLogLevel::Error, "Request %s not available", path);
219 }
220
221 return result;
222 }
223
225 void replyChunked(const char* contentType, Stream& inputStream,
226 int status = 200, const char* msg = SUCCESS) override {
227 replyChunked(contentType, status, msg);
228 HttpChunkWriter chunk_writer;
229 while (inputStream.available()) {
230 int len = inputStream.readBytes(buffer.data(), buffer.size());
231 chunk_writer.writeChunk(*client_ptr, (const char*)buffer.data(), len);
232 }
233 // final chunk
234 chunk_writer.writeEnd(*client_ptr);
235 endClient();
236 }
237
239 void replyChunked(const char* contentType, int status = 200,
240 const char* msg = SUCCESS) override {
241 DlnaLogger.log(DlnaLogLevel::Info, "reply %s", "replyChunked");
242 reply_header.setValues(status, msg);
244 reply_header.put(CONTENT_TYPE, contentType);
246 reply_header.write(this->client());
247 }
248
250 void reply(const char* contentType, Stream& inputStream, int size,
251 int status = 200, const char* msg = SUCCESS) override {
252 DlnaLogger.log(DlnaLogLevel::Info, "reply %s", "stream");
253 reply_header.setValues(status, msg);
255 reply_header.put(CONTENT_TYPE, contentType);
257 reply_header.write(this->client());
258
259 while (inputStream.available()) {
260 int len = inputStream.readBytes(buffer.data(), buffer.size());
261 int written = client_ptr->write((const uint8_t*)buffer.data(), len);
262 }
263 // inputStream.close();
264 endClient();
265 }
266
268 void reply(const char* contentType, size_t (*callback)(Print& out, void* ref),
269 int status = 200, const char* msg = SUCCESS,
270 void* ref = nullptr) override {
271 DlnaLogger.log(DlnaLogLevel::Info, "reply %s", "via callback");
272 NullPrint nop;
273 size_t size = callback(nop, ref);
274 reply_header.setValues(status, msg);
275 reply_header.put(CONTENT_TYPE, contentType);
278 reply_header.write(this->client());
279 callback(*client_ptr, ref);
280#if DLNA_LOG_XML
281 callback(Serial, ref);
282#endif
283#if DLNA_CHECK_XML_LENGTH
284 StrPrint test;
285 size_t test_len = callback(test, ref);
286 if (strlen(test.c_str()) != size) {
287 DlnaLogger.log(DlnaLogLevel::Error,
288 "HttpServer wrote %d bytes: expected %d / strlen: %d", test_len, size, strlen(test.c_str()));
289 }
290#endif
291
292 endClient();
293 }
294
296 void reply(const char* contentType, const char* str, int status = 200,
297 const char* msg = SUCCESS) override {
298 DlnaLogger.log(DlnaLogLevel::Info, "reply %s", str);
299 int len = strlen(str);
300 reply_header.setValues(status, msg);
302 reply_header.put(CONTENT_TYPE, contentType);
304 reply_header.write(this->client());
305 client_ptr->write((const uint8_t*)str, len);
306 endClient();
307 }
308
309 void reply(const char* contentType, const uint8_t* str, int len,
310 int status = 200, const char* msg = SUCCESS) override {
311 DlnaLogger.log(DlnaLogLevel::Info, "reply %s: %d bytes", contentType, len);
312 reply_header.setValues(status, msg);
314 reply_header.put(CONTENT_TYPE, contentType);
316 reply_header.write(this->client());
317 client_ptr->write((const uint8_t*)str, len);
318 endClient();
319 }
320
322 void replyOK() override { reply(200, SUCCESS); }
323
325 void replyNotFound() override {
326 DlnaLogger.log(DlnaLogLevel::Info, "reply %s", "404");
327 reply(404, "Page Not Found");
328 }
329
330 void replyError(int err, const char* msg = "Internal Server Error") override {
331 DlnaLogger.log(DlnaLogLevel::Info, "reply %s", "error");
332 reply(err, msg);
333 }
334
337
340
342 void endClient() override {
343 DlnaLogger.log(DlnaLogLevel::Info, "HttpServer %s", "endClient");
344 // client_ptr->flush();
345 client_ptr->stop();
346 }
347
349 void crlf() override {
350 client_ptr->print("\r\n");
351 // client_ptr->flush();
352 }
353
355 void addHandler(HttpRequestHandlerLine* handlerLinePtr) override {
356 handler_collection.push_back(handlerLinePtr);
357 }
358
360 bool doLoop() override { return copy(); }
361
363 bool copy() override {
364 bool result = false;
365 if (!is_active) {
366 delay(no_connect_delay);
367 return false;
368 }
369
370 // Accept new client and add to list if connected
371 ClientType client = server_ptr->accept();
372 if (client.connected()) {
373 DlnaLogger.log(DlnaLogLevel::Info, "copy: accepted new client");
376 }
377
378 // Stop when nothing to process
379 if (open_clients.empty()) {
380 // delay(no_connect_delay);
381 return false;
382 }
383
387 delay(no_connect_delay);
388 return false;
389 }
390 }
391
392 client_ptr = &(*current_client_iterator);
393 if (!client_ptr->connected()) {
394 DlnaLogger.log(DlnaLogLevel::Debug, "copy: removing disconnected client");
396 // Do not advance iterator here; erase returns the next valid iterator
397 return false;
398 }
399
401 result = true;
404 !open_clients.empty()) {
406 }
407
408 // cleanup clients
410
411 return result;
412 }
413
415 ClientType& client() override { return *client_ptr; }
416
418 operator bool() override { return is_active; }
419
420 bool isActive() override { return is_active; }
421
423 const char* localHost() override {
424 if (local_host == nullptr) {
425 local_host = toStr(WiFi.localIP());
426 }
427 return local_host;
428 }
429
430 void setNoConnectDelay(int delay) override { no_connect_delay = delay; }
431
433 Str contentStr() override {
434 uint8_t buffer[1024];
435 Str result;
436 while (client_ptr->available()) {
437 int len = client_ptr->readBytes(buffer, sizeof(buffer));
438 result.add((const uint8_t*)buffer, len);
439 }
440 result.add("\0");
441 return result;
442 }
443
445 void setReference(void* reference) override { ref = reference; }
446
448 void* getReference() override { return ref; }
449
450 protected:
451 // data
459 ClientType* client_ptr = nullptr;
460 ServerType* server_ptr = nullptr;
463 const char* local_host = nullptr;
465 void* ref = nullptr;
466
467 /* Remove all closed/disconnected clients from the open_clients list.
468 * This also keeps `current_client_iterator` valid by advancing it to
469 * the next element when the erased element equals the current iterator.
470 */
472 auto it = open_clients.begin();
473 while (it != open_clients.end()) {
474 if ((!(*it).connected())) {
475 auto next = open_clients.erase(it);
476 if (current_client_iterator == it) {
478 }
479 it = next;
480 } else {
481 ++it;
482 }
483 }
484 if (open_clients.empty()) {
486 }
487 }
488
490 void reply(int status, const char* msg) {
491 reply_header.setValues(status, msg);
492 reply_header.write(this->client());
493 endClient();
494 }
495
497 const char* nullstr(const char* in) { return in == nullptr ? "" : in; }
498
499 // process a full request and send the reply
501 DlnaLogger.log(DlnaLogLevel::Info, "processRequest");
502 request_header.read(this->client());
503 // provide reply with empty header
504 reply_header.clear();
505 // determine the path
506 const char* path = request_header.urlPath();
507 path = resolveRewrite(path);
508 bool processed = onRequest(path);
509 if (!processed) {
511 }
512 }
513
516 const char* resolveRewrite(const char* from) {
517 for (auto i = rewrite_collection.begin(); i != rewrite_collection.end();
518 ++i) {
520 if (rewrite->from.matches(from)) {
521 return rewrite->to.c_str();
522 }
523 }
524 return from;
525 }
526
529 bool matchesMime(const char* handler_mime, const char* request_mime) {
530 DlnaLogger.log(DlnaLogLevel::Debug, "matchesMime: %s vs %s", handler_mime,
531 request_mime);
532 if (StrView(handler_mime).isEmpty() || StrView(request_mime).isEmpty()) {
533 return true;
534 }
535 bool result = StrView(request_mime).contains(handler_mime);
536 return result;
537 }
538};
539
541
542} // namespace tiny_dlna
Writes the data chunked to the actual client.
Definition: HttpChunkWriter.h:19
int writeChunk(Client &client, const char *str, int len, const char *str1=nullptr, int len1=0)
Definition: HttpChunkWriter.h:21
void writeEnd(Client &client)
Definition: HttpChunkWriter.h:38
const char * accept()
Definition: HttpHeader.h:221
void read(Client &in)
Definition: HttpHeader.h:233
HttpHeader & clear(bool activeFlag=true)
clears the data - usually we do not delete but we just set the active flag
Definition: HttpHeader.h:86
TinyMethodID method()
Definition: HttpHeader.h:219
const char * urlPath()
Definition: HttpHeader.h:215
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:105
void write(Client &out)
Definition: HttpHeader.h:267
Reading and Writing of Http Replys.
Definition: HttpHeader.h:412
void setValues(int statusCode, const char *msg="", const char *protocol=nullptr)
Definition: HttpHeader.h:415
Used to register and process callbacks.
Definition: HttpRequestHandlerLine.h:15
web_callback_fn fn
Definition: HttpRequestHandlerLine.h:39
Str path
Definition: HttpRequestHandlerLine.h:37
TinyMethodID method
Definition: HttpRequestHandlerLine.h:36
void ** context
Definition: HttpRequestHandlerLine.h:40
const char * mime
Definition: HttpRequestHandlerLine.h:38
int contextCount
Definition: HttpRequestHandlerLine.h:41
Reading and writing of Http Requests.
Definition: HttpHeader.h:358
Object which information about the rewrite rule.
Definition: HttpRequestRewrite.h:11
Header-only HTTP server wrapper that registers callback handlers.
Definition: HttpServer.h:35
ClientType * client_ptr
Definition: HttpServer.h:459
void processRequest()
Definition: HttpServer.h:500
void setReference(void *reference) override
Definesa reference/context object.
Definition: HttpServer.h:445
void reply(const char *contentType, size_t(*callback)(Print &out, void *ref), int status=200, const char *msg=SUCCESS, void *ref=nullptr) override
write reply - using callback that writes to stream
Definition: HttpServer.h:268
void replyNotFound() override
write 404 reply
Definition: HttpServer.h:325
bool copy() override
Call this method from your loop!
Definition: HttpServer.h:363
void replyOK() override
write OK reply with 200 SUCCESS
Definition: HttpServer.h:322
void reply(int status, const char *msg)
Writes the status and message to the reply.
Definition: HttpServer.h:490
HttpReplyHeader reply_header
Definition: HttpServer.h:453
void on(const char *url, TinyMethodID method, web_callback_fn fn, void *ctx[]=nullptr, int ctxCount=0) override
register a generic handler
Definition: HttpServer.h:80
void replyChunked(const char *contentType, int status=200, const char *msg=SUCCESS) override
start of chunked reply: use HttpChunkWriter to provde the data
Definition: HttpServer.h:239
List< ClientType >::Iterator current_client_iterator
Definition: HttpServer.h:457
void on(const char *url, TinyMethodID method, Url &redirect) override
register a redirection
Definition: HttpServer.h:160
bool isActive() override
Check if server is active.
Definition: HttpServer.h:420
void setNoConnectDelay(int delay) override
Set no-connect delay.
Definition: HttpServer.h:430
void removeClosedClients()
Definition: HttpServer.h:471
bool onRequest(const char *path) override
Definition: HttpServer.h:190
bool matchesMime(const char *handler_mime, const char *request_mime)
Definition: HttpServer.h:529
void crlf() override
print a CR LF
Definition: HttpServer.h:349
List< ClientType > open_clients
Definition: HttpServer.h:456
void on(const char *url, TinyMethodID method, const char *mime, const uint8_t *data, int len) override
register a handler which provides the indicated string
Definition: HttpServer.h:131
void on(const char *url, TinyMethodID method, const char *mime, const char *result) override
register a handler which provides the indicated string
Definition: HttpServer.h:106
HttpRequestHeader & requestHeader() override
provides the request header
Definition: HttpServer.h:336
HttpServer(ServerType &server, int bufferSize=1024)
Definition: HttpServer.h:37
int no_connect_delay
Definition: HttpServer.h:464
void rewrite(const char *from, const char *to) override
adds a rewrite rule
Definition: HttpServer.h:73
ServerType * server_ptr
Definition: HttpServer.h:460
bool doLoop() override
Legacy method: same as copy();.
Definition: HttpServer.h:360
ClientType & client() override
Provides the current client.
Definition: HttpServer.h:415
void reply(const char *contentType, Stream &inputStream, int size, int status=200, const char *msg=SUCCESS) override
write reply - copies data from input stream with header size
Definition: HttpServer.h:250
bool is_active
Definition: HttpServer.h:461
~HttpServer() override
Definition: HttpServer.h:43
List< HttpRequestRewrite * > rewrite_collection
Definition: HttpServer.h:455
HttpRequestHeader request_header
Definition: HttpServer.h:452
const char * nullstr(const char *in)
Converts null to an empty string.
Definition: HttpServer.h:497
IPAddress & localIP() override
Provides the local ip address.
Definition: HttpServer.h:52
void end() override
stops the server_ptr
Definition: HttpServer.h:67
void replyChunked(const char *contentType, Stream &inputStream, int status=200, const char *msg=SUCCESS) override
chunked reply with data from an input stream
Definition: HttpServer.h:225
void replyError(int err, const char *msg="Internal Server Error") override
Send error response with status code.
Definition: HttpServer.h:330
const char * local_host
Definition: HttpServer.h:463
void reply(const char *contentType, const uint8_t *str, int len, int status=200, const char *msg=SUCCESS) override
Send binary data response.
Definition: HttpServer.h:309
void addHandler(HttpRequestHandlerLine *handlerLinePtr) override
adds a new handler
Definition: HttpServer.h:355
bool begin() override
Starts the server.
Definition: HttpServer.h:59
List< HttpRequestHandlerLine * > handler_collection
Definition: HttpServer.h:454
const char * resolveRewrite(const char *from)
Definition: HttpServer.h:516
void * ref
Definition: HttpServer.h:465
void * getReference() override
Provides access to a reference/context object.
Definition: HttpServer.h:448
void reply(const char *contentType, const char *str, int status=200, const char *msg=SUCCESS) override
write reply - string with header size
Definition: HttpServer.h:296
HttpReplyHeader & replyHeader() override
provides the reply header
Definition: HttpServer.h:339
Vector< char > buffer
Definition: HttpServer.h:462
void on(const char *url, TinyMethodID method, const char *mime, web_callback_fn fn) override
register a handler with mime
Definition: HttpServer.h:94
Str contentStr() override
converts the client content to a string
Definition: HttpServer.h:433
const char * localHost() override
Determines the local ip address.
Definition: HttpServer.h:423
void endClient() override
closes the connection to the current client_ptr
Definition: HttpServer.h:342
Abstract interface for HTTP server functionality.
Definition: IHttpServer.h:30
List Iterator.
Definition: List.h:29
Double linked list.
Definition: List.h:19
Iterator begin()
Definition: List.h:309
bool empty()
Definition: List.h:331
bool push_back(T &data)
Definition: List.h:128
Iterator end()
Definition: List.h:314
Iterator erase(Iterator it)
Definition: List.h:290
Class with does not do any output: it can be used to determine the length of the output.
Definition: NullPrint.h:12
Print to a dynamic string.
Definition: StrPrint.h:13
const char * c_str()
Definition: StrPrint.h:39
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:18
virtual bool matches(const char *pattern)
Definition: StrView.h:207
virtual bool replace(const char *toReplace, const int replaced)
Replaces the first instance of toReplace with replaced.
Definition: StrView.h:395
virtual bool contains(const char *str)
checks if the string contains a substring
Definition: StrView.h:284
Heap-backed string utility used throughout tiny_dlna.
Definition: Str.h:27
void add(const char *append)
Append C-string (ignored if nullptr)
Definition: Str.h:96
URL parser which breaks a full url string up into its individual parts.
Definition: Url.h:18
const char * url()
Definition: Url.h:39
Lightweight wrapper around std::vector with Arduino-friendly helpers and a pluggable allocator.
Definition: Vector.h:39
#define DLNA_HTTP_REQUEST_TIMEOUT_MS
Define the default http request timeout.
Definition: dlna_config.h:20
Definition: Allocator.h:13
const char * CONTENT_TYPE
Definition: HttpHeader.h:16
const char * CON_KEEP_ALIVE
Definition: HttpHeader.h:20
const char * methods[]
Definition: HttpHeader.h:50
const char * CONTENT_LENGTH
Definition: HttpHeader.h:17
TinyMethodID
Definition: HttpHeader.h:35
const char * TRANSFER_ENCODING
Definition: HttpHeader.h:21
const char * SUCCESS
Definition: HttpHeader.h:25
void(*)(IHttpServer *server, const char *requestPath, HttpRequestHandlerLine *handlerLine) web_callback_fn
Definition: IHttpServer.h:20
const char * CHUNKED
Definition: HttpHeader.h:22
const char * CONNECTION
Definition: HttpHeader.h:18
const char * LOCATION
Definition: HttpHeader.h:32