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); }
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); }
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(); }
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; }
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(); }