You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					285 lines
				
				10 KiB
			
		
		
			
		
	
	
					285 lines
				
				10 KiB
			|   
											8 years ago
										 | /*
 | ||
|  |              LUFA Library | ||
|  |      Copyright (C) Dean Camera, 2017. | ||
|  | 
 | ||
|  |   dean [at] fourwalledcubicle [dot] com | ||
|  |            www.lufa-lib.org | ||
|  | */ | ||
|  | 
 | ||
|  | /*
 | ||
|  |   Copyright 2017  Dean Camera (dean [at] fourwalledcubicle [dot] com) | ||
|  | 
 | ||
|  |   Permission to use, copy, modify, distribute, and sell this | ||
|  |   software and its documentation for any purpose is hereby granted | ||
|  |   without fee, provided that the above copyright notice appear in | ||
|  |   all copies and that both that the copyright notice and this | ||
|  |   permission notice and warranty disclaimer appear in supporting | ||
|  |   documentation, and that the name of the author not be used in | ||
|  |   advertising or publicity pertaining to distribution of the | ||
|  |   software without specific, written prior permission. | ||
|  | 
 | ||
|  |   The author disclaims all warranties with regard to this | ||
|  |   software, including all implied warranties of merchantability | ||
|  |   and fitness.  In no event shall the author be liable for any | ||
|  |   special, indirect or consequential damages or any damages | ||
|  |   whatsoever resulting from loss of use, data or profits, whether | ||
|  |   in an action of contract, negligence or other tortious action, | ||
|  |   arising out of or in connection with the use or performance of | ||
|  |   this software. | ||
|  | */ | ||
|  | 
 | ||
|  | /** \file
 | ||
|  |  * | ||
|  |  *  Simple HTTP Webserver Application. When connected to the uIP stack, | ||
|  |  *  this will serve out files to HTTP clients on port 80. | ||
|  |  */ | ||
|  | 
 | ||
|  | #define  INCLUDE_FROM_HTTPSERVERAPP_C
 | ||
|  | #include "HTTPServerApp.h"
 | ||
|  | 
 | ||
|  | /** HTTP server response header, for transmission before the page contents. This indicates to the host that a page exists at the
 | ||
|  |  *  given location, and gives extra connection information. | ||
|  |  */ | ||
|  | const char PROGMEM HTTP200Header[] = "HTTP/1.1 200 OK\r\n" | ||
|  |                                      "Server: LUFA " LUFA_VERSION_STRING "\r\n" | ||
|  |                                      "Connection: close\r\n" | ||
|  |                                      "MIME-version: 1.0\r\n" | ||
|  |                                      "Content-Type: "; | ||
|  | 
 | ||
|  | /** HTTP server response header, for transmission before a resource not found error. This indicates to the host that the given
 | ||
|  |  *  URL is invalid, and gives extra error information. | ||
|  |  */ | ||
|  | const char PROGMEM HTTP404Header[] = "HTTP/1.1 404 Not Found\r\n" | ||
|  |                                      "Server: LUFA " LUFA_VERSION_STRING "\r\n" | ||
|  |                                      "Connection: close\r\n" | ||
|  |                                      "MIME-version: 1.0\r\n" | ||
|  |                                      "Content-Type: text/plain\r\n\r\n" | ||
|  |                                      "Error 404: File Not Found: /"; | ||
|  | 
 | ||
|  | /** Default filename to fetch when a directory is requested */ | ||
|  | const char PROGMEM DefaultDirFileName[] = "index.htm"; | ||
|  | 
 | ||
|  | /** Default MIME type sent if no other MIME type can be determined. */ | ||
|  | const char PROGMEM DefaultMIMEType[] = "text/plain"; | ||
|  | 
 | ||
