Fix >2gb streamed HTTP responses
This commit is contained in:
+26
-8
@@ -61,6 +61,15 @@ private:
|
||||
Super::write(buf, length);
|
||||
}
|
||||
|
||||
/* Write an unsigned 64-bit integer */
|
||||
void writeUnsigned64(uint64_t value) {
|
||||
char buf[20];
|
||||
int length = utils::u64toa(value, buf);
|
||||
|
||||
/* For now we do this copy */
|
||||
Super::write(buf, length);
|
||||
}
|
||||
|
||||
/* Write an unsigned 32-bit integer */
|
||||
void writeUnsigned(unsigned int value) {
|
||||
char buf[10];
|
||||
@@ -90,13 +99,13 @@ private:
|
||||
|
||||
/* Returns true on success, indicating that it might be feasible to write more data.
|
||||
* Will start timeout if stream reaches totalSize or write failure. */
|
||||
bool internalEnd(std::string_view data, int totalSize, bool optional, bool allowContentLength = true) {
|
||||
bool internalEnd(std::string_view data, size_t totalSize, bool optional, bool allowContentLength = true) {
|
||||
/* Write status if not already done */
|
||||
writeStatus(HTTP_200_OK);
|
||||
|
||||
/* If no total size given then assume this chunk is everything */
|
||||
if (!totalSize) {
|
||||
totalSize = (int) data.length();
|
||||
totalSize = data.length();
|
||||
}
|
||||
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
@@ -132,7 +141,7 @@ private:
|
||||
if (allowContentLength) {
|
||||
/* Even zero is a valid content-length */
|
||||
Super::write("Content-Length: ", 16);
|
||||
writeUnsigned(totalSize);
|
||||
writeUnsigned64(totalSize);
|
||||
Super::write("\r\n\r\n", 4);
|
||||
} else {
|
||||
Super::write("\r\n", 2);
|
||||
@@ -146,11 +155,20 @@ private:
|
||||
* if it failed to drain any prior failed header writes */
|
||||
|
||||
/* Write as much as possible without causing backpressure */
|
||||
auto [written, failed] = Super::write(data.data(), (int) data.length(), optional);
|
||||
size_t written = 0;
|
||||
bool failed = false;
|
||||
while (written < data.length() && !failed) {
|
||||
/* uSockets only deals with int sizes, so pass chunks of max signed int size */
|
||||
auto writtenFailed = Super::write(data.data() + written, (int) std::min<size_t>(data.length() - written, INT_MAX), optional);
|
||||
|
||||
written += writtenFailed.first;
|
||||
failed = writtenFailed.second;
|
||||
}
|
||||
|
||||
httpResponseData->offset += written;
|
||||
|
||||
/* Success is when we wrote the entire thing without any failures */
|
||||
bool success = (unsigned int) written == data.length() && !failed;
|
||||
bool success = written == data.length() && !failed;
|
||||
|
||||
/* If we are now at the end, start a timeout. Also start a timeout if we failed. */
|
||||
if (!success || httpResponseData->offset == totalSize) {
|
||||
@@ -368,12 +386,12 @@ public:
|
||||
|
||||
/* End the response with an optional data chunk. Always starts a timeout. */
|
||||
void end(std::string_view data = {}) {
|
||||
internalEnd(data, (int) data.length(), false);
|
||||
internalEnd(data, data.length(), false);
|
||||
}
|
||||
|
||||
/* Try and end the response. Returns [true, true] on success.
|
||||
* Starts a timeout in some cases. Returns [ok, hasResponded] */
|
||||
std::pair<bool, bool> tryEnd(std::string_view data, int totalSize = 0) {
|
||||
std::pair<bool, bool> tryEnd(std::string_view data, size_t totalSize = 0) {
|
||||
return {internalEnd(data, totalSize, true), hasResponded()};
|
||||
}
|
||||
|
||||
@@ -446,7 +464,7 @@ public:
|
||||
}
|
||||
|
||||
/* Attach handler for writable HTTP response */
|
||||
HttpResponse *onWritable(fu2::unique_function<bool(int)> &&handler) {
|
||||
HttpResponse *onWritable(fu2::unique_function<bool(size_t)> &&handler) {
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
httpResponseData->onWritable = std::move(handler);
|
||||
|
||||
@@ -43,11 +43,11 @@ private:
|
||||
};
|
||||
|
||||
/* Per socket event handlers */
|
||||
fu2::unique_function<bool(int)> onWritable;
|
||||
fu2::unique_function<bool(size_t)> onWritable;
|
||||
fu2::unique_function<void()> onAborted;
|
||||
fu2::unique_function<void(std::string_view, bool)> inStream; // onData
|
||||
/* Outgoing offset */
|
||||
int offset = 0;
|
||||
size_t offset = 0;
|
||||
|
||||
/* Current state (content-length sent, status sent, write called, etc */
|
||||
int state = 0;
|
||||
|
||||
@@ -60,6 +60,23 @@ inline int u32toa(uint32_t value, char *dst) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline int u64toa(uint64_t value, char *dst) {
|
||||
char temp[20];
|
||||
char *p = temp;
|
||||
do {
|
||||
*p++ = (char) ((value % 10) + '0');
|
||||
value /= 10;
|
||||
} while (value > 0);
|
||||
|
||||
int ret = (int) (p - temp);
|
||||
|
||||
do {
|
||||
*dst++ = *--p;
|
||||
} while (p != temp);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ private:
|
||||
|
||||
/* We store old backpressure since it is unclear whether write drained anything,
|
||||
* however, in case of coming here with 0 backpressure we still need to emit drain event */
|
||||
int backpressure = asyncSocket->getBufferedAmount();
|
||||
unsigned int backpressure = asyncSocket->getBufferedAmount();
|
||||
|
||||
/* Drain as much as possible */
|
||||
asyncSocket->write(nullptr, 0);
|
||||
|
||||
Reference in New Issue
Block a user