Arduino DLNA Server
HttpServer.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include <WiFi.h>
4 #include <stdlib.h>
5 
6 #include "Client.h"
7 #include "HardwareSerial.h"
8 #include "HttpChunkWriter.h"
9 #include "HttpHeader.h"
10 #include "HttpRequestHandlerLine.h"
11 #include "HttpRequestRewrite.h"
12 #include "HttpTunnel.h"
13 #include "Server.h"
14 #include "basic/List.h"
15 
16 namespace tiny_dlna {
17 
24 class HttpServer {
25  public:
26  HttpServer(WiFiServer& server, int bufferSize = 1024) {
27  DlnaLogger.log(DlnaInfo, "HttpServer");
28  this->server_ptr = &server;
29  this->buffer.resize(bufferSize);
30  }
31 
33  DlnaLogger.log(DlnaInfo, "~HttpServer");
34  handler_collection.clear();
35  request_header.clear(false);
36  reply_header.clear(false);
37  rewrite_collection.clear();
38  }
39 
41  IPAddress& localIP() {
42  static IPAddress address;
43  address = WiFi.localIP();
44  return address;
45  }
46 
49  bool begin(int port, const char* ssid, const char* password) {
50  if (WiFi.status() != WL_CONNECTED && ssid != nullptr &&
51  password != nullptr) {
52  WiFi.begin(ssid, password);
53  while (WiFi.status() != WL_CONNECTED) {
54  delay(500);
55  Serial.print(".");
56  }
57 
58  Serial.println();
59  Serial.print("Started Server at ");
60  Serial.print(WiFi.localIP());
61  Serial.print(":");
62  Serial.println(port);
63  }
64  return begin(port);
65  }
66 
68  bool begin(int port) {
69  DlnaLogger.log(DlnaInfo, "HttpServer begin at port %d", port);
70  is_active = true;
71  server_ptr->begin(port);
72  return true;
73  }
74 
76  void end() {
77  DlnaLogger.log(DlnaInfo, "HttpServer %s", "stop");
78  is_active = false;
79  }
80 
82  void rewrite(const char* from, const char* to) {
83  DlnaLogger.log(DlnaInfo, "Rewriting %s to %s", from, to);
84  HttpRequestRewrite* line = new HttpRequestRewrite(from, to);
85  rewrite_collection.push_back(line);
86  }
87 
89  void on(const char* url, TinyMethodID method, web_callback_fn fn,
90  void* ctx[] = nullptr, int ctxCount = 0) {
91  DlnaLogger.log(DlnaInfo, "Serving at %s", url);
93  hl->path = url;
94  hl->fn = fn;
95  hl->method = method;
96  // hl->context = ctx;
97  memmove(hl->context, ctx, ctxCount * sizeof(void*));
98  hl->contextCount = ctxCount;
99  addHandler(hl);
100  }
101 
103  void on(const char* url, TinyMethodID method, const char* mime,
104  web_callback_fn fn) {
105  DlnaLogger.log(DlnaInfo, "Serving at %s", url);
107  hl->path = url;
108  hl->fn = fn;
109  hl->method = method;
110  hl->mime = mime;
111  addHandler(hl);
112  }
113 
115  void on(const char* url, TinyMethodID method, const char* mime,
116  const char* result) {
117  DlnaLogger.log(DlnaInfo, "Serving at %s", url);
118 
119  auto lambda = [](HttpServer* server_ptr, const char* requestPath,
121  DlnaLogger.log(DlnaInfo, "on-strings %s", "lambda");
122  if (hl->contextCount < 2) {
123  DlnaLogger.log(DlnaError, "The context is not available");
124  return;
125  }
126  const char* mime = (const char*)hl->context[0];
127  const char* msg = (const char*)hl->context[1];
128  server_ptr->reply(mime, msg, 200);
129  };
131  hl->context[0] = (void*)mime;
132  hl->context[1] = (void*)result;
133  hl->path = url;
134  hl->fn = lambda;
135  hl->method = method;
136  addHandler(hl);
137  }
138 
140  void on(const char* url, TinyMethodID method, const char* mime,
141  const uint8_t* data, int len) {
142  DlnaLogger.log(DlnaInfo, "Serving at %s", url);
143 
144  auto lambda = [](HttpServer* server_ptr, const char* requestPath,
146  DlnaLogger.log(DlnaInfo, "on-strings %s", "lambda");
147  if (hl->contextCount < 3) {
148  DlnaLogger.log(DlnaError, "The context is not available");
149  return;
150  }
151  const char* mime = static_cast<char*>(hl->context[0]);
152  const uint8_t* data = static_cast<uint8_t*>(hl->context[1]);
153  int* p_len = (int*)hl->context[2];
154  int len = *p_len;
155  DlnaLogger.log(DlnaDebug, "Mime %d - Len: %d", mime, len);
156  server_ptr->reply(mime, data, len, 200);
157  };
159  hl->context[0] = (void*)mime;
160  hl->context[1] = (void*)data;
161  hl->context[2] = new int(len);
162  hl->path = url;
163  hl->fn = lambda;
164  hl->method = method;
165  addHandler(hl);
166  }
167 
169  void on(const char* url, TinyMethodID method, Url& redirect) {
170  DlnaLogger.log(DlnaInfo, "Serving at %s", url);
171  auto lambda = [](HttpServer* server_ptr, const char* requestPath,
173  if (hl->contextCount < 1) {
174  DlnaLogger.log(DlnaError, "The context is not available");
175  return;
176  }
177  DlnaLogger.log(DlnaInfo, "on-redirect %s", "lambda");
179  Url* url = static_cast<Url*>(hl->context[0]);
180  reply_header.setValues(301, "Moved");
181  reply_header.put(LOCATION, url->url());
182  reply_header.put("X-Forwarded-Host", (const char*)hl->context[1]);
183  reply_header.write(server_ptr->client());
184  server_ptr->endClient();
185  };
186 
188  const char* lh = localHost();
189  hl->context[0] = new Url(redirect);
190  hl->context[1] = (void*)lh;
191  hl->path = url;
192  hl->fn = lambda;
193  hl->method = method;
194  addHandler(hl);
195  }
196 
198  void on(const char* url, TinyMethodID method, HttpTunnel& tunnel) {
199  DlnaLogger.log(DlnaInfo, "Serving at %s", url);
200 
201  auto lambda = [](HttpServer* server_ptr, const char* requestPath,
203  DlnaLogger.log(DlnaInfo, "on-HttpTunnel %s", "lambda");
204  HttpTunnel* p_tunnel = static_cast<HttpTunnel*>(hl->context[0]);
205  if (p_tunnel == nullptr) {
206  DlnaLogger.log(DlnaError, "p_tunnel is null");
207  server_ptr->replyNotFound();
208  return;
209  }
210  const char* mime = hl->mime;
211  // execute T_GET request
212  Stream* p_in = p_tunnel->get();
213  if (p_in == nullptr) {
214  DlnaLogger.log(DlnaError, "p_in is null");
215  server_ptr->replyNotFound();
216  return;
217  }
218  const char* content_len = p_tunnel->request().reply().get(CONTENT_LENGTH);
219  StrView content_len_str{content_len};
220  // provide result
221  server_ptr->reply(mime, *p_in, content_len_str.toInt());
222  };
223 
225  hl->path = url;
226  hl->method = method;
227  hl->mime = tunnel.mime();
228  hl->context[0] = &tunnel;
229  hl->fn = lambda;
230  addHandler(hl);
231  }
232 
235  bool onRequest(const char* path) {
236  DlnaLogger.log(DlnaInfo, "Serving at %s", path);
237 
238  bool result = false;
239  // check in registered handlers
240  StrView pathStr = StrView(path);
241  for (auto handler_line_ptr : handler_collection) {
242  DlnaLogger.log(DlnaInfo, "onRequest: %s vs: %s %s %s",
243  path,
244  nullstr(handler_line_ptr->path.c_str()),
245  methods[handler_line_ptr->method],
246  nullstr(handler_line_ptr->mime));
247 
248  if (pathStr.matches(handler_line_ptr->path.c_str()) &&
249  request_header.method() == handler_line_ptr->method &&
250  matchesMime(handler_line_ptr->mime, request_header.accept())) {
251  // call registed handler function
252  DlnaLogger.log(DlnaInfo, "onRequest %s", "->found",
253  nullstr(handler_line_ptr->path.c_str()));
254  handler_line_ptr->fn(this, path, handler_line_ptr);
255  result = true;
256  break;
257  }
258  }
259 
260  if (!result) {
261  DlnaLogger.log(DlnaError, "Request %s not available", path);
262  }
263 
264  return result;
265  }
266 
268  void replyChunked(const char* contentType, Stream& inputStream,
269  int status = 200, const char* msg = SUCCESS) {
270  replyChunked(contentType, status, msg);
271  HttpChunkWriter chunk_writer;
272  while (inputStream.available()) {
273  int len = inputStream.readBytes(buffer.data(), buffer.size());
274  chunk_writer.writeChunk(*client_ptr, (const char*)buffer.data(), len);
275  }
276  // final chunk
277  chunk_writer.writeEnd(*client_ptr);
278  endClient();
279  }
280 
282  void replyChunked(const char* contentType, int status = 200,
283  const char* msg = SUCCESS) {
284  DlnaLogger.log(DlnaInfo, "reply %s", "replyChunked");
285  reply_header.setValues(status, msg);
287  reply_header.put(CONTENT_TYPE, contentType);
289  reply_header.write(this->client());
290  }
291 
293  void reply(const char* contentType, Stream& inputStream, int size,
294  int status = 200, const char* msg = SUCCESS) {
295  DlnaLogger.log(DlnaInfo, "reply %s", "stream");
296  reply_header.setValues(status, msg);
298  reply_header.put(CONTENT_TYPE, contentType);
300  reply_header.write(this->client());
301 
302  while (inputStream.available()) {
303  int len = inputStream.readBytes(buffer.data(), buffer.size());
304  int written = client_ptr->write((const uint8_t*)buffer.data(), len);
305  }
306  // inputStream.close();
307  endClient();
308  }
309 
311  void reply(const char* contentType, void (*callback)(Stream& out),
312  int status = 200, const char* msg = SUCCESS) {
313  DlnaLogger.log(DlnaInfo, "reply %s", "callback");
314  reply_header.setValues(status, msg);
315  reply_header.put(CONTENT_TYPE, contentType);
317  reply_header.write(this->client());
318  callback(*client_ptr);
319  // inputStream.close();
320  endClient();
321  }
322 
324  void reply(const char* contentType, void (*callback)(Print& out),
325  int status = 200, const char* msg = SUCCESS) {
326  DlnaLogger.log(DlnaInfo, "reply %s", "callback");
327  reply_header.setValues(status, msg);
328  reply_header.put(CONTENT_TYPE, contentType);
330  reply_header.write(this->client());
331  callback(*client_ptr);
332  // inputStream.close();
333  endClient();
334  }
335 
337  void reply(const char* contentType, const char* str, int status = 200,
338  const char* msg = SUCCESS) {
339  DlnaLogger.log(DlnaInfo, "reply %s", "str");
340  int len = strlen(str);
341  reply_header.setValues(status, msg);
343  reply_header.put(CONTENT_TYPE, contentType);
345  reply_header.write(this->client());
346  client_ptr->write((const uint8_t*)str, len);
347  endClient();
348  }
349 
350  void reply(const char* contentType, const uint8_t* str, int len,
351  int status = 200, const char* msg = SUCCESS) {
352  DlnaLogger.log(DlnaInfo, "reply %s", "str");
353  reply_header.setValues(status, msg);
355  reply_header.put(CONTENT_TYPE, contentType);
357  reply_header.write(this->client());
358  client_ptr->write((const uint8_t*)str, len);
359  endClient();
360  }
361 
363  void replyOK() { reply(200, SUCCESS); }
364 
366  void replyNotFound() {
367  DlnaLogger.log(DlnaInfo, "reply %s", "404");
368  reply(404, "Page Not Found");
369  }
370 
372  void reply(int status, const char* msg) {
373  DlnaLogger.log(DlnaInfo, "reply %d", status);
374  reply_header.setValues(status, msg);
375  reply_header.write(this->client());
376  endClient();
377  }
378 
381 
384 
386  void endClient() {
387  DlnaLogger.log(DlnaInfo, "HttpServer %s", "endClient");
388  client_ptr->flush();
389  client_ptr->stop();
390  }
391 
393  void crlf() {
394  client_ptr->print("\r\n");
395  client_ptr->flush();
396  }
397 
399  void addHandler(HttpRequestHandlerLine* handlerLinePtr) {
400  handler_collection.push_back(handlerLinePtr);
401  }
402 
404  bool doLoop() { return copy(); }
405 
407  bool copy() {
408  bool result = false;
409  // get the actual client_ptr
410  if (is_active) {
411  WiFiClient client = server_ptr->accept();
412  if (client) {
413  DlnaLogger.log(DlnaInfo, "doLoop->hasClient");
414  client_ptr = &client;
415 
416  // process the new client with standard functionality
417  if (client.available() > 5) {
418  processRequest();
419  }
420 
421  // process doLoop of all registed (and opened) extension_collection
422  // processExtensions();
423  result = true;
424  } else {
425  // give other tasks a chance
426  delay(no_connect_delay);
427  // DlnaLogger.log(DlnaDebug, "HttpServer no client available");
428  }
429  } else {
430  DlnaLogger.log(DlnaWarning, "HttpServer inactive");
431  }
432  return result;
433  }
434 
436  Client& client() { return *client_ptr; }
437 
439  operator bool() { return is_active; }
440 
442  const char* localHost() {
443  if (local_host == nullptr) {
444  local_host = toStr(WiFi.localIP());
445  }
446  return local_host;
447  }
448 
449  void setNoConnectDelay(int delay) { no_connect_delay = delay; }
450 
451  protected:
452  // data
456  // List<Extension*> extension_collection;
458  Client* client_ptr;
459  WiFiServer* server_ptr;
460  bool is_active;
462  const char* local_host = nullptr;
464 
466  const char* nullstr(const char* in) { return in == nullptr ? "" : in; }
467 
468  // process a full request and send the reply
469  void processRequest() {
470  DlnaLogger.log(DlnaInfo, "processRequest");
471  request_header.read(this->client());
472  // provide reply with empty header
473  reply_header.clear();
474  // determine the path
475  const char* path = request_header.urlPath();
476  path = resolveRewrite(path);
477  bool processed = onRequest(path);
478  if (!processed) {
479  replyNotFound();
480  }
481  }
482 
485  const char* resolveRewrite(const char* from) {
486  for (auto i = rewrite_collection.begin(); i != rewrite_collection.end();
487  ++i) {
488  HttpRequestRewrite* rewrite = (*i);
489  if (rewrite->from.matches(from)) {
490  return rewrite->to.c_str();
491  }
492  }
493  return from;
494  }
495 
498  bool matchesMime(const char* handler_mime, const char* request_mime) {
499  if (handler_mime == nullptr || request_mime == nullptr) {
500  return true;
501  }
502  bool result = StrView(request_mime).contains(handler_mime);
503  return result;
504  }
505 };
506 
507 } // namespace tiny_dlna
Writes the data chunked to the actual client.
Definition: HttpChunkWriter.h:12
int writeChunk(Client &client, const char *str, int len, const char *str1=nullptr, int len1=0)
Definition: HttpChunkWriter.h:14
void writeEnd(Client &client)
Definition: HttpChunkWriter.h:31
void read(Client &in)
Definition: HttpHeader.h:234
const char * accept()
Definition: HttpHeader.h:222
TinyMethodID method()
Definition: HttpHeader.h:220
HttpHeader & put(const char *key, const char *value)
Definition: HttpHeader.h:102
HttpHeader & clear(bool activeFlag=true)
clears the data - usually we do not delete but we just set the active flag
Definition: HttpHeader.h:83
const char * get(const char *key)
Definition: HttpHeader.h:162
const char * urlPath()
Definition: HttpHeader.h:216
void write(Client &out)
Definition: HttpHeader.h:266
Reading and Writing of Http Replys.
Definition: HttpHeader.h:411
void setValues(int statusCode, const char *msg="", const char *protocol=nullptr)
Definition: HttpHeader.h:414
Used to register and process callbacks.
Definition: HttpRequestHandlerLine.h:19
web_callback_fn fn
Definition: HttpRequestHandlerLine.h:38
Str path
Definition: HttpRequestHandlerLine.h:36
TinyMethodID method
Definition: HttpRequestHandlerLine.h:35
void ** context
Definition: HttpRequestHandlerLine.h:39
const char * mime
Definition: HttpRequestHandlerLine.h:37
int contextCount
Definition: HttpRequestHandlerLine.h:40
Reading and writing of Http Requests.
Definition: HttpHeader.h:357
Object which information about the rewrite rule.
Definition: HttpRequestRewrite.h:11
virtual HttpReplyHeader & reply()
Definition: HttpRequest.h:109
A Simple Header only implementation of Http Server that allows the registration of callback functions...
Definition: HttpServer.h:24
bool is_active
Definition: HttpServer.h:460
Client & client()
Provides the current client.
Definition: HttpServer.h:436
HttpRequestHeader & requestHeader()
provides the request header
Definition: HttpServer.h:380
bool begin(int port, const char *ssid, const char *password)
Definition: HttpServer.h:49
void setNoConnectDelay(int delay)
Definition: HttpServer.h:449
Client * client_ptr
Definition: HttpServer.h:458
bool doLoop()
Legacy method: same as copy();.
Definition: HttpServer.h:404
Vector< char > buffer
Definition: HttpServer.h:461
const char * resolveRewrite(const char *from)
Definition: HttpServer.h:485
void on(const char *url, TinyMethodID method, web_callback_fn fn, void *ctx[]=nullptr, int ctxCount=0)
register a generic handler
Definition: HttpServer.h:89
void replyOK()
write OK reply with 200 SUCCESS
Definition: HttpServer.h:363
List< HttpRequestRewrite * > rewrite_collection
Definition: HttpServer.h:457
bool copy()
Call this method from your loop!
Definition: HttpServer.h:407
void on(const char *url, TinyMethodID method, Url &redirect)
register a redirection
Definition: HttpServer.h:169
void endClient()
closes the connection to the current client_ptr
Definition: HttpServer.h:386
List< HttpRequestHandlerLine * > handler_collection
Definition: HttpServer.h:455
bool onRequest(const char *path)
Definition: HttpServer.h:235
const char * local_host
Definition: HttpServer.h:462
void reply(const char *contentType, void(*callback)(Print &out), int status=200, const char *msg=SUCCESS)
write reply - using callback that writes to stream
Definition: HttpServer.h:324
bool matchesMime(const char *handler_mime, const char *request_mime)
Definition: HttpServer.h:498
void processRequest()
Definition: HttpServer.h:469
void reply(const char *contentType, Stream &inputStream, int size, int status=200, const char *msg=SUCCESS)
write reply - copies data from input stream with header size
Definition: HttpServer.h:293
HttpRequestHeader request_header
Definition: HttpServer.h:453
IPAddress & localIP()
Provides the local ip address.
Definition: HttpServer.h:41
WiFiServer * server_ptr
Definition: HttpServer.h:459
void addHandler(HttpRequestHandlerLine *handlerLinePtr)
adds a new handler
Definition: HttpServer.h:399
void on(const char *url, TinyMethodID method, const char *mime, const char *result)
register a handler which provides the indicated string
Definition: HttpServer.h:115
void on(const char *url, TinyMethodID method, HttpTunnel &tunnel)
register a redirection
Definition: HttpServer.h:198
const char * localHost()
Determines the local ip address.
Definition: HttpServer.h:442
void reply(int status, const char *msg)
Writes the status and message to the reply.
Definition: HttpServer.h:372
HttpReplyHeader reply_header
Definition: HttpServer.h:454
void end()
stops the server_ptr
Definition: HttpServer.h:76
void replyNotFound()
write 404 reply
Definition: HttpServer.h:366
void crlf()
print a CR LF
Definition: HttpServer.h:393
void reply(const char *contentType, const char *str, int status=200, const char *msg=SUCCESS)
write reply - string with header size
Definition: HttpServer.h:337
void rewrite(const char *from, const char *to)
adds a rewrite rule
Definition: HttpServer.h:82
void reply(const char *contentType, void(*callback)(Stream &out), int status=200, const char *msg=SUCCESS)
write reply - using callback that writes to stream
Definition: HttpServer.h:311
int no_connect_delay
Definition: HttpServer.h:463
const char * nullstr(const char *in)
Converts null to an empty string.
Definition: HttpServer.h:466
void replyChunked(const char *contentType, int status=200, const char *msg=SUCCESS)
start of chunked reply: use HttpChunkWriter to provde the data
Definition: HttpServer.h:282
void reply(const char *contentType, const uint8_t *str, int len, int status=200, const char *msg=SUCCESS)
Definition: HttpServer.h:350
void replyChunked(const char *contentType, Stream &inputStream, int status=200, const char *msg=SUCCESS)
chunked reply with data from an input stream
Definition: HttpServer.h:268
HttpServer(WiFiServer &server, int bufferSize=1024)
Definition: HttpServer.h:26
void on(const char *url, TinyMethodID method, const char *mime, const uint8_t *data, int len)
register a handler which provides the indicated string
Definition: HttpServer.h:140
HttpReplyHeader & replyHeader()
provides the reply header
Definition: HttpServer.h:383
~HttpServer()
Definition: HttpServer.h:32
void on(const char *url, TinyMethodID method, const char *mime, web_callback_fn fn)
register a handler with mime
Definition: HttpServer.h:103
bool begin(int port)
Starts the server on the indicated port.
Definition: HttpServer.h:68
Forwards a request to a destination URL and provides a pointer to the result stream.
Definition: HttpTunnel.h:12
Stream * get()
Executes the get request.
Definition: HttpTunnel.h:21
const char * mime()
Definition: HttpTunnel.h:31
HttpRequest & request()
Definition: HttpTunnel.h:29
Double linked list.
Definition: List.h:19
void log(DlnaLogLevel current_level, const char *fmt...)
Print log message.
Definition: Logger.h:40
A simple wrapper to provide string functions on char*. If the underlying char* is a const we do not a...
Definition: StrView.h:25
virtual bool matches(const char *pattern)
Definition: StrView.h:200
virtual bool contains(const char *str)
checks if the string contains a substring
Definition: StrView.h:277
URL parser which breaks a full url string up into its individual parts.
Definition: Url.h:18
const char * url()
Definition: Url.h:46
bool resize(int newSize, T value)
Definition: Vector.h:247
int size()
Definition: Vector.h:163
T * data()
Definition: Vector.h:294
Definition: Allocator.h:6
const char * CONTENT_TYPE
Definition: HttpHeader.h:16
const char * CON_KEEP_ALIVE
Definition: HttpHeader.h:20
void(* web_callback_fn)(HttpServer *server, const char *requestPath, HttpRequestHandlerLine *handlerLine)
Definition: HttpRequestHandlerLine.h:12
@ DlnaDebug
Definition: Logger.h:16
@ DlnaInfo
Definition: Logger.h:16
@ DlnaWarning
Definition: Logger.h:16
@ DlnaError
Definition: Logger.h:16
const char * methods[]
Definition: HttpHeader.h:47
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
LoggerClass DlnaLogger
Definition: Logger.cpp:5
const char * CHUNKED
Definition: HttpHeader.h:22
const char * CONNECTION
Definition: HttpHeader.h:18
const char * LOCATION
Definition: HttpHeader.h:32