|  | /** List of MIME types for each supported file extension. */ | ||
|  | const MIME_Type_t MIMETypes[] = | ||
|  | 	{ | ||
|  | 		{.Extension = "htm", .MIMEType = "text/html"}, | ||
|  | 		{.Extension = "jpg", .MIMEType = "image/jpeg"}, | ||
|  | 		{.Extension = "gif", .MIMEType = "image/gif"}, | ||
|  | 		{.Extension = "bmp", .MIMEType = "image/bmp"}, | ||
|  | 		{.Extension = "png", .MIMEType = "image/png"}, | ||
|  | 		{.Extension = "ico", .MIMEType = "image/x-icon"}, | ||
|  | 		{.Extension = "exe", .MIMEType = "application/octet-stream"}, | ||
|  | 		{.Extension = "gz",  .MIMEType = "application/x-gzip"}, | ||
|  | 		{.Extension = "zip", .MIMEType = "application/zip"}, | ||
|  | 		{.Extension = "pdf", .MIMEType = "application/pdf"}, | ||
|  | 	}; | ||
|  | 
 | ||
|  | /** FATFs structure to hold the internal state of the FAT driver for the Dataflash contents. */ | ||
|  | FATFS DiskFATState; | ||
|  | 
 | ||
|  | 
 | ||
|  | /** Initialization function for the simple HTTP webserver. */ | ||
|  | void HTTPServerApp_Init(void) | ||
|  | { | ||
|  | 	/* Listen on port 80 for HTTP connections from hosts */ | ||
|  | 	uip_listen(HTONS(HTTP_SERVER_PORT)); | ||
|  | 
 | ||
|  | 	/* Mount the Dataflash disk via FatFS */ | ||
|  | 	f_mount(0, &DiskFATState); | ||
|  | } | ||
|  | 
 | ||
