#ifndef HTTPRESPONSE_H #define HTTPRESPONSE_H /* An HttpResponse is the channel on which you send back a response */ #include "AsyncSocket.h" #include "HttpResponseData.h" namespace uWS { /* Some pre-defined status constants to use with writeStatus */ const char *HTTP_200_OK = "200 OK"; template struct HttpResponse : public AsyncSocket { private: HttpResponseData *getHttpResponseData() { return (HttpResponseData *) AsyncSocket::getExt(); } int u32toaHex(uint32_t value, char *dst) { char palette[] = "0123456789abcdef"; char temp[10]; char *p = temp; do { *p++ = palette[value % 16]; value /= 16; } while (value > 0); int ret = p - temp; do { *dst++ = *--p; } while (p != temp); return ret; } /* Write an unsigned 32-bit integer in hex */ void writeUnsignedHex(unsigned int value) { char buf[10]; int length = u32toaHex(value, buf); /* For now we do this copy */ AsyncSocket::write(buf, length); } int u32toa(uint32_t value, char *dst) { char temp[10]; char *p = temp; do { *p++ = (char) (value % 10) + '0'; value /= 10; } while (value > 0); int ret = p - temp; do { *dst++ = *--p; } while (p != temp); return ret; } /* Write an unsigned 32-bit integer */ void writeUnsigned(unsigned int value) { char buf[10]; int length = u32toa(value, buf); /* For now we do this copy */ AsyncSocket::write(buf, length); } public: /* Write the HTTP status */ HttpResponse *writeStatus(std::string_view status) { HttpResponseData *httpResponseData = getHttpResponseData(); /* Do not allow writing more than one status */ if (httpResponseData->state & HttpResponseData::HTTP_STATUS_SENT) { return this; } /* Update status */ httpResponseData->state |= HttpResponseData::HTTP_STATUS_SENT; AsyncSocket::write("HTTP/1.1 ", 9); AsyncSocket::write(status.data(), status.length()); AsyncSocket::write("\r\n", 2); return this; } /* Write an HTTP header with string value */ HttpResponse *writeHeader(std::string_view key, std::string_view value) { AsyncSocket::write(key.data(), key.length()); AsyncSocket::write(": ", 2); AsyncSocket::write(value.data(), value.length()); AsyncSocket::write("\r\n", 2); return this; } /* Write an HTTP header with unsigned int value */ HttpResponse *writeHeader(std::string_view key, unsigned int value) { AsyncSocket::write(key.data(), key.length()); AsyncSocket::write(": ", 2); writeUnsigned(value); AsyncSocket::write("\r\n", 2); return this; } /* End the response with an optional data chunk */ void end(std::string_view data = {}) { HttpResponseData *httpResponseData = getHttpResponseData(); if (httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED) { /* Do not allow sending 0 chunk here */ if (data.length()) { AsyncSocket::write("\r\n", 2); writeUnsignedHex(data.length()); AsyncSocket::write("\r\n", 2); AsyncSocket::write(data.data(), data.length()); } /* Terminating 0 chunk */ AsyncSocket::write("\r\n0\r\n\r\n", 7); } else { /* We have a known send size */ AsyncSocket::write("Content-Length: ", 16); writeUnsigned(data.length()); AsyncSocket::write("\r\n\r\n", 4); AsyncSocket::write(data.data(), data.length()); } } /* Write parts of the response in chunking fashion */ bool write(std::string_view data) { /* Do not allow sending 0 chunks, they mark end of response */ if (!data.length()) { return true; // are we corked still? } HttpResponseData *httpResponseData = getHttpResponseData(); if (!(httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED)) { writeHeader("Transfer-Encoding", "chunked"); httpResponseData->state |= HttpResponseData::HTTP_WRITE_CALLED; } AsyncSocket::write("\r\n", 2); writeUnsignedHex(data.length()); AsyncSocket::write("\r\n", 2); AsyncSocket::write(data.data(), data.length()); // are we corked still? return true; } // we really want tryEnd(data) integer to try and stream something with known size // write/tryWrite called first should enter into chunked? // tryWrite(char *, length) int // write(char *, length) bool /* Attach an output stream function. Chunks may be read more than once. Negative offset mean broken stream */ void writeOldRemoveMe(std::function(int)> cb, int length = 0) { HttpResponseData *httpResponseData = getHttpResponseData(); /* Do not allow write if already called */ if (httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED) { return; } /* Write 200 OK if not already written any status */ writeStatus(HTTP_200_OK); /* Update status */ httpResponseData->state |= HttpResponseData::HTTP_WRITE_CALLED; if (length) { httpResponseData->state |= HttpResponseData::HTTP_KNOWN_STREAM_OUT_SIZE; /* Rely on FIN to signal end if we do not pass any length */ AsyncSocket::write("Content-Length: ", 16); writeUnsigned(length); AsyncSocket::write("\r\n", 2); } /* HTTP body separator */ AsyncSocket::write("\r\n", 2); for (int offset = 0; length == 0 || offset < length; ) { /* Pull a chunk from stream */ auto [msg_more, chunk] = cb(offset); /* Handle PAUSE and FIN */ if (chunk.length() == 0) { /* FIN */ if (chunk.data()) { /* Try flush and shut down */ AsyncSocket::uncork(); if (!AsyncSocket::hasBuffer()) { /* Uncork finished with no buffered data */ AsyncSocket::shutdown(); } else { /* Let it shut down when drained */ httpResponseData->state |= HttpResponseData::HTTP_ENDED_STREAM_OUT; } } else { /* Disable timeout and mark this stream as paused (important to silence spurious onWritable events) */ AsyncSocket::timeout(0); httpResponseData->state |= HttpResponseData::HTTP_PAUSED_STREAM_OUT; // forgot about this path, if we sent things off and then ended up pausing! httpResponseData->offset = offset; httpResponseData->outStream = cb; } return; } /* Send off the chunk */ int written = AsyncSocket::write(chunk.data(), chunk.length(), true); /* If we failed to send everything, exit */ if (written < chunk.length()) { httpResponseData->offset = offset + written; httpResponseData->outStream = cb; return; } offset += written; } } /* Attach a read handler for data sent. Will be called with a chunk of size 0 when FIN */ void read(std::function handler) { HttpResponseData *data = getHttpResponseData(); data->inStream = handler; } }; } #endif // HTTPRESPONSE_H