Esempio n. 1
0
   virtual void handleRegexRequest(ServiceChannel* ch)
   {
      // send 200 OK
      HttpResponseHeader* h = ch->getResponse()->getHeader();
      h->setStatus(200, "OK");
      //h->setField("Content-Length", 0);
      h->setField("Transfer-Encoding", "chunked");
      h->setField("Connection", "close");
      ch->getResponse()->sendHeader();

      ByteArrayInputStream bais(mRegexContent, strlen(mRegexContent));
      ch->getResponse()->sendBody(&bais);
   }
Esempio n. 2
0
   virtual void handleRegexRequest2(ServiceChannel* ch)
   {
      // send 200 OK
      HttpResponseHeader* h = ch->getResponse()->getHeader();
      h->setStatus(200, "OK");
      //h->setField("Content-Length", 0);
      h->setField("Transfer-Encoding", "chunked");
      h->setField("Connection", "close");
      ch->getResponse()->sendHeader();

      string out = JsonWriter::writeToString(ch->getHandlerInfo());
      ByteArrayInputStream bais(out.c_str(), out.length());
      ch->getResponse()->sendBody(&bais);
   }
Esempio n. 3
0
static void runHttpHeaderTest(TestRunner& tr)
{
   tr.group("HttpHeader");

   tr.test("Bicapitalization");
   {
      // test bicapitalization of http headers
      const char* tests[] = {
         "", "",
         "a", "A",
         "-", "-",
         "a--a", "A--A",
         "-aa-", "-Aa-",
         "-aa", "-Aa",
         "aa-", "Aa-",
         "aaa-zzz", "Aaa-Zzz",
         "ThIs-a-BICaPitAlized-hEADer", "This-A-Bicapitalized-Header",
         "Message-ID", "Message-Id",
         NULL
      };
      for(int i = 0; tests[i] != NULL; i +=2)
      {
         char* bic = strdup(tests[i]);
         HttpHeader::biCapitalize(bic);
         assertStrCmp(bic, tests[i+1]);
         free(bic);
      }
   }
   tr.passIfNoException();

   tr.test("HttpRequestHeader parse");
   {
      HttpRequestHeader header;
      header.setDate();
      header.setMethod("GET");
      header.setPath("/");
      header.setVersion("HTTP/1.1");
      header.setField("host", "localhost:80");
      header.setField("Content-Type", "text/html");
      header.setField("Connection", "close");

      string date;
      string expect;
      expect.append("GET / HTTP/1.1\r\n");
      expect.append("Connection: close\r\n");
      expect.append("Content-Type: text/html\r\n");
      expect.append("Date: ");
      header.getField("Date", date);
      expect.append(date);
      expect.append("\r\n");
      expect.append("Host: localhost:80\r\n");
      expect.append("\r\n");

      string str = header.toString();
      assertStrCmp(str.c_str(), expect.c_str());

      HttpRequestHeader header2;
      header2.parse(str);

      string str2 = header2.toString();
      assertStrCmp(str2.c_str(), expect.c_str());
   }
   tr.passIfNoException();

   tr.test("HttpResponseHeader parse");
   {
      HttpResponseHeader header;
      header.setDate();
      header.setVersion("HTTP/1.1");
      header.setStatus(404, "Not Found");
      header.setField("host", "localhost:80");
      header.setField("Content-Type", "text/html");
      header.setField("Connection", "close");

      string date;
      string expect;
      expect.append("HTTP/1.1 404 Not Found\r\n");
      expect.append("Connection: close\r\n");
      expect.append("Content-Type: text/html\r\n");
      expect.append("Date: ");
      header.getField("Date", date);
      expect.append(date);
      expect.append("\r\n");
      expect.append("Host: localhost:80\r\n");
      expect.append("\r\n");

      string str = header.toString();
      assertStrCmp(str.c_str(), expect.c_str());

      HttpResponseHeader header2;
      header2.parse(str);

      string str2 = header2.toString();
      assertStrCmp(str2.c_str(), expect.c_str());
   }
   tr.passIfNoException();

   tr.test("Multiple fields with same name");
   {
      HttpResponseHeader header;
      header.setDate();
      header.setVersion("HTTP/1.1");
      header.setStatus(404, "Not Found");
      header.setField("host", "localhost:80");
      header.setField("Content-Type", "text/html");
      header.setField("Connection", "close");
      header.addField("Set-Cookie", "cookie1=value1; max-age=0; path=/");
      header.addField("Set-Cookie", "cookie2=value2; max-age=0; path=/");
      header.addField("Set-Cookie", "cookie3=value3; max-age=0; path=/");

      string date;
      string expect;
      expect.append("HTTP/1.1 404 Not Found\r\n");
      expect.append("Connection: close\r\n");
      expect.append("Content-Type: text/html\r\n");
      expect.append("Date: ");
      header.getField("Date", date);
      expect.append(date);
      expect.append("\r\n");
      expect.append("Host: localhost:80\r\n");
      expect.append("Set-Cookie: cookie1=value1; max-age=0; path=/\r\n");
      expect.append("Set-Cookie: cookie2=value2; max-age=0; path=/\r\n");
      expect.append("Set-Cookie: cookie3=value3; max-age=0; path=/\r\n");
      expect.append("\r\n");

      string str = header.toString();
      assertStrCmp(str.c_str(), expect.c_str());

      HttpResponseHeader header2;
      header2.parse(str);

      string str2 = header2.toString();
      assertStrCmp(str2.c_str(), expect.c_str());
   }
   tr.passIfNoException();

   tr.test("hasContentType");
   {
      HttpResponseHeader header;

      header.clearFields();
      header.setField("Content-Type", "text/html");
      assert(header.hasContentType("text/html"));

      header.clearFields();
      header.setField("Content-Type", "text/html; params");
      assert(header.hasContentType("text/html"));

      header.clearFields();
      header.setField("Content-Type", "text/html; params");
      assert(!header.hasContentType("text/plain"));
   }
   tr.passIfNoException();

   tr.ungroup();
}
Esempio n. 4
0
bool SampleService::sendFile(BtpAction* action, FileInfo& fi)
{
   bool rval = false;

   // FIXME: only audio supported at this time

   // FIXME: get content type for file info instead of "extension"
   const char* extension = fi["extension"]->getString();
   if((rval = (strcmp(extension, "mp3") == 0)))
   {
      // ensure file exists, get sample range for media
      File file(fi["path"]->getString());
      Media media;
      BM_ID_SET(media["id"], BM_MEDIA_ID(fi["mediaId"]));
      if((rval = (file->exists() && getSampleRange(media))))
      {
         // set up response header
         HttpResponseHeader* header = action->getResponse()->getHeader();
         header->setField("Content-Type", "audio/mpeg");
         header->setField(
            "Content-Disposition", "attachment; filename=bitmunk-sample.mp3");

         // calculate content length for sample for HTTP/1.0
         if(strcmp(
            action->getRequest()->getHeader()->getVersion(), "HTTP/1.0") == 0)
         {
            // check for cached sample content length
            int64_t contentLength = 0;
            if(media->hasMember("sampleContentLength"))
            {
               contentLength = media["sampleContentLength"]->getUInt64();
            }
            else
            {
               // create time parser to produce sample
               MpegAudioTimeParser matp;
               matp.addTimeSet(
                  media["sampleRange"][0]->getUInt32(),
                  media["sampleRange"][1]->getUInt32());

               // FIXME: build a method into the catalog that will return
               // the content-length for a sample

               // read data, strip id3 tag, parse mp3 data, add new id3 tag
               // get content length
               Id3v2Tag tag(media);
               Id3v2TagWriter stripper(NULL);
               Id3v2TagWriter embedder(
                  &tag, false, tag.getFrameSource(), false);

               FileInputStream fis(file);
               MutatorInputStream strip(&fis, false, &stripper, false);
               MutatorInputStream parse(&strip, false, &matp, false);
               MutatorInputStream embed(&parse, false, &embedder, false);
               int64_t numBytes;
               while((numBytes = embed.skip(file->getLength())) > 0)
               {
                  contentLength += numBytes;
               }
               embed.close();

               if(numBytes != -1)
               {
                  // cache content length
                  media["sampleContentLength"] = contentLength;
               }
            }

            // replace chunked encoding with content length
            header->setField("Connection", "close");
            header->removeField("Transfer-Encoding");
            header->removeField("TE");
            header->setField("Content-Length", contentLength);
         }

         // create time parser to produce sample
         MpegAudioTimeParser matp;
         matp.addTimeSet(
            media["sampleRange"][0]->getUInt32(),
            media["sampleRange"][1]->getUInt32());

         // read data, strip id3 tag, embed new id3 tag, produce sample
         Id3v2Tag tag(media);
         Id3v2TagWriter stripper(NULL);
         Id3v2TagWriter embedder(
            &tag, false, tag.getFrameSource(), false);

         FileInputStream fis(file);
         MutatorInputStream strip(&fis, false, &stripper, false);
         MutatorInputStream parse(&strip, false, &matp, false);
         MutatorInputStream embed(&parse, false, &embedder, false);

         // send sample, remove any special encoding to prevent compression
         action->getRequest()->getHeader()->removeField("Accept-Encoding");
         action->getResponse()->getHeader()->setStatus(200, "OK");
         action->sendResult(&embed);

         // close stream
         embed.close();
      }
   }

   return rval;
}
Esempio n. 5
0
bool SampleService::sendm3u(BtpAction* action, DynamicObject& fileInfos)
{
   bool rval = true;

   // build m3u file
   string content;
   content.append("#EXTM3U\n");

   // create play list
   char temp[22];
   FileInfoIterator i = fileInfos.getIterator();
   while(i->hasNext())
   {
      FileInfo& fi = i->next();
      Media& media = fi["media"];
      int sampleLength =
         media["sampleRange"][1]->getUInt32() -
         media["sampleRange"][0]->getUInt32();
      snprintf(temp, 22, "%u", (sampleLength < 0 ? 0 : sampleLength));

      string artist;
      if(media["contributors"]->hasMember("Performer"))
      {
         artist = media["contributors"]["Performer"][0]["name"]->getString();
      }
      else if(media["contributors"]->hasMember("Artist"))
      {
         artist = media["contributors"]["Artist"][0]["name"]->getString();
      }

      content.append("#EXTINF:");
      content.append(temp);
      content.push_back(',');
      content.append(artist);
      content.append(" - ");
      content.append(media["title"]->getString());
      content.push_back('\n');

      // FIXME: get host from configuration or something
      // needs to be public IP that remote side can access
      DynamicObject vars;
      action->getResourceQuery(vars);
      if(!vars->hasMember("nodeuser"))
      {
         BM_ID_SET(vars["nodeuser"], mNode->getDefaultUserId());
      }

      HttpRequestHeader* header = action->getRequest()->getHeader();
      string host = header->getFieldValue("X-Forwarded-Host");
      if(host.length() == 0)
      {
         host = header->getFieldValue("Host");
      }
      content.append("http://");
      content.append(host);
      content.append("/api/3.0/sales/samples/file/");
      content.append(fi["mediaId"]->getString());
      content.push_back('/');
      content.append(fi["id"]->getString());
      content.push_back('\n');
   }

   // set up response header
   HttpResponseHeader* header = action->getResponse()->getHeader();
   header->setField("Content-Type", "audio/x-mpegurl");
   header->setField(
      "Content-Disposition", "attachment; filename=bitmunk-sample.m3u");

   // create content input stream
   ByteBuffer b(content.length());
   b.put(content.c_str(), content.length(), false);
   ByteArrayInputStream bais(&b);

   // send sample
   action->getResponse()->getHeader()->setStatus(200, "OK");
   action->sendResult(&bais);

   return rval;
}
void ProxyResourceHandler::operator()(BtpAction* action)
{
   // get request host
   HttpRequestHeader* hrh = action->getRequest()->getHeader();
   string host = hrh->getFieldValue("X-Forwarded-Host");
   if(host.length() == 0)
   {
      host = hrh->getFieldValue("Host");
   }

   // find a rule
   Rule* rule = findRule(action, host);
   if(rule == NULL)
   {
      // delegate to rest resource handler
      RestResourceHandler::operator()(action);
   }
   else
   {
      // get URL to proxy or redirect to
      UrlRef url = rule->url;

      // get url host
      string urlHost;
      HttpResponse* res = action->getResponse();
      bool secure = res->getConnection()->isSecure();

      // if URL has no host, reuse incoming host
      if(url->getHost().length() == 0)
      {
         urlHost = host;
      }
      // if URL has no port or uses a default port number, only use URL host
      else if(
         (url->getPort() == 0) ||
         (secure && url->getPort() == 443) ||
         (!secure && url->getPort() == 80))
      {
         urlHost = url->getHost();
      }
      // use URL host and port
      else
      {
         urlHost = url->getHostAndPort();
      }

      // handle 0.0.0.0 (any host) by replacing it with the request host
      if(strncmp(urlHost.c_str(), "0.0.0.0", 8) == 0)
      {
         // 0.0.0.0 is 8 chars long
         urlHost.replace(0, 8, host.substr(0, host.find(':')).c_str());
      }

      // rewrite the request path if it does not match URL path
      string path = hrh->getPath();
      if(strcmp(path.c_str(), url->getPath().c_str()) != 0)
      {
         // check for path wildcard
         if(strcmp(rule->path, "*") == 0)
         {
            // since a wildcard is used, prepend the URL path to the
            // resource (if the url path isn't '/')
            string urlPath = url->getPath();
            if(urlPath.length() > 1)
            {
               path.insert(0, url->getPath().c_str());
            }
         }
         else
         {
            // replace the part of the resource that matched the proxy
            // rule with the rewrite path from the proxy URL
            path.replace(0, strlen(rule->path), url->getPath().c_str());
         }
      }

      // do redirect if appropriate
      if(rule->type == Rule::Redirect)
      {
         // set response code
         HttpResponseHeader* header = res->getHeader();
         if(rule->permanent)
         {
            header->setStatus(301, "Moved Permanently");
         }
         else
         {
            header->setStatus(302, "Found");
         }

         // build new location url
         bool secure = res->getConnection()->isSecure();
         header->setField("Location", StringTools::format("%s://%s%s",
            secure ? "https" : "http", urlHost.c_str(), path.c_str()));
         action->sendResult();
      }
      // do proxy
      else if(rule->type == Rule::Proxy)
      {
         // get client-side request
         HttpRequest* req = action->getRequest();

         // do path rewrite
         hrh->setPath(path.c_str());

         // add X-Forwarded headers
         hrh->appendFieldValue("X-Forwarded-For",
            req->getConnection()->getRemoteAddress()->toString(true).c_str());
         hrh->appendFieldValue("X-Forwarded-Host",
            hrh->getFieldValue("Host").c_str());
         hrh->appendFieldValue("X-Forwarded-Server",
            SocketTools::getHostname().c_str());

         // rewrite host if rule specifies it
         if(rule->rewriteHost)
         {
            hrh->setField("Host", urlHost.c_str());
         }

         // do proxy:
         MO_CAT_INFO(BM_NODE_CAT,
            "ProxyResourceHandler proxying %s%s => %s%s",
            host.c_str(), hrh->getPath(), urlHost.c_str(), path.c_str());
         MO_CAT_DEBUG(BM_NODE_CAT,
            "ProxyResourceHandler request header for %s%s => %s%s:\n%s",
            host.c_str(), hrh->getPath(),
            urlHost.c_str(), path.c_str(),
            hrh->toString().c_str());

         // get a connection
         BtpClient* btpc = mNode->getMessenger()->getBtpClient();
         HttpConnection* conn = btpc->createConnection(false, &(*url));
         if(conn == NULL)
         {
            // send service unavailable
            HttpResponseHeader* header = action->getResponse()->getHeader();
            header->setStatus(503, "Service Unavailable");
            string content =
               "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
               "<html><head>\n"
               "<title>503 Service Unavailable</title>\n"
               "</head><body>\n"
               "<h1>Service Unavailable</h1>\n"
               "<p>The service was not available.</p>\n"
               "</body></html>";
            ByteBuffer b(content.length());
            b.put(content.c_str(), content.length(), false);
            ByteArrayInputStream bais(&b);
            action->sendResult(&bais);
         }
         else
         {
            // proxy the client's request and receive server's header (by
            // writing it into the client's response header)
            HttpResponse* res = action->getResponse();
            if(_proxyHttp(req->getHeader(), req->getConnection(), conn) &&
               conn->receiveHeader(res->getHeader()))
            {
               // proxy the server's response, consider result sent
               _proxyHttp(res->getHeader(), conn, req->getConnection());
               action->setResultSent(true);
            }

            // close connection
            conn->close();

            // clean up
            delete conn;
         }

         if(!action->isResultSent())
         {
            // send exception (client's fault if code < 500)
            ExceptionRef e = Exception::get();
            action->sendException(e, e->getCode() < 500);
         }
      }
   }
}
void HttpConnectionServicer::serviceConnection(Connection* c)
{
   // wrap connection, set default timeouts to 30 seconds
   HttpConnection hc(c, false);
   hc.setReadTimeout(30000);
   hc.setWriteTimeout(30000);

   // monitor connection
   if(!mConnectionMonitor.isNull())
   {
      mConnectionMonitor->beforeServicingConnection(&hc);
   }

   // create request
   HttpRequest* request = hc.createRequest();
   HttpRequestHeader* reqHeader = request->getHeader();

   // create response
   HttpResponse* response = request->createResponse();
   HttpResponseHeader* resHeader = response->getHeader();

   // handle keep-alive (HTTP/1.1 keep-alive is on by default)
   bool keepAlive = true;
   bool noerror = true;
   while(keepAlive && noerror)
   {
      // set defaults
      resHeader->setVersion("HTTP/1.1");
      resHeader->setDate();
      resHeader->setField("Server", mServerName);

      // monitor request waiting
      if(!mConnectionMonitor.isNull())
      {
         mConnectionMonitor->beforeRequest(&hc);
      }

      // receive request header
      if((noerror = request->receiveHeader()))
      {
         // begin new request state
         hc.getRequestState()->beginRequest();

         // monitor received request
         if(!mConnectionMonitor.isNull())
         {
            mConnectionMonitor->beforeServicingRequest(
               &hc, request, response);
         }

         // do request modification
         if(mRequestModifier != NULL)
         {
            mRequestModifier->modifyRequest(request);
         }

         // check http version
         bool version10 = (strcmp(reqHeader->getVersion(), "HTTP/1.0") == 0);
         bool version11 = (strcmp(reqHeader->getVersion(), "HTTP/1.1") == 0);

         // only version 1.0 and 1.1 supported
         if(version10 || version11)
         {
            // set response version according to request version
            resHeader->setVersion(reqHeader->getVersion());

            // use proxy'd host field if one was used
            // else use host field if one was used
            string host;
            if(reqHeader->getField("X-Forwarded-Host", host) ||
               reqHeader->getField("Host", host))
            {
               resHeader->setField("Host", host);
            }

            // get connection header
            string connHeader;
            if(reqHeader->getField("Connection", connHeader))
            {
               if(strcasecmp(connHeader.c_str(), "close") == 0)
               {
                  keepAlive = false;
               }
               else if(strcasecmp(connHeader.c_str(), "keep-alive") == 0)
               {
                  keepAlive = true;
               }
            }
            else if(version10)
            {
               // if HTTP/1.0 and no keep-alive header, keep-alive is off
               keepAlive = false;
            }

            // get request path and normalize it
            const char* inPath = reqHeader->getPath();
            char outPath[strlen(inPath) + 2];
            HttpRequestServicer::normalizePath(inPath, outPath);

            // find appropriate request servicer for path
            HttpRequestServicer* hrs = NULL;

            // find secure/non-secure servicer
            hrs = findRequestServicer(host, outPath, hc.isSecure());
            if(hrs != NULL)
            {
               // service request
               hrs->serviceRequest(request, response);

               // turn off keep-alive if response has close connection field
               if(keepAlive)
               {
                  if(resHeader->getField("Connection", connHeader) &&
                     strcasecmp(connHeader.c_str(), "close") == 0)
                  {
                     keepAlive = false;
                  }
               }

               // if servicer closed connection, turn off keep-alive
               if(keepAlive && c->isClosed())
               {
                  keepAlive = false;
               }
            }
            else
            {
               // no servicer, so send 404 Not Found
               const char* html =
                  "<html><body><h2>404 Not Found</h2></body></html>";
               resHeader->setStatus(404, "Not Found");
               resHeader->setField("Content-Type", "text/html");
               resHeader->setField("Content-Length", 48);
               resHeader->setField("Connection", "close");
               if((noerror = response->sendHeader()))
               {
                  ByteArrayInputStream is(html, 48);
                  noerror = response->sendBody(&is);
               }
            }
         }
         else
         {
            // send 505 HTTP Version Not Supported
            const char* html =
               "<html><body>"
               "<h2>505 HTTP Version Not Supported</h2>"
               "</body></html>";
            resHeader->setStatus(505, "HTTP Version Not Supported");
            resHeader->setField("Content-Type", "text/html");
            resHeader->setField("Content-Length", 65);
            resHeader->setField("Connection", "close");
            if((noerror = response->sendHeader()))
            {
               ByteArrayInputStream is(html, 65);
               noerror = response->sendBody(&is);
            }
         }

         // monitor serviced request
         if(!mConnectionMonitor.isNull())
         {
            mConnectionMonitor->afterServicingRequest(
               &hc, request, response);
         }
      }
      else
      {
         // begin new request state
         hc.getRequestState()->beginRequest();

         // monitor request error
         if(!mConnectionMonitor.isNull())
         {
            mConnectionMonitor->beforeRequestError(
               &hc, request, response);
         }

         // exception occurred while receiving header
         ExceptionRef e = Exception::get();
         // if no header then drop through and close connection
         if(e->isType("monarch.http.NoHeader"))
         {
            keepAlive = false;
         }
         // for bad header or request set 400
         else if(e->isType("monarch.http.BadHeader") ||
            e->isType("monarch.http.BadRequest"))
         {
            // send 400 Bad Request
            const char* html =
               "<html><body><h2>400 Bad Request</h2></body></html>";
            response->getHeader()->setStatus(400, "Bad Request");
            response->getHeader()->setField("Content-Type", "text/html");
            response->getHeader()->setField("Content-Length", 50);
            response->getHeader()->setField("Connection", "close");
            if(response->sendHeader())
            {
               ByteArrayInputStream is(html, 50);
               response->sendBody(&is);
            }
         }
         // if the exception was an interruption, then send a 503
         else if(e->isType("monarch.io.InterruptedException") ||
                 e->isType("monarch.rt.Interrupted"))
         {
            // send 503 Service Unavailable
            const char* html =
               "<html><body><h2>503 Service Unavailable</h2></body></html>";
            resHeader->setStatus(503, "Service Unavailable");
            resHeader->setField("Content-Type", "text/html");
            resHeader->setField("Content-Length", 58);
            resHeader->setField("Connection", "close");
            if((noerror = response->sendHeader()))
            {
               ByteArrayInputStream is(html, 58);
               noerror = response->sendBody(&is);
            }
         }
         // if the exception was not a socket error then send an internal
         // server error response
         else if(!e->isType("monarch.net.Socket", true))
         {
            // send 500 Internal Server Error
            const char* html =
               "<html><body><h2>500 Internal Server Error</h2></body></html>";
            resHeader->setStatus(500, "Internal Server Error");
            resHeader->setField("Content-Type", "text/html");
            resHeader->setField("Content-Length", 60);
            resHeader->setField("Connection", "close");
            if((noerror = response->sendHeader()))
            {
               ByteArrayInputStream is(html, 60);
               noerror = response->sendBody(&is);
            }
         }
         else
         {
            // log socket error
            if(e->getDetails()->hasMember("error"))
            {
               // build error string
               string error;
               DynamicObjectIterator i =
                  e->getDetails()["error"].getIterator();
               while(i->hasNext())
               {
                  error.append(i->next()->getString());
                  if(i->hasNext())
                  {
                     error.push_back(',');
                  }
               }
               MO_CAT_ERROR(MO_HTTP_CAT,
                  "Connection error: ['%s','%s','%s']",
                  e->getMessage(), e->getType(), error.c_str());
            }
            else
            {
               MO_CAT_ERROR(MO_HTTP_CAT,
                  "Connection error: ['%s','%s']",
                  e->getMessage(), e->getType());
            }
         }

         // monitor request error
         if(!mConnectionMonitor.isNull())
         {
            mConnectionMonitor->afterRequestError(
               &hc, request, response, e);
         }
      }

      // monitor request
      if(!mConnectionMonitor.isNull())
      {
         mConnectionMonitor->afterRequest(&hc);
      }

      if(keepAlive && noerror)
      {
         // set keep-alive timeout (defaults to 5 minutes)
         hc.setReadTimeout(1000 * 60 * 5);

         // clear request and response header fields
         reqHeader->clearFields();
         resHeader->clearFields();
         resHeader->clearStatus();
      }
   }

   // clean up request and response
   delete request;
   delete response;

   // monitor connection
   if(!mConnectionMonitor.isNull())
   {
      mConnectionMonitor->afterServicingConnection(&hc);
   }

   // close connection
   hc.close();
}