Begin work on new streams API
This commit is contained in:
@@ -35,14 +35,45 @@ std::string_view getFile(std::string_view file) {
|
||||
}
|
||||
}
|
||||
|
||||
#include <set>
|
||||
|
||||
std::set<uWS::HttpResponse<true> *> delayedResponses;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
// create a timer that resumes sockets
|
||||
auto *timer = us_create_timer((us_loop *) uWS::Loop::defaultLoop(), 1, 0);
|
||||
us_timer_set(timer, [](auto *timer) {
|
||||
|
||||
for (auto *x : delayedResponses) {
|
||||
std::cout << "Resuming a response now!" << std::endl;
|
||||
|
||||
// resume should take a string_view!
|
||||
x->resume();
|
||||
}
|
||||
|
||||
delayedResponses.clear();
|
||||
|
||||
}, 1000, 1000);
|
||||
|
||||
uWS::SSLApp({
|
||||
.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("/hello", [](auto *res, auto *req) {
|
||||
}).get("/", [](auto *res, auto *req) {
|
||||
res->writeStatus(uWS::HTTP_200_OK)->write("Hello world!");
|
||||
}).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) {
|
||||
|
||||
std::cout << "Delaying stream now" << std::endl;
|
||||
delayedResponses.insert(res);
|
||||
|
||||
//asyncFetchData(res)
|
||||
return uWS::HTTP_STREAM_PAUSE;
|
||||
|
||||
}, 100);
|
||||
}).get("/:folder/:file", [](auto *res, auto *req) {
|
||||
res->writeStatus(uWS::HTTP_200_OK)->write(getFile((req->getUrl() == "/" ? "/rocket_files/rocket.html" : req->getUrl()).substr(1)));
|
||||
}).listen(3000, [](auto *token) {
|
||||
|
||||
+2
-1
@@ -86,6 +86,7 @@ private:
|
||||
|
||||
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) static_dispatch(us_ssl_socket_ext, us_socket_ext)((SOCKET_TYPE *) s);
|
||||
httpResponseData->offset = 0;
|
||||
httpResponseData->state = 0;
|
||||
|
||||
// route it!
|
||||
typename uWS::HttpContextData<SSL>::UserData userData = {
|
||||
@@ -122,7 +123,7 @@ private:
|
||||
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getExt();
|
||||
|
||||
if (httpResponseData->outStream) {
|
||||
std::string_view chunk = httpResponseData->outStream(httpResponseData->offset);
|
||||
auto [msg_more, chunk] = httpResponseData->outStream(httpResponseData->offset);
|
||||
|
||||
// send, including any buffered up
|
||||
httpResponseData->offset += asyncSocket->mergeDrain(chunk);
|
||||
|
||||
+142
-12
@@ -11,6 +11,12 @@ namespace uWS {
|
||||
/* Some pre-defined status constants to use with writeStatus */
|
||||
const char *HTTP_200_OK = "200 OK";
|
||||
|
||||
/* Return this from a stream callback to signal pause */
|
||||
const std::pair<bool, std::string_view> HTTP_STREAM_PAUSE = {false, std::string_view(nullptr, 0)};
|
||||
|
||||
/* Return this from a stream callback to signal FIN */
|
||||
const std::pair<bool, std::string_view> HTTP_STREAM_FIN = {false, std::string_view((const char *) 1, 0)};
|
||||
|
||||
template <bool SSL>
|
||||
struct HttpResponse : public AsyncSocket<SSL> {
|
||||
private:
|
||||
@@ -47,6 +53,16 @@ private:
|
||||
public:
|
||||
/* Write the HTTP status */
|
||||
HttpResponse *writeStatus(std::string_view status) {
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
/* Do not allow writing more than one status */
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_STATUS_SENT) {
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Update status */
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_STATUS_SENT;
|
||||
|
||||
AsyncSocket<SSL>::write("HTTP/1.1 ", 9);
|
||||
AsyncSocket<SSL>::write(status.data(), status.length());
|
||||
AsyncSocket<SSL>::write("\r\n", 2);
|
||||
@@ -62,24 +78,138 @@ public:
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Write an HTTP header with unsigned int value */
|
||||
HttpResponse *writeHeader(std::string_view key, unsigned int value) {
|
||||
AsyncSocket<SSL>::write(key.data(), key.length());
|
||||
AsyncSocket<SSL>::write(": ", 2);
|
||||
writeUnsigned(value);
|
||||
AsyncSocket<SSL>::write("\r\n", 2);
|
||||
return this;
|
||||
}
|
||||
|
||||
/* Resume response streaming as far as possible */
|
||||
void resume(std::string_view chunk = {}) {
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
/* Do nothing if not even paused */
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove paused status */
|
||||
httpResponseData->state &= ~HttpResponseData<SSL>::HTTP_PAUSED_STREAM_OUT;
|
||||
|
||||
int written = AsyncSocket<SSL>::write(chunk.data(), chunk.length(), true);
|
||||
|
||||
if (written == chunk.length()) {
|
||||
// pull a new chunk from the callback (basically call onWritable)
|
||||
}
|
||||
|
||||
// no, basically just write this off and if all written, call streamOut callback
|
||||
}
|
||||
|
||||
/* Attach a write handler for sending data. Length must be specified up front and chunks might be read more than once */
|
||||
void write(std::function<std::string_view(int)> cb, int length) {
|
||||
std::string_view chunk = cb(0);
|
||||
AsyncSocket<SSL>::write("Content-Length: ", 16);
|
||||
writeUnsigned(chunk.length());
|
||||
AsyncSocket<SSL>::write("\r\n\r\n", 4);
|
||||
|
||||
// chunk = stream(int offset) is current interface
|
||||
|
||||
// we need more information such as:
|
||||
|
||||
// 1. (RETURN) was the short response of data meant to be msg_more-d? basically: should the stream be called again? needed to allow expose of msg_more to streamer!
|
||||
|
||||
// 2. (RETURN) what data are we returning, or are we returning no data (pause signal)
|
||||
|
||||
// 3. (RETURN) how do we signal FIN for length-less data? return a different string_view with 0 length? HTTP_STREAM_FIN?
|
||||
|
||||
// 4. (INPUT ARGS) how much has been acked of what we send? int offset is an indication? what about indicating ONLY the ack?
|
||||
|
||||
|
||||
void write(std::function<std::pair<bool, std::string_view>(int)> cb, int length = 0) {
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
/* Do not allow write if already called */
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Write 200 OK if not already written any status */
|
||||
writeStatus(HTTP_200_OK);
|
||||
|
||||
/* Update status */
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
|
||||
if (length) {
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_KNOWN_STREAM_OUT_SIZE;
|
||||
}
|
||||
|
||||
// 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
|
||||
// also add bool isAck to writable stream to be treated as ack signalling (return is never mind)
|
||||
|
||||
// we need resume() to resume sending (very simple to just call the onWritable callback again)
|
||||
// returning less than the kernel buffer will pause the streaming, so a return of 0 length is a pause
|
||||
// returning less than but still something should call the callback again!
|
||||
|
||||
// with no length, it makes sense to use chunked transfer? keep alive
|
||||
|
||||
// if we have 0 length we fall back to connection close!
|
||||
// return uWS::HTTP_STREAM_FIN to signal closure
|
||||
|
||||
|
||||
|
||||
|
||||
// this check should be its own shared function?
|
||||
auto [msg_more, chunk] = cb(0);
|
||||
|
||||
|
||||
if (length) {
|
||||
/* Rely on FIN to signal end if we do not pass any length */
|
||||
AsyncSocket<SSL>::write("Content-Length: ", 16);
|
||||
writeUnsigned(chunk.length());
|
||||
AsyncSocket<SSL>::write("\r\n", 2);
|
||||
}
|
||||
|
||||
/* HTTP body separator */
|
||||
AsyncSocket<SSL>::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<SSL>::write(chunk.data(), chunk.length(), true)) < length) {
|
||||
std::cout << "HttpResponse::write failed to write everything" << std::endl;
|
||||
getHttpResponseData()->offset = written;
|
||||
getHttpResponseData()->outStream = cb;
|
||||
httpResponseData->offset = written;
|
||||
httpResponseData->outStream = cb;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convenience function for static data (I don't like this one!) */
|
||||
void write(std::string_view data) {
|
||||
write([data](int offset) {
|
||||
return data.substr(offset);
|
||||
}, data.length());
|
||||
/* Convenience function for static data */
|
||||
void write(std::string_view data, std::function<void(std::string_view)> cb = nullptr) {
|
||||
if (cb) {
|
||||
// todo: think about how the stream will signal done (streams API challenge overall)
|
||||
write([data](int offset, int length) {
|
||||
|
||||
// if offset == length then we know it is end
|
||||
|
||||
// what if we want to return both fin and data? can't do that
|
||||
|
||||
|
||||
|
||||
return {false, data.substr(offset)};
|
||||
}, data.length());
|
||||
} else {
|
||||
// requires no extra alloc
|
||||
write([data](int offset) {
|
||||
return {false, data.substr(offset)};
|
||||
}, data.length());
|
||||
}
|
||||
}
|
||||
|
||||
/* Attach a read handler for data sent. Will be called with a chunk of size 0 when FIN */
|
||||
|
||||
+15
-1
@@ -11,10 +11,24 @@ namespace uWS {
|
||||
|
||||
template <bool SSL>
|
||||
struct HttpResponseData : HttpParser, AsyncSocketData<SSL> {
|
||||
template <bool> friend struct HttpResponse;
|
||||
template <bool> friend struct HttpContext;
|
||||
private:
|
||||
/* Bits of status */
|
||||
enum {
|
||||
HTTP_STATUS_SENT = 1,
|
||||
HTTP_WRITE_CALLED = 2,
|
||||
HTTP_KNOWN_STREAM_OUT_SIZE = 4,
|
||||
HTTP_PAUSED_STREAM_OUT = 8
|
||||
};
|
||||
|
||||
std::function<void(std::string_view)> inStream;
|
||||
std::function<std::string_view(int)> outStream;
|
||||
std::function<std::pair<bool, std::string_view>(int)> outStream;
|
||||
/* Outgoing offset */
|
||||
int offset = 0;
|
||||
|
||||
/* Current state (content-length sent, status sent, write called, etc */
|
||||
int state = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user