A simple web server that can be bound to either the EthernetInterface or the WiflyInterface.
Dependents: Smart-WiFly-WebServer WattEye X10Svr SSDP_Server
SW_HTTPServer.h@48:078adbe279ac, 2017-04-08 (annotated)
- Committer:
- WiredHome
- Date:
- Sat Apr 08 19:39:51 2017 +0000
- Revision:
- 48:078adbe279ac
- Parent:
- 46:eaa86d48be6f
- Child:
- 49:cd391662f254
Minor change - an additional optional parameter on the constructor that defines the blocking time (in msec) when Poll() is called. The default was way too long for most needs.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
WiredHome | 1:54353af0d20a | 1 | |
WiredHome | 1:54353af0d20a | 2 | #ifndef SW_HTTPSERVER_H |
WiredHome | 1:54353af0d20a | 3 | #define SW_HTTPSERVER_H |
WiredHome | 1:54353af0d20a | 4 | #include "mbed.h" |
WiredHome | 1:54353af0d20a | 5 | #include "TCPSocketServer.h" |
WiredHome | 1:54353af0d20a | 6 | #include "TCPSocketConnection.h" |
WiredHome | 1:54353af0d20a | 7 | |
WiredHome | 1:54353af0d20a | 8 | #ifdef MODSERIAL_H |
WiredHome | 1:54353af0d20a | 9 | #define PC MODSERIAL |
WiredHome | 1:54353af0d20a | 10 | #else |
WiredHome | 36:1bb5fa6b109c | 11 | #define PC RawSerial |
WiredHome | 1:54353af0d20a | 12 | #endif |
WiredHome | 1:54353af0d20a | 13 | |
WiredHome | 9:2ea342765c9d | 14 | /// This is the default buffer size used to send files. You might size |
WiredHome | 9:2ea342765c9d | 15 | /// this to be equal or less than the payload size of 1460 bytes. |
WiredHome | 44:71f09e4255f4 | 16 | /// For WiFly: see User Manual 3.6.1. |
WiredHome | 44:71f09e4255f4 | 17 | #define FILESEND_BUF_SIZE 500 |
WiredHome | 3:17928786bdb5 | 18 | |
WiredHome | 3:17928786bdb5 | 19 | |
WiredHome | 12:109bf1558300 | 20 | /// MAX_HEADER_SIZE is the default size to contain the largest header. |
WiredHome | 9:2ea342765c9d | 21 | /// This is the size of the URL and query string, and also all the |
WiredHome | 3:17928786bdb5 | 22 | /// other header information about the client. This can be |
WiredHome | 12:109bf1558300 | 23 | /// a couple of K, larger if you have big forms as it includes the |
WiredHome | 3:17928786bdb5 | 24 | /// form data that is submitted. |
WiredHome | 3:17928786bdb5 | 25 | #define MAX_HEADER_SIZE 1000 |
WiredHome | 3:17928786bdb5 | 26 | |
WiredHome | 9:2ea342765c9d | 27 | |
WiredHome | 39:0427544a5c08 | 28 | /// HTTPServer is a simple web server leveraging a network interface. |
WiredHome | 12:109bf1558300 | 29 | /// |
WiredHome | 3:17928786bdb5 | 30 | /// While simple, it is a capable, web server. The basic mode |
WiredHome | 3:17928786bdb5 | 31 | /// of operation is for it to serve static web pages from an available |
WiredHome | 3:17928786bdb5 | 32 | /// file system. |
WiredHome | 12:109bf1558300 | 33 | /// |
WiredHome | 3:17928786bdb5 | 34 | /// The default page is index.htm (compile time defined) |
WiredHome | 3:17928786bdb5 | 35 | /// standard support to serve a number of standard file types; |
WiredHome | 3:17928786bdb5 | 36 | /// gif, jpg, jpeg, ico, png, zip, gz, tar, txt, pdf, htm, html |
WiredHome | 3:17928786bdb5 | 37 | /// (this list is also compile time defined) |
WiredHome | 3:17928786bdb5 | 38 | /// |
WiredHome | 3:17928786bdb5 | 39 | /// It can also serve dynamically generated pages, and therefore |
WiredHome | 3:17928786bdb5 | 40 | /// respond to form submission. Through the dynamic interface it is |
WiredHome | 3:17928786bdb5 | 41 | /// then quite easy to interact with the hardware, reading the inputs |
WiredHome | 3:17928786bdb5 | 42 | /// or signaling outputs. |
WiredHome | 3:17928786bdb5 | 43 | /// |
WiredHome | 3:17928786bdb5 | 44 | /// @code |
WiredHome | 39:0427544a5c08 | 45 | /// HTTPServer svr(HTTP_SERVER_PORT, "/local", 15, 30, 10, &pc); |
WiredHome | 3:17928786bdb5 | 46 | /// svr.RegisterHandler("/dyn1", SimpleDynamicPage); |
WiredHome | 44:71f09e4255f4 | 47 | /// while (true) { |
WiredHome | 44:71f09e4255f4 | 48 | /// svr.Poll(); // this is non blocking process, with variable execution |
WiredHome | 44:71f09e4255f4 | 49 | /// } |
WiredHome | 3:17928786bdb5 | 50 | /// @endcode |
WiredHome | 3:17928786bdb5 | 51 | /// |
WiredHome | 44:71f09e4255f4 | 52 | /// This web server used nweb as a starting point, but expanded well beyond that. |
WiredHome | 46:eaa86d48be6f | 53 | /// http://nmon.sourceforge.net/pmwiki.php?n=Site.Nweb&cm_mc_uid=21286415979014862330090&cm_mc_sid_50200000=1486233009 |
WiredHome | 0:729320f63c5c | 54 | /// |
WiredHome | 3:17928786bdb5 | 55 | /// Given: scheme://server:port/path?query_string#fragment_id |
WiredHome | 0:729320f63c5c | 56 | /// @li scheme is "http" |
WiredHome | 3:17928786bdb5 | 57 | /// @li server is whatever IP the server has |
WiredHome | 0:729320f63c5c | 58 | /// @li port is the registered port |
WiredHome | 0:729320f63c5c | 59 | /// @li /path is the reference to the file (actual or logical) on the server |
WiredHome | 0:729320f63c5c | 60 | /// @li query_string is any combination of name=value pairs |
WiredHome | 0:729320f63c5c | 61 | /// @li fragment_id is a reference to an anchor on the page |
WiredHome | 0:729320f63c5c | 62 | /// |
WiredHome | 3:17928786bdb5 | 63 | /// Features: |
WiredHome | 3:17928786bdb5 | 64 | /// @li Serves static pages from a file system. Many normal filetypes are |
WiredHome | 3:17928786bdb5 | 65 | /// supported. |
WiredHome | 3:17928786bdb5 | 66 | /// @li Compile time configurable for the "default" file, typically index.htm. |
WiredHome | 3:17928786bdb5 | 67 | /// @li Provides a registration interface for dynamically generated pages that |
WiredHome | 3:17928786bdb5 | 68 | /// can then interact with other hardware. |
WiredHome | 3:17928786bdb5 | 69 | /// @li Revised to be Non-blocking, however the execution time is variable |
WiredHome | 44:71f09e4255f4 | 70 | /// depending on the actions being performed and can span hundreds of msec |
WiredHome | 44:71f09e4255f4 | 71 | /// when using a WiFly module as the network interface. |
WiredHome | 44:71f09e4255f4 | 72 | /// @li Support for filenames aliases, which permit using long filenames with |
WiredHome | 44:71f09e4255f4 | 73 | /// the LocalFileSystem (which has an 8.3 constraint). |
WiredHome | 3:17928786bdb5 | 74 | /// |
WiredHome | 3:17928786bdb5 | 75 | /// Limitations: |
WiredHome | 39:0427544a5c08 | 76 | /// @li When used with Wifly network interface it supports only a single |
WiredHome | 39:0427544a5c08 | 77 | /// connection at a time. A web page with served objects (img src=...) |
WiredHome | 39:0427544a5c08 | 78 | /// is rarely served properly. It might trace to forcing the connection to |
WiredHome | 39:0427544a5c08 | 79 | /// close, but not yet sure. Explore "Set Uart Rx Data Buffer" in |
WiredHome | 39:0427544a5c08 | 80 | /// WiFly manual 2.3.65. This is a limitation of the Wifly module. |
WiredHome | 39:0427544a5c08 | 81 | /// No solution is forthcoming, so a crude workaround is to use javascript |
WiredHome | 39:0427544a5c08 | 82 | /// to load the images after the page loads. |
WiredHome | 3:17928786bdb5 | 83 | /// @li Rapid requests for page objects (e.g. embedded images) are lost. Still |
WiredHome | 3:17928786bdb5 | 84 | /// working to understand this issue. |
WiredHome | 3:17928786bdb5 | 85 | /// |
WiredHome | 43:3fc773c2986e | 86 | /// Improvements (TODO): |
WiredHome | 43:3fc773c2986e | 87 | /// @li Add hook for a custom 404 page, which might show a page, or might redirect. |
WiredHome | 43:3fc773c2986e | 88 | /// @li Combine the API for accessing parameters for the GET and POST, as they |
WiredHome | 43:3fc773c2986e | 89 | /// are normally [always?] mutually exclusive. |
WiredHome | 43:3fc773c2986e | 90 | /// @li Keep only the public functions public, make the rest private/protected. |
WiredHome | 43:3fc773c2986e | 91 | /// Add another interface for dynamic pages to give them access to private/protected |
WiredHome | 43:3fc773c2986e | 92 | /// functions. |
WiredHome | 43:3fc773c2986e | 93 | /// @li Try to reduce the size of the header buffer, since that is quite large. |
WiredHome | 43:3fc773c2986e | 94 | /// @li General clean-up, refactoring, de-duplication. |
WiredHome | 43:3fc773c2986e | 95 | /// @li Leverage mbed os, so one receive handler can spawn up to N response handlers. |
WiredHome | 43:3fc773c2986e | 96 | /// This may improve overall performance. |
WiredHome | 43:3fc773c2986e | 97 | /// @li Since some transactions can't be easily buffered (the stream may be too |
WiredHome | 43:3fc773c2986e | 98 | /// large, consider how to shuttle parts of the transaction back and forth |
WiredHome | 43:3fc773c2986e | 99 | /// between the user-code and the server. |
WiredHome | 18:6199558632c0 | 100 | /// |
WiredHome | 43:3fc773c2986e | 101 | /// History (TO-Done): |
WiredHome | 48:078adbe279ac | 102 | /// @li 20170408 Added another optional parameter to the constructor to control the |
WiredHome | 48:078adbe279ac | 103 | /// blocking time when Poll() is called. |
WiredHome | 43:3fc773c2986e | 104 | /// @li 20140913 Removed the relationship to the Wifly module, which caused an API change |
WiredHome | 43:3fc773c2986e | 105 | /// in the constructor by elimination of the first parameter. |
WiredHome | 43:3fc773c2986e | 106 | /// @li 20140913 parses the header similar to the query string, and then makes |
WiredHome | 43:3fc773c2986e | 107 | /// those parameters accessible. |
WiredHome | 43:3fc773c2986e | 108 | /// @li 20140913 Added basic password capability to dynamic web pages. |
WiredHome | 43:3fc773c2986e | 109 | /// @li 20140913 move part of the POST method handler to the registered handler, so |
WiredHome | 0:729320f63c5c | 110 | /// it can decide if it should allocate the needed memory. |
WiredHome | 43:3fc773c2986e | 111 | /// @li 20140201 hunted down several lengthy operations - the speed of the file system |
WiredHome | 43:3fc773c2986e | 112 | /// and the "close" operation which requires <delay 0.25s>$$$<delay>close\r. |
WiredHome | 2:a29c32190037 | 113 | /// @li 20130530 Initial version |
WiredHome | 2:a29c32190037 | 114 | /// @li 20130601 Renamed ip_process to Poll |
WiredHome | 3:17928786bdb5 | 115 | /// @li 20130617 Cleaned up some of the documentation changes |
WiredHome | 3:17928786bdb5 | 116 | /// @li 20130623 Make it non-blocking. "Poll" takes a variable amount |
WiredHome | 3:17928786bdb5 | 117 | /// of time, based on whether it is idle, or how much it |
WiredHome | 3:17928786bdb5 | 118 | /// has to do. |
WiredHome | 18:6199558632c0 | 119 | /// @li 20130911 Lots of incremental changes along this way, this update |
WiredHome | 18:6199558632c0 | 120 | /// refreshes the documentation. |
WiredHome | 0:729320f63c5c | 121 | /// |
WiredHome | 46:eaa86d48be6f | 122 | /// @note Copyright © 2014-2017 by Smartware Computing, all rights reserved. |
WiredHome | 0:729320f63c5c | 123 | /// Individuals may use this application for evaluation or non-commercial |
WiredHome | 0:729320f63c5c | 124 | /// purposes. Within this restriction, changes may be made to this application |
WiredHome | 0:729320f63c5c | 125 | /// as long as this copyright notice is retained. The user shall make |
WiredHome | 0:729320f63c5c | 126 | /// clear that their work is a derived work, and not the original. |
WiredHome | 0:729320f63c5c | 127 | /// Users of this application and sources accept this application "as is" and |
WiredHome | 0:729320f63c5c | 128 | /// shall hold harmless Smartware Computing, for any undesired results while |
WiredHome | 0:729320f63c5c | 129 | /// using this application - whether real or imagined. |
WiredHome | 0:729320f63c5c | 130 | /// |
WiredHome | 0:729320f63c5c | 131 | /// @author David Smart, Smartware Computing |
WiredHome | 0:729320f63c5c | 132 | /// |
WiredHome | 0:729320f63c5c | 133 | class HTTPServer |
WiredHome | 0:729320f63c5c | 134 | { |
WiredHome | 0:729320f63c5c | 135 | public: |
WiredHome | 0:729320f63c5c | 136 | /** |
WiredHome | 3:17928786bdb5 | 137 | * name-value pairs for parameters |
WiredHome | 0:729320f63c5c | 138 | */ |
WiredHome | 3:17928786bdb5 | 139 | typedef struct NAMEVALUE { |
WiredHome | 0:729320f63c5c | 140 | char * name; |
WiredHome | 0:729320f63c5c | 141 | char * value; |
WiredHome | 0:729320f63c5c | 142 | } namevalue; |
WiredHome | 12:109bf1558300 | 143 | |
WiredHome | 2:a29c32190037 | 144 | /** |
WiredHome | 3:17928786bdb5 | 145 | * Indicates the purpose of the Handler callback |
WiredHome | 3:17928786bdb5 | 146 | * |
WiredHome | 12:109bf1558300 | 147 | * Application code in a dynamic page uses this to determine the state |
WiredHome | 3:17928786bdb5 | 148 | * and therefore the needed operation to be performed. |
WiredHome | 3:17928786bdb5 | 149 | * |
WiredHome | 3:17928786bdb5 | 150 | * @code |
WiredHome | 13:8975d7928678 | 151 | * bool SimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, |
WiredHome | 13:8975d7928678 | 152 | * const char * path, const HTTPServer::namevalue *queryParams, |
WiredHome | 13:8975d7928678 | 153 | * int queryParamCount) { |
WiredHome | 3:17928786bdb5 | 154 | * char buf[100]; |
WiredHome | 3:17928786bdb5 | 155 | * bool ret = false; |
WiredHome | 12:109bf1558300 | 156 | * |
WiredHome | 3:17928786bdb5 | 157 | * switch (type) { |
WiredHome | 3:17928786bdb5 | 158 | * case HTTPServer::SEND_PAGE: |
WiredHome | 3:17928786bdb5 | 159 | * svr->header(200, "OK", "Content-Type: text/html\r\n"); |
WiredHome | 3:17928786bdb5 | 160 | * svr->send("<html><head><title>Dynamic Page</title></head>\r\n"); |
WiredHome | 3:17928786bdb5 | 161 | * svr->send("<body>\r\n"); |
WiredHome | 3:17928786bdb5 | 162 | * svr->send("This page was generated dynamically. Create your own name=value pairs on the URL " |
WiredHome | 3:17928786bdb5 | 163 | * "which uses the GET method.<br/>\r\n"); |
WiredHome | 13:8975d7928678 | 164 | * sprintf(buf, "%d parameters passed to {%s}:<br/>\r\n", queryParamCount, path); |
WiredHome | 3:17928786bdb5 | 165 | * svr->send(buf); |
WiredHome | 13:8975d7928678 | 166 | * for (int i=0; i<queryParamCount; i++) { |
WiredHome | 13:8975d7928678 | 167 | * sprintf(buf, "%d: %s = %s<br/>\r\n", i, queryParams[i].name, queryParams[i].value); |
WiredHome | 3:17928786bdb5 | 168 | * svr->send(buf); |
WiredHome | 3:17928786bdb5 | 169 | * } |
WiredHome | 3:17928786bdb5 | 170 | * svr->send("<br/><a href='/'>back to main</a></body></html>\r\n"); |
WiredHome | 3:17928786bdb5 | 171 | * ret = true; |
WiredHome | 3:17928786bdb5 | 172 | * break; |
WiredHome | 3:17928786bdb5 | 173 | * case HTTPServer::CONTENT_LENGTH_REQUEST: |
WiredHome | 3:17928786bdb5 | 174 | * ret = true; |
WiredHome | 3:17928786bdb5 | 175 | * break; |
WiredHome | 3:17928786bdb5 | 176 | * case HTTPServer::DATA_TRANSFER: |
WiredHome | 3:17928786bdb5 | 177 | * ret = true; |
WiredHome | 3:17928786bdb5 | 178 | * break; |
WiredHome | 3:17928786bdb5 | 179 | * default: |
WiredHome | 3:17928786bdb5 | 180 | * ret = false; |
WiredHome | 3:17928786bdb5 | 181 | * break; |
WiredHome | 3:17928786bdb5 | 182 | * } |
WiredHome | 3:17928786bdb5 | 183 | * return ret; |
WiredHome | 3:17928786bdb5 | 184 | * } |
WiredHome | 3:17928786bdb5 | 185 | * @endcode |
WiredHome | 2:a29c32190037 | 186 | */ |
WiredHome | 3:17928786bdb5 | 187 | typedef enum CALLBACKTYPE { |
WiredHome | 14:19c5f6151319 | 188 | CONTENT_LENGTH_REQUEST, ///< ask the client if they wish to accept the data, typically from a POST event |
WiredHome | 27:90a1f5a5392f | 189 | DATA_TRANSFER, ///< used when submitting a file via a form |
WiredHome | 33:ef165a67ab22 | 190 | DATA_TRANSFER_END, ///< used when all data has been given to the client (may be used to close the file) |
WiredHome | 3:17928786bdb5 | 191 | SEND_PAGE, ///< the activated method should now send the page |
WiredHome | 2:a29c32190037 | 192 | } CallBackType; |
WiredHome | 0:729320f63c5c | 193 | |
WiredHome | 28:f93ef41b78e1 | 194 | typedef enum CALLBACKRESULTS { |
WiredHome | 28:f93ef41b78e1 | 195 | ACCEPT_ERROR, ///< client not accepting the request. |
WiredHome | 28:f93ef41b78e1 | 196 | ACCEPT_COMPLETE, ///< client accepted the request, the work is done. |
WiredHome | 28:f93ef41b78e1 | 197 | ACCEPT_CONTINUE, ///< client accepted the request, additional transactions to complete. |
WiredHome | 28:f93ef41b78e1 | 198 | } CallBackResults; |
WiredHome | 28:f93ef41b78e1 | 199 | |
WiredHome | 44:71f09e4255f4 | 200 | /// This is the set of header codes that are generally recognized. Note that many are |
WiredHome | 44:71f09e4255f4 | 201 | /// not supported, so may not be listed here. |
WiredHome | 44:71f09e4255f4 | 202 | typedef enum HEADERCODES { |
WiredHome | 44:71f09e4255f4 | 203 | // 1xx: Information |
WiredHome | 44:71f09e4255f4 | 204 | //Continue = 100, ///< Server has received the headers, client should send the body. |
WiredHome | 44:71f09e4255f4 | 205 | //Switching = 101, ///< Request to switch protocols - not supported. |
WiredHome | 44:71f09e4255f4 | 206 | //Checkpoint = 102, ///< resume aborted PUT/POST - not supported. |
WiredHome | 44:71f09e4255f4 | 207 | |
WiredHome | 44:71f09e4255f4 | 208 | // 2xx: Successful |
WiredHome | 44:71f09e4255f4 | 209 | OK = 200, ///< request is OK. |
WiredHome | 44:71f09e4255f4 | 210 | //Created = 201, ///< The request has been fulfilled - not supported. |
WiredHome | 44:71f09e4255f4 | 211 | //Accepted = 202, ///< The request has been accepted for processing - not supported. |
WiredHome | 44:71f09e4255f4 | 212 | //Non_Auth = 203, ///< The request has been successfully processed - not supported. |
WiredHome | 44:71f09e4255f4 | 213 | //No_Content = 204, ///< The request has been successfully processed, but is not returning any content |
WiredHome | 44:71f09e4255f4 | 214 | //Reset_Content = 205, ///< The request has been successfully processed, but is not returning any content, |
WiredHome | 44:71f09e4255f4 | 215 | // /// and requires that the requester reset the document view |
WiredHome | 44:71f09e4255f4 | 216 | //Partial_Content = 206, /// The server is delivering only part of the resource due to a range header sent by the client |
WiredHome | 44:71f09e4255f4 | 217 | |
WiredHome | 44:71f09e4255f4 | 218 | // 3xx: Redirection |
WiredHome | 44:71f09e4255f4 | 219 | //Multiple_Choices = 300, /// A link list. The user can select a link and go to that location. Maximum five addresses. |
WiredHome | 44:71f09e4255f4 | 220 | Moved_Permanently = 301, ///< The requested page has moved to a new URL. |
WiredHome | 44:71f09e4255f4 | 221 | //Found = 302, /// The requested page has moved temporarily to a new URL |
WiredHome | 44:71f09e4255f4 | 222 | //See_Other = 303, /// The requested page can be found under a different URL |
WiredHome | 44:71f09e4255f4 | 223 | //Not_Modified = 304, /// Indicates the requested page has not been modified since last requested |
WiredHome | 44:71f09e4255f4 | 224 | // 306 Switch Proxy No longer used |
WiredHome | 44:71f09e4255f4 | 225 | // 307 Temporary Redirect The requested page has moved temporarily to a new URL |
WiredHome | 44:71f09e4255f4 | 226 | // 308 Resume Incomplete Used in the resumable requests proposal to resume aborted PUT or POST requests |
WiredHome | 44:71f09e4255f4 | 227 | |
WiredHome | 44:71f09e4255f4 | 228 | // 4xx: Client Error |
WiredHome | 44:71f09e4255f4 | 229 | Bad_Request = 400, ///< The request cannot be fulfilled due to bad syntax |
WiredHome | 44:71f09e4255f4 | 230 | Unauthorized = 401, ///< The request was a legal request, but the server is refusing to respond to it. For use when authentication is possible but has failed or not yet been provided |
WiredHome | 44:71f09e4255f4 | 231 | // 402 Payment Required Reserved for future use |
WiredHome | 44:71f09e4255f4 | 232 | // 403 Forbidden The request was a legal request, but the server is refusing to respond to it |
WiredHome | 44:71f09e4255f4 | 233 | Not_Found = 404, ///< The requested page could not be found but may be available again in the future |
WiredHome | 44:71f09e4255f4 | 234 | // Method Not Allowed 405 A request was made of a page using a request method not supported by that page |
WiredHome | 44:71f09e4255f4 | 235 | // Not Acceptable 406 The server can only generate a response that is not accepted by the client |
WiredHome | 44:71f09e4255f4 | 236 | // Proxy Auth Reqd 407 The client must first authenticate itself with the proxy |
WiredHome | 44:71f09e4255f4 | 237 | Request_Timeout = 408, ///< The server timed out waiting for the request |
WiredHome | 44:71f09e4255f4 | 238 | // 409 Conflict The request could not be completed because of a conflict in the request |
WiredHome | 44:71f09e4255f4 | 239 | // 410 Gone The requested page is no longer available |
WiredHome | 44:71f09e4255f4 | 240 | // 411 Length Required The "Content-Length" is not defined. The server will not accept the request without it |
WiredHome | 44:71f09e4255f4 | 241 | // 412 Precondition Failed The precondition given in the request evaluated to false by the server |
WiredHome | 44:71f09e4255f4 | 242 | // 413 Request Entity Too Large The server will not accept the request, because the request entity is too large |
WiredHome | 44:71f09e4255f4 | 243 | // 414 Request-URI Too Long The server will not accept the request, because the URL is too long. Occurs when you convert a POST request to a GET request with a long query information |
WiredHome | 44:71f09e4255f4 | 244 | Unsupported_Media_Type = 415, ///< The server will not accept the request, because the media type is not supported |
WiredHome | 44:71f09e4255f4 | 245 | // 416 Requested Range Not Satisfiable The client has asked for a portion of the file, but the server cannot supply that portion |
WiredHome | 44:71f09e4255f4 | 246 | // 417 Expectation Failed The server cannot meet the requirements of the Expect request-header field |
WiredHome | 44:71f09e4255f4 | 247 | |
WiredHome | 44:71f09e4255f4 | 248 | // 5xx: Server Error |
WiredHome | 44:71f09e4255f4 | 249 | // Message: Description: |
WiredHome | 44:71f09e4255f4 | 250 | Server_Error = 500, ///< A generic error message, given when no more specific message is suitable |
WiredHome | 44:71f09e4255f4 | 251 | // 501 Not Implemented The server either does not recognize the request method, or it lacks the ability to fulfill the request |
WiredHome | 44:71f09e4255f4 | 252 | // 502 Bad Gateway The server was acting as a gateway or proxy and received an invalid response from the upstream server |
WiredHome | 44:71f09e4255f4 | 253 | // 503 Service Unavailable The server is currently unavailable (overloaded or down) |
WiredHome | 44:71f09e4255f4 | 254 | // 504 Gateway Timeout The server was acting as a gateway or proxy and did not receive a timely response from the upstream server |
WiredHome | 44:71f09e4255f4 | 255 | // 505 HTTP Version Not Supported The server does not support the HTTP protocol version used in the request |
WiredHome | 44:71f09e4255f4 | 256 | // 511 Network Authentication Required The client needs to authenticate to gain network access |
WiredHome | 44:71f09e4255f4 | 257 | } HeaderCodes; |
WiredHome | 44:71f09e4255f4 | 258 | |
WiredHome | 12:109bf1558300 | 259 | /** |
WiredHome | 3:17928786bdb5 | 260 | * This is the prototype for custom handlers that are activated via a callback |
WiredHome | 0:729320f63c5c | 261 | * |
WiredHome | 37:0cb2774e2410 | 262 | * This callback gets overloaded for a few purposes, which can be identified by the |
WiredHome | 37:0cb2774e2410 | 263 | * \see CallBackType parameter. |
WiredHome | 37:0cb2774e2410 | 264 | * |
WiredHome | 27:90a1f5a5392f | 265 | * @li CONTENT_LENGTH_REQUEST - the server is asking the callback if it wants to receive the message, |
WiredHome | 27:90a1f5a5392f | 266 | * which may require significant memory. If the request is accepted, true should be returned. |
WiredHome | 27:90a1f5a5392f | 267 | * If the request is denied, false should be returned. |
WiredHome | 27:90a1f5a5392f | 268 | * @li DATA_TRANSFER - the server is handing off a large body of data, which was accepted based |
WiredHome | 27:90a1f5a5392f | 269 | * on the CONTENT_LENGTH_REQUEST callback. The data is now available for processing. |
WiredHome | 27:90a1f5a5392f | 270 | * The callback should return true to continue the processing. |
WiredHome | 12:109bf1558300 | 271 | * @li SEND_PAGE - the callback should now send the html page, using as many svr->send() as needed. |
WiredHome | 29:00116fc9da74 | 272 | * When the callback returns, it should always indicate true that it has sent the page. |
WiredHome | 27:90a1f5a5392f | 273 | * |
WiredHome | 27:90a1f5a5392f | 274 | * @note The queryParams pointer purpose depends on the callback type. |
WiredHome | 33:ef165a67ab22 | 275 | * For CONTENT_LENGTH_REQUEST, the pointer points to the name=value pairs from the |
WiredHome | 27:90a1f5a5392f | 276 | * header. |
WiredHome | 33:ef165a67ab22 | 277 | * For DATA_TRANSFER, the pointer points to the start of the actual data. |
WiredHome | 29:00116fc9da74 | 278 | * For SEND_PAGE, ... <to be determined> |
WiredHome | 12:109bf1558300 | 279 | * |
WiredHome | 48:078adbe279ac | 280 | * @param[in] svr is a handle to this class, so the callback has access to member functions |
WiredHome | 48:078adbe279ac | 281 | * @param[in] type is the callback type @see CallBackType |
WiredHome | 48:078adbe279ac | 282 | * @param[in] path is the pointer to a large block of information being transferred. This pointer |
WiredHome | 37:0cb2774e2410 | 283 | * references a dynamically managed resource, so any information of value must be |
WiredHome | 37:0cb2774e2410 | 284 | * extracted from here, and not referenced into this memory space. |
WiredHome | 48:078adbe279ac | 285 | * @param[in] queryParams is a pointer based on the callback type. |
WiredHome | 48:078adbe279ac | 286 | * @param[in] count is the number of items - for type = CONTENT_LENGTH_REQUEST this is the number of |
WiredHome | 33:ef165a67ab22 | 287 | * name=value pars in the queryParams parameter, and for the DATA_TRANSFER this is the |
WiredHome | 33:ef165a67ab22 | 288 | * number of bytes being passed in the path parameters. |
WiredHome | 33:ef165a67ab22 | 289 | * @return one of the @see CallBackResults signals indicating error or successes |
WiredHome | 0:729320f63c5c | 290 | */ |
WiredHome | 37:0cb2774e2410 | 291 | typedef CallBackResults (* Handler)(HTTPServer * svr, CallBackType type, const char *path, |
WiredHome | 37:0cb2774e2410 | 292 | const namevalue *queryParams, int queryParamCount); |
WiredHome | 12:109bf1558300 | 293 | |
WiredHome | 0:729320f63c5c | 294 | /** |
WiredHome | 0:729320f63c5c | 295 | * Create the HTTPServer object. |
WiredHome | 12:109bf1558300 | 296 | * |
WiredHome | 48:078adbe279ac | 297 | * @param[in] port is the optional parameter for the port number to use, default is 80. |
WiredHome | 48:078adbe279ac | 298 | * @param[in] webroot is a file system path to the root folder for the web space. If any trailing '/' |
WiredHome | 27:90a1f5a5392f | 299 | * is included (e.g. "/web/path/") it will be removed (to "/web/path"). |
WiredHome | 48:078adbe279ac | 300 | * @param[in] maxheaderParams defines the maximum number of parameters to extract from a header |
WiredHome | 37:0cb2774e2410 | 301 | * (Host: 192..\r\nConnection: keep-alive\r\n...) |
WiredHome | 48:078adbe279ac | 302 | * @param[in] maxqueryParams defines the maximum number of query parameters to a dynamic function |
WiredHome | 37:0cb2774e2410 | 303 | * (and the memory to support them). |
WiredHome | 48:078adbe279ac | 304 | * @param[in] maxdynamicpages defines the maximum number of dynamic pages that can be registered. |
WiredHome | 48:078adbe279ac | 305 | * @param[in] pc is the serial port for debug information (I should transform this to a log interface) |
WiredHome | 48:078adbe279ac | 306 | * @param[in] allocforheader is the memory allocation to support the largest expected header from a client |
WiredHome | 48:078adbe279ac | 307 | * @param[in] allocforfile is the memory allocation to support sending a file to the client. This is |
WiredHome | 37:0cb2774e2410 | 308 | * typically sized to fit an ethernet frame. |
WiredHome | 48:078adbe279ac | 309 | * @param[in] blockingtime is the time that the Poll process will pause, waiting for input. |
WiredHome | 0:729320f63c5c | 310 | */ |
WiredHome | 39:0427544a5c08 | 311 | HTTPServer(int port = 80, const char * webroot = "/", int maxheaderParams = 15, |
WiredHome | 37:0cb2774e2410 | 312 | int maxqueryParams = 30, int maxdynamicpages = 10, |
WiredHome | 48:078adbe279ac | 313 | PC * pc = NULL, int _allocforheader = MAX_HEADER_SIZE, int _allocforfile = FILESEND_BUF_SIZE, |
WiredHome | 48:078adbe279ac | 314 | int blockingtime = 10); |
WiredHome | 12:109bf1558300 | 315 | |
WiredHome | 0:729320f63c5c | 316 | /** |
WiredHome | 3:17928786bdb5 | 317 | * Destructor, which can clean up memory. |
WiredHome | 0:729320f63c5c | 318 | */ |
WiredHome | 0:729320f63c5c | 319 | ~HTTPServer(); |
WiredHome | 12:109bf1558300 | 320 | |
WiredHome | 0:729320f63c5c | 321 | /** |
WiredHome | 27:90a1f5a5392f | 322 | * Get the path to the webroot, for applications that need to |
WiredHome | 27:90a1f5a5392f | 323 | * reference the file system relative to that point. |
WiredHome | 24:062431453abb | 324 | * |
WiredHome | 27:90a1f5a5392f | 325 | * @note The returned value may not be exactly as set at instantiation |
WiredHome | 27:90a1f5a5392f | 326 | * as trailing '/' were removed (unless the web root == "/"). |
WiredHome | 27:90a1f5a5392f | 327 | * e.g. "/msc/web/" becomes "/msc/web" |
WiredHome | 27:90a1f5a5392f | 328 | * |
WiredHome | 27:90a1f5a5392f | 329 | * @returns pointer to the webroot string. |
WiredHome | 24:062431453abb | 330 | */ |
WiredHome | 24:062431453abb | 331 | const char * GetWebRoot() { |
WiredHome | 27:90a1f5a5392f | 332 | return (const char *)webroot; |
WiredHome | 27:90a1f5a5392f | 333 | }; |
WiredHome | 24:062431453abb | 334 | |
WiredHome | 44:71f09e4255f4 | 335 | |
WiredHome | 44:71f09e4255f4 | 336 | /** |
WiredHome | 44:71f09e4255f4 | 337 | * Search a haystack of name:value pairs for the needle. |
WiredHome | 44:71f09e4255f4 | 338 | * |
WiredHome | 44:71f09e4255f4 | 339 | * This is a case-sensitive search. If it cannot find an alias, |
WiredHome | 44:71f09e4255f4 | 340 | * it returns the needle. |
WiredHome | 44:71f09e4255f4 | 341 | * |
WiredHome | 44:71f09e4255f4 | 342 | * This is used in the web server for conveniently mapping long filenames |
WiredHome | 44:71f09e4255f4 | 343 | * to short filenames if you are using the LocalFileSystem which only |
WiredHome | 44:71f09e4255f4 | 344 | * supports short filenames. |
WiredHome | 44:71f09e4255f4 | 345 | * |
WiredHome | 44:71f09e4255f4 | 346 | * @code |
WiredHome | 44:71f09e4255f4 | 347 | * char * ptr; |
WiredHome | 44:71f09e4255f4 | 348 | * namevalue list[] = { |
WiredHome | 44:71f09e4255f4 | 349 | * {"/local/longfilename.ext", "/local/short.ext"}, |
WiredHome | 44:71f09e4255f4 | 350 | * {"/local/verylongname2.ext", "/local/verylo~1.ext"}, |
WiredHome | 44:71f09e4255f4 | 351 | * {NULL, NULL} |
WiredHome | 44:71f09e4255f4 | 352 | * }; |
WiredHome | 44:71f09e4255f4 | 353 | * |
WiredHome | 44:71f09e4255f4 | 354 | * ptr = FindAlias(list, "/local/verylongname2.ext"); |
WiredHome | 44:71f09e4255f4 | 355 | * // ptr now references "/local/verylo~1.ext" |
WiredHome | 44:71f09e4255f4 | 356 | * @endcode |
WiredHome | 44:71f09e4255f4 | 357 | * |
WiredHome | 44:71f09e4255f4 | 358 | * @param[in] haystack is the NULL terminated namevalue pair list. |
WiredHome | 44:71f09e4255f4 | 359 | * @param[in] needle is a pointer to the name to find. |
WiredHome | 44:71f09e4255f4 | 360 | * @returns the alias (value) if the needle is found, otherwise |
WiredHome | 44:71f09e4255f4 | 361 | * returns the needle. |
WiredHome | 44:71f09e4255f4 | 362 | */ |
WiredHome | 44:71f09e4255f4 | 363 | const char * FindAlias(const namevalue * haystack, const char * needle); |
WiredHome | 44:71f09e4255f4 | 364 | |
WiredHome | 44:71f09e4255f4 | 365 | |
WiredHome | 44:71f09e4255f4 | 366 | /** |
WiredHome | 44:71f09e4255f4 | 367 | * Register a list of filename aliases to webroot. |
WiredHome | 44:71f09e4255f4 | 368 | * |
WiredHome | 44:71f09e4255f4 | 369 | * Some uses of this could be on the LocalFileSystem, which only supports |
WiredHome | 44:71f09e4255f4 | 370 | * an 8.3 naming convention. This API lets you register a list of |
WiredHome | 44:71f09e4255f4 | 371 | * name:value pairs, where the name is the long filename and the |
WiredHome | 44:71f09e4255f4 | 372 | * value is the corresponding short filename. |
WiredHome | 44:71f09e4255f4 | 373 | * |
WiredHome | 44:71f09e4255f4 | 374 | * @param[in] namevaluelist is a pointer to a NULL terminated long |
WiredHome | 44:71f09e4255f4 | 375 | * to short filename list. |
WiredHome | 44:71f09e4255f4 | 376 | */ |
WiredHome | 44:71f09e4255f4 | 377 | void RegisterFilenameAliasList(const namevalue * namevaluelist); |
WiredHome | 44:71f09e4255f4 | 378 | |
WiredHome | 24:062431453abb | 379 | /** |
WiredHome | 3:17928786bdb5 | 380 | * The process to call whenever there is free time, as this basically does |
WiredHome | 0:729320f63c5c | 381 | * all the work to monitor for connections and handle replies. |
WiredHome | 2:a29c32190037 | 382 | * |
WiredHome | 44:71f09e4255f4 | 383 | * Activate this API as often as you can, typically from the loop in main. |
WiredHome | 44:71f09e4255f4 | 384 | * |
WiredHome | 48:078adbe279ac | 385 | * @note This API will pause execution for the "blocking time" value configured |
WiredHome | 48:078adbe279ac | 386 | * in the constructor. |
WiredHome | 0:729320f63c5c | 387 | */ |
WiredHome | 2:a29c32190037 | 388 | void Poll(); |
WiredHome | 12:109bf1558300 | 389 | |
WiredHome | 0:729320f63c5c | 390 | /** |
WiredHome | 12:109bf1558300 | 391 | * Send typical header data, and some optional data back to the client. |
WiredHome | 3:17928786bdb5 | 392 | * |
WiredHome | 3:17928786bdb5 | 393 | * This forms and sends the typical header back to the client. It may also send |
WiredHome | 3:17928786bdb5 | 394 | * optional data (which must end with "\r\n"). It then sends the second newline |
WiredHome | 3:17928786bdb5 | 395 | * sequence that signals the end of the header. |
WiredHome | 0:729320f63c5c | 396 | * |
WiredHome | 44:71f09e4255f4 | 397 | * @code |
WiredHome | 44:71f09e4255f4 | 398 | * svr->header(200, "OK", "Content-Type: text/html\r\n"); |
WiredHome | 44:71f09e4255f4 | 399 | * ... |
WiredHome | 44:71f09e4255f4 | 400 | * svr->header(200, "OK", "Content-Type: text/html\r\n", "SpecialParam: 13\r\n"); |
WiredHome | 44:71f09e4255f4 | 401 | * svr->header("NextParam: 14\r\n"); |
WiredHome | 44:71f09e4255f4 | 402 | * svr->header(""); // sends the final \r\n to end the header |
WiredHome | 44:71f09e4255f4 | 403 | * @endcode |
WiredHome | 44:71f09e4255f4 | 404 | * |
WiredHome | 44:71f09e4255f4 | 405 | * @param[in] code is the optional return code; 200 = OK, if not provided then 404 = Not found is returned |
WiredHome | 44:71f09e4255f4 | 406 | * @param[in] code_text is the text to align with the code (e.g. 404, "Not Found") |
WiredHome | 44:71f09e4255f4 | 407 | * @param[in] content_type is a pointer to "Content-Type: text/html\r\n" (for example). The string |
WiredHome | 44:71f09e4255f4 | 408 | * must have a \r\n termination. If this parameter is NULL, no alternate is substituted. |
WiredHome | 44:71f09e4255f4 | 409 | * @param[in] optional_text is a pointer to any other text that is part of the header, which must |
WiredHome | 44:71f09e4255f4 | 410 | * have \r\n termination. It is permissible to string several header items together, |
WiredHome | 44:71f09e4255f4 | 411 | * each with the \r\n termination (which includes \r\n termination at the end). |
WiredHome | 44:71f09e4255f4 | 412 | * If this parameter is NULL, a standard template response will be sent consisting |
WiredHome | 44:71f09e4255f4 | 413 | * of "Max-age: 0\r\nServer: Smart_Server v0.2\r\nConnection: close\r\n\r\n" |
WiredHome | 44:71f09e4255f4 | 414 | * If this parameter is not NULL, the user must call headerend(), or ensure that |
WiredHome | 44:71f09e4255f4 | 415 | * the termination double (\r\n\r\n) ends the optional_text. |
WiredHome | 0:729320f63c5c | 416 | */ |
WiredHome | 44:71f09e4255f4 | 417 | void header(HeaderCodes code = Not_Found, const char * code_text = "Not Found", const char * content_type = NULL, |
WiredHome | 37:0cb2774e2410 | 418 | const char * optional_text = NULL); |
WiredHome | 0:729320f63c5c | 419 | |
WiredHome | 0:729320f63c5c | 420 | /** |
WiredHome | 44:71f09e4255f4 | 421 | * Send a fragment of a header to the client. |
WiredHome | 44:71f09e4255f4 | 422 | * |
WiredHome | 44:71f09e4255f4 | 423 | * This API lets you send header information a fragment at a time. A fragment can be a single |
WiredHome | 44:71f09e4255f4 | 424 | * line of text, or it can be several strung together with \r\n. |
WiredHome | 44:71f09e4255f4 | 425 | * |
WiredHome | 44:71f09e4255f4 | 426 | * @param[in] partialheader is a pointer to \r\n terminated text. If partial is a pointer to NULL, |
WiredHome | 44:71f09e4255f4 | 427 | * then a terminating \r\n is sent, which ends the header record. |
WiredHome | 44:71f09e4255f4 | 428 | */ |
WiredHome | 44:71f09e4255f4 | 429 | void header(const char * partialheader); |
WiredHome | 44:71f09e4255f4 | 430 | |
WiredHome | 44:71f09e4255f4 | 431 | /** |
WiredHome | 0:729320f63c5c | 432 | * Send text to the client |
WiredHome | 0:729320f63c5c | 433 | * |
WiredHome | 3:17928786bdb5 | 434 | * This sends the specified text to the client. If the number of bytes is not set, |
WiredHome | 3:17928786bdb5 | 435 | * then it calculates the number of bytes as a string. For binary transfers, the |
WiredHome | 3:17928786bdb5 | 436 | * number of bytes to send is required for proper operation. |
WiredHome | 3:17928786bdb5 | 437 | * |
WiredHome | 44:71f09e4255f4 | 438 | * @param[in] msg is the text string to send |
WiredHome | 44:71f09e4255f4 | 439 | * @param[in] bytes is the number of bytes to send. If not set, then strlen is calculated. |
WiredHome | 0:729320f63c5c | 440 | */ |
WiredHome | 0:729320f63c5c | 441 | void send(const char * msg, int bytes = -1); |
WiredHome | 12:109bf1558300 | 442 | |
WiredHome | 0:729320f63c5c | 443 | /** |
WiredHome | 44:71f09e4255f4 | 444 | * Get the size of the file |
WiredHome | 44:71f09e4255f4 | 445 | * |
WiredHome | 44:71f09e4255f4 | 446 | * This returns the size of the file. If the specified file is not found, it returns zero. |
WiredHome | 44:71f09e4255f4 | 447 | * |
WiredHome | 44:71f09e4255f4 | 448 | * @param[in] filename is the file to read. |
WiredHome | 44:71f09e4255f4 | 449 | * @returns the size of the file, or zero if the file was not found/opened. |
WiredHome | 44:71f09e4255f4 | 450 | */ |
WiredHome | 44:71f09e4255f4 | 451 | uint32_t FileSize(const char * filename); |
WiredHome | 44:71f09e4255f4 | 452 | |
WiredHome | 44:71f09e4255f4 | 453 | /** |
WiredHome | 3:17928786bdb5 | 454 | * Send a referenced file to the client, including the header |
WiredHome | 3:17928786bdb5 | 455 | * |
WiredHome | 3:17928786bdb5 | 456 | * This sends a file from the filesystem to the client. It must be of a supported type |
WiredHome | 3:17928786bdb5 | 457 | * in order to properly create the header. |
WiredHome | 0:729320f63c5c | 458 | * |
WiredHome | 44:71f09e4255f4 | 459 | * @param[in] filename is the fully qualified path and filename |
WiredHome | 44:71f09e4255f4 | 460 | * @param[in] filetype is the header information (e.g. "Content-Type: application/pdf") |
WiredHome | 0:729320f63c5c | 461 | * @return true if it thinks it sent ok, false otherwise. |
WiredHome | 0:729320f63c5c | 462 | */ |
WiredHome | 0:729320f63c5c | 463 | bool SendFile(const char * filename, const char * filetype); |
WiredHome | 12:109bf1558300 | 464 | |
WiredHome | 12:109bf1558300 | 465 | /** |
WiredHome | 0:729320f63c5c | 466 | * register a handler for a specific URL. |
WiredHome | 0:729320f63c5c | 467 | * |
WiredHome | 3:17928786bdb5 | 468 | * This api lets you register a dynamic handler in the web server. This is |
WiredHome | 3:17928786bdb5 | 469 | * most useful for interactive web pages, rather than simply serving static |
WiredHome | 3:17928786bdb5 | 470 | * pages. |
WiredHome | 3:17928786bdb5 | 471 | * |
WiredHome | 3:17928786bdb5 | 472 | * @code |
WiredHome | 12:109bf1558300 | 473 | * |
WiredHome | 3:17928786bdb5 | 474 | * ... |
WiredHome | 44:71f09e4255f4 | 475 | * svr.RegisterHandler("/dyn1", SimpleDynamicPage); |
WiredHome | 3:17928786bdb5 | 476 | * ... |
WiredHome | 3:17928786bdb5 | 477 | * |
WiredHome | 46:eaa86d48be6f | 478 | * HTTPServer::CallBackResults SimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, const char * path, |
WiredHome | 37:0cb2774e2410 | 479 | * const HTTPServer::namevalue *queryParams, int queryParamCount) { |
WiredHome | 3:17928786bdb5 | 480 | * char buf[100]; |
WiredHome | 46:eaa86d48be6f | 481 | * HTTPServer::CallBackResults ret = HTTPServer::ACCEPT_ERROR; |
WiredHome | 12:109bf1558300 | 482 | * |
WiredHome | 3:17928786bdb5 | 483 | * switch (type) { |
WiredHome | 3:17928786bdb5 | 484 | * case HTTPServer::SEND_PAGE: |
WiredHome | 46:eaa86d48be6f | 485 | * svr->header(HTTPServer::OK, "OK", "Content-Type: text/html\r\n"); |
WiredHome | 3:17928786bdb5 | 486 | * svr->send("<html><head><title>Dynamic Page</title></head>\r\n"); |
WiredHome | 3:17928786bdb5 | 487 | * svr->send("<body>\r\n"); |
WiredHome | 3:17928786bdb5 | 488 | * svr->send("This page was generated dynamically. Create your own name=value pairs on the URL " |
WiredHome | 3:17928786bdb5 | 489 | * "which uses the GET method.<br/>\r\n"); |
WiredHome | 13:8975d7928678 | 490 | * sprintf(buf, "%d parameters passed to {%s}:<br/>\r\n", queryParamCount, path); |
WiredHome | 3:17928786bdb5 | 491 | * svr->send(buf); |
WiredHome | 13:8975d7928678 | 492 | * for (int i=0; i<queryParamCount; i++) { |
WiredHome | 13:8975d7928678 | 493 | * sprintf(buf, "%d: %s = %s<br/>\r\n", i, queryParams[i].name, queryParams[i].value); |
WiredHome | 3:17928786bdb5 | 494 | * svr->send(buf); |
WiredHome | 3:17928786bdb5 | 495 | * } |
WiredHome | 3:17928786bdb5 | 496 | * svr->send("Stats:<br/>\r\n"); |
WiredHome | 3:17928786bdb5 | 497 | * sprintf(buf,"Free memory space: %d<br/>\r\n", Free()); |
WiredHome | 3:17928786bdb5 | 498 | * svr->send(buf); |
WiredHome | 3:17928786bdb5 | 499 | * sprintf(buf,"Max Header size: %d<br/>\r\n", svr->GetMaxHeaderSize()); |
WiredHome | 3:17928786bdb5 | 500 | * svr->send(buf); |
WiredHome | 3:17928786bdb5 | 501 | * svr->send("<br/><a href='/'>back to main</a></body></html>\r\n"); |
WiredHome | 46:eaa86d48be6f | 502 | * ret = HTTPServer::ACCEPT_COMPLETE; |
WiredHome | 3:17928786bdb5 | 503 | * break; |
WiredHome | 3:17928786bdb5 | 504 | * case HTTPServer::CONTENT_LENGTH_REQUEST: |
WiredHome | 46:eaa86d48be6f | 505 | * ret = HTTPServer::ACCEPT_COMPLETE; |
WiredHome | 3:17928786bdb5 | 506 | * break; |
WiredHome | 3:17928786bdb5 | 507 | * case HTTPServer::DATA_TRANSFER: |
WiredHome | 46:eaa86d48be6f | 508 | * ret = HTTPServer::ACCEPT_COMPLETE; |
WiredHome | 3:17928786bdb5 | 509 | * break; |
WiredHome | 3:17928786bdb5 | 510 | * default: |
WiredHome | 46:eaa86d48be6f | 511 | * ret = HTTPServer::ACCEPT_ERROR; |
WiredHome | 3:17928786bdb5 | 512 | * break; |
WiredHome | 3:17928786bdb5 | 513 | * } |
WiredHome | 46:eaa86d48be6f | 514 | * return ret; |
WiredHome | 3:17928786bdb5 | 515 | * } |
WiredHome | 3:17928786bdb5 | 516 | * @endcode |
WiredHome | 3:17928786bdb5 | 517 | * |
WiredHome | 44:71f09e4255f4 | 518 | * @param[in] path to register |
WiredHome | 44:71f09e4255f4 | 519 | * @param[in] callback of type Handler |
WiredHome | 0:729320f63c5c | 520 | * @return true if successfully registered |
WiredHome | 0:729320f63c5c | 521 | */ |
WiredHome | 0:729320f63c5c | 522 | bool RegisterHandler(const char * path, Handler callback); |
WiredHome | 12:109bf1558300 | 523 | |
WiredHome | 0:729320f63c5c | 524 | /** |
WiredHome | 16:6ebacf2946d8 | 525 | * determine if the named file is a supported type (htm, html, jpg, etc) |
WiredHome | 0:729320f63c5c | 526 | * |
WiredHome | 3:17928786bdb5 | 527 | * if you pass in a filename, it will attempt to extract the extension |
WiredHome | 3:17928786bdb5 | 528 | * and compare that to the list of supported file types. If it finds a |
WiredHome | 3:17928786bdb5 | 529 | * match, then it will return a pointer to the content-type string. |
WiredHome | 3:17928786bdb5 | 530 | * |
WiredHome | 3:17928786bdb5 | 531 | * @code |
WiredHome | 3:17928786bdb5 | 532 | * fType = GetSupportedType("mypix.jpg"); |
WiredHome | 3:17928786bdb5 | 533 | * if (fType) { |
WiredHome | 3:17928786bdb5 | 534 | * ... |
WiredHome | 3:17928786bdb5 | 535 | * @endcode |
WiredHome | 12:109bf1558300 | 536 | * |
WiredHome | 44:71f09e4255f4 | 537 | * @param[in] filename is the filename to test, based on the extension |
WiredHome | 0:729320f63c5c | 538 | * @return pointer to a Content-Type string if supported, or NULL if not. |
WiredHome | 0:729320f63c5c | 539 | */ |
WiredHome | 0:729320f63c5c | 540 | const char * GetSupportedType(const char * filename); |
WiredHome | 0:729320f63c5c | 541 | |
WiredHome | 0:729320f63c5c | 542 | /** |
WiredHome | 39:0427544a5c08 | 543 | * search the available query parameters for 'name' and if found, return the 'value' |
WiredHome | 0:729320f63c5c | 544 | * |
WiredHome | 12:109bf1558300 | 545 | * After the querystring is parsed, the server maintains an array of |
WiredHome | 3:17928786bdb5 | 546 | * name=value pairs. This Get function will search for the passed in name |
WiredHome | 3:17928786bdb5 | 547 | * and provide access to the value. |
WiredHome | 3:17928786bdb5 | 548 | * |
WiredHome | 3:17928786bdb5 | 549 | * @code |
WiredHome | 3:17928786bdb5 | 550 | * BusOut leds(LED1,LED2,LED3,LED4); |
WiredHome | 3:17928786bdb5 | 551 | * ... |
WiredHome | 3:17928786bdb5 | 552 | * leds = atoi(svr->GetParameter("leds")); |
WiredHome | 3:17928786bdb5 | 553 | * @endcode |
WiredHome | 3:17928786bdb5 | 554 | * |
WiredHome | 44:71f09e4255f4 | 555 | * @param[in] name is the name to search for |
WiredHome | 0:729320f63c5c | 556 | * @return pointer to the value, or NULL |
WiredHome | 0:729320f63c5c | 557 | */ |
WiredHome | 0:729320f63c5c | 558 | const char * GetParameter(const char * name); |
WiredHome | 0:729320f63c5c | 559 | |
WiredHome | 0:729320f63c5c | 560 | /** |
WiredHome | 39:0427544a5c08 | 561 | * get a pointer to a name-value pair based on the index. |
WiredHome | 39:0427544a5c08 | 562 | * |
WiredHome | 44:71f09e4255f4 | 563 | * @param[in] index is the item being referenced |
WiredHome | 39:0427544a5c08 | 564 | * @return pointer to the namevalue, or NULL |
WiredHome | 39:0427544a5c08 | 565 | */ |
WiredHome | 44:71f09e4255f4 | 566 | const namevalue * GetParameter(int index); |
WiredHome | 39:0427544a5c08 | 567 | |
WiredHome | 39:0427544a5c08 | 568 | /** |
WiredHome | 39:0427544a5c08 | 569 | * Get the count of query parameters from the active transaction. |
WiredHome | 39:0427544a5c08 | 570 | * |
WiredHome | 39:0427544a5c08 | 571 | * @returns count of parameters. |
WiredHome | 39:0427544a5c08 | 572 | */ |
WiredHome | 39:0427544a5c08 | 573 | int GetParameterCount(void) |
WiredHome | 39:0427544a5c08 | 574 | { |
WiredHome | 39:0427544a5c08 | 575 | return queryParamCount; |
WiredHome | 39:0427544a5c08 | 576 | }; |
WiredHome | 39:0427544a5c08 | 577 | |
WiredHome | 39:0427544a5c08 | 578 | /** |
WiredHome | 39:0427544a5c08 | 579 | * search the available post parameters for 'name' and if found, return the 'value' |
WiredHome | 39:0427544a5c08 | 580 | * |
WiredHome | 39:0427544a5c08 | 581 | * After the post parameter string is parsed, the server maintains an array of |
WiredHome | 39:0427544a5c08 | 582 | * name=value pairs. This Get function will search for the passed in name |
WiredHome | 39:0427544a5c08 | 583 | * and provide access to the value. |
WiredHome | 39:0427544a5c08 | 584 | * |
WiredHome | 39:0427544a5c08 | 585 | * @code |
WiredHome | 39:0427544a5c08 | 586 | * BusOut leds(LED1,LED2,LED3,LED4); |
WiredHome | 39:0427544a5c08 | 587 | * ... |
WiredHome | 39:0427544a5c08 | 588 | * leds = atoi(svr->GetPostParameter("leds")); |
WiredHome | 39:0427544a5c08 | 589 | * @endcode |
WiredHome | 39:0427544a5c08 | 590 | * |
WiredHome | 44:71f09e4255f4 | 591 | * @param[in] name is the name to search for |
WiredHome | 39:0427544a5c08 | 592 | * @return pointer to the value, or NULL |
WiredHome | 39:0427544a5c08 | 593 | */ |
WiredHome | 39:0427544a5c08 | 594 | const char * GetPostParameter(const char * name); |
WiredHome | 39:0427544a5c08 | 595 | |
WiredHome | 39:0427544a5c08 | 596 | /** |
WiredHome | 39:0427544a5c08 | 597 | * get a pointer to a post parameter name-value pair based on the index. |
WiredHome | 39:0427544a5c08 | 598 | * |
WiredHome | 44:71f09e4255f4 | 599 | * @param[in] index is the item being referenced |
WiredHome | 39:0427544a5c08 | 600 | * @return pointer to the namevalue, or NULL |
WiredHome | 39:0427544a5c08 | 601 | */ |
WiredHome | 39:0427544a5c08 | 602 | namevalue * GetPostParameter(int index); |
WiredHome | 39:0427544a5c08 | 603 | |
WiredHome | 39:0427544a5c08 | 604 | /** |
WiredHome | 39:0427544a5c08 | 605 | * Get the count of post parameters from the active transaction. |
WiredHome | 39:0427544a5c08 | 606 | * |
WiredHome | 39:0427544a5c08 | 607 | * @returns count of parameters. |
WiredHome | 39:0427544a5c08 | 608 | */ |
WiredHome | 39:0427544a5c08 | 609 | int GetPostParameterCount(void) |
WiredHome | 39:0427544a5c08 | 610 | { |
WiredHome | 39:0427544a5c08 | 611 | return postParamCount; |
WiredHome | 39:0427544a5c08 | 612 | }; |
WiredHome | 39:0427544a5c08 | 613 | |
WiredHome | 39:0427544a5c08 | 614 | /** |
WiredHome | 12:109bf1558300 | 615 | * Parse the text string into name=value parameters. |
WiredHome | 3:17928786bdb5 | 616 | * |
WiredHome | 12:109bf1558300 | 617 | * This will directly modify the referenced string. If there is a |
WiredHome | 3:17928786bdb5 | 618 | * #fragment_id on the end of the string, it will be removed. |
WiredHome | 0:729320f63c5c | 619 | * |
WiredHome | 44:71f09e4255f4 | 620 | * @param[in] qP is a pointer to a namevalue set |
WiredHome | 44:71f09e4255f4 | 621 | * @param[in] qpCount is a pointer to a counter of what is in the set |
WiredHome | 44:71f09e4255f4 | 622 | * @param[in] maxP is the maximum number of parameters for which space has been allocated. |
WiredHome | 44:71f09e4255f4 | 623 | * @param[in,out] pName is a pointer to the string. |
WiredHome | 37:0cb2774e2410 | 624 | * @returns The total number of items that have been parsed, |
WiredHome | 37:0cb2774e2410 | 625 | * which can include a count from a url query string. |
WiredHome | 0:729320f63c5c | 626 | */ |
WiredHome | 39:0427544a5c08 | 627 | int ParseParameters(namevalue * qP, int * qpCount, int maxP, char * pName); |
WiredHome | 12:109bf1558300 | 628 | |
WiredHome | 0:729320f63c5c | 629 | /** |
WiredHome | 16:6ebacf2946d8 | 630 | * Unescape string converts a coded string "in place" into a normal string. |
WiredHome | 3:17928786bdb5 | 631 | * |
WiredHome | 3:17928786bdb5 | 632 | * A query string will have a number of characters replaced for communication |
WiredHome | 3:17928786bdb5 | 633 | * which includes spaces, quotes, question marks and more. Most of them |
WiredHome | 12:109bf1558300 | 634 | * will be replaced with a %xx format, where xx is the hex code for the |
WiredHome | 3:17928786bdb5 | 635 | * character. Since the string will only get shorter when this happens |
WiredHome | 3:17928786bdb5 | 636 | * the operation is performed in place. |
WiredHome | 3:17928786bdb5 | 637 | * |
WiredHome | 0:729320f63c5c | 638 | * this "This%20is%20a%20question%3F%20and%20an%20answer." |
WiredHome | 12:109bf1558300 | 639 | * |
WiredHome | 0:729320f63c5c | 640 | * becomes "This is a question? and an answer." |
WiredHome | 3:17928786bdb5 | 641 | * |
WiredHome | 0:729320f63c5c | 642 | * @note '+' is another form of space, so is converted to a space before the %xx |
WiredHome | 0:729320f63c5c | 643 | * |
WiredHome | 44:71f09e4255f4 | 644 | * @param[in,out] encoded string to be converted |
WiredHome | 0:729320f63c5c | 645 | */ |
WiredHome | 0:729320f63c5c | 646 | void UnescapeString(char * encoded); |
WiredHome | 12:109bf1558300 | 647 | |
WiredHome | 12:109bf1558300 | 648 | /** |
WiredHome | 16:6ebacf2946d8 | 649 | * This is used to force a connection to close. |
WiredHome | 3:17928786bdb5 | 650 | * |
WiredHome | 44:71f09e4255f4 | 651 | * @note When WiFly is the interface, this switches the module into |
WiredHome | 44:71f09e4255f4 | 652 | * command mode, performs the close, and then switches it back to data mode. |
WiredHome | 44:71f09e4255f4 | 653 | * So, this is a time-expensive command. |
WiredHome | 7:99ad7a67f05e | 654 | * |
WiredHome | 7:99ad7a67f05e | 655 | * @returns true if successful |
WiredHome | 0:729320f63c5c | 656 | */ |
WiredHome | 7:99ad7a67f05e | 657 | bool close_connection(); |
WiredHome | 12:109bf1558300 | 658 | |
WiredHome | 3:17928786bdb5 | 659 | /** |
WiredHome | 16:6ebacf2946d8 | 660 | * Diagnostic to get the size of the largest header. |
WiredHome | 3:17928786bdb5 | 661 | * |
WiredHome | 12:109bf1558300 | 662 | * This is a diagnostic function, so you can resize the allocated |
WiredHome | 12:109bf1558300 | 663 | * buffer for your application. With proper sizing, more of the |
WiredHome | 3:17928786bdb5 | 664 | * system memory is available for your application. |
WiredHome | 3:17928786bdb5 | 665 | * |
WiredHome | 3:17928786bdb5 | 666 | * @code |
WiredHome | 3:17928786bdb5 | 667 | * sprintf(buf,"Max Header size: %d<br/>\r\n", svr->GetMaxHeaderSize()); |
WiredHome | 3:17928786bdb5 | 668 | * svr->send(buf); |
WiredHome | 3:17928786bdb5 | 669 | * @endcode |
WiredHome | 12:109bf1558300 | 670 | * |
WiredHome | 3:17928786bdb5 | 671 | * @returns size in bytes of the larger header measured. |
WiredHome | 3:17928786bdb5 | 672 | */ |
WiredHome | 3:17928786bdb5 | 673 | int GetMaxHeaderSize(); |
WiredHome | 3:17928786bdb5 | 674 | |
WiredHome | 13:8975d7928678 | 675 | /** |
WiredHome | 16:6ebacf2946d8 | 676 | * Get a value from the http header, if it exists. |
WiredHome | 13:8975d7928678 | 677 | * |
WiredHome | 48:078adbe279ac | 678 | * @param[in] hdr is the string to search for (e.g. "Content-Length") |
WiredHome | 48:078adbe279ac | 679 | * @returns pointer to the value associated with that header. |
WiredHome | 13:8975d7928678 | 680 | * @returns NULL if the header is not found. |
WiredHome | 13:8975d7928678 | 681 | */ |
WiredHome | 13:8975d7928678 | 682 | const char * GetHeaderValue(const char * hdr); |
WiredHome | 3:17928786bdb5 | 683 | |
WiredHome | 3:17928786bdb5 | 684 | /** |
WiredHome | 3:17928786bdb5 | 685 | * Performance parameter |
WiredHome | 3:17928786bdb5 | 686 | */ |
WiredHome | 3:17928786bdb5 | 687 | typedef struct SW_PERFPARAM { |
WiredHome | 3:17928786bdb5 | 688 | unsigned long long TotalTime_us; |
WiredHome | 3:17928786bdb5 | 689 | unsigned long Samples; |
WiredHome | 3:17928786bdb5 | 690 | unsigned long MaxTime_us; |
WiredHome | 3:17928786bdb5 | 691 | } SW_PerformanceParam; |
WiredHome | 12:109bf1558300 | 692 | |
WiredHome | 3:17928786bdb5 | 693 | /** |
WiredHome | 3:17928786bdb5 | 694 | * Performance metrics |
WiredHome | 3:17928786bdb5 | 695 | */ |
WiredHome | 3:17928786bdb5 | 696 | typedef struct SW_PERFDATA { |
WiredHome | 17:69ff00ce39f4 | 697 | SW_PerformanceParam ConnectionAccepted; |
WiredHome | 17:69ff00ce39f4 | 698 | SW_PerformanceParam HeaderParsed; |
WiredHome | 17:69ff00ce39f4 | 699 | SW_PerformanceParam ResponseSent; |
WiredHome | 17:69ff00ce39f4 | 700 | SW_PerformanceParam ConnectionClosed; |
WiredHome | 3:17928786bdb5 | 701 | //SW_PerformanceParam SendFile; |
WiredHome | 3:17928786bdb5 | 702 | } SW_PerformanceData; |
WiredHome | 12:109bf1558300 | 703 | |
WiredHome | 3:17928786bdb5 | 704 | /** |
WiredHome | 3:17928786bdb5 | 705 | * Get performance metrics from the web server. |
WiredHome | 3:17928786bdb5 | 706 | * |
WiredHome | 3:17928786bdb5 | 707 | * This is a diagnostic function, and gathers data on the internal |
WiredHome | 3:17928786bdb5 | 708 | * performance of the server, as it works various actions. |
WiredHome | 3:17928786bdb5 | 709 | * |
WiredHome | 44:71f09e4255f4 | 710 | * @param[in] p is a pointer to a SW_PerformanceData structure to be populated |
WiredHome | 3:17928786bdb5 | 711 | */ |
WiredHome | 3:17928786bdb5 | 712 | void GetPerformanceData(SW_PerformanceData * p); |
WiredHome | 12:109bf1558300 | 713 | |
WiredHome | 3:17928786bdb5 | 714 | /** |
WiredHome | 3:17928786bdb5 | 715 | * Reset performance metrics. |
WiredHome | 3:17928786bdb5 | 716 | */ |
WiredHome | 3:17928786bdb5 | 717 | void ResetPerformanceData(); |
WiredHome | 17:69ff00ce39f4 | 718 | |
WiredHome | 17:69ff00ce39f4 | 719 | /** |
WiredHome | 17:69ff00ce39f4 | 720 | * Get performance clock |
WiredHome | 17:69ff00ce39f4 | 721 | */ |
WiredHome | 17:69ff00ce39f4 | 722 | unsigned int GetPerformanceClock(); |
WiredHome | 12:109bf1558300 | 723 | |
WiredHome | 0:729320f63c5c | 724 | private: |
WiredHome | 0:729320f63c5c | 725 | char * webroot; |
WiredHome | 0:729320f63c5c | 726 | PC * pc; |
WiredHome | 0:729320f63c5c | 727 | TCPSocketServer * server; |
WiredHome | 0:729320f63c5c | 728 | TCPSocketConnection client; |
WiredHome | 0:729320f63c5c | 729 | char * rewriteWithDefaultFile(char * queryString); |
WiredHome | 0:729320f63c5c | 730 | char * rewritePrependWebroot(char * queryString); |
WiredHome | 13:8975d7928678 | 731 | |
WiredHome | 13:8975d7928678 | 732 | namevalue *queryParams; // Query Parameters from the URL this=that&sky=blue&... |
WiredHome | 13:8975d7928678 | 733 | int maxqueryParams; |
WiredHome | 13:8975d7928678 | 734 | int queryParamCount; |
WiredHome | 13:8975d7928678 | 735 | |
WiredHome | 39:0427544a5c08 | 736 | namevalue *postParams; // Same as Query params, but for post method |
WiredHome | 39:0427544a5c08 | 737 | int maxPostParams; |
WiredHome | 39:0427544a5c08 | 738 | int postParamCount; |
WiredHome | 39:0427544a5c08 | 739 | |
WiredHome | 13:8975d7928678 | 740 | namevalue *headerParams; // Header params Host: 192.168...\r\nConnection: keep-alive\r\n... |
WiredHome | 13:8975d7928678 | 741 | int maxheaderParams; |
WiredHome | 13:8975d7928678 | 742 | int headerParamCount; |
WiredHome | 13:8975d7928678 | 743 | |
WiredHome | 3:17928786bdb5 | 744 | int maxheaderbytes; |
WiredHome | 3:17928786bdb5 | 745 | char * headerbuffer; |
WiredHome | 3:17928786bdb5 | 746 | int headerbuffersize; |
WiredHome | 12:109bf1558300 | 747 | |
WiredHome | 10:9c8d2c6a3469 | 748 | Timer PerformanceTimer; |
WiredHome | 3:17928786bdb5 | 749 | /** |
WiredHome | 3:17928786bdb5 | 750 | * Records performance data |
WiredHome | 12:109bf1558300 | 751 | * |
WiredHome | 3:17928786bdb5 | 752 | * This will take a pointer to a SW_PerformanceParam, and it will |
WiredHome | 3:17928786bdb5 | 753 | * take the time when the performance measurement started. It locally |
WiredHome | 3:17928786bdb5 | 754 | * accesses the current time to measure the elapsed. |
WiredHome | 3:17928786bdb5 | 755 | * |
WiredHome | 48:078adbe279ac | 756 | * @param[in] param is the performance parameter to update |
WiredHome | 48:078adbe279ac | 757 | * @param[in] value is the reference time. |
WiredHome | 3:17928786bdb5 | 758 | * @returns the current time which may be used as the reference time |
WiredHome | 3:17928786bdb5 | 759 | * for further measurements. |
WiredHome | 3:17928786bdb5 | 760 | */ |
WiredHome | 16:6ebacf2946d8 | 761 | unsigned int RecordPerformanceData(SW_PerformanceParam * param, unsigned int value); |
WiredHome | 3:17928786bdb5 | 762 | SW_PerformanceData perfData; |
WiredHome | 12:109bf1558300 | 763 | |
WiredHome | 3:17928786bdb5 | 764 | typedef struct HANDLER { |
WiredHome | 0:729320f63c5c | 765 | char * path; |
WiredHome | 0:729320f63c5c | 766 | Handler callback; |
WiredHome | 0:729320f63c5c | 767 | } handler; |
WiredHome | 0:729320f63c5c | 768 | int maxdynamicpages; |
WiredHome | 0:729320f63c5c | 769 | handler *handlers; |
WiredHome | 0:729320f63c5c | 770 | int handlercount; |
WiredHome | 3:17928786bdb5 | 771 | |
WiredHome | 3:17928786bdb5 | 772 | char * queryType; |
WiredHome | 44:71f09e4255f4 | 773 | char * queryString; // the query string [and 'GET' data] passed on the URL (e.g. ?name1=value1&name2=value2...) |
WiredHome | 44:71f09e4255f4 | 774 | char * postQueryString; // the post data |
WiredHome | 44:71f09e4255f4 | 775 | const namevalue * filenameAliasList; // pointer to a filename alias list |
WiredHome | 44:71f09e4255f4 | 776 | |
WiredHome | 0:729320f63c5c | 777 | /** |
WiredHome | 8:262583f054f6 | 778 | * Extract the parameter from the record, by searching for the needle in the haystack. |
WiredHome | 8:262583f054f6 | 779 | * |
WiredHome | 8:262583f054f6 | 780 | * The parameter of interest follows the needle, and may be ' ' delimited |
WiredHome | 0:729320f63c5c | 781 | * Can damage haystack while processing it. |
WiredHome | 0:729320f63c5c | 782 | * |
WiredHome | 48:078adbe279ac | 783 | * @param[in] haystack is the record to search |
WiredHome | 48:078adbe279ac | 784 | * @param[in] needle is the text to search for, which precedes the text to return |
WiredHome | 48:078adbe279ac | 785 | * @param[out] string is the text following the needle |
WiredHome | 0:729320f63c5c | 786 | * @return true if it extracted something successfully |
WiredHome | 0:729320f63c5c | 787 | */ |
WiredHome | 48:078adbe279ac | 788 | bool Extract(char * haystack, char * needle, char ** string); |
WiredHome | 0:729320f63c5c | 789 | |
WiredHome | 3:17928786bdb5 | 790 | void SendResponse(); |
WiredHome | 29:00116fc9da74 | 791 | HTTPServer::CallBackResults ParseHeader(char * bPtr); |
WiredHome | 3:17928786bdb5 | 792 | bool CheckDynamicHandlers(); |
WiredHome | 3:17928786bdb5 | 793 | |
WiredHome | 0:729320f63c5c | 794 | int HexCharToInt(char c); |
WiredHome | 0:729320f63c5c | 795 | char HexPairToChar(char * p); |
WiredHome | 29:00116fc9da74 | 796 | |
WiredHome | 29:00116fc9da74 | 797 | #ifdef DEBUG |
WiredHome | 29:00116fc9da74 | 798 | void * MyMalloc(int x, int y); |
WiredHome | 29:00116fc9da74 | 799 | char toP(void * x); |
WiredHome | 29:00116fc9da74 | 800 | #endif |
WiredHome | 0:729320f63c5c | 801 | }; |
WiredHome | 0:729320f63c5c | 802 | #endif //SW_HTTPSERVER_H |
WiredHome | 4:f34642902056 | 803 | |
WiredHome | 7:99ad7a67f05e | 804 |