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