static void myaction(HttpConn *conn) { HttpQueue *q; q = conn->writeq; /* Set the HTTP response status */ httpSetStatus(conn, 200); /* Add desired headers. "Set" will overwrite, add will create if not already defined. */ httpAddHeaderString(conn, "Content-Type", "text/plain"); httpSetHeaderString(conn, "Cache-Control", "no-cache"); httpWrite(q, "<html><title>simpleAction</title><body>\r\n"); httpWrite(q, "<p>Name: %s</p>\n", httpGetParam(conn, "name", "-")); httpWrite(q, "<p>Address: %s</p>\n", httpGetParam(conn, "address", "-")); httpWrite(q, "</body></html>\r\n"); /* Call finalize output and close the request. Delay closing if you want to do asynchronous output and close later. */ httpFinalize(conn); #if POSSIBLE /* Useful things to do in actions */ httpRedirect(conn, 302, "/other-uri"); httpError(conn, 409, "My message : %d", 5); #endif }
/* See if there is acceptable cached content for this request. If so, return true. Will setup tx->cacheBuffer as a side-effect if the output should be captured and cached. */ static bool fetchCachedResponse(HttpConn *conn) { HttpTx *tx; MprTime modified, when; cchar *value, *key, *tag; int status, cacheOk, canUseClientCache; tx = conn->tx; /* Transparent caching. Manual caching must manually call httpWriteCached() */ key = makeCacheKey(conn); if ((value = httpGetHeader(conn, "Cache-Control")) != 0 && (scontains(value, "max-age=0") == 0 || scontains(value, "no-cache") == 0)) { mprLog(3, "Client reload. Cache-control header '%s' rejects use of cached content.", value); } else if ((tx->cachedContent = mprReadCache(conn->host->responseCache, key, &modified, 0)) != 0) { /* See if a NotModified response can be served. This is much faster than sending the response. Observe headers: If-None-Match: "ec18d-54-4d706a63" If-Modified-Since: Fri, 04 Mar 2012 04:28:19 GMT Set status to OK when content must be transmitted. */ cacheOk = 1; canUseClientCache = 0; tag = mprGetMD5(key); if ((value = httpGetHeader(conn, "If-None-Match")) != 0) { canUseClientCache = 1; if (scmp(value, tag) != 0) { cacheOk = 0; } } if (cacheOk && (value = httpGetHeader(conn, "If-Modified-Since")) != 0) { canUseClientCache = 1; mprParseTime(&when, value, 0, 0); if (modified > when) { cacheOk = 0; } } status = (canUseClientCache && cacheOk) ? HTTP_CODE_NOT_MODIFIED : HTTP_CODE_OK; mprLog(3, "cacheHandler: Use cached content for %s, status %d", key, status); httpSetStatus(conn, status); httpSetHeader(conn, "Etag", mprGetMD5(key)); httpSetHeader(conn, "Last-Modified", mprFormatUniversalTime(MPR_HTTP_DATE, modified)); return 1; } mprLog(3, "cacheHandler: No cached content for %s", key); return 0; }
static void handleDeleteRequest(HttpQueue *q) { HttpConn *conn; HttpTx *tx; conn = q->conn; tx = conn->tx; assure(tx->filename); assure(tx->fileInfo.checked); if (!tx->fileInfo.isReg) { httpError(conn, HTTP_CODE_NOT_FOUND, "URI not found"); return; } if (mprDeletePath(tx->filename) < 0) { httpError(conn, HTTP_CODE_NOT_FOUND, "Can't remove URI"); return; } httpSetStatus(conn, HTTP_CODE_NO_CONTENT); }
/* This is called to setup for a HTTP PUT request. It is called before receiving the post data via incomingFileData */ static void handlePutRequest(HttpQueue *q) { HttpConn *conn; HttpTx *tx; MprFile *file; char *path; assure(q->pair->queueData == 0); conn = q->conn; tx = conn->tx; assure(tx->filename); assure(tx->fileInfo.checked); path = tx->filename; if (tx->outputRanges) { /* Open an existing file with fall-back to create */ if ((file = mprOpenFile(path, O_BINARY | O_WRONLY, 0644)) == 0) { if ((file = mprOpenFile(path, O_CREAT | O_TRUNC | O_BINARY | O_WRONLY, 0644)) == 0) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Can't create the put URI"); return; } } else { mprSeekFile(file, SEEK_SET, 0); } } else { if ((file = mprOpenFile(path, O_CREAT | O_TRUNC | O_BINARY | O_WRONLY, 0644)) == 0) { httpError(conn, HTTP_CODE_INTERNAL_SERVER_ERROR, "Can't create the put URI"); return; } } if (!tx->fileInfo.isReg) { httpSetHeader(conn, "Location", conn->rx->uri); } httpSetStatus(conn, tx->fileInfo.isReg ? HTTP_CODE_NO_CONTENT : HTTP_CODE_CREATED); q->pair->queueData = (void*) file; }
/* Parse the CGI output headers. Sample CGI program output: Content-type: text/html <html..... */ static bool parseCgiHeaders(Cgi *cgi, HttpPacket *packet) { HttpConn *conn; MprBuf *buf; char *endHeaders, *headers, *key, *value; ssize blen; int len; conn = cgi->conn; value = 0; buf = packet->content; headers = mprGetBufStart(buf); blen = mprGetBufLength(buf); /* Split the headers from the body. Add null to ensure we can search for line terminators. */ len = 0; if ((endHeaders = sncontains(headers, "\r\n\r\n", blen)) == NULL) { if ((endHeaders = sncontains(headers, "\n\n", blen)) == NULL) { if (mprGetCmdFd(cgi->cmd, MPR_CMD_STDOUT) >= 0 && strlen(headers) < ME_MAX_HEADERS) { /* Not EOF and less than max headers and have not yet seen an end of headers delimiter */ return 0; } } len = 2; } else { len = 4; } if (endHeaders > buf->end) { assert(endHeaders <= buf->end); return 0; } if (endHeaders) { endHeaders[len - 1] = '\0'; endHeaders += len; } /* Want to be tolerant of CGI programs that omit the status line. */ if (strncmp((char*) buf->start, "HTTP/1.", 7) == 0) { if (!parseFirstCgiResponse(cgi, packet)) { /* httpError already called */ return 0; } } if (endHeaders && strchr(mprGetBufStart(buf), ':')) { while (mprGetBufLength(buf) > 0 && buf->start[0] && (buf->start[0] != '\r' && buf->start[0] != '\n')) { if ((key = getCgiToken(buf, ":")) == 0) { key = "Bad Header"; } value = getCgiToken(buf, "\n"); while (isspace((uchar) *value)) { value++; } len = (int) strlen(value); while (len > 0 && (value[len - 1] == '\r' || value[len - 1] == '\n')) { value[len - 1] = '\0'; len--; } key = slower(key); if (strcmp(key, "location") == 0) { cgi->location = value; } else if (strcmp(key, "status") == 0) { httpSetStatus(conn, atoi(value)); } else if (strcmp(key, "content-type") == 0) { httpSetHeaderString(conn, "Content-Type", value); } else if (strcmp(key, "content-length") == 0) { httpSetContentLength(conn, (MprOff) stoi(value)); httpSetChunkSize(conn, 0); } else { /* Now pass all other headers back to the client */ key = ssplit(key, ":\r\n\t ", NULL); httpSetHeaderString(conn, key, value); } } buf->start = endHeaders; } return 1; }
static void readFromCgi(Cgi *cgi, int channel) { HttpConn *conn; HttpPacket *packet; HttpTx *tx; HttpQueue *q, *writeq; MprCmd *cmd; ssize nbytes; int err; cmd = cgi->cmd; conn = cgi->conn; tx = conn->tx; q = cgi->readq; writeq = conn->writeq; assert(conn->sock); assert(conn->state > HTTP_STATE_BEGIN); if (tx->finalized) { mprCloseCmdFd(cmd, channel); } while (mprGetCmdFd(cmd, channel) >= 0 && !tx->finalized && writeq->count < writeq->max) { if ((packet = cgi->headers) != 0) { if (mprGetBufSpace(packet->content) < ME_MAX_BUFFER && mprGrowBuf(packet->content, ME_MAX_BUFFER) < 0) { break; } } else if ((packet = httpCreateDataPacket(ME_MAX_BUFFER)) == 0) { break; } nbytes = mprReadCmd(cmd, channel, mprGetBufEnd(packet->content), ME_MAX_BUFFER); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprCloseCmdFd(cmd, channel); break; } else if (nbytes == 0) { mprCloseCmdFd(cmd, channel); break; } else { traceData(cmd, mprGetBufEnd(packet->content), nbytes); mprAdjustBufEnd(packet->content, nbytes); } if (channel == MPR_CMD_STDERR) { mprLog("error cgi", 0, "CGI failed uri=\"%s\", details: %s", conn->rx->uri, mprGetBufStart(packet->content)); httpSetStatus(conn, HTTP_CODE_SERVICE_UNAVAILABLE); cgi->seenHeader = 1; } if (!cgi->seenHeader) { if (!parseCgiHeaders(cgi, packet)) { cgi->headers = packet; return; } cgi->headers = 0; cgi->seenHeader = 1; } if (!tx->finalizedOutput && httpGetPacketLength(packet) > 0) { /* Put the data to the CGI readq, then cgiToBrowserService will take care of it */ httpPutPacket(q, packet); } } }
void espSetStatus(HttpConn *conn, int status) { httpSetStatus(conn, status); }
static void openFileHandler(HttpQueue *q) { HttpRx *rx; HttpTx *tx; HttpRoute *route; HttpConn *conn; MprPath *info; char *date; conn = q->conn; tx = conn->tx; rx = conn->rx; route = rx->route; info = &tx->fileInfo; if (rx->flags & (HTTP_PUT | HTTP_DELETE)) { if (!(route->flags & HTTP_ROUTE_PUT_DELETE_METHODS)) { httpError(q->conn, HTTP_CODE_BAD_METHOD, "The \"%s\" method is not supported by file handler", rx->method); } } else { if (rx->flags & (HTTP_GET | HTTP_HEAD | HTTP_POST)) { if (!(info->valid || info->isDir)) { if (rx->referrer) { mprLog(2, "fileHandler: Cannot find filename %s from referrer %s", tx->filename, rx->referrer); } else { mprLog(2, "fileHandler: Cannot find filename %s", tx->filename); } httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot find %s", rx->uri); } else if (info->valid) { if (!tx->etag) { /* Set the etag for caching in the client */ tx->etag = sfmt("\"%Lx-%Lx-%Lx\"", (int64) info->inode, (int64) info->size, (int64) info->mtime); } } } if (rx->flags & (HTTP_GET | HTTP_HEAD | HTTP_POST) && !conn->error) { if (tx->fileInfo.valid && tx->fileInfo.mtime) { // TODO - OPT could cache this date = httpGetDateString(&tx->fileInfo); httpSetHeader(conn, "Last-Modified", date); } if (httpContentNotModified(conn)) { httpSetStatus(conn, HTTP_CODE_NOT_MODIFIED); httpOmitBody(conn); tx->length = -1; } if (!tx->fileInfo.isReg && !tx->fileInfo.isLink) { httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot locate document: %s", rx->uri); } else if (tx->fileInfo.size > conn->limits->transmissionBodySize) { httpError(conn, HTTP_ABORT | HTTP_CODE_REQUEST_TOO_LARGE, "Http transmission aborted. File size exceeds max body of %,Ld bytes", conn->limits->transmissionBodySize); } else if (!(tx->connector == conn->http->sendConnector)) { /* If using the net connector, open the file if a body must be sent with the response. The file will be automatically closed when the request completes. */ if (!(tx->flags & HTTP_TX_NO_BODY)) { tx->file = mprOpenFile(tx->filename, O_RDONLY | O_BINARY, 0); if (tx->file == 0) { if (rx->referrer) { httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot open document: %s from %s", tx->filename, rx->referrer); } else { httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot open document: %s from %s", tx->filename); } } } } } else if (rx->flags & (HTTP_OPTIONS | HTTP_TRACE)) { if (route->flags & HTTP_ROUTE_PUT_DELETE_METHODS) { httpHandleOptionsTrace(q->conn, "DELETE,GET,HEAD,POST,PUT"); } else { httpHandleOptionsTrace(q->conn, "GET,HEAD,POST"); } } } }
static void readFromCgi(Cgi *cgi, int channel) { HttpConn *conn; HttpPacket *packet; HttpTx *tx; HttpQueue *q, *writeq; MprCmd *cmd; ssize nbytes; int err; cmd = cgi->cmd; conn = cgi->conn; tx = conn->tx; q = cgi->readq; writeq = conn->writeq; assert(conn->sock); assert(conn->state > HTTP_STATE_BEGIN); if (tx->finalized) { mprCloseCmdFd(cmd, channel); } while (mprGetCmdFd(cmd, channel) >= 0 && !tx->finalized && writeq->count < writeq->max) { if ((packet = cgi->headers) != 0) { if (mprGetBufSpace(packet->content) < ME_MAX_BUFFER && mprGrowBuf(packet->content, ME_MAX_BUFFER) < 0) { break; } } else if ((packet = httpCreateDataPacket(ME_MAX_BUFFER)) == 0) { break; } nbytes = mprReadCmd(cmd, channel, mprGetBufEnd(packet->content), ME_MAX_BUFFER); if (nbytes < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { break; } mprTrace(6, "CGI: Gateway read error %d for %s", err, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); mprCloseCmdFd(cmd, channel); break; } else if (nbytes == 0) { mprTrace(6, "CGI: Gateway EOF for %s, pid %d", (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr", cmd->pid); mprCloseCmdFd(cmd, channel); break; } else { mprTrace(6, "CGI: Gateway read %d bytes from %s", nbytes, (channel == MPR_CMD_STDOUT) ? "stdout" : "stderr"); traceData(cmd, mprGetBufEnd(packet->content), nbytes); mprAdjustBufEnd(packet->content, nbytes); } if (channel == MPR_CMD_STDERR) { // FUTURE - should be an option to keep going despite stderr output mprError("CGI: Error for \"%s\"\n\n%s", conn->rx->uri, mprGetBufStart(packet->content)); httpSetStatus(conn, HTTP_CODE_SERVICE_UNAVAILABLE); cgi->seenHeader = 1; } if (!cgi->seenHeader) { if (!parseCgiHeaders(cgi, packet)) { cgi->headers = packet; return; } cgi->headers = 0; cgi->seenHeader = 1; } if (!tx->finalizedOutput && httpGetPacketLength(packet) > 0) { /* Put the data to the CGI readq, then cgiToBrowserService will take care of it */ httpPutPacket(q, packet); } } }
/* Run an action (may yield) */ static int runAction(HttpConn *conn) { HttpRx *rx; HttpRoute *route; EspRoute *eroute; EspReq *req; EspAction action; rx = conn->rx; req = conn->reqData; route = rx->route; eroute = route->eroute; assert(eroute); if (eroute->edi && eroute->edi->flags & EDI_PRIVATE) { cloneDatabase(conn); } else { req->edi = eroute->edi; } if (route->sourceName == 0 || *route->sourceName == '\0') { if (eroute->commonController) { (eroute->commonController)(conn); } return 1; } #if !ME_STATIC if (!eroute->combine && (route->update || !mprLookupKey(eroute->actions, rx->target))) { cchar *errMsg, *controllers, *controller; if ((controllers = httpGetDir(route, "CONTROLLERS")) == 0) { controllers = "."; } controllers = mprJoinPath(route->home, controllers); controller = schr(route->sourceName, '$') ? stemplateJson(route->sourceName, rx->params) : route->sourceName; controller = controllers ? mprJoinPath(controllers, controller) : mprJoinPath(route->home, controller); if (espLoadModule(route, conn->dispatcher, "controller", controller, &errMsg) < 0) { if (mprPathExists(controller, R_OK)) { httpError(conn, HTTP_CODE_NOT_FOUND, "%s", errMsg); return 0; } } } #endif /* !ME_STATIC */ assert(eroute->top); action = mprLookupKey(eroute->top->actions, rx->target); if (route->flags & HTTP_ROUTE_XSRF && !(rx->flags & HTTP_GET)) { if (!httpCheckSecurityToken(conn)) { httpSetStatus(conn, HTTP_CODE_UNAUTHORIZED); if (smatch(route->responseFormat, "json")) { httpTrace(conn, "esp.xsrf.error", "error", 0); espRenderString(conn, "{\"retry\": true, \"success\": 0, \"feedback\": {\"error\": \"Security token is stale. Please retry.\"}}"); espFinalize(conn); } else { httpError(conn, HTTP_CODE_UNAUTHORIZED, "Security token is stale. Please reload page."); } return 0; } } if (action) { httpAuthenticate(conn); setupFlash(conn); if (eroute->commonController) { (eroute->commonController)(conn); } if (!httpIsFinalized(conn)) { (action)(conn); } } return 1; }
static int openFileHandler(HttpQueue *q) { HttpRx *rx; HttpTx *tx; HttpConn *conn; MprPath *info; char *date, dbuf[16]; MprHash *dateCache; conn = q->conn; tx = conn->tx; rx = conn->rx; info = &tx->fileInfo; if (conn->error) { return MPR_ERR_CANT_OPEN; } if (rx->flags & (HTTP_GET | HTTP_HEAD | HTTP_POST)) { if (!(info->valid || info->isDir)) { httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot find document"); return 0; } if (!tx->etag) { /* Set the etag for caching in the client */ tx->etag = sfmt("\"%llx-%llx-%llx\"", (int64) info->inode, (int64) info->size, (int64) info->mtime); } if (info->mtime) { dateCache = conn->http->dateCache; if ((date = mprLookupKey(dateCache, itosbuf(dbuf, sizeof(dbuf), (int64) info->mtime, 10))) == 0) { if (!dateCache || mprGetHashLength(dateCache) > 128) { conn->http->dateCache = dateCache = mprCreateHash(0, 0); } date = httpGetDateString(&tx->fileInfo); mprAddKey(dateCache, itosbuf(dbuf, sizeof(dbuf), (int64) info->mtime, 10), date); } httpSetHeaderString(conn, "Last-Modified", date); } if (httpContentNotModified(conn)) { httpSetStatus(conn, HTTP_CODE_NOT_MODIFIED); httpOmitBody(conn); tx->length = -1; } if (!tx->fileInfo.isReg && !tx->fileInfo.isLink) { httpTrace(conn, "request.document.error", "error", "msg: 'Document is not a regular file', filename: '%s'", tx->filename); httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot serve document"); } else if (tx->fileInfo.size > conn->limits->transmissionBodySize) { httpError(conn, HTTP_ABORT | HTTP_CODE_REQUEST_TOO_LARGE, "Http transmission aborted. File size exceeds max body of %'lld bytes", conn->limits->transmissionBodySize); } else if (!(tx->connector == conn->http->sendConnector)) { /* If using the net connector, open the file if a body must be sent with the response. The file will be automatically closed when the request completes. */ if (!(tx->flags & HTTP_TX_NO_BODY)) { tx->file = mprOpenFile(tx->filename, O_RDONLY | O_BINARY, 0); if (tx->file == 0) { if (rx->referrer && *rx->referrer) { httpTrace(conn, "request.document.error", "error", "msg: 'Cannot open document', filename: '%s', referrer: '%s'", tx->filename, rx->referrer); } else { httpTrace(conn, "request.document.error", "error", "msg: 'Cannot open document', filename: '%s'", tx->filename); } httpError(conn, HTTP_CODE_NOT_FOUND, "Cannot open document"); } } } } else if (rx->flags & (HTTP_DELETE | HTTP_OPTIONS | HTTP_PUT)) { ; } else { httpError(conn, HTTP_CODE_BAD_METHOD, "Unsupported method"); } return 0; }