From 178dae70c2247442bc87a9bd50beb2fccecba25b Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Thu, 20 Sep 2018 23:14:50 +0200 Subject: [PATCH] Support responses with no specified length --- main.cpp | 15 ++++++-- src/AsyncSocket.h | 1 + src/HttpResponse.h | 85 ++++++++++++++++++++++++++++++---------------- 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/main.cpp b/main.cpp index db1403d..f0bd86e 100644 --- a/main.cpp +++ b/main.cpp @@ -37,7 +37,7 @@ std::string_view getFile(std::string_view file) { #include -std::set *> delayedResponses; +std::set *> delayedResponses; int main(int argc, char **argv) { @@ -56,13 +56,22 @@ int main(int argc, char **argv) { }, 1000, 1000); - uWS::SSLApp({ + uWS::/*SSL*/App(/*{ .key_file_name = "/home/alexhultman/uWebSockets/misc/ssl/key.pem", .cert_file_name = "/home/alexhultman/uWebSockets/misc/ssl/cert.pem", .dh_params_file_name = "/home/alexhultman/dhparams.pem", .passphrase = "1234" - }).get("/", [](auto *res, auto *req) { + }*/).get("/", [](auto *res, auto *req) { res->writeStatus(uWS::HTTP_200_OK)->write("Hello world!"); + }).get("/endless", [](auto *res, auto *req) { + /* This route doesn't specify any length and only returns 1 char per chunk */ + res->write([](int offset) { + std::string_view data("

Hello, world

"); + if (offset < data.length()) { + return std::make_pair(offset < data.length() - 1, data.substr(offset, 1)); + } + return uWS::HTTP_STREAM_FIN; + }); }).get("/delayed", [](auto *res, auto *req) { /* This route streams back chunks of data in delayed fashion */ res->writeStatus(uWS::HTTP_200_OK)->write([res](int offset) { diff --git a/src/AsyncSocket.h b/src/AsyncSocket.h index ecc0287..1087729 100644 --- a/src/AsyncSocket.h +++ b/src/AsyncSocket.h @@ -35,6 +35,7 @@ public: } /* Write in three levels of prioritization: cork-buffer, syscall, socket-buffer */ + // todo: consider supporting nextLength = UNKNOWN as -1 (more but unknown size) int write(const char *src, int length, bool optionally = false, int nextLength = 0) { LoopData *loopData = getLoopData(); diff --git a/src/HttpResponse.h b/src/HttpResponse.h index e42df5e..e6f27f9 100644 --- a/src/HttpResponse.h +++ b/src/HttpResponse.h @@ -138,8 +138,16 @@ public: 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); + // int tail, int head. tail is what has been sent off, head is where we read from. they can differ - does it matter? // just do resume() and pause() to throttle onwritable calling @@ -156,45 +164,64 @@ public: + // flow below should be: - // this check should be its own shared function? - auto [msg_more, chunk] = cb(0); + // 1. pull a chunk + // 2. check for signals in this chunk and break appropriately + // 3. if no signals, send this chunk + // 4. if sent fine but still has data to write, goto 1 + // 5. if sent less than expected, set offset and callback to httpResponseData and exit + int offset = 0; + while (true) { + // pull chunk + auto [msg_more, chunk] = cb(offset); - if (length) { - /* Rely on FIN to signal end if we do not pass any length */ - AsyncSocket::write("Content-Length: ", 16); - writeUnsigned(chunk.length()); - AsyncSocket::write("\r\n", 2); + // check for signals + if (chunk.length() == 0) { + if (chunk == HTTP_STREAM_FIN.second) { + // flush and FIN + AsyncSocket::uncork(); + us_socket_shutdown((us_socket *) this); + return; + } else { + std::cout << "Paused stream!" << std::endl; + } + + // skip sending optional, yet keep refusing to call onWritable while in paused mode (SSL may poll for writable!) + // we thus need a status: paused to check for before requesting more data (also check for this in resume call!) + return; + } + + // send this chunk + int written = AsyncSocket::write(chunk.data(), chunk.length(), true); + + // if we sent less than expected, always end + if (written < chunk.length()) { + std::cout << "HttpResponse::write failed to write everything" << std::endl; + httpResponseData->offset = offset + written; + httpResponseData->outStream = cb; + } + + // we have more to send or are still not FINed + if (length == 0 || offset + written < length) { + offset += written; + continue; + } + + // when do we get here? + std::cout << "How did we get here?" << std::endl; + break; } - /* HTTP body separator */ - AsyncSocket::write("\r\n", 2); - /* Did the user pause ? */ - if (chunk.length() == 0) { - - // skip sending optional, yet keep refusing to call onWritable while in paused mode (SSL may poll for writable!) - - // we thus need a status: paused to check for before requesting more data (also check for this in resume call!) - - std::cout << "Paused stream!" << std::endl; - return; - } - - /* Write as much as possible, optionally */ - if (int written; (written = AsyncSocket::write(chunk.data(), chunk.length(), true)) < length) { - std::cout << "HttpResponse::write failed to write everything" << std::endl; - httpResponseData->offset = written; - httpResponseData->outStream = cb; - } } /* Convenience function for static data */ void write(std::string_view data, std::function cb = nullptr) { if (cb) { // todo: think about how the stream will signal done (streams API challenge overall) - write([data](int offset, int length) { + write([data](int offset) { // if offset == length then we know it is end @@ -202,12 +229,12 @@ public: - return {false, data.substr(offset)}; + return std::make_pair(false, data.substr(offset));//{false, data.substr(offset)}; }, data.length()); } else { // requires no extra alloc write([data](int offset) { - return {false, data.substr(offset)}; + return std::make_pair(false, data.substr(offset));//{false, data.substr(offset)}; }, data.length()); } }