|  | /** uIP stack application callback for the simple HTTP webserver. This function must be called each time the
 | ||
|  |  *  TCP/IP stack needs a TCP packet to be processed. | ||
|  |  */ | ||
|  | void HTTPServerApp_Callback(void) | ||
|  | { | ||
|  | 	uip_tcp_appstate_t* const AppState = &uip_conn->appstate; | ||
|  | 
 | ||
|  | 	if (uip_aborted() || uip_timedout() || uip_closed()) | ||
|  | 	{ | ||
|  | 		/* Lock to the closed state so that no further processing will occur on the connection */ | ||
|  | 		AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_Closing; | ||
|  | 		AppState->HTTPServer.NextState     = WEBSERVER_STATE_Closing; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (uip_connected()) | ||
|  | 	{ | ||
|  | 		/* New connection - initialize connection state values */ | ||
|  | 		AppState->HTTPServer.CurrentState  = WEBSERVER_STATE_OpenRequestedFile; | ||
|  | 		AppState->HTTPServer.NextState     = WEBSERVER_STATE_OpenRequestedFile; | ||
|  | 		AppState->HTTPServer.FileOpen      = false; | ||
|  | 		AppState->HTTPServer.ACKedFilePos  = 0; | ||
|  | 		AppState->HTTPServer.SentChunkSize = 0; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (uip_acked()) | ||
|  | 	{ | ||
|  | 		/* Add the amount of ACKed file data to the total sent file bytes counter */ | ||
|  | 		AppState->HTTPServer.ACKedFilePos += AppState->HTTPServer.SentChunkSize; | ||
|  | 
 | ||
|  | 		/* Progress to the next state once the current state's data has been ACKed */ | ||
|  | 		AppState->HTTPServer.CurrentState = AppState->HTTPServer.NextState; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (uip_rexmit()) | ||
|  | 	{ | ||
|  | 		/* Return file pointer to the last ACKed position */ | ||
|  | 		f_lseek(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.ACKedFilePos); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (uip_rexmit() || uip_acked() || uip_newdata() || uip_connected() || uip_poll()) | ||
|  | 	{ | ||
|  | 		switch (AppState->HTTPServer.CurrentState) | ||
|  | 		{ | ||
|  | 			case WEBSERVER_STATE_OpenRequestedFile: | ||
|  | 				HTTPServerApp_OpenRequestedFile(); | ||
|  | 				break; | ||
|  | 			case WEBSERVER_STATE_SendResponseHeader: | ||
|  | 				HTTPServerApp_SendResponseHeader(); | ||
|  | 				break; | ||
|  | 			case WEBSERVER_STATE_SendData: | ||
|  | 				HTTPServerApp_SendData(); | ||
|  | 				break; | ||
|  | 			case WEBSERVER_STATE_Closing: | ||
|  | 				/* Connection is being terminated for some reason - close file handle */ | ||
|  | 				f_close(&AppState->HTTPServer.FileHandle); | ||
|  | 				AppState->HTTPServer.FileOpen = false; | ||
|  | 
 | ||
|  | 				/* If connection is not already closed, close it */ | ||
|  | 				uip_close(); | ||
|  | 
 | ||
|  | 				AppState->HTTPServer.CurrentState = WEBSERVER_STATE_Closed; | ||
|  | 				AppState->HTTPServer.NextState    = WEBSERVER_STATE_Closed; | ||
|  | 				break; | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** HTTP Server State handler for the Request Process state. This state manages the processing of incoming HTTP
 | ||
|  |  *  GET requests to the server from the receiving HTTP client. | ||
|  |  */ | ||
|  | static void HTTPServerApp_OpenRequestedFile(void) | ||
|  | { | ||
|  | 	uip_tcp_appstate_t* const AppState    = &uip_conn->appstate; | ||
|  | 	char*               const AppData     = (char*)uip_appdata; | ||
|  | 
 | ||
|  | 	/* No HTTP header received from the client, abort processing */ | ||
|  | 	if (!(uip_newdata())) | ||
|  | 	  return; | ||
|  | 
 | ||
|  | 	char* RequestToken      = strtok(AppData, " "); | ||
|  | 	char* RequestedFileName = strtok(NULL, " "); | ||
|  | 
 | ||
|  | 	/* Must be a GET request, abort otherwise */ | ||
|  | 	if (strcmp_P(RequestToken, PSTR("GET")) != 0) | ||
|  | 	{ | ||
|  | 		uip_abort(); | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* Copy over the requested filename */ | ||
|  | 	strlcpy(AppState->HTTPServer.FileName, &RequestedFileName[1], sizeof(AppState->HTTPServer.FileName)); | ||
|  | 
 | ||
|  | 	/* Determine the length of the URI so that it can be checked to see if it is a directory */ | ||
|  | 	uint8_t FileNameLen = strlen(AppState->HTTPServer.FileName); | ||
|  | 
 | ||
|  | 	/* If the URI is a directory, append the default filename */ | ||
|  | 	if ((AppState->HTTPServer.FileName[FileNameLen - 1] == '/') || !(FileNameLen)) | ||
|  | 	{ | ||
|  | 		strlcpy_P(&AppState->HTTPServer.FileName[FileNameLen], DefaultDirFileName, | ||
|  | 		          (sizeof(AppState->HTTPServer.FileName) - FileNameLen)); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* Try to open the file from the Dataflash disk */ | ||
|  | 	AppState->HTTPServer.FileOpen     = (f_open(&AppState->HTTPServer.FileHandle, AppState->HTTPServer.FileName, | ||
|  | 	                                            (FA_OPEN_EXISTING | FA_READ)) == FR_OK); | ||
|  | 
 | ||
|  | 	/* Lock to the SendResponseHeader state until connection terminated */ | ||
|  | 	AppState->HTTPServer.CurrentState = WEBSERVER_STATE_SendResponseHeader; | ||
|  | 	AppState->HTTPServer.NextState    = WEBSERVER_STATE_SendResponseHeader; | ||
|  | } | ||
|  | 
 | ||
|  | /** HTTP Server State handler for the HTTP Response Header Send state. This state manages the transmission of
 | ||
|  |  *  the HTTP response header to the receiving HTTP client. | ||
|  |  */ | ||
|  | static void HTTPServerApp_SendResponseHeader(void) | ||
|  | { | ||
|  | 	uip_tcp_appstate_t* const AppState    = &uip_conn->appstate; | ||
|  | 	char*               const AppData     = (char*)uip_appdata; | ||
|  | 
 | ||
|  | 	char* Extension     = strpbrk(AppState->HTTPServer.FileName, "."); | ||
|  | 	bool  FoundMIMEType = false; | ||
|  | 
 | ||
|  | 	/* If the file isn't already open, it wasn't found - send back a 404 error response and abort */ | ||
|  | 	if (!(AppState->HTTPServer.FileOpen)) | ||
|  | 	{ | ||
|  | 		/* Copy over the HTTP 404 response header and send it to the receiving client */ | ||
|  | 		strcpy_P(AppData, HTTP404Header); | ||
|  | 		strcat(AppData, AppState->HTTPServer.FileName); | ||
|  | 		uip_send(AppData, strlen(AppData)); | ||
|  | 
 | ||
|  | 		AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing; | ||
|  | 		return; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* Copy over the HTTP 200 response header and send it to the receiving client */ | ||
|  | 	strcpy_P(AppData, HTTP200Header); | ||
|  | 
 | ||
|  | 	/* Check to see if a MIME type for the requested file's extension was found */ | ||
|  | 	if (Extension != NULL) | ||
|  | 	{ | ||
|  | 		/* Look through the MIME type list, copy over the required MIME type if found */ | ||
|  | 		for (uint8_t i = 0; i < (sizeof(MIMETypes) / sizeof(MIMETypes[0])); i++) | ||
|  | 		{ | ||
|  | 			if (strcmp(&Extension[1], MIMETypes[i].Extension) == 0) | ||
|  | 			{ | ||
|  | 				strcat(AppData, MIMETypes[i].MIMEType); | ||
|  | 				FoundMIMEType = true; | ||
|  | 				break; | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* Check if a MIME type was found and copied to the output buffer */ | ||
|  | 	if (!(FoundMIMEType)) | ||
|  | 	{ | ||
|  | 		/* MIME type not found - copy over the default MIME type */ | ||
|  | 		strcat_P(AppData, DefaultMIMEType); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* Add the end-of-line terminator and end-of-headers terminator after the MIME type */ | ||
|  | 	strcat_P(AppData, PSTR("\r\n\r\n")); | ||
|  | 
 | ||
|  | 	/* Send the MIME header to the receiving client */ | ||
|  | 	uip_send(AppData, strlen(AppData)); | ||
|  | 
 | ||
|  | 	/* When the MIME header is ACKed, progress to the data send stage */ | ||
|  | 	AppState->HTTPServer.NextState = WEBSERVER_STATE_SendData; | ||
|  | } | ||
|  | 
 | ||
|  | /** HTTP Server State handler for the Data Send state. This state manages the transmission of file chunks
 | ||
|  |  *  to the receiving HTTP client. | ||
|  |  */ | ||
|  | static void HTTPServerApp_SendData(void) | ||
|  | { | ||
|  | 	uip_tcp_appstate_t* const AppState    = &uip_conn->appstate; | ||
|  | 	char*               const AppData     = (char*)uip_appdata; | ||
|  | 
 | ||
|  | 	/* Get the maximum segment size for the current packet */ | ||
|  | 	uint16_t MaxChunkSize = uip_mss(); | ||
|  | 
 | ||
|  | 	/* Read the next chunk of data from the open file */ | ||
|  | 	f_read(&AppState->HTTPServer.FileHandle, AppData, MaxChunkSize, &AppState->HTTPServer.SentChunkSize); | ||
|  | 
 | ||
|  | 	/* Send the next file chunk to the receiving client */ | ||
|  | 	uip_send(AppData, AppState->HTTPServer.SentChunkSize); | ||
|  | 
 | ||
|  | 	/* Check if we are at the last chunk of the file, if so next ACK should close the connection */ | ||
|  | 	if (MaxChunkSize != AppState->HTTPServer.SentChunkSize) | ||
|  | 	  AppState->HTTPServer.NextState = WEBSERVER_STATE_Closing; | ||
|  | } | ||
|  | 
 |