A web server for monitoring and controlling a MakerBot Replicator over the USB host and ethernet.
Dependencies: IAP NTPClient RTC mbed-rtos mbed Socket lwip-sys lwip BurstSPI
Fork of LPC1768_Mini-DK by
httpd.cpp
00001 // Copyright (c) 2013, jake (at) allaboutjake (dot) com 00002 // All rights reserved. 00003 // 00004 // Redistribution and use in source and binary forms, with or without 00005 // modification, are permitted provided that the following conditions are met: 00006 // * Redistributions of source code must retain the above copyright 00007 // notice, this list of conditions and the following disclaimer. 00008 // * Redistributions in binary form must reproduce the above copyright 00009 // notice, this list of conditions and the following disclaimer in the 00010 // documentation and/or other materials provided with the distribution. 00011 // * The name of the author and/or copyright holder nor the 00012 // names of its contributors may be used to endorse or promote products 00013 // derived from this software without specific prior written permission. 00014 // 00015 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 00016 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 00017 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 00018 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, AUTHOR, OR ANY CONTRIBUTORS 00019 // BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 00020 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 00021 // GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 00022 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00023 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 00024 // OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 00025 00026 // DESCRIPTION OF FILE: 00027 // 00028 // This file contains the source to the http server. 00029 // 00030 // The main function is "http_server_task" which is intended to be run in its 00031 // own thread. The other functions are helper functions. This function assumes 00032 // that the EthernetInterface is already configured and ready for use. 00033 // 00034 00035 #include "httpd.h" 00036 #include "readline.h" 00037 #include "main.h" 00038 #include "settings.h" 00039 00040 // Some Private methods 00041 // HTML rendering 00042 void sendHTTPHeader(ClientSocket* sock, int response_code, char* response_descr, char* contentType); 00043 void sendStatusContent(ClientSocket* sock); 00044 void sendPageHeader(ClientSocket* sock); 00045 void sendPageFooter(ClientSocket* sock); 00046 void sendCommandTable(ClientSocket* sock); 00047 void sendHTTPRedirect(ClientSocket* sock, char* redirectTo); 00048 void redirectToMessage(ClientSocket* sock, char* message, int isErr); 00049 void renderMessageBox(ClientSocket* sock, char* message, int isBadMessage); 00050 int handleAuthorizationHeaders(ClientSocket* sock, char* token); 00051 void discardHeaders(ClientSocket* socket); 00052 00053 //Helper functions 00054 int findOccuranceOfChar(char* buffer, char c, int occur, int max_length); 00055 char *url_encode(char *str); 00056 char *url_decode(char *str); 00057 00058 // Network server task: setup and listen for network connections 00059 void http_server_task(void const*) { 00060 //This thread assumes that the EthernetInterface is connected with an IP address. 00061 static int HTTPD_PORT = 80; 00062 00063 //Use SimpleSocket library to setup a server socket 00064 DISPLAY("Setting up httpd on %d\r\n", HTTPD_PORT); 00065 ServerSocket serverSocket(HTTPD_PORT); 00066 00067 while (1) { 00068 //Debug: DISPLAY("Waiting for connection\r\n"); 00069 //Single-threaded: get the next incoming connection 00070 ClientSocket socket = serverSocket.accept(); 00071 00072 retryAuth: 00073 //Get the request line (the first line) of the HTTP request 00074 char linebuffer[HTTP_LINE_BUFFER_SIZE]; 00075 memset(linebuffer, 0, HTTP_LINE_BUFFER_SIZE); 00076 int length = readline(&socket, linebuffer, HTTP_LINE_BUFFER_SIZE); 00077 00078 //debug: printf("%s: '%s'\r\n", socket.get_address(), linebuffer); 00079 00080 // Make sure its a GET (we only support GET currently) 00081 if (memcmp(linebuffer, "GET ", 4) != 0) { 00082 printf("Request Line: '%s'\r\n", linebuffer); 00083 DISPLAY("SENDING 400 Bad Request\r\n"); 00084 sendHTTPHeader(&socket, 400, "Bad Request", "text/html"); 00085 sendPageHeader(&socket); 00086 socket.printf("400 Bad Request"); 00087 sendPageFooter(&socket); 00088 socket.close(); 00089 continue; 00090 } 00091 00092 // If we have a password stored in flash, then check to see if we're authorized. 00093 char* token = getAuthenticationToken(); 00094 if (token != NULL) { 00095 //Process the headers, look for an authoirzation header and discard the rest. 00096 char* base64token=base64(token); 00097 int authState = handleAuthorizationHeaders(&socket, base64token); 00098 free(base64token); 00099 discardHeaders(&socket); 00100 00101 // If authentication was not good, then send out the 401 00102 if (!authState) { 00103 //Auth failed or not present. 00104 socket.printf("HTTP/1.1 %d %s\r\n", 401, "Not Authorized"); 00105 socket.printf("WWW-Authenticate: Basic realm=\"makerbot_server\"\r\n"); 00106 socket.printf("\r\n"); 00107 Thread::yield(); 00108 goto retryAuth; //TODO: goto's bad, but I'm lazy. Can probably be refactored somehow 00109 } else { 00110 //Authentication OK 00111 } 00112 } else { 00113 //A password wasn't set in flash, we don't need the rest of the headers, discard 00114 discardHeaders(&socket); 00115 } 00116 00117 //Find the path section of the request line. 00118 int endOfPath = findOccuranceOfChar(linebuffer, ' ', 2, HTTP_LINE_BUFFER_SIZE); 00119 int lengthOfPath = endOfPath - 4; 00120 char* path = (char*)malloc(lengthOfPath+1); 00121 strncpy(path, linebuffer+4, lengthOfPath); 00122 path[lengthOfPath]=0; 00123 00124 //debugging: DISPLAY("PATH (%d): '%s'\r\n", lengthOfPath, path); 00125 00126 // Process the given path and return the page 00127 if (lengthOfPath == 1 && path[0] == '/') { 00128 //request for the root (default) page 00129 sendHTTPHeader(&socket, 200, "OK", "text/html"); 00130 sendPageHeader(&socket); 00131 sendStatusContent(&socket); 00132 sendCommandTable(&socket); 00133 sendPageFooter(&socket); 00134 } else if (lengthOfPath > 6 && memcmp(path, "/?msg=",6) == 0) { 00135 char* message = url_decode(path+6); 00136 sendHTTPHeader(&socket, 200, "OK", "text/html"); 00137 sendPageHeader(&socket); 00138 renderMessageBox(&socket, message, 0); 00139 sendStatusContent(&socket); 00140 sendCommandTable(&socket); 00141 sendPageFooter(&socket); 00142 free(message); 00143 } else if (lengthOfPath > 6 && memcmp(path, "/?err=",6) == 0) { 00144 char* message = url_decode(path+6); 00145 sendHTTPHeader(&socket, 200, "OK", "text/html"); 00146 sendPageHeader(&socket); 00147 renderMessageBox(&socket, message, 1); 00148 sendStatusContent(&socket); 00149 sendCommandTable(&socket); 00150 sendPageFooter(&socket); 00151 free(message); 00152 } else if (strncmp(path, "/command?", 9) == 0) { 00153 //This is a command, figure out which one 00154 if (lengthOfPath >= 9+5 && memcmp(path+9, "pause", 5) == 0) { 00155 //pause command 00156 //GET /command?pause HTTP/1.1 00157 if (bot) { 00158 Makerbot::MakerbotBuildState state = bot->getBuildState(); 00159 if (state.build_state == Makerbot::BUILD_STATE_ERROR) { 00160 redirectToMessage(&socket, "Error: unable to determine build state. Sending pause/resume command in the blind.", 1); 00161 bot->pauseResume(); 00162 } else if (state.build_state == Makerbot::BUILD_RUNNING) { 00163 bot->pauseResume(); 00164 redirectToMessage(&socket, "Pausing build.", 0); 00165 } else { 00166 redirectToMessage(&socket, "Error: Not currently running. Cannot pause.", 1); 00167 } 00168 } else { 00169 redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); 00170 } 00171 } else if (lengthOfPath >= 9+6 && memcmp(path+9, "cancel", 6) == 0) { 00172 if (bot) { 00173 bot->abortImmediately(); 00174 redirectToMessage(&socket, "Cancelling build.", 0); 00175 } else { 00176 redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); 00177 } 00178 } else if (lengthOfPath >= 9+6 && memcmp(path+9, "resume", 6) == 0) { 00179 if (bot) { 00180 Makerbot::MakerbotBuildState state = bot->getBuildState(); 00181 if (state.build_state == Makerbot::BUILD_STATE_ERROR) { 00182 redirectToMessage(&socket, "Error: unable to determine build state. Sending pause/resume command in the blind.", 1); 00183 bot->pauseResume(); 00184 } else if (state.build_state == Makerbot::BUILD_PAUSED) { 00185 bot->pauseResume(); 00186 redirectToMessage(&socket, "Resuming build.", 0); 00187 } else { 00188 redirectToMessage(&socket, "Error: Not currently paused. Cannot resume.", 1); 00189 } 00190 } else { 00191 redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); 00192 } 00193 } else if (lengthOfPath >= 9+6 && memcmp(path + 9, "build=", 6) == 0) { 00194 int startOfFilename = findOccuranceOfChar(path, '=', 1, lengthOfPath)+1; 00195 int lengthOfFileName = lengthOfPath - startOfFilename; 00196 char* filename = path+startOfFilename; 00197 if (lengthOfFileName < 5) { 00198 //Minimum filename is of the form "x.x3g" = 5 characters 00199 redirectToMessage(&socket, "Filename too short", 1); 00200 } else if (lengthOfFileName > 12) { 00201 //According to the s3g spec, only 12-character FN's supported 00202 redirectToMessage(&socket, "Filename too long", 1); 00203 } else if (memcmp(filename+lengthOfFileName-4, ".X3G", 4)==0 || 00204 memcmp(filename+lengthOfFileName-4, ".x3g", 4)==0) { 00205 //Send command to bot to print 00206 if (bot) { 00207 bot->playbackCapture(filename); 00208 redirectToMessage(&socket, "Beginning build", 0); 00209 } else { 00210 redirectToMessage(&socket, "Error: connection to Makerbot not established.", 1); 00211 } 00212 } else { 00213 redirectToMessage(&socket, "Error: filename doesn't end with X3G.", 1); 00214 } 00215 } else { 00216 redirectToMessage(&socket, "Unknown command", 1); 00217 } 00218 } else { 00219 sendHTTPHeader(&socket, 404, "Not Found", "text/html"); 00220 sendPageHeader(&socket); 00221 socket.printf("404 Not Found"); 00222 sendPageFooter(&socket); 00223 } 00224 00225 //DISPLAY("Closing socket\r\n"); 00226 socket.close(); 00227 free(path); 00228 } 00229 00230 // never gets here 00231 //EthernetInterface::disconnect(); 00232 } 00233 00234 int findOccuranceOfChar(char* buffer, char c, int occur, int max_length) { 00235 int cnt = 0; 00236 for (int x=0; x<max_length; x++) { 00237 if (buffer[x] == c) { 00238 cnt++; 00239 if (cnt == occur) { 00240 return x; 00241 } 00242 } 00243 } 00244 return -1; 00245 } 00246 00247 void sendPageHeader(ClientSocket* sock) { 00248 sock->printf("<html>"); 00249 sock->printf("<head><style>"); 00250 sock->printf("body {font-family:Helvetica, Arial, sans-serif}"); 00251 sock->printf("table { border:1px solid #000; border-collapse:collapse;}"); 00252 sock->printf("td {border: 1px solid}"); 00253 sock->printf("tr { vertical-align:middle; }"); 00254 sock->printf("td.icon { text-align:center; padding:5px; }"); 00255 sock->printf("a {color: #000000; text-decoration: none; }"); 00256 sock->printf("</style>"); 00257 sock->printf("<body>"); 00258 } 00259 00260 void sendStatusContent(ClientSocket* sock) { 00261 if (bot) { 00262 Makerbot::MakerbotBuildState state = bot->getBuildState(); 00263 sock->printf("<H1>Build Status</H1>"); 00264 sock->printf("Machine Name: %s<br>", bot->getMachineName()); 00265 sock->printf("Bot firmware version: %0.1f<br><br>", bot->getMakerbotVersion()); 00266 sock->printf("<table width=350>"); 00267 sock->printf("<tr><td>Build state</td><td>%s</td></tr>", Makerbot::humanReadableBuildState(state)); 00268 sock->printf("<tr><Td>Build file name</td><td>%s</td></tr>", bot->getBuildName()); 00269 sock->printf("<tr><td>Elapsed Build Time</td><td>%d:%02d</td></tr>", state.hours, state.minutes); 00270 sock->printf("<tr><td>Line Number</td><td>%d</td></tr>", state.line); 00271 sock->printf("</table>"); 00272 00273 sock->printf("<H1>Temperatures</H1>"); 00274 sock->printf("<table width=350>"); 00275 for (int x=0; x<bot->getToolCount(); x++) { 00276 sock->printf("<tr><td>Tool %d</td><td>%d°C</td><td>%d°C</td></tr>", x, bot->getToolTemperature(x), bot->getToolSetPoint(x)); 00277 } 00278 if (bot->hasPlatform()) { 00279 sock->printf("<tr><td>Platform</td><td>%d°C</td><td>%d°C</td></tr>", bot->getPlatformTemperature(0), bot->getPlatformSetPoint(0)); 00280 } 00281 sock->printf("</table>"); 00282 } else { 00283 sock->printf("<H1>No connection to Makerbot</H1>"); 00284 } 00285 } 00286 00287 void sendHTTPHeader(ClientSocket* sock, int response_code, char* response_descr, char* contentType) { 00288 sock->printf("HTTP/1.1 %d %s\r\n", response_code, response_descr); 00289 if (contentType != NULL) 00290 sock->printf("Content-Type: %s\r\n", contentType); 00291 sock->printf("\r\n"); 00292 } 00293 00294 void sendHTTPRedirect(ClientSocket* sock, char* redirectTo) { 00295 sock->printf("HTTP/1.1 %d %s\r\n", 302, "Found"); 00296 sock->printf("Location: %s\r\n", redirectTo); 00297 sock->printf("\r\n"); 00298 } 00299 00300 void redirectToMessage(ClientSocket* sock, char* message, int isErr) { 00301 sock->printf("HTTP/1.1 %d %s\r\n", 302, "Found"); 00302 char* escapedMsg = url_encode(message); 00303 sock->printf("Location: /?%s=%s\r\n", isErr?"err":"msg", escapedMsg); 00304 free(escapedMsg); 00305 sock->printf("\r\n"); 00306 } 00307 00308 void renderMessageBox(ClientSocket* sock, char* message, int isBadMessage) { 00309 if (isBadMessage) 00310 sock->printf("<table><tr><td style='padding: 15px; background: #FF9999'>"); 00311 else 00312 sock->printf("<table><tr><td style='padding: 15px; background: #99FF99'>"); 00313 sock->printf("%s", message); 00314 sock->printf("</td><tr></table>"); 00315 00316 } 00317 00318 00319 void sendCommandTable(ClientSocket* sock) { 00320 if (bot) { 00321 Makerbot::MakerbotBuildState state = bot->getBuildState(); 00322 sock->printf("<H1>Commands</H1>"); 00323 sock->printf("<table width=500>"); 00324 if (state.build_state == Makerbot::BUILD_RUNNING || 00325 state.build_state == Makerbot::BUILD_PAUSED) { 00326 sock->printf("<tr><td class=icon><a href='/command?cancel'>█</a></td><td><a href='/command?cancel'>Cancel</a></td><td>Halt build immediately (does not clear build plate)</td></tr>"); 00327 } 00328 if (state.build_state == Makerbot::BUILD_RUNNING) { 00329 sock->printf("<tr><td class=icon><a href='/command?pause'>❚ ❚</a></td><td><a hred='/command?pause'>Pause</a></td><td>Pause build and clear build area</td></tr>"); 00330 } 00331 if (state.build_state == Makerbot::BUILD_PAUSED) { 00332 sock->printf("<tr><td class=icon><a href='/command?resume'>►</a></td><td><a href='/command?resume'>Resume</a></td><td>Resume from pause</td></tr>"); 00333 } 00334 if (state.build_state == Makerbot::NO_BUILD || 00335 state.build_state == Makerbot::BUILD_FINISHED_NORMALLY || 00336 state.build_state == Makerbot::BUILD_CANCELLED || 00337 state.build_state == Makerbot::BUILD_SLEEPING) { 00338 sock->printf("<tr><td class=icon>►</td><td>Play</TD><TD><form action='/command' method=get><input length=12 maxlength=12 name=build><input type=submit></form></td></tr>"); 00339 } 00340 sock->printf("</table>"); 00341 } 00342 } 00343 00344 void sendPageFooter(ClientSocket* sock) { 00345 sock->printf("<br><hr><br>Server firmware version: %s<br>", FIRMWARE_VERSION); 00346 time_t ctTime = time(NULL); 00347 sock->printf("Page generated at %s\r\n", ctime(&ctTime)); 00348 sock->printf("</body>"); 00349 sock->printf("</html>"); 00350 } 00351 00352 // Public domain code from http://www.geekhideout.com/urlcode.shtml 00353 // To the extent possible under law, Fred Bulback has waived all copyright and related or neighboring rights to URL Encoding/Decoding in C. This work is published from: United States. 00354 bool isalnum(char c) { 00355 if (c >= 48 && c <= 57) return 1; //numbers 00356 if (c >= 65 && c <= 90) return 1; //upercase 00357 if (c >= 97 && c <= 122) return 1; //lowercase 00358 return 0; 00359 } 00360 00361 bool isdigit(char c) { 00362 if (c >= 48 && c <= 57) return 1; //numbers 00363 return 0; 00364 } 00365 00366 char tolower(char c) { 00367 if (c >= 65 && c <= 90) { 00368 return c+32; 00369 } else { 00370 return c; 00371 } 00372 } 00373 00374 00375 /* Converts a hex character to its integer value */ 00376 char from_hex(char ch) { 00377 return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; 00378 } 00379 00380 /* Converts an integer value to its hex character*/ 00381 char to_hex(char code) { 00382 static char hex[] = "0123456789abcdef"; 00383 return hex[code & 15]; 00384 } 00385 00386 /* Returns a url-encoded version of str */ 00387 /* IMPORTANT: be sure to free() the returned string after use */ 00388 char *url_encode(char *str) { 00389 char *pstr = str, *buf = (char*)malloc(strlen(str) * 3 + 1), *pbuf = buf; 00390 while (*pstr) { 00391 if (isalnum(*pstr) || *pstr == '-' || *pstr == '_' || *pstr == '.' || *pstr == '~') 00392 *pbuf++ = *pstr; 00393 else if (*pstr == ' ') 00394 *pbuf++ = '+'; 00395 else 00396 *pbuf++ = '%', *pbuf++ = to_hex(*pstr >> 4), *pbuf++ = to_hex(*pstr & 15); 00397 pstr++; 00398 } 00399 *pbuf = '\0'; 00400 return buf; 00401 } 00402 00403 /* Returns a url-decoded version of str */ 00404 /* IMPORTANT: be sure to free() the returned string after use */ 00405 char *url_decode(char *str) { 00406 char *pstr = str, *buf = (char*)malloc(strlen(str) + 1), *pbuf = buf; 00407 while (*pstr) { 00408 if (*pstr == '%') { 00409 if (pstr[1] && pstr[2]) { 00410 *pbuf++ = from_hex(pstr[1]) << 4 | from_hex(pstr[2]); 00411 pstr += 2; 00412 } 00413 } else if (*pstr == '+') { 00414 *pbuf++ = ' '; 00415 } else { 00416 *pbuf++ = *pstr; 00417 } 00418 pstr++; 00419 } 00420 *pbuf = '\0'; 00421 return buf; 00422 } 00423 00424 // Search through the HTTP headers and if there is an authentication line, process it 00425 // Discard headers that are not relevant. 00426 int handleAuthorizationHeaders(ClientSocket* socket, char* desiredToken) { 00427 char line [HTTP_LINE_BUFFER_SIZE]; 00428 int length=0; 00429 char* authToken = NULL; 00430 while ((length = readline(socket, line, HTTP_LINE_BUFFER_SIZE)) > 0) { 00431 if (memcmp(line, "Authorization: Basic ", 21) == 0) { 00432 //we found the authorization line. 00433 authToken = (char*)malloc(length - 21 + 1); 00434 memcpy(authToken, line+21, length-21); 00435 authToken[length-21] = 0; //terminator 00436 break; 00437 } else { 00438 //nothing 00439 } 00440 Thread::yield(); 00441 } 00442 00443 if (authToken != NULL) { 00444 int match = memcmp(desiredToken, authToken, strlen(authToken)); 00445 free(authToken); 00446 if (match == 0) return 1; 00447 } 00448 00449 return 0; 00450 } 00451 00452 // Discard line by line until a blank line 00453 // (the separator between the header and the body of the HTTP request) 00454 void discardHeaders(ClientSocket* socket) { 00455 if (!socket->available()) { 00456 //printf("nothing available, so not doing anything about the headers\r\n"); 00457 return; 00458 } 00459 00460 char line [HTTP_LINE_BUFFER_SIZE]; 00461 while (readline(socket, line, HTTP_LINE_BUFFER_SIZE) > 0) { 00462 Thread::yield(); 00463 } 00464 } 00465 00466 // Return the base64 representation of the input 00467 // IMPORTANT: caller must call free() on the result when done with it. 00468 char* base64(char* input) { 00469 int inputLen = strlen(input); 00470 int padLength = (3-(inputLen % 3))%3; 00471 00472 int outNdx=0; 00473 char* output = (char*)malloc(((4*inputLen)/3)+1); 00474 00475 for (int i=0; i < inputLen; i+=3) { 00476 //Assemble the 24 bit number 00477 uint32_t a = input[i] << 16; 00478 if (i+1 < inputLen) a += input[i+1] << 8; 00479 if (i+2 < inputLen) a += input[i+2]; 00480 00481 //Split the 24 bit number into 4 parts 00482 uint8_t p1 = (uint8_t)(a >> 18) & 0x3F; 00483 uint8_t p2 = (uint8_t)(a >> 12) & 0x3F; 00484 uint8_t p3 = (uint8_t)(a >> 6) & 0x3F; 00485 uint8_t p4 = (uint8_t)a & 0x3F; 00486 00487 //write the data to the output 00488 output[outNdx++] = base64table[p1]; 00489 output[outNdx++] = base64table[p2]; 00490 output[outNdx++] = base64table[p3]; 00491 output[outNdx++] = base64table[p4]; 00492 } 00493 00494 // Put the padding in at the end 00495 for (int i=0; i<padLength; i++) { 00496 output[outNdx-i-1]='='; 00497 } 00498 00499 // Terminating \0 00500 output[outNdx]='\0'; 00501 00502 return output; 00503 }
Generated on Tue Jul 12 2022 17:52:03 by 1.7.2