* this will serve out files to HTTP clients.\r
*/\r
\r
+#define INCLUDE_FROM_HTTPSERVERAPP_C\r
#include "HTTPServerApp.h"\r
\r
/** HTTP server response header, for transmission before the page contents. This indicates to the host that a page exists at the\r
*/\r
char PROGMEM HTTP200Header[] = "HTTP/1.1 200 OK\r\n"\r
"Server: LUFA RNDIS\r\n"\r
- "Content-type: text/html\r\n"\r
- "Connection: close\r\n\r\n";\r
+ "Connection: close\r\n"\r
+ "MIME-version: 1.0\r\n"\r
+ "Content-Type: ";\r
\r
/** HTTP server response header, for transmission before a resource not found error. This indicates to the host that the given\r
* given URL is invalid, and gives extra error information.\r
*/\r
char PROGMEM HTTP404Header[] = "HTTP/1.1 404 Not Found\r\n"\r
"Server: LUFA RNDIS\r\n"\r
- "Connection: close\r\n\r\n"\r
- "The requested file was not found.";\r
+ "Connection: close\r\n"\r
+ "MIME-version: 1.0\r\n"\r
+ "Content-Type: text/plain\r\n\r\n"\r
+ "Error 404: File Not Found";\r
+\r
+/** Default MIME type sent if no other MIME type can be determined */\r
+char PROGMEM DefaultMIMEType[] = "text/plain";\r
+\r
+/** List of MIME types for each supported file extension. */\r
+MIME_Type_t PROGMEM MIMETypes[] =\r
+ {\r
+ {.Extension = "htm", .MIMEType = "text/html"},\r
+ {.Extension = "jpg", .MIMEType = "image/jpeg"},\r
+ {.Extension = "gif", .MIMEType = "image/gif"},\r
+ {.Extension = "bmp", .MIMEType = "image/bmp"},\r
+ {.Extension = "png", .MIMEType = "image/png"},\r
+ {.Extension = "ico", .MIMEType = "image/x-icon"},\r
+ {.Extension = "exe", .MIMEType = "application/octet-stream"},\r
+ {.Extension = "gz", .MIMEType = "application/x-gzip"},\r
+ {.Extension = "zip", .MIMEType = "application/zip"},\r
+ {.Extension = "pdf", .MIMEType = "application/pdf"},\r
+ };\r
\r
/** FAT Fs structure to hold the internal state of the FAT driver for the dataflash contents. */\r
FATFS DiskFATState;\r
\r
+\r
/** Initialization function for the simple HTTP webserver. */\r
-void WebserverApp_Init(void)\r
+void HTTPServerApp_Init(void)\r
{\r
/* Listen on port 80 for HTTP connections from hosts */\r
uip_listen(HTONS(HTTP_SERVER_PORT));\r
/** uIP stack application callback for the simple HTTP webserver. This function must be called each time the\r
* TCP/IP stack needs a TCP packet to be processed.\r
*/\r
-void WebserverApp_Callback(void)\r
+void HTTPServerApp_Callback(void)\r
{\r
- uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
- char* AppData = (char*)uip_appdata;\r
- uint16_t AppDataSize = 0;\r
+ uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
\r
- if (uip_aborted() || uip_timedout())\r
+ if (uip_aborted() || uip_timedout() || uip_closed())\r
{\r
- /* Close the file before terminating, if it is open */\r
- f_close(&AppState->FileToSend);\r
+ /* Connection is being terminated for some reason - close file handle */\r
+ f_close(&AppState->HTTPServer.FileHandle);\r
+ AppState->HTTPServer.FileOpen = false;\r
+ \r
+ /* Lock to the closed state so that no further processing will occur on the connection */\r
+ AppState->HTTPServer.CurrentState = WEBSERVER_STATE_Closed;\r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_Closed;\r
+ }\r
\r
- AppState->CurrentState = WEBSERVER_STATE_Closed;\r
+ if (uip_connected())\r
+ {\r
+ /* New connection - initialize connection state values */\r
+ AppState->HTTPServer.CurrentState = WEBSERVER_STATE_OpenRequestedFile;\r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_OpenRequestedFile;\r
+ AppState->HTTPServer.FileOpen = false;\r
+ AppState->HTTPServer.ACKedFilePos = 0;\r
+ AppState->HTTPServer.SentChunkSize = 0;\r
+ }\r
\r
- return;\r
+ if (uip_acked())\r
+ {\r
+ /* Add the amount of ACKed file data to the total sent file bytes counter */\r
+ AppState->HTTPServer.ACKedFilePos += AppState->HTTPServer.SentChunkSize;\r
+\r
+ /* Progress to the next state once the current state's data has been ACKed */\r
+ AppState->HTTPServer.CurrentState = AppState->HTTPServer.NextState;\r
}\r
- else if (uip_closed())\r
+\r
+ if (uip_rexmit() || uip_acked() || uip_newdata() || uip_connected() || uip_poll())\r
{\r
- /* Completed connection, just return */\r
+ switch (AppState->HTTPServer.CurrentState)\r
+ {\r
+ case WEBSERVER_STATE_OpenRequestedFile:\r
+ HTTPServerApp_OpenRequestedFile();\r
+ break;\r
+ case WEBSERVER_STATE_SendResponseHeader:\r
+ HTTPServerApp_SendResponseHeader();\r
+ break;\r
+ case WEBSERVER_STATE_SendMIMETypeHeader:\r
+ HTTPServerApp_SendMIMETypeHeader(); \r
+ break;\r
+ case WEBSERVER_STATE_SendData:\r
+ HTTPServerApp_SendData();\r
+ break;\r
+ case WEBSERVER_STATE_Closing:\r
+ uip_close();\r
+ \r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_Closed;\r
+ break;\r
+ } \r
+ } \r
+}\r
+\r
+/** HTTP Server State handler for the Request Process state. This state manages the processing of incomming HTTP\r
+ * GET requests to the server from the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_OpenRequestedFile(void)\r
+{\r
+ uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
+ char* const AppData = (char*)uip_appdata;\r
+ \r
+ /* No HTTP header received from the client, abort processing */\r
+ if (!(uip_newdata()))\r
+ return;\r
+ \r
+ char* RequestToken = strtok(AppData, " ");\r
+ \r
+ /* Must be a GET request, abort otherwise */\r
+ if (strcmp(RequestToken, "GET") != 0)\r
+ {\r
+ uip_abort();\r
return;\r
}\r
- else if (uip_connected())\r
+\r
+ char* RequestedFileName = strtok(NULL, " ");\r
+ \r
+ /* If the requested filename has more that just the leading '/' path in it, copy it over */\r
+ if (strlen(RequestedFileName) > 1)\r
+ strncpy(AppState->HTTPServer.FileName, &RequestedFileName[1], (sizeof(AppState->HTTPServer.FileName) - 1));\r
+ else\r
+ strcpy(AppState->HTTPServer.FileName, "index.htm");\r
+\r
+ /* Ensure filename is null-terminated */\r
+ AppState->HTTPServer.FileName[(sizeof(AppState->HTTPServer.FileName) - 1)] = 0x00;\r
+ \r
+ /* Try to open the file from the Dataflash disk */\r
+ AppState->HTTPServer.FileOpen = (f_open(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.FileName, FA_OPEN_EXISTING | FA_READ) == FR_OK);\r
+\r
+ /* Lock to the SendResponseHeader state until connection terminated */\r
+ AppState->HTTPServer.CurrentState = WEBSERVER_STATE_SendResponseHeader;\r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_SendResponseHeader;\r
+}\r
+\r
+/** HTTP Server State handler for the HTTP Response Header Send state. This state manages the transmission of\r
+ * the HTTP response header to the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_SendResponseHeader(void)\r
+{\r
+ uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
+ char* const AppData = (char*)uip_appdata;\r
+\r
+ char* HeaderToSend;\r
+\r
+ /* Determine which HTTP header should be sent to the client */\r
+ if (AppState->HTTPServer.FileOpen)\r
{\r
- /* New connection - initialize connection state and data pointer to the appropriate HTTP header */\r
- AppState->CurrentState = WEBSERVER_STATE_OpenRequestedFile;\r
+ HeaderToSend = HTTP200Header;\r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_SendMIMETypeHeader;\r
}\r
- \r
- switch (AppState->CurrentState)\r
+ else\r
{\r
- case WEBSERVER_STATE_OpenRequestedFile:\r
- /* Wait for the packet containing the request header */\r
- if (uip_datalen())\r
- {\r
- /* Must be a GET request, abort otherwise */\r
- if (strncmp(AppData, "GET ", (sizeof("GET ") - 1)) != 0)\r
- {\r
- uip_abort();\r
- break;\r
- }\r
- \r
- char FileName[13];\r
-\r
- /* Copy over the requested filename from the GET request */\r
- for (uint8_t i = 0; i < (sizeof(FileName) - 1); i++)\r
- {\r
- FileName[i] = AppData[sizeof("GET ") + i];\r
- \r
- if (FileName[i] == ' ')\r
- {\r
- FileName[i] = 0x00;\r
- break;\r
- }\r
- }\r
- \r
- /* Ensure requested filename is null-terminated */\r
- FileName[(sizeof(FileName) - 1)] = 0x00;\r
- \r
- /* If no filename specified, assume the default of INDEX.HTM */\r
- if (FileName[0] == 0x00)\r
- strcpy(FileName, "INDEX.HTM");\r
- \r
- /* Try to open the file from the Dataflash disk */\r
- AppState->FileOpen = (f_open(&AppState->FileToSend, FileName, FA_OPEN_EXISTING | FA_READ) == FR_OK);\r
+ HeaderToSend = HTTP404Header;\r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing;\r
+ }\r
\r
- AppState->CurrentState = WEBSERVER_STATE_SendHeaders;\r
- }\r
+ /* Copy over the HTTP response header and send it to the receiving client */\r
+ strcpy_P(AppData, HeaderToSend);\r
+ uip_send(AppData, strlen(AppData));\r
+}\r
\r
- break;\r
- case WEBSERVER_STATE_SendHeaders:\r
- /* Determine what HTTP header should be sent to the client */\r
- if (AppState->FileOpen)\r
- {\r
- AppDataSize = strlen_P(HTTP200Header);\r
- strncpy_P(AppData, HTTP200Header, AppDataSize); \r
- }\r
- else\r
- {\r
- AppDataSize = strlen_P(HTTP404Header);\r
- strncpy_P(AppData, HTTP404Header, AppDataSize); \r
- }\r
- \r
- uip_send(AppData, AppDataSize);\r
- \r
- AppState->CurrentState = WEBSERVER_STATE_SendData;\r
- break;\r
- case WEBSERVER_STATE_SendData:\r
- /* If end of file/file not open, progress to the close state */\r
- if (!(AppState->FileOpen))\r
+/** HTTP Server State handler for the MIME Header Send state. This state manages the transmission of the file\r
+ * MIME type header for the requested file to the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_SendMIMETypeHeader(void)\r
+{\r
+ uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
+ char* const AppData = (char*)uip_appdata;\r
+\r
+ char* Extension = strpbrk(AppState->HTTPServer.FileName, ".");\r
+ uint16_t MIMEHeaderLength = 0;\r
+\r
+ /* Check to see if a file extension was found for the requested filename */\r
+ if (Extension != NULL)\r
+ {\r
+ /* Look through the MIME type list, copy over the required MIME type if found */\r
+ for (int i = 0; i < (sizeof(MIMETypes) / sizeof(MIMETypes[0])); i++)\r
+ {\r
+ if (strcmp_P(&Extension[1], MIMETypes[i].Extension) == 0)\r
{\r
- f_close(&AppState->FileToSend);\r
- uip_close();\r
- AppState->CurrentState = WEBSERVER_STATE_Closed;\r
+ MIMEHeaderLength = strlen_P(MIMETypes[i].MIMEType);\r
+ strncpy_P(AppData, MIMETypes[i].MIMEType, MIMEHeaderLength); \r
break;\r
}\r
+ } \r
+ }\r
\r
- uint16_t MaxSegSize = uip_mss();\r
- \r
- /* Read the next chunk of data from the open file */\r
- f_read(&AppState->FileToSend, AppData, MaxSegSize, &AppDataSize);\r
- AppState->FileOpen = (MaxSegSize == AppDataSize);\r
-\r
- /* If data was read, send it to the client */\r
- if (AppDataSize)\r
- uip_send(AppData, AppDataSize);\r
- \r
- break;\r
+ /* Check if a MIME type was found and copied to the output buffer */\r
+ if (!(MIMEHeaderLength))\r
+ {\r
+ /* MIME type not found - copy over the default MIME type */\r
+ MIMEHeaderLength = strlen_P(DefaultMIMEType);\r
+ strncpy_P(AppData, DefaultMIMEType, MIMEHeaderLength);\r
}\r
+ \r
+ /* Add the end-of line terminator and end-of-headers terminator after the MIME type */\r
+ strncpy(&AppData[MIMEHeaderLength], "\r\n\r\n", sizeof("\r\n\r\n"));\r
+ MIMEHeaderLength += (sizeof("\r\n\r\n") - 1);\r
+ \r
+ /* Send the MIME header to the receiving client */\r
+ uip_send(AppData, MIMEHeaderLength);\r
+ \r
+ /* When the MIME header is ACKed, progress to the data send stage */\r
+ AppState->HTTPServer.NextState = WEBSERVER_STATE_SendData;\r
+}\r
+\r
+/** HTTP Server State handler for the Data Send state. This state manages the transmission of file chunks\r
+ * to the receiving HTTP client.\r
+ */\r
+static void HTTPServerApp_SendData(void)\r
+{\r
+ uip_tcp_appstate_t* const AppState = &uip_conn->appstate;\r
+ char* const AppData = (char*)uip_appdata;\r
+\r
+ /* Must determine the maximum segment size to determine maximum file chunk size */\r
+ uint16_t MaxSegmentSize = uip_mss();\r
+\r
+ /* Return file pointer to the last ACKed position */\r
+ f_lseek(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.ACKedFilePos);\r
+ \r
+ /* Read the next chunk of data from the open file */\r
+ f_read(&AppState->HTTPServer.FileHandle, AppData, MaxSegmentSize, &AppState->HTTPServer.SentChunkSize);\r
+ \r
+ /* Send the next file chunk to the receiving client */\r
+ uip_send(AppData, AppState->HTTPServer.SentChunkSize);\r
+ \r
+ /* Check if we are at the last chunk of the file, if so next ACK should close the connection */\r
+ AppState->HTTPServer.NextState = (MaxSegmentSize != AppState->HTTPServer.SentChunkSize) ? WEBSERVER_STATE_Closing : WEBSERVER_STATE_SendData;\r
}\r