bool simpleGet(MprTestGroup *gp, cchar *uri, int expectStatus) { HttpConn *conn; int status; if (expectStatus <= 0) { expectStatus = 200; } if (startRequest(gp, "GET", uri) < 0) { return 0; } conn = getConn(gp); httpFinalizeOutput(conn); if (httpWait(conn, HTTP_STATE_COMPLETE, -1) < 0) { return MPR_ERR_CANT_READ; } status = httpGetStatus(gp->conn); tassert(status == expectStatus); if (status != expectStatus) { mprLog("appweb test get", 0, "HTTP response code %d, expected %d", status, expectStatus); return 0; } tassert(httpGetError(gp->conn) != 0); gp->content = httpReadString(gp->conn); tassert(gp->content != NULL); httpDestroyConn(gp->conn); gp->conn = 0; return 1; }
static int processThread(HttpConn *conn, MprEvent *event) { ThreadData *td; cchar *path; char *url; int next; td = mprGetCurrentThread()->data; httpFollowRedirects(conn, !app->nofollow); httpSetTimeout(conn, app->timeout, app->timeout); if (strcmp(app->protocol, "HTTP/1.0") == 0) { httpSetKeepAliveCount(conn, 0); httpSetProtocol(conn, "HTTP/1.0"); } if (app->username) { if (app->password == 0 && !strchr(app->username, ':')) { app->password = getPassword(); } httpSetCredentials(conn, app->username, app->password); } while (!mprShouldDenyNewRequests(conn) && (app->success || app->continueOnErrors)) { if (app->singleStep) waitForUser(); if (app->files && !app->upload) { for (next = 0; (path = mprGetNextItem(app->files, &next)) != 0; ) { /* If URL ends with "/", assume it is a directory on the target and append each file name */ if (app->target[strlen(app->target) - 1] == '/') { url = mprJoinPath(app->target, mprGetPathBase(path)); } else { url = app->target; } app->requestFiles = mprCreateList(-1, MPR_LIST_STATIC_VALUES); mprAddItem(app->requestFiles, path); td->url = url = resolveUrl(conn, url); if (app->verbose) { mprPrintf("putting: %s to %s\n", path, url); } if (doRequest(conn, url, app->requestFiles) < 0) { app->success = 0; break; } } } else { td->url = url = resolveUrl(conn, app->target); if (doRequest(conn, url, app->files) < 0) { app->success = 0; break; } } if (iterationsComplete()) { break; } } httpDestroyConn(conn); finishThread((MprThread*) event->data); return -1; }
/* Convenience method to issue a client http request. Assumes the Mpr and Http services are created and initialized. */ PUBLIC HttpConn *httpRequest(cchar *method, cchar *uri, cchar *data, char **err) { HttpConn *conn; MprDispatcher *dispatcher; ssize len; if (err) { *err = 0; } dispatcher = mprCreateDispatcher("httpRequest", MPR_DISPATCHER_AUTO); mprStartDispatcher(dispatcher); conn = httpCreateConn(NULL, dispatcher); mprAddRoot(conn); /* Open a connection to issue the request. Then finalize the request output - this forces the request out. */ if (httpConnect(conn, method, uri, NULL) < 0) { mprRemoveRoot(conn); httpDestroyConn(conn); *err = sfmt("Cannot connect to %s", uri); return 0; } if (data) { len = slen(data); if (httpWriteBlock(conn->writeq, data, len, HTTP_BLOCK) != len) { *err = sclone("Cannot write request body data"); } } httpFinalizeOutput(conn); if (httpWait(conn, HTTP_STATE_CONTENT, MPR_MAX_TIMEOUT) < 0) { mprRemoveRoot(conn); httpDestroyConn(conn); *err = sclone("No response"); return 0; } mprRemoveRoot(conn); return conn; }
/* function close(): Void */ static EjsObj *http_close(Ejs *ejs, EjsHttp *hp, int argc, EjsObj **argv) { if (hp->conn) { httpFinalize(hp->conn); sendHttpCloseEvent(ejs, hp); httpDestroyConn(hp->conn); // TODO OPT - Better to do this on demand. This consumes a conn until GC. hp->conn = httpCreateConn(ejs->http, NULL, ejs->dispatcher); httpPrepClientConn(hp->conn, 0); httpSetConnNotifier(hp->conn, httpEventChange); httpSetConnContext(hp->conn, hp); } return 0; }
static void connTimeout(HttpConn *conn, MprEvent *mprEvent) { HttpLimits *limits; cchar *event, *msg, *prefix; if (conn->destroyed) { return; } assert(conn->tx); assert(conn->rx); msg = 0; event = 0; limits = conn->limits; assert(limits); if (conn->timeoutCallback) { (conn->timeoutCallback)(conn); } if (!conn->connError) { prefix = (conn->state == HTTP_STATE_BEGIN) ? "Idle connection" : "Request"; if (conn->timeout == HTTP_PARSE_TIMEOUT) { msg = sfmt("%s exceeded parse headers timeout of %lld sec", prefix, limits->requestParseTimeout / 1000); event = "timeout.parse"; } else if (conn->timeout == HTTP_INACTIVITY_TIMEOUT) { msg = sfmt("%s exceeded inactivity timeout of %lld sec", prefix, limits->inactivityTimeout / 1000); event = "timeout.inactivity"; } else if (conn->timeout == HTTP_REQUEST_TIMEOUT) { msg = sfmt("%s exceeded timeout %lld sec", prefix, limits->requestTimeout / 1000); event = "timeout.duration"; } if (conn->state < HTTP_STATE_FIRST) { httpDisconnect(conn); if (msg) { httpTrace(conn, event, "error", "msg:'%s'", msg); } } else { httpError(conn, HTTP_CODE_REQUEST_TIMEOUT, "%s", msg); } } if (httpClientConn(conn)) { httpDestroyConn(conn); } else { httpEnableConnEvents(conn); } }
/* Called to close all connections owned by a service (e.g. ejs) */ PUBLIC void httpStopConnections(void *data) { Http *http; HttpConn *conn; int next; if ((http = HTTP) == 0) { return; } lock(http->connections); for (next = 0; (conn = mprGetNextItem(http->connections, &next)) != 0; ) { if (data == 0 || conn->data == data) { httpDestroyConn(conn); } } unlock(http->connections); }
static int destroyEndpointConnections(HttpEndpoint *endpoint) { HttpConn *conn; Http *http; int next; http = endpoint->http; lock(http); for (next = 0; (conn = mprGetNextItem(http->connections, &next)) != 0; ) { if (conn->endpoint == endpoint) { conn->endpoint = 0; httpDestroyConn(conn); next--; } } unlock(http); return 0; }
/* Accept a new client connection on a new socket. If multithreaded, this will come in on a worker thread dedicated to this connection. This is called from the listen wait handler. */ HttpConn *httpAcceptConn(HttpEndpoint *endpoint, MprEvent *event) { HttpConn *conn; MprSocket *sock; MprDispatcher *dispatcher; MprEvent e; int level; mprAssert(endpoint); mprAssert(event); /* This will block in sync mode until a connection arrives */ if ((sock = mprAcceptSocket(endpoint->sock)) == 0) { if (endpoint->sock->handler) { mprEnableSocketEvents(endpoint->sock, MPR_READABLE); } return 0; } if (endpoint->ssl) { if (mprUpgradeSocket(sock, endpoint->ssl, 1) < 0) { mprCloseSocket(sock, 0); return 0; } } if (endpoint->sock->handler) { /* Re-enable events on the listen socket */ mprEnableSocketEvents(endpoint->sock, MPR_READABLE); } dispatcher = event->dispatcher; if (mprShouldDenyNewRequests()) { mprCloseSocket(sock, 0); return 0; } if ((conn = httpCreateConn(endpoint->http, endpoint, dispatcher)) == 0) { mprCloseSocket(sock, 0); return 0; } conn->notifier = endpoint->notifier; conn->async = endpoint->async; conn->endpoint = endpoint; conn->sock = sock; conn->port = sock->port; conn->ip = sclone(sock->ip); conn->secure = (endpoint->ssl != 0); if (!httpValidateLimits(endpoint, HTTP_VALIDATE_OPEN_CONN, conn)) { conn->endpoint = 0; httpDestroyConn(conn); return 0; } mprAssert(conn->state == HTTP_STATE_BEGIN); httpSetState(conn, HTTP_STATE_CONNECTED); if ((level = httpShouldTrace(conn, HTTP_TRACE_RX, HTTP_TRACE_CONN, NULL)) >= 0) { mprLog(level, "### Incoming connection from %s:%d to %s:%d %s", conn->ip, conn->port, sock->acceptIp, sock->acceptPort, conn->secure ? "(secure)" : ""); } e.mask = MPR_READABLE; e.timestamp = conn->http->now; (conn->ioCallback)(conn, &e); return conn; }
/* Handle IO on the connection. Initially the conn->dispatcher will be set to the server->dispatcher and the first I/O event will be handled on the server thread (or main thread). A request handler may create a new conn->dispatcher and transfer execution to a worker thread if required. */ PUBLIC void httpIO(HttpConn *conn, int eventMask) { MprSocket *sp; sp = conn->sock; if (conn->destroyed) { /* Connection has been destroyed */ return; } if (conn->state < HTTP_STATE_PARSED && mprShouldDenyNewRequests()) { httpDestroyConn(conn); return; } assert(conn->tx); assert(conn->rx); #if DEPRECATED || 1 /* Just IO state asserting */ if (conn->io) { assert(!conn->io); return; } conn->io = 1; #endif if ((eventMask & MPR_WRITABLE) && conn->connectorq) { httpResumeQueue(conn->connectorq); } if (eventMask & MPR_READABLE) { readPeerData(conn); } if (sp->secured && !conn->secure) { conn->secure = 1; if (sp->peerCert) { httpTrace(conn, "connection.ssl", "context", "msg:'Connection secured with peer certificate'," \ "secure:true,cipher:'%s',peerName:'%s',subject:'%s',issuer:'%s',session:'%s'", sp->cipher, sp->peerName, sp->peerCert, sp->peerCertIssuer, sp->session); } else { httpTrace(conn, "connection.ssl", "context", "msg:'Connection secured without peer certificate',secure:true,cipher:'%s',session:'%s'", sp->cipher, sp->session); } if (mprGetLogLevel() >= 5) { mprLog("info http ssl", 5, "SSL State: %s", mprGetSocketState(sp)); } } /* Process one or more complete requests in the packet */ do { /* This is and must be the only place httpProtocol is ever called */ httpProtocol(conn); } while (conn->endpoint && conn->state == HTTP_STATE_COMPLETE && prepForNext(conn)); /* When a request completes, prepForNext will reset the state to HTTP_STATE_BEGIN */ if (conn->state < HTTP_STATE_PARSED && conn->endpoint && (mprIsSocketEof(conn->sock) || (conn->keepAliveCount <= 0))) { httpDestroyConn(conn); } else if (!mprIsSocketEof(conn->sock) && conn->async && !conn->delay) { httpEnableConnEvents(conn); } conn->io = 0; }
/* Accept a new client connection on a new socket. This will come in on a worker thread with a new dispatcher dedicated to this connection. */ PUBLIC HttpConn *httpAcceptConn(HttpEndpoint *endpoint, MprEvent *event) { Http *http; HttpConn *conn; HttpAddress *address; MprSocket *sock; int64 value; assert(event); assert(event->dispatcher); assert(endpoint); sock = event->sock; http = endpoint->http; if (mprShouldDenyNewRequests()) { mprCloseSocket(sock, 0); return 0; } if ((conn = httpCreateConn(endpoint, event->dispatcher)) == 0) { mprCloseSocket(sock, 0); return 0; } conn->notifier = endpoint->notifier; conn->async = endpoint->async; conn->endpoint = endpoint; conn->sock = sock; conn->port = sock->port; conn->ip = sclone(sock->ip); if ((value = httpMonitorEvent(conn, HTTP_COUNTER_ACTIVE_CONNECTIONS, 1)) > conn->limits->connectionsMax) { httpTrace(conn, "connection.accept.error", "error", "msg:'Too many concurrent connections',active:%d,max:%d", (int) value, conn->limits->connectionsMax); httpDestroyConn(conn); return 0; } if (mprGetHashLength(http->addresses) > conn->limits->clientMax) { httpTrace(conn, "connection.accept.error", "error", "msg:'Too many concurrent clients',active:%d,max:%d", mprGetHashLength(http->addresses), conn->limits->clientMax); httpDestroyConn(conn); return 0; } address = conn->address; if (address && address->banUntil) { if (address->banUntil < http->now) { httpTrace(conn, "monitor.ban.stop", "context", "client:'%s'", conn->ip); address->banUntil = 0; } else { if (address->banStatus) { httpError(conn, HTTP_CLOSE | address->banStatus, "Connection refused, client banned: %s", address->banMsg ? address->banMsg : ""); } else { httpDestroyConn(conn); return 0; } } } if (endpoint->ssl) { if (mprUpgradeSocket(sock, endpoint->ssl, 0) < 0) { httpDisconnect(conn); httpTrace(conn, "connection.upgrade.error", "error", "msg:'Cannot upgrade socket. %s'", sock->errorMsg); httpMonitorEvent(conn, HTTP_COUNTER_SSL_ERRORS, 1); httpDestroyConn(conn); return 0; } } assert(conn->state == HTTP_STATE_BEGIN); httpSetState(conn, HTTP_STATE_CONNECTED); httpTrace(conn, "connection.accept.new", "context", "peer:'%s',endpoint:'%s:%d'", conn->ip, sock->acceptIp, sock->acceptPort); event->mask = MPR_READABLE; event->timestamp = conn->http->now; (conn->ioCallback)(conn, event); return conn; }
/* Per-thread execution. Called for main thread and helper threads. */ static void threadMain(void *data, MprThread *tp) { ThreadData *td; HttpConn *conn; cchar *path; char *url; int next, count; td = tp->data; /* Create and start a dispatcher. This ensures that all activity on the connection in this thread will be serialized with respect to all I/O events and httpProtocol work. This also ensures that I/O events will be handled by this thread from httpWait. */ td->dispatcher = mprCreateDispatcher(tp->name, 0); mprStartDispatcher(td->dispatcher); td->conn = conn = httpCreateConn(NULL, td->dispatcher); httpFollowRedirects(conn, !app->nofollow); httpSetTimeout(conn, app->timeout, app->timeout); if (strcmp(app->protocol, "HTTP/1.0") == 0) { httpSetKeepAliveCount(conn, 0); httpSetProtocol(conn, "HTTP/1.0"); } if (app->iterations == 1) { conn->limits->keepAliveMax = 0; } if (app->username) { if (app->password == 0 && !strchr(app->username, ':')) { app->password = getPassword(); } httpSetCredentials(conn, app->username, app->password, app->authType); } for (count = 0; count < app->iterations; count++) { if (mprShouldDenyNewRequests(conn)) { break; } if (!app->success && !app->continueOnErrors) { break; } if (app->singleStep) waitForUser(); if (app->files && !app->upload) { for (next = 0; (path = mprGetNextItem(app->files, &next)) != 0; ) { /* If URL ends with "/", assume it is a directory on the target and append each file name */ if (app->target[strlen(app->target) - 1] == '/') { url = mprJoinPath(app->target, mprGetPathBase(path)); } else { url = app->target; } app->requestFiles = mprCreateList(-1, MPR_LIST_STATIC_VALUES | MPR_LIST_STABLE); mprAddItem(app->requestFiles, path); td->url = url = resolveUrl(conn, url); if (app->verbose) { mprPrintf("putting: %s to %s\n", path, url); } if (doRequest(conn, url, app->requestFiles) < 0) { app->success = 0; break; } } } else { td->url = url = resolveUrl(conn, app->target); if (doRequest(conn, url, app->files) < 0) { app->success = 0; break; } } if (app->verbose > 1) { mprPrintf("."); } } httpDestroyConn(conn); mprDestroyDispatcher(conn->dispatcher); finishThread(tp); }