Skip to content
Snippets Groups Projects
LinuxTinyServer.cpp 8.97 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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 );
          if ( fullUrl )
             {
             std::cout << "Requested url = " << fullUrl << std::endl;
             if( strcmp(fullUrl, "/query") == 0)
                {
                std::cout << "GET REQUEST" << std::endl;
                
                }
    
    
             std::string completePath = strcat(getenv( "PWD" ), (RootDirectory));
             //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 );
       }