/* The service callback will be invoked to service outgoing packets on the service queue. It will only be called once all incoming data has been received and then there after when the downstream queues drain sufficiently to absorb more data. This routine may flow control if the downstream stage cannot accept all the file data. It will then be re-called as required to send more data. */ static void outgoingFileService(HttpQueue *q) { HttpConn *conn; HttpTx *tx; HttpPacket *packet; bool usingSend; int rc; conn = q->conn; tx = conn->tx; usingSend = (tx->connector == conn->http->sendConnector); for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (!usingSend && !tx->outputRanges && packet->esize) { if ((rc = prepPacket(q, packet)) < 0) { return; } else if (rc == 0) { mprLog(7, "OutgoingFileService downstream full, putback"); httpPutBackPacket(q, packet); return; } mprLog(7, "OutgoingFileService readData %d", rc); } httpPutPacketToNext(q, packet); } mprLog(7, "OutgoingFileService complete"); }
static void browserToCgiService(HttpQueue *q) { HttpConn *conn; HttpPacket *packet; Cgi *cgi; MprCmd *cmd; MprBuf *buf; ssize rc, len; int err; if ((cgi = q->queueData) == 0) { return; } assert(q == cgi->writeq); cmd = cgi->cmd; assert(cmd); conn = cgi->conn; for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if ((buf = packet->content) == 0) { /* End packet */ continue; } len = mprGetBufLength(buf); rc = mprWriteCmd(cmd, MPR_CMD_STDIN, mprGetBufStart(buf), len); if (rc < 0) { err = mprGetError(); if (err == EINTR) { continue; } else if (err == EAGAIN || err == EWOULDBLOCK) { httpPutBackPacket(q, packet); break; } mprLog(2, "CGI: write to gateway failed for %d bytes, rc %d, errno %d", len, rc, mprGetOsError()); mprCloseCmdFd(cmd, MPR_CMD_STDIN); httpDiscardQueueData(q, 1); httpError(conn, HTTP_CODE_BAD_GATEWAY, "Cannot write body data to CGI gateway"); break; } mprTrace(6, "CGI: browserToCgiService %d/%d, qmax %d", rc, len, q->max); mprAdjustBufStart(buf, rc); if (mprGetBufLength(buf) > 0) { httpPutBackPacket(q, packet); break; } } if (q->count > 0) { /* Wait for writable event so cgiCallback can recall this routine */ mprEnableCmdEvents(cmd, MPR_CMD_STDIN); } else if (conn->rx->eof) { mprCloseCmdFd(cmd, MPR_CMD_STDIN); } else { mprDisableCmdEvents(cmd, MPR_CMD_STDIN); } }
PUBLIC void httpDefaultOutgoingServiceStage(HttpQueue *q) { HttpPacket *packet; for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (!httpWillNextQueueAcceptPacket(q, packet)) { httpPutBackPacket(q, packet); return; } httpPutPacketToNext(q, packet); } }
static void outgoingProxyService(HttpQueue *q) { HttpPacket *packet; for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (packet->flags & HTTP_PACKET_DATA) { if (!httpWillNextQueueAcceptPacket(q, packet)) { mprTrace(7, "OutgoingProxyService downstream full, putback"); httpPutBackPacket(q, packet); return; } } httpPutPacketToNext(q, packet); } }
static void freeNetPackets(HttpQueue *q, ssize bytes) { HttpPacket *packet; ssize len; mprAssert(q->count >= 0); mprAssert(bytes >= 0); while (bytes > 0 && (packet = q->first) != 0) { if (packet->prefix) { len = mprGetBufLength(packet->prefix); len = min(len, bytes); mprAdjustBufStart(packet->prefix, len); bytes -= len; /* Prefixes don't count in the q->count. No need to adjust */ if (mprGetBufLength(packet->prefix) == 0) { packet->prefix = 0; } } if (packet->content) { len = mprGetBufLength(packet->content); len = min(len, bytes); mprAdjustBufStart(packet->content, len); bytes -= len; q->count -= len; mprAssert(q->count >= 0); } if (packet->content == 0 || mprGetBufLength(packet->content) == 0) { /* This will remove the packet from the queue and will re-enable upstream disabled queues. */ httpGetPacket(q); } } }
static void freeSendPackets(HttpQueue *q, MprOff bytes) { HttpPacket *packet; ssize len; assert(q->first); assert(q->count >= 0); assert(bytes >= 0); /* Loop while data to be accounted for and we have not hit the end of data packet There should be 2-3 packets on the queue. A header packet for the HTTP response headers, an optional data packet with packet->esize set to the size of the file, and an end packet with no content. Must leave this routine with the end packet still on the queue and all bytes accounted for. */ while ((packet = q->first) != 0 && !(packet->flags & HTTP_PACKET_END) && bytes > 0) { if (packet->prefix) { len = mprGetBufLength(packet->prefix); len = (ssize) min(len, bytes); mprAdjustBufStart(packet->prefix, len); bytes -= len; /* Prefixes don't count in the q->count. No need to adjust */ if (mprGetBufLength(packet->prefix) == 0) { packet->prefix = 0; } } if (packet->esize) { len = (ssize) min(packet->esize, bytes); packet->esize -= len; packet->epos += len; bytes -= len; assert(packet->esize >= 0); } else if ((len = httpGetPacketLength(packet)) > 0) { /* Header packets come here */ len = (ssize) min(len, bytes); mprAdjustBufStart(packet->content, len); bytes -= len; q->count -= len; assert(q->count >= 0); } if (packet->esize == 0 && httpGetPacketLength(packet) == 0) { /* Done with this packet - consume it */ assert(!(packet->flags & HTTP_PACKET_END)); httpGetPacket(q); } else { break; } } assert(bytes == 0); }
static void outgoingRangeService(HttpQueue *q) { HttpPacket *packet; HttpStream *stream; HttpTx *tx; stream = q->stream; tx = stream->tx; if (!(q->flags & HTTP_QUEUE_SERVICED)) { /* The httpContentNotModified routine can set outputRanges to zero if returning not-modified. */ if (!fixRangeLength(stream, q)) { if (!q->servicing) { httpRemoveQueue(q); } tx->outputRanges = 0; tx->status = HTTP_CODE_OK; } } for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (packet->flags & HTTP_PACKET_DATA) { if ((packet = selectBytes(q, packet)) == 0) { continue; } } else if (packet->flags & HTTP_PACKET_END) { if (tx->rangeBoundary) { httpPutPacketToNext(q, createFinalRangePacket(stream)); } } if (!httpWillNextQueueAcceptPacket(q, packet)) { httpPutBackPacket(q, packet); return; } httpPutPacketToNext(q, packet); } }
/* The service callback will be invoked to service outgoing packets on the service queue. It will only be called once all incoming data has been received and then when the downstream queues drain sufficiently to absorb more data. This routine may flow control if the downstream stage cannot accept all the file data. It will then be re-called as required to send more data. */ static void outgoingFileService(HttpQueue *q) { HttpConn *conn; HttpTx *tx; HttpPacket *packet; bool usingSend; int rc; conn = q->conn; tx = conn->tx; usingSend = (tx->connector == conn->http->sendConnector); for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (!usingSend && !tx->outputRanges && packet->esize) { if ((rc = prepPacket(q, packet)) < 0) { return; } else if (rc == 0) { httpPutBackPacket(q, packet); return; } } httpPutPacketToNext(q, packet); } }
/* Read data. If sync mode, this will block. If async, will never block. Will return what data is available up to the requested size. Returns a count of bytes read. Returns zero if not data. EOF if returns zero and conn->state is > HTTP_STATE_CONTENT. */ PUBLIC ssize httpRead(HttpConn *conn, char *buf, ssize size) { HttpPacket *packet; HttpQueue *q; MprBuf *content; ssize nbytes, len; q = conn->readq; assert(q->count >= 0); assert(size >= 0); VERIFY_QUEUE(q); while (q->count <= 0 && !conn->async && !conn->error && conn->sock && (conn->state <= HTTP_STATE_CONTENT)) { httpServiceQueues(conn); if (conn->sock) { httpWait(conn, 0, MPR_TIMEOUT_NO_BUSY); } } conn->lastActivity = conn->http->now; for (nbytes = 0; size > 0 && q->count > 0; ) { if ((packet = q->first) == 0) { break; } content = packet->content; len = mprGetBufLength(content); len = min(len, size); assert(len <= q->count); if (len > 0) { len = mprGetBlockFromBuf(content, buf, len); assert(len <= q->count); } buf += len; size -= len; q->count -= len; assert(q->count >= 0); nbytes += len; if (mprGetBufLength(content) == 0) { httpGetPacket(q); } } assert(q->count >= 0); if (nbytes < size) { buf[nbytes] = '\0'; } return nbytes; }
/* Event callback. Invoked for incoming web socket messages and other events of interest. */ static void chat_callback(HttpConn *conn, int event, int arg) { HttpPacket *packet; HttpConn *client; Msg *msg; int next; if (event == HTTP_EVENT_READABLE) { packet = httpGetPacket(conn->readq); if (packet->type == WS_MSG_TEXT || packet->type == WS_MSG_BINARY) { for (ITERATE_ITEMS(clients, client, next)) { msg = mprAllocObj(Msg, manageMsg); msg->conn = client; msg->packet = packet; mprCreateEvent(client->dispatcher, "chat", 0, chat, msg, 0); } } } else if (event == HTTP_EVENT_APP_CLOSE) {
static void echo_callback(HttpConn *conn, int event, int arg) { HttpPacket *packet; HttpWebSocket *ws; cchar *data; traceEvent(conn, event, arg); if (event == HTTP_EVENT_READABLE) { packet = httpGetPacket(conn->readq); assure(packet); /* Ignore precedding packets and just trace the last */ if (packet->last) { ws = conn->rx->webSocket; httpSend(conn, "{type: %d, last: %d, length: %d, data: \"%s\"}\n", packet->type, packet->last, ws->messageLength, snclone(mprGetBufStart(packet->content), 10)); } } }
/* Event callback. Invoked for incoming web socket messages and other events of interest. */ static void echo_callback(HttpConn *conn, int event, int arg) { HttpPacket *packet; if (event == HTTP_EVENT_READABLE) { /* Grab the packet off the read queue. */ packet = httpGetPacket(conn->readq); if (packet->type == WS_MSG_TEXT || packet->type == WS_MSG_BINARY) { /* Echo back the contents */ httpSendBlock(conn, packet->type, httpGetPacketStart(packet), httpGetPacketLength(packet), 0); } } else if (event == HTTP_EVENT_APP_CLOSE) { mprLog("info echo", 0, "close event. Status status %d, orderly closed %d, reason %s", arg, httpWebSocketOrderlyClosed(conn), httpGetWebSocketCloseReason(conn)); } else if (event == HTTP_EVENT_ERROR) { mprLog("info echo", 0, "error event"); } }
/* This will be enabled when caching is enabled for the route and there is no acceptable cache data to use. OR - manual caching has been enabled. */ static void outgoingCacheFilterService(HttpQueue *q) { HttpPacket *packet, *data; HttpConn *conn; HttpTx *tx; MprKey *kp; cchar *cachedData; ssize size; int foundDataPacket; conn = q->conn; tx = conn->tx; foundDataPacket = 0; cachedData = 0; if (tx->status < 200 || tx->status > 299) { tx->cacheBuffer = 0; } /* This routine will save cached responses to tx->cacheBuffer. It will also send cached data if the X-SendCache header is present. Normal caching is done by cacheHandler */ if (mprLookupKey(conn->tx->headers, "X-SendCache") != 0) { if (fetchCachedResponse(conn)) { mprLog(3, "cacheFilter: write cached content for '%s'", conn->rx->uri); cachedData = setHeadersFromCache(conn, tx->cachedContent); tx->length = slen(cachedData); } } for (packet = httpGetPacket(q); packet; packet = httpGetPacket(q)) { if (!httpWillNextQueueAcceptPacket(q, packet)) { httpPutBackPacket(q, packet); return; } if (packet->flags & HTTP_PACKET_HEADER) { if (!cachedData && tx->cacheBuffer) { /* Add defined headers to the start of the cache buffer. Separate with a double newline. */ mprPutFmtToBuf(tx->cacheBuffer, "X-Status: %d\n", tx->status); for (kp = 0; (kp = mprGetNextKey(tx->headers, kp)) != 0; ) { mprPutFmtToBuf(tx->cacheBuffer, "%s: %s\n", kp->key, kp->data); } mprPutCharToBuf(tx->cacheBuffer, '\n'); } } else if (packet->flags & HTTP_PACKET_DATA) { if (cachedData) { /* Using X-SendCache. Replace the data with the cached response. */ mprFlushBuf(packet->content); mprPutBlockToBuf(packet->content, cachedData, (ssize) tx->length); } else if (tx->cacheBuffer) { /* Save the response packet to the cache buffer. Will write below in saveCachedResponse. */ size = mprGetBufLength(packet->content); if ((tx->cacheBufferLength + size) < conn->limits->cacheItemSize) { mprPutBlockToBuf(tx->cacheBuffer, mprGetBufStart(packet->content), mprGetBufLength(packet->content)); tx->cacheBufferLength += size; } else { tx->cacheBuffer = 0; mprLog(3, "cacheFilter: Item too big to cache %d bytes, limit %d", tx->cacheBufferLength + size, conn->limits->cacheItemSize); } } foundDataPacket = 1; } else if (packet->flags & HTTP_PACKET_END) { if (cachedData && !foundDataPacket) { /* Using X-SendCache but there was no data packet to replace. So do the write here */ data = httpCreateDataPacket((ssize) tx->length); mprPutBlockToBuf(data->content, cachedData, (ssize) tx->length); httpPutPacketToNext(q, data); } else if (tx->cacheBuffer) { /* Save the cache buffer to the cache store */ saveCachedResponse(conn); } } httpPutPacketToNext(q, packet); } }
PUBLIC void httpSendOutgoingService(HttpQueue *q) { HttpConn *conn; HttpTx *tx; MprFile *file; MprOff written; int errCode; conn = q->conn; tx = conn->tx; conn->lastActivity = conn->http->now; if (tx->finalizedConnector) { return; } if (tx->flags & HTTP_TX_NO_BODY) { httpDiscardQueueData(q, 1); } if ((tx->bytesWritten + q->ioCount) > conn->limits->txBodySize && conn->limits->txBodySize < HTTP_UNLIMITED) { httpLimitError(conn, HTTP_ABORT | HTTP_CODE_REQUEST_TOO_LARGE | ((tx->bytesWritten) ? HTTP_ABORT : 0), "Http transmission aborted. Exceeded max body of %lld bytes", conn->limits->txBodySize); if (tx->bytesWritten) { httpFinalizeConnector(conn); return; } } tx->writeBlocked = 0; if (q->ioIndex == 0) { buildSendVec(q); } /* No need to loop around as send file tries to write as much of the file as possible. If not eof, will always have the socket blocked. */ file = q->ioFile ? tx->file : 0; written = mprSendFileToSocket(conn->sock, file, q->ioPos, q->ioCount, q->iovec, q->ioIndex, NULL, 0); if (written < 0) { errCode = mprGetError(); if (errCode == EAGAIN || errCode == EWOULDBLOCK) { /* Socket full, wait for an I/O event */ tx->writeBlocked = 1; } else { if (errCode != EPIPE && errCode != ECONNRESET && errCode != ECONNABORTED && errCode != ENOTCONN) { httpError(conn, HTTP_ABORT | HTTP_CODE_COMMS_ERROR, "sendConnector: error, errCode %d", errCode); } else { httpDisconnect(conn); } httpFinalizeConnector(conn); } httpTrace(conn, "connection.io.error", "error", "msg:'Connector write error',errno:%d", errCode); } else if (written > 0) { tx->bytesWritten += written; freeSendPackets(q, written); adjustSendVec(q, written); } if (q->first && q->first->flags & HTTP_PACKET_END) { httpFinalizeConnector(conn); httpGetPacket(q); } }
/* Connection callback */ static void webSocketNotify(HttpConn *conn, int event, int arg) { Ejs *ejs; EjsWebSocket *ws; EjsByteArray *ba; EjsAny *data; HttpPacket *packet; MprBuf *content; ssize len; if ((ws = httpGetConnContext(conn)) == 0) { return; } ejs = ws->ejs; if (!ejs->service) { /* Shutting down */ return; } switch (event) { case HTTP_EVENT_STATE: if (arg == HTTP_STATE_CONTENT) { ws->protocol = (char*) httpGetHeader(conn, "Sec-WebSocket-Protocol"); mprTrace(3, "Web socket protocol %s", ws->protocol); onWebSocketEvent(ws, HTTP_EVENT_APP_OPEN, 0, 0); } break; case HTTP_EVENT_READABLE: packet = httpGetPacket(conn->readq); content = packet->content; if (packet->type == WS_MSG_TEXT) { data = ejsCreateStringFromBytes(ejs, mprGetBufStart(content), mprGetBufLength(content)); } else { len = httpGetPacketLength(packet); assert(len > 0); if ((ba = ejsCreateByteArray(ejs, len)) == 0) { return; } memcpy(ba->value, mprGetBufStart(content), len); ejsSetByteArrayPositions(ejs, ba, -1, len); data = ba; } onWebSocketEvent(ws, event, data, packet); break; case HTTP_EVENT_ERROR: if (!ws->error && !ws->closed) { ws->error = 1; onWebSocketEvent(ws, event, 0, 0); ws->closed = 1; onWebSocketEvent(ws, HTTP_EVENT_APP_CLOSE, 0, 0); } break; case HTTP_EVENT_APP_CLOSE: if (!ws->closed) { ws->closed = 1; onWebSocketEvent(ws, event, 0, 0); } break; } }
/* Read data. If sync mode, this will block. If async, will never block. Will return what data is available up to the requested size. Timeout in milliseconds to wait. Set to -1 to use the default inactivity timeout. Set to zero to wait forever. Returns a count of bytes read. Returns zero if no data. EOF if returns zero and conn->state is > HTTP_STATE_CONTENT. */ PUBLIC ssize httpReadBlock(HttpConn *conn, char *buf, ssize size, MprTicks timeout, int flags) { HttpPacket *packet; HttpQueue *q; HttpLimits *limits; MprBuf *content; MprTicks start, delay; ssize nbytes, len; int64 dispatcherMark; q = conn->readq; assert(q->count >= 0); assert(size >= 0); limits = conn->limits; if (flags == 0) { flags = conn->async ? HTTP_NON_BLOCK : HTTP_BLOCK; } if (timeout < 0) { timeout = limits->inactivityTimeout; } else if (timeout == 0) { timeout = MPR_MAX_TIMEOUT; } if (flags & HTTP_BLOCK) { start = conn->http->now; dispatcherMark = mprGetEventMark(conn->dispatcher); while (q->count <= 0 && !conn->error && (conn->state <= HTTP_STATE_CONTENT)) { if (httpRequestExpired(conn, -1)) { break; } delay = min(limits->inactivityTimeout, mprGetRemainingTicks(start, timeout)); httpEnableConnEvents(conn); mprWaitForEvent(conn->dispatcher, delay, dispatcherMark); if (mprGetRemainingTicks(start, timeout) <= 0) { break; } dispatcherMark = mprGetEventMark(conn->dispatcher); } } for (nbytes = 0; size > 0 && q->count > 0; ) { if ((packet = q->first) == 0) { break; } content = packet->content; len = mprGetBufLength(content); len = min(len, size); assert(len <= q->count); if (len > 0) { len = mprGetBlockFromBuf(content, buf, len); assert(len <= q->count); } buf += len; size -= len; q->count -= len; assert(q->count >= 0); nbytes += len; if (mprGetBufLength(content) == 0) { httpGetPacket(q); } if (flags & HTTP_NON_BLOCK) { break; } } assert(q->count >= 0); if (nbytes < size) { buf[nbytes] = '\0'; } return nbytes; }
/* Incoming data acceptance routine. The service queue is used, but not a service routine as the data is processed immediately. Partial data is buffered on the service queue until a correct mime boundary is seen. */ static void incomingUpload(HttpQueue *q, HttpPacket *packet) { HttpConn *conn; HttpRx *rx; MprBuf *content; Upload *up; char *line, *nextTok; ssize count; int done, rc; assert(packet); conn = q->conn; rx = conn->rx; up = q->queueData; if (conn->error) { return; } if (httpGetPacketLength(packet) == 0) { if (up->contentState != HTTP_UPLOAD_CONTENT_END) { httpError(conn, HTTP_CODE_BAD_REQUEST, "Client supplied insufficient upload data"); } httpPutPacketToNext(q, packet); return; } /* Put the packet data onto the service queue for buffering. This aggregates input data incase we don't have a complete mime record yet. */ httpJoinPacketForService(q, packet, 0); packet = q->first; content = packet->content; count = httpGetPacketLength(packet); for (done = 0, line = 0; !done; ) { if (up->contentState == HTTP_UPLOAD_BOUNDARY || up->contentState == HTTP_UPLOAD_CONTENT_HEADER) { /* Parse the next input line */ line = mprGetBufStart(content); if ((nextTok = memchr(line, '\n', mprGetBufLength(content))) == 0) { /* Incomplete line */ break; } *nextTok++ = '\0'; mprAdjustBufStart(content, (int) (nextTok - line)); line = strim(line, "\r", MPR_TRIM_END); } switch (up->contentState) { case HTTP_UPLOAD_BOUNDARY: if (processUploadBoundary(q, line) < 0) { done++; } break; case HTTP_UPLOAD_CONTENT_HEADER: if (processUploadHeader(q, line) < 0) { done++; } break; case HTTP_UPLOAD_CONTENT_DATA: rc = processUploadData(q); if (rc < 0) { done++; } if (httpGetPacketLength(packet) < up->boundaryLen) { /* Incomplete boundary - return to get more data */ done++; } break; case HTTP_UPLOAD_CONTENT_END: done++; break; } } q->count -= (count - httpGetPacketLength(packet)); assert(q->count >= 0); if (httpGetPacketLength(packet) == 0) { /* Quicker to remove the buffer so the packets don't have to be joined the next time */ httpGetPacket(q); } else { /* Compact the buffer to prevent memory growth. There is often residual data after the boundary for the next block. */ if (packet != rx->headerPacket) { mprCompactBuf(content); } } }