mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
More node:http compatibility (#18339)
Co-authored-by: Ciro Spaciari <ciro.spaciari@gmail.com>
This commit is contained in:
@@ -412,14 +412,14 @@ public:
|
||||
webSocketContext->getExt()->messageHandler = std::move(behavior.message);
|
||||
webSocketContext->getExt()->drainHandler = std::move(behavior.drain);
|
||||
webSocketContext->getExt()->subscriptionHandler = std::move(behavior.subscription);
|
||||
webSocketContext->getExt()->closeHandler = std::move([closeHandler = std::move(behavior.close)](WebSocket<SSL, true, UserData> *ws, int code, std::string_view message) mutable {
|
||||
webSocketContext->getExt()->closeHandler = [closeHandler = std::move(behavior.close)](WebSocket<SSL, true, UserData> *ws, int code, std::string_view message) mutable {
|
||||
if (closeHandler) {
|
||||
closeHandler(ws, code, message);
|
||||
}
|
||||
|
||||
/* Destruct user data after returning from close handler */
|
||||
((UserData *) ws->getUserData())->~UserData();
|
||||
});
|
||||
};
|
||||
webSocketContext->getExt()->pingHandler = std::move(behavior.ping);
|
||||
webSocketContext->getExt()->pongHandler = std::move(behavior.pong);
|
||||
|
||||
@@ -432,8 +432,8 @@ public:
|
||||
webSocketContext->getExt()->maxLifetime = behavior.maxLifetime;
|
||||
webSocketContext->getExt()->compression = behavior.compression;
|
||||
|
||||
/* Calculate idleTimeoutCompnents */
|
||||
webSocketContext->getExt()->calculateIdleTimeoutCompnents(behavior.idleTimeout);
|
||||
/* Calculate idleTimeoutComponents */
|
||||
webSocketContext->getExt()->calculateIdleTimeoutComponents(behavior.idleTimeout);
|
||||
|
||||
httpContext->onHttp("GET", pattern, [webSocketContext, behavior = std::move(behavior)](auto *res, auto *req) mutable {
|
||||
|
||||
@@ -619,6 +619,11 @@ public:
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
TemplatedApp &&setUsingCustomExpectHandler(bool value) {
|
||||
httpContext->getSocketContextData()->usingCustomExpectHandler = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
typedef TemplatedApp<false> App;
|
||||
|
||||
@@ -81,7 +81,7 @@ struct AsyncSocketData {
|
||||
|
||||
}
|
||||
|
||||
/* Or emppty */
|
||||
/* Or empty */
|
||||
AsyncSocketData() = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -43,10 +43,10 @@ private:
|
||||
HttpContext() = delete;
|
||||
|
||||
/* Maximum delay allowed until an HTTP connection is terminated due to outstanding request or rejected data (slow loris protection) */
|
||||
static const int HTTP_IDLE_TIMEOUT_S = 10;
|
||||
static constexpr int HTTP_IDLE_TIMEOUT_S = 10;
|
||||
|
||||
/* Minimum allowed receive throughput per second (clients uploading less than 16kB/sec get dropped) */
|
||||
static const int HTTP_RECEIVE_THROUGHPUT_BYTES = 16 * 1024;
|
||||
static constexpr int HTTP_RECEIVE_THROUGHPUT_BYTES = 16 * 1024;
|
||||
|
||||
us_socket_context_t *getSocketContext() {
|
||||
return (us_socket_context_t *) this;
|
||||
@@ -199,6 +199,8 @@ private:
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE;
|
||||
}
|
||||
|
||||
httpResponseData->fromAncientRequest = httpRequest->isAncient();
|
||||
|
||||
/* Select the router based on SNI (only possible for SSL) */
|
||||
auto *selectedRouter = &httpContextData->router;
|
||||
if constexpr (SSL) {
|
||||
@@ -360,9 +362,8 @@ private:
|
||||
|
||||
/* Handle HTTP write out (note: SSL_read may trigger this spuriously, the app need to handle spurious calls) */
|
||||
us_socket_context_on_writable(SSL, getSocketContext(), [](us_socket_t *s) {
|
||||
|
||||
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
|
||||
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getAsyncSocketData();
|
||||
auto *asyncSocket = reinterpret_cast<AsyncSocket<SSL> *>(s);
|
||||
auto *httpResponseData = reinterpret_cast<HttpResponseData<SSL> *>(asyncSocket->getAsyncSocketData());
|
||||
|
||||
/* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */
|
||||
if (httpResponseData->onWritable) {
|
||||
@@ -371,7 +372,7 @@ private:
|
||||
|
||||
/* We expect the developer to return whether or not write was successful (true).
|
||||
* If write was never called, the developer should still return true so that we may drain. */
|
||||
bool success = httpResponseData->callOnWritable((HttpResponse<SSL> *)asyncSocket, httpResponseData->offset);
|
||||
bool success = httpResponseData->callOnWritable(reinterpret_cast<HttpResponse<SSL> *>(asyncSocket), httpResponseData->offset);
|
||||
|
||||
/* The developer indicated that their onWritable failed. */
|
||||
if (!success) {
|
||||
@@ -398,28 +399,26 @@ private:
|
||||
}
|
||||
|
||||
/* Expect another writable event, or another request within the timeout */
|
||||
((HttpResponse<SSL> *) s)->resetTimeout();
|
||||
reinterpret_cast<HttpResponse<SSL> *>(s)->resetTimeout();
|
||||
|
||||
return s;
|
||||
});
|
||||
|
||||
/* Handle FIN, HTTP does not support half-closed sockets, so simply close */
|
||||
us_socket_context_on_end(SSL, getSocketContext(), [](us_socket_t *s) {
|
||||
((AsyncSocket<SSL> *)s)->uncorkWithoutSending();
|
||||
|
||||
auto *asyncSocket = reinterpret_cast<AsyncSocket<SSL> *>(s);
|
||||
asyncSocket->uncorkWithoutSending();
|
||||
/* We do not care for half closed sockets */
|
||||
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
|
||||
return asyncSocket->close();
|
||||
|
||||
});
|
||||
|
||||
/* Handle socket timeouts, simply close them so to not confuse client with FIN */
|
||||
us_socket_context_on_timeout(SSL, getSocketContext(), [](us_socket_t *s) {
|
||||
|
||||
/* Force close rather than gracefully shutdown and risk confusing the client with a complete download */
|
||||
AsyncSocket<SSL> *asyncSocket = (AsyncSocket<SSL> *) s;
|
||||
AsyncSocket<SSL> *asyncSocket = reinterpret_cast<AsyncSocket<SSL> *>(s);
|
||||
// Node.js by default closes the connection but they emit the timeout event before that
|
||||
HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) asyncSocket->getAsyncSocketData();
|
||||
HttpResponseData<SSL> *httpResponseData = reinterpret_cast<HttpResponseData<SSL> *>(asyncSocket->getAsyncSocketData());
|
||||
|
||||
if (httpResponseData->onTimeout) {
|
||||
httpResponseData->onTimeout((HttpResponse<SSL> *)s, httpResponseData->userData);
|
||||
@@ -495,16 +494,20 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
httpContextData->currentRouter->add(methods, pattern, [handler = std::move(handler), parameterOffsets = std::move(parameterOffsets)](auto *r) mutable {
|
||||
const bool &customContinue = httpContextData->usingCustomExpectHandler;
|
||||
|
||||
httpContextData->currentRouter->add(methods, pattern, [handler = std::move(handler), parameterOffsets = std::move(parameterOffsets), &customContinue](auto *r) mutable {
|
||||
auto user = r->getUserData();
|
||||
user.httpRequest->setYield(false);
|
||||
user.httpRequest->setParameters(r->getParameters());
|
||||
user.httpRequest->setParameterOffsets(¶meterOffsets);
|
||||
|
||||
/* Middleware? Automatically respond to expectations */
|
||||
std::string_view expect = user.httpRequest->getHeader("expect");
|
||||
if (expect.length() && expect == "100-continue") {
|
||||
user.httpResponse->writeContinue();
|
||||
if (!customContinue) {
|
||||
/* Middleware? Automatically respond to expectations */
|
||||
std::string_view expect = user.httpRequest->getHeader("expect");
|
||||
if (expect.length() && expect == "100-continue") {
|
||||
user.httpResponse->writeContinue();
|
||||
}
|
||||
}
|
||||
|
||||
handler(user.httpResponse, user.httpRequest);
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace uWS {
|
||||
template<bool> struct HttpResponse;
|
||||
struct HttpRequest;
|
||||
|
||||
|
||||
template <bool SSL>
|
||||
struct alignas(16) HttpContextData {
|
||||
template <bool> friend struct HttpContext;
|
||||
@@ -52,6 +51,7 @@ private:
|
||||
void *upgradedWebSocket = nullptr;
|
||||
bool isParsingHttp = false;
|
||||
bool rejectUnauthorized = false;
|
||||
bool usingCustomExpectHandler = false;
|
||||
|
||||
/* Used to simulate Node.js socket events. */
|
||||
OnSocketClosedCallback onSocketClosed = nullptr;
|
||||
|
||||
@@ -118,7 +118,7 @@ public:
|
||||
}
|
||||
|
||||
/* if write was called and there was previously no Content-Length header set */
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED && !(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER)) {
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED && !(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER) && !httpResponseData->fromAncientRequest) {
|
||||
|
||||
/* We do not have tryWrite-like functionalities, so ignore optional in this path */
|
||||
|
||||
@@ -482,7 +482,7 @@ public:
|
||||
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER)) {
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER) && !httpResponseData->fromAncientRequest) {
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WRITE_CALLED)) {
|
||||
/* Write mark on first call to write */
|
||||
writeMark();
|
||||
|
||||
@@ -36,7 +36,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
|
||||
using OnAbortedCallback = void (*)(uWS::HttpResponse<SSL>*, void*);
|
||||
using OnTimeoutCallback = void (*)(uWS::HttpResponse<SSL>*, void*);
|
||||
using OnDataCallback = void (*)(uWS::HttpResponse<SSL>* response, const char* chunk, size_t chunk_length, bool, void*);
|
||||
|
||||
|
||||
/* When we are done with a response we mark it like so */
|
||||
void markDone() {
|
||||
onAborted = nullptr;
|
||||
@@ -53,7 +53,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
|
||||
}
|
||||
|
||||
/* Caller of onWritable. It is possible onWritable calls markDone so we need to borrow it. */
|
||||
bool callOnWritable( uWS::HttpResponse<SSL>* response, uint64_t offset) {
|
||||
bool callOnWritable(uWS::HttpResponse<SSL>* response, uint64_t offset) {
|
||||
/* Borrow real onWritable */
|
||||
auto* borrowedOnWritable = std::move(onWritable);
|
||||
|
||||
@@ -100,6 +100,7 @@ struct HttpResponseData : AsyncSocketData<SSL>, HttpParser {
|
||||
/* Current state (content-length sent, status sent, write called, etc */
|
||||
uint8_t state = 0;
|
||||
uint8_t idleTimeout = 10; // default HTTP_TIMEOUT 10 seconds
|
||||
bool fromAncientRequest = false;
|
||||
|
||||
#ifdef UWS_WITH_PROXY
|
||||
ProxyParser proxyParser;
|
||||
|
||||
@@ -115,7 +115,7 @@ public:
|
||||
char header[10];
|
||||
int header_length = (int) protocol::formatMessage<isServer>(header, "", 0, opCode, message.length(), compress, fin);
|
||||
int written = us_socket_write2(0, (struct us_socket_t *)this, header, header_length, message.data(), (int) message.length());
|
||||
|
||||
|
||||
if (written != header_length + (int) message.length()) {
|
||||
/* Buffer up backpressure */
|
||||
if (written > header_length) {
|
||||
@@ -289,7 +289,7 @@ public:
|
||||
);
|
||||
|
||||
WebSocketData *webSocketData = (WebSocketData *) us_socket_ext(SSL, (us_socket_t *) this);
|
||||
|
||||
|
||||
if (!webSocketData->subscriber) { return false; }
|
||||
|
||||
/* Cannot return numSubscribers as this is only for this particular websocket context */
|
||||
|
||||
@@ -82,7 +82,7 @@ public:
|
||||
std::pair<unsigned short, unsigned short> idleTimeoutComponents;
|
||||
|
||||
/* This is run once on start-up */
|
||||
void calculateIdleTimeoutCompnents(unsigned short idleTimeout) {
|
||||
void calculateIdleTimeoutComponents(unsigned short idleTimeout) {
|
||||
unsigned short margin = 4;
|
||||
/* 4, 8 or 16 seconds margin based on idleTimeout */
|
||||
while ((int) idleTimeout - margin * 2 >= margin * 2 && margin < 16) {
|
||||
|
||||
Reference in New Issue
Block a user