Skip to content
Snippets Groups Projects
LinuxTinyServer.cpp 8.57 KiB
Newer Older
  • Learn to ignore specific revisions
  • jsclose's avatar
    jsclose committed
    // Linux tiny HTTP server.
    // Nicole Hamilton  nham@umich.edu
    
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netdb.h>
    #include <pthread.h>
    #include <fcntl.h>
    #include <iostream>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <string.h>
    #include <string>
    
    #include <cassert>
    //using namespace std;
    //  Multipurpose Internet Mail Extensions (MIME) types
    
    struct MimetypeMap
    	{
    	const char *Extension,
    			*Mimetype;
    	};
    
    const MimetypeMap MimeTable[] =
    		{
    				// List of some of the most common MIME types.
    				// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
    				".aac", "audio/aac",
    				".abw", "application/x-abiword",
    				".arc", "application/octet-stream",
    				".avi", "video/x-msvideo",
    				".azw", "application/vnd.amazon.ebook",
    				".bin", "application/octet-stream",
    				".bz", "application/x-bzip",
    				".bz2", "application/x-bzip2",
    				".csh", "application/x-csh",
    				".css", "text/css",
    				".csv", "text/csv",
    				".doc", "application/msword",
    				".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    				".eot", "application/vnd.ms-fontobject",
    				".epub", "application/epub+zip",
    				".gif", "image/gif",
    				".htm", "text/html",
    				".html", "text/html",
    				".ico", "image/x-icon",
    				".ics", "text/calendar",
    				".jar", "application/java-archive",
    				".jpeg", "image/jpeg",
    				".jpg", "image/jpeg",
    				".js", "application/javascript",
    				".json", "application/json",
    				".mid", "audio/midi",
    				".midi", "audio/midi",
    				".mpeg", "video/mpeg",
    				".mpkg", "application/vnd.apple.installer+xml",
    				".odp", "application/vnd.oasis.opendocument.presentation",
    				".ods", "application/vnd.oasis.opendocument.spreadsheet",
    				".odt", "application/vnd.oasis.opendocument.text",
    				".oga", "audio/ogg",
    				".ogv", "video/ogg",
    				".ogx", "application/ogg",
    				".otf", "font/otf",
    				".png", "image/png",
    				".pdf", "application/pdf",
    				".ppt", "application/vnd.ms-powerpoint",
    				".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation",
    				".rar", "application/x-rar-compressed",
    				".rtf", "application/rtf",
    				".sh", "application/x-sh",
    				".svg", "image/svg+xml",
    				".swf", "application/x-shockwave-flash",
    				".tar", "application/x-tar",
    				".tif", "image/tiff",
    				".tiff", "image/tiff",
    				".ts", "application/typescript",
    				".ttf", "font/ttf",
    				".vsd", "application/vnd.visio",
    				".wav", "audio/x-wav",
    				".weba", "audio/webm",
    				".webm", "video/webm",
    				".webp", "image/webp",
    				".woff", "font/woff",
    				".woff2", "font/woff2",
    				".xhtml", "application/xhtml+xml",
    				".xls", "application/vnd.ms-excel",
    				".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    				".xml", "application/xml",
    				".xul", "application/vnd.mozilla.xul+xml",
    				".zip", "application/zip",
    				".3gp", "video/3gpp",
    				".3g2", "video/3gpp2",
    				".7z", "application/x-7z-compressed"
    		};
    
    const char *Mimetype( const std::string filename )
    	{
    	const char *begin = filename.c_str( ),
    			*p = begin + filename.length( ) - 1;
    	// Scan back from the end for an extension.
    	while ( p >= begin && *p != '.' )
    		p--;
    	if ( *p == '.' )
    		{
    		// Found an extension.  Skip over the dot, then
    		// binary search for a matching mimetype.
    
    		p++;
    		int i = 0, j = sizeof( MimeTable ) / sizeof( MimetypeMap ) - 1;
    		while ( i <= j )
    			{
    			int mid = (i + j) / 2,
    					compare = strcasecmp( p, MimeTable[ mid ].Extension + 1 );
    			if ( compare == 0 )
    				return MimeTable[ mid ].Mimetype;
    			if ( compare < 0 )
    				j = mid - 1;
    			else
    				i = mid + 1;
    			}
    		}
    
    	// Anything not matched is an "octet-stream", treated
    	// as an unknown binary, which can be downloaded.
    	return "application/octet-stream";
    	}
    
    char *ParseGetRequest( char *request )
    	{
    	// If it's a GET request, null-terminate and
    	// return the URL.
    
    	if ( strncmp( request, "GET ", 4 ) == 0 )
    		{
    		// Skip the get and any additional white space.
    		for ( request += 4; *request == ' '; request++ );
    
    		// Now have the start of the URL.
    		char *p;
    		for ( p = request; *p != ' '; p++ );
    		*p = 0;  // null-terminate the URL.
    
    		return request;   // Return the URL.
    
    		}
    	return nullptr;   // Not a GET request.
    	}
    
    
    char *RootDirectory;
    
    
    off_t FileSize( int f )
    	{
    	struct stat fileInfo;
    	fstat( f, &fileInfo );
    	return fileInfo.st_size;
    	}
    
    
    void *Talk( void *p )
    	{
    	// Look for a GET message, then reply with the
    	// requested file.
    
    	char buffer[10240];
    	int bytes;
    	int *pp = (int *) p, s = *pp;
    	delete pp;
    
    	std::cout << "Starting recv, s = " << s << std::endl;
    
    	while ((bytes = recv( s, buffer, sizeof( buffer ) - 1, 0 )) > 0 )
    		{
    		buffer[ bytes ] = 0;
    		std::cout << "Request received, s = " << s << std::endl;
    		std::cout << buffer << std::endl;
    		char *fullUrl = ParseGetRequest( buffer );
    		std::string potentialSearch = std::string( fullUrl, strlen( fullUrl ));
    		if ( fullUrl )
    			{
    			std::cout << "Requested url = " << fullUrl << std::endl;
    			std::string search = "/search";
    			bool isSearch = true;
    			for ( int i = 0; i < search.size( ); i++ )
    				{
    				if ( potentialSearch[ i ] != search[ i ] )
    					isSearch = false;
    				break;
    
    				}
    			if ( isSearch )
    				{
    
    
    				std::cout << "---Making a search GET---" << std::endl;
    				std::cout << potentialSearch << std::endl;
    				}
    			else
    				{
    
    				std::string completePath = "";
    				std::string Root = std::string( RootDirectory, strlen( RootDirectory ));
    				std::string cwd = getenv( "PWD" );
    				completePath = cwd + Root + fullUrl;
    
    
    				//completePath += fullUrl;
    				std::cout << "Reqested file = " << completePath << std::endl;
    
    				int f = open( completePath.c_str( ), O_RDONLY );
    
    				if ( f != -1 )
    					{
    					off_t filesize = FileSize( f );
    					std::string okMessage = "HTTP/1.1 200 OK\r\n"
    							"Content-Length: ";
    					okMessage += std::to_string( filesize );
    					okMessage += "\r\nConnection: close\r\nContent-Type: ";
    					okMessage += Mimetype( completePath );
    					okMessage += "\r\n\r\n";
    
    					std::cout << "Sending" << std::endl;
    					std::cout << okMessage;
    
    					send( s, okMessage.c_str( ), okMessage.length( ), 0 );
    
    					while ( bytes = read( f, buffer, sizeof( buffer )))
    						send( s, buffer, bytes, 0 );
    
    					close( f );
    					}
    				else
    					{
    					std::string fileNotFound = "HTTP/1.1 404 Not Found\r\n"
    							"Content-Length: 0\r\n"
    							"Connection: close\r\n\r\n";
    					send( s, fileNotFound.c_str( ), fileNotFound.length( ), 0 );
    					}
    				}
    			}
    		}
    	close( s );
    	}
    
    
    void PrintAddress( const sockaddr_in *s, const size_t saLength )
    	{
    	const struct in_addr *ip = &s->sin_addr;
    	uint32_t a = ntohl( ip->s_addr );
    
    	std::cout << "Host address length = " << saLength << " bytes" << std::endl;
    	std::cout << "Family = " << s->sin_family <<
    				 ", port = " << ntohs( s->sin_port ) <<
    				 ", address = " << (a >> 24) << '.' <<
    				 ((a >> 16) & 0xff) << '.' <<
    				 ((a >> 8) & 0xff) << '.' <<
    				 (a & 0xff) << std::endl;
    	}
    
    
    int main( int argc, char **argv )
    	{
    	if ( argc != 3 )
    		{
    		std::cerr << "Usage:  LinuxTinyServer port rootdirectory" << std::endl;
    		return 1;
    		}
    
    	int port = atoi( argv[ 1 ] );
    	RootDirectory = argv[ 2 ];
    
    	// Create TCP/IP sockets for listening and talking.
    
    	struct sockaddr_in listenAddress, talkAddress;
    	socklen_t talkAddressLength;
    	int listenSocket, talkSocket;
    
    	memset( &listenAddress, 0, sizeof( listenAddress ));
    	memset( &talkAddress, 0, sizeof( talkAddress ));
    
    	listenAddress.sin_family = AF_INET;
    	listenAddress.sin_port = htons( port );
    	listenAddress.sin_addr.s_addr = htonl( INADDR_ANY );
    
    	listenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
    	assert( listenSocket != -1 );
    
    	int bindResult = bind( listenSocket, (sockaddr *) &listenAddress, sizeof( listenAddress ));
    	assert( bindResult == 0 );
    
    	int listenResult = listen( listenSocket, SOMAXCONN );
    	assert( listenResult == 0 );
    
    	std::cout << "Listening" << std::endl;
    	PrintAddress( &listenAddress, sizeof( listenAddress ));
    
    	while ((talkAddressLength = sizeof( talkAddress ),
    			talkSocket = accept( listenSocket, (sockaddr *) &talkAddress,
    										&talkAddressLength )) && talkSocket != -1 )
    		{
    		std::cout << "Connection accepted, talkSocket = " << talkSocket << std::endl;
    		PrintAddress( &talkAddress, talkAddressLength );
    		pthread_t child;
    		pthread_create( &child, nullptr, Talk, new int( talkSocket ));
    		}
    
    	close( listenSocket );
    	}