]> git.saurik.com Git - apt.git/commitdiff
webserver: use pthreads to handle multiple clients
authorDavid Kalnischkies <kalnischkies@gmail.com>
Wed, 16 Oct 2013 20:45:37 +0000 (22:45 +0200)
committerDavid Kalnischkies <kalnischkies@gmail.com>
Sat, 30 Nov 2013 11:38:26 +0000 (12:38 +0100)
Clients like browsers prefer to open many connections and keep them open
for a while, so that pages with lot of subelements would take a while to
load (if at all), by using threads as all servers do some way or another
we can resolve this. libapt is not intended to be pthread-safe and stuff
like the storage of the last return code doesn't make too much sense if
multiple clients interact with us, but it is good enough for now and an
other interesting (mis)use of libapt in general.

Git-Dch: Ignore

test/interactive-helper/aptwebserver.cc
test/interactive-helper/makefile

index 94f63bb39e590d3427fab91eb0d5d0e2d87f79f7..7d3589c9dbe154bc4cc40f8e96b81a854e1cc971 100644 (file)
@@ -112,7 +112,7 @@ bool sendHead(int const client, int const httpcode, std::list<std::string> &head
    date.append(TimeRFC1123(time(NULL)));
    headers.push_back(date);
 
-   std::clog << ">>> RESPONSE >>>" << std::endl;
+   std::clog << ">>> RESPONSE to " << client << " >>>" << std::endl;
    bool Success = true;
    for (std::list<std::string>::const_iterator h = headers.begin();
        Success == true && h != headers.end(); ++h)
@@ -470,6 +470,173 @@ bool handleOnTheFlyReconfiguration(int const client, std::string const &request,
    return false;
 }
                                                                        /*}}}*/
+void * handleClient(void * voidclient)                                 /*{{{*/
+{
+   int client = *((int*)(voidclient));
+   std::clog << "ACCEPT client " << client << std::endl;
+   std::vector<std::string> messages;
+   while (ReadMessages(client, messages))
+   {
+      bool closeConnection = false;
+      for (std::vector<std::string>::const_iterator m = messages.begin();
+           m != messages.end() && closeConnection == false; ++m) {
+        std::clog << ">>> REQUEST from " << client << " >>>" << std::endl << *m
+           << std::endl << "<<<<<<<<<<<<<<<<" << std::endl;
+        std::list<std::string> headers;
+        std::string filename;
+        std::string params;
+        bool sendContent = true;
+        if (parseFirstLine(client, *m, filename, params, sendContent, closeConnection) == false)
+           continue;
+
+        // special webserver command request
+        if (filename.length() > 1 && filename[0] == '_')
+        {
+           std::vector<std::string> parts = VectorizeString(filename, '/');
+           if (parts[0] == "_config")
+           {
+              handleOnTheFlyReconfiguration(client, *m, parts);
+              continue;
+           }
+        }
+
+        // string replacements in the requested filename
+        ::Configuration::Item const *Replaces = _config->Tree("aptwebserver::redirect::replace");
+        if (Replaces != NULL)
+        {
+           std::string redirect = "/" + filename;
+           for (::Configuration::Item *I = Replaces->Child; I != NULL; I = I->Next)
+              redirect = SubstVar(redirect, I->Tag, I->Value);
+           redirect.erase(0,1);
+           if (redirect != filename)
+           {
+              sendRedirect(client, 301, redirect, *m, sendContent);
+              continue;
+           }
+        }
+
+        ::Configuration::Item const *Overwrite = _config->Tree("aptwebserver::overwrite");
+        if (Overwrite != NULL)
+        {
+           for (::Configuration::Item *I = Overwrite->Child; I != NULL; I = I->Next)
+           {
+              regex_t *pattern = new regex_t;
+              int const res = regcomp(pattern, I->Tag.c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB);
+              if (res != 0)
+              {
+                 char error[300];
+                 regerror(res, pattern, error, sizeof(error));
+                 sendError(client, 500, *m, sendContent, error);
+                 continue;
+              }
+              if (regexec(pattern, filename.c_str(), 0, 0, 0) == 0)
+              {
+                 filename = _config->Find("aptwebserver::overwrite::" + I->Tag + "::filename", filename);
+                 if (filename[0] == '/')
+                    filename.erase(0,1);
+                 regfree(pattern);
+                 break;
+              }
+              regfree(pattern);
+           }
+        }
+
+        // deal with the request
+        if (RealFileExists(filename) == true)
+        {
+           FileFd data(filename, FileFd::ReadOnly);
+           std::string condition = LookupTag(*m, "If-Modified-Since", "");
+           if (condition.empty() == false)
+           {
+              time_t cache;
+              if (RFC1123StrToTime(condition.c_str(), cache) == true &&
+                    cache >= data.ModificationTime())
+              {
+                 sendHead(client, 304, headers);
+                 continue;
+              }
+           }
+
+           if (_config->FindB("aptwebserver::support::range", true) == true)
+              condition = LookupTag(*m, "Range", "");
+           else
+              condition.clear();
+           if (condition.empty() == false && strncmp(condition.c_str(), "bytes=", 6) == 0)
+           {
+              time_t cache;
+              std::string ifrange;
+              if (_config->FindB("aptwebserver::support::if-range", true) == true)
+                 ifrange = LookupTag(*m, "If-Range", "");
+              bool validrange = (ifrange.empty() == true ||
+                    (RFC1123StrToTime(ifrange.c_str(), cache) == true &&
+                     cache <= data.ModificationTime()));
+
+              // FIXME: support multiple byte-ranges (APT clients do not do this)
+              if (condition.find(',') == std::string::npos)
+              {
+                 size_t start = 6;
+                 unsigned long long filestart = strtoull(condition.c_str() + start, NULL, 10);
+                 // FIXME: no support for last-byte-pos being not the end of the file (APT clients do not do this)
+                 size_t dash = condition.find('-') + 1;
+                 unsigned long long fileend = strtoull(condition.c_str() + dash, NULL, 10);
+                 unsigned long long filesize = data.FileSize();
+                 if ((fileend == 0 || (fileend == filesize && fileend >= filestart)) &&
+                       validrange == true)
+                 {
+                    if (filesize > filestart)
+                    {
+                       data.Skip(filestart);
+                       std::ostringstream contentlength;
+                       contentlength << "Content-Length: " << (filesize - filestart);
+                       headers.push_back(contentlength.str());
+                       std::ostringstream contentrange;
+                       contentrange << "Content-Range: bytes " << filestart << "-"
+                          << filesize - 1 << "/" << filesize;
+                       headers.push_back(contentrange.str());
+                       sendHead(client, 206, headers);
+                       if (sendContent == true)
+                          sendFile(client, data);
+                       continue;
+                    }
+                    else
+                    {
+                       headers.push_back("Content-Length: 0");
+                       std::ostringstream contentrange;
+                       contentrange << "Content-Range: bytes */" << filesize;
+                       headers.push_back(contentrange.str());
+                       sendHead(client, 416, headers);
+                       continue;
+                    }
+                 }
+              }
+           }
+
+           addFileHeaders(headers, data);
+           sendHead(client, 200, headers);
+           if (sendContent == true)
+              sendFile(client, data);
+        }
+        else if (DirectoryExists(filename) == true)
+        {
+           if (filename[filename.length()-1] == '/')
+              sendDirectoryListing(client, filename, *m, sendContent);
+           else
+              sendRedirect(client, 301, filename.append("/"), *m, sendContent);
+        }
+        else
+           sendError(client, 404, *m, sendContent);
+      }
+      _error->DumpErrors(std::cerr);
+      messages.clear();
+      if (closeConnection == true)
+        break;
+   }
+   close(client);
+   std::clog << "CLOSE client " << client << std::endl;
+   return NULL;
+}
+                                                                       /*}}}*/
+
 int main(int const argc, const char * argv[])
 {
    CommandLine::Args Args[] = {
@@ -490,6 +657,9 @@ int main(int const argc, const char * argv[])
    // create socket, bind and listen to it {{{
    // ignore SIGPIPE, this can happen on write() if the socket closes connection
    signal(SIGPIPE, SIG_IGN);
+   // we don't care for our slaves, so ignore their death
+   signal(SIGCHLD, SIG_IGN);
+
    int sock = socket(AF_INET6, SOCK_STREAM, 0);
    if(sock < 0)
    {
@@ -557,179 +727,47 @@ int main(int const argc, const char * argv[])
 
    std::clog << "Serving ANY file on port: " << port << std::endl;
 
-   listen(sock, 1);
+   int const slaves = _config->FindB("aptwebserver::slaves", SOMAXCONN);
+   listen(sock, slaves);
    /*}}}*/
 
    _config->CndSet("aptwebserver::response-header::Server", "APT webserver");
    _config->CndSet("aptwebserver::response-header::Accept-Ranges", "bytes");
    _config->CndSet("aptwebserver::directoryindex", "index.html");
 
-   std::vector<std::string> messages;
-   int client;
-   while ((client = accept(sock, NULL, NULL)) != -1)
-   {
-      std::clog << "ACCEPT client " << client
-               << " on socket " << sock << std::endl;
+   std::list<int> accepted_clients;
 
-      while (ReadMessages(client, messages))
+   while (true)
+   {
+      int client = accept(sock, NULL, NULL);
+      if (client == -1)
       {
-        bool closeConnection = false;
-        for (std::vector<std::string>::const_iterator m = messages.begin();
-             m != messages.end() && closeConnection == false; ++m) {
-           std::clog << ">>> REQUEST >>>>" << std::endl << *m
-                     << std::endl << "<<<<<<<<<<<<<<<<" << std::endl;
-           std::list<std::string> headers;
-           std::string filename;
-           bool sendContent = true;
-           if (parseFirstLine(client, *m, filename, sendContent, closeConnection) == false)
-              continue;
-
-           // special webserver command request
-           if (filename.length() > 1 && filename[0] == '_')
-           {
-              std::vector<std::string> parts = VectorizeString(filename, '/');
-              if (parts[0] == "_config")
-              {
-                 handleOnTheFlyReconfiguration(client, *m, parts);
-                 continue;
-              }
-           }
-
-           // string replacements in the requested filename
-           ::Configuration::Item const *Replaces = _config->Tree("aptwebserver::redirect::replace");
-           if (Replaces != NULL)
-           {
-              std::string redirect = "/" + filename;
-              for (::Configuration::Item *I = Replaces->Child; I != NULL; I = I->Next)
-                 redirect = SubstVar(redirect, I->Tag, I->Value);
-              redirect.erase(0,1);
-              if (redirect != filename)
-              {
-                 sendRedirect(client, 301, redirect, *m, sendContent);
-                 continue;
-              }
-           }
-
-           ::Configuration::Item const *Overwrite = _config->Tree("aptwebserver::overwrite");
-           if (Overwrite != NULL)
-           {
-              for (::Configuration::Item *I = Overwrite->Child; I != NULL; I = I->Next)
-              {
-                 regex_t *pattern = new regex_t;
-                 int const res = regcomp(pattern, I->Tag.c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB);
-                 if (res != 0)
-                 {
-                    char error[300];
-                    regerror(res, pattern, error, sizeof(error));
-                    sendError(client, 500, *m, sendContent, error);
-                    continue;
-                 }
-                 if (regexec(pattern, filename.c_str(), 0, 0, 0) == 0)
-                 {
-                     filename = _config->Find("aptwebserver::overwrite::" + I->Tag + "::filename", filename);
-                     if (filename[0] == '/')
-                        filename.erase(0,1);
-                     regfree(pattern);
-                     break;
-                 }
-                 regfree(pattern);
-              }
-           }
-
-           // deal with the request
-           if (RealFileExists(filename) == true)
-           {
-              FileFd data(filename, FileFd::ReadOnly);
-              std::string condition = LookupTag(*m, "If-Modified-Since", "");
-              if (condition.empty() == false)
-              {
-                 time_t cache;
-                 if (RFC1123StrToTime(condition.c_str(), cache) == true &&
-                       cache >= data.ModificationTime())
-                 {
-                    sendHead(client, 304, headers);
-                    continue;
-                 }
-              }
-
-              if (_config->FindB("aptwebserver::support::range", true) == true)
-                 condition = LookupTag(*m, "Range", "");
-              else
-                 condition.clear();
-              if (condition.empty() == false && strncmp(condition.c_str(), "bytes=", 6) == 0)
-              {
-                 time_t cache;
-                 std::string ifrange;
-                 if (_config->FindB("aptwebserver::support::if-range", true) == true)
-                    ifrange = LookupTag(*m, "If-Range", "");
-                 bool validrange = (ifrange.empty() == true ||
-                       (RFC1123StrToTime(ifrange.c_str(), cache) == true &&
-                        cache <= data.ModificationTime()));
-
-                 // FIXME: support multiple byte-ranges (APT clients do not do this)
-                 if (condition.find(',') == std::string::npos)
-                 {
-                    size_t start = 6;
-                    unsigned long long filestart = strtoull(condition.c_str() + start, NULL, 10);
-                    // FIXME: no support for last-byte-pos being not the end of the file (APT clients do not do this)
-                    size_t dash = condition.find('-') + 1;
-                    unsigned long long fileend = strtoull(condition.c_str() + dash, NULL, 10);
-                    unsigned long long filesize = data.FileSize();
-                    if ((fileend == 0 || (fileend == filesize && fileend >= filestart)) &&
-                          validrange == true)
-                    {
-                       if (filesize > filestart)
-                       {
-                          data.Skip(filestart);
-                          std::ostringstream contentlength;
-                          contentlength << "Content-Length: " << (filesize - filestart);
-                          headers.push_back(contentlength.str());
-                          std::ostringstream contentrange;
-                          contentrange << "Content-Range: bytes " << filestart << "-"
-                             << filesize - 1 << "/" << filesize;
-                          headers.push_back(contentrange.str());
-                          sendHead(client, 206, headers);
-                          if (sendContent == true)
-                             sendFile(client, data);
-                          continue;
-                       }
-                       else
-                       {
-                          headers.push_back("Content-Length: 0");
-                          std::ostringstream contentrange;
-                          contentrange << "Content-Range: bytes */" << filesize;
-                          headers.push_back(contentrange.str());
-                          sendHead(client, 416, headers);
-                          continue;
-                       }
-                    }
-                 }
-              }
+        if (errno == EINTR)
+           continue;
+        _error->Errno("accept", "Couldn't accept client on socket %d", sock);
+        _error->DumpErrors(std::cerr);
+        return 6;
+      }
 
-              addFileHeaders(headers, data);
-              sendHead(client, 200, headers);
-              if (sendContent == true)
-                 sendFile(client, data);
-           }
-           else if (DirectoryExists(filename) == true)
-           {
-              if (filename[filename.length()-1] == '/')
-                 sendDirectoryListing(client, filename, *m, sendContent);
-              else
-                 sendRedirect(client, 301, filename.append("/"), *m, sendContent);
-           }
-           else
-              sendError(client, 404, *m, sendContent);
-        }
+      pthread_attr_t attr;
+      if (pthread_attr_init(&attr) != 0 || pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0)
+      {
+        _error->Errno("pthread_attr", "Couldn't set detach attribute for a fresh thread to handle client %d on socket %d", client, sock);
         _error->DumpErrors(std::cerr);
-        messages.clear();
-        if (closeConnection == true)
-           break;
+        close(client);
+        continue;
       }
 
-      std::clog << "CLOSE client " << client
-               << " on socket " << sock << std::endl;
-      close(client);
+      pthread_t tid;
+      // thats rather dirty, but we need to store the client socket somewhere safe
+      accepted_clients.push_front(client);
+      if (pthread_create(&tid, &attr, &handleClient, &(*accepted_clients.begin())) != 0)
+      {
+        _error->Errno("pthread_create", "Couldn't create a fresh thread to handle client %d on socket %d", client, sock);
+        _error->DumpErrors(std::cerr);
+        close(client);
+        continue;
+      }
    }
    pidfile.Close();
 
index f43df97e315e83fed726e7647accfd5ff2877904..8dc014b9883f229669f34d316b883151ec959a93 100644 (file)
@@ -41,7 +41,7 @@ include $(PROGRAM_H)
 
 # Program for testing udevcdrom
 PROGRAM=aptwebserver
-SLIBS = -lapt-pkg
+SLIBS = -lapt-pkg -lpthread
 LIB_MAKES = apt-pkg/makefile
 SOURCE = aptwebserver.cc
 include $(PROGRAM_H)