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 Frank Vannieuwkerke

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers httpd.cpp Source File

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&#176;C</td><td>%d&#176;C</td></tr>", x,  bot->getToolTemperature(x), bot->getToolSetPoint(x));
00277         }            
00278         if (bot->hasPlatform()) {
00279             sock->printf("<tr><td>Platform</td><td>%d&#176;C</td><td>%d&#176;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'>&#9608</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'>&#x275a; &#x275a;</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'>&#9658</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>&#9658</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 }