Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,14 @@ struct TemplatedApp {
return std::move(*this);
}

/* While on, a route is registered with its pattern as a literal path. See HttpRouter::setLiteral. */
TemplatedApp &&literalRoutes(bool on) {
if (httpContext) {
httpContext->getSocketContextData()->router.setLiteral(on);
}
return std::move(*this);
}

TemplatedApp &&get(std::string_view pattern, MoveOnlyFunction<void(HttpResponse<SSL> *, HttpRequest *)> &&handler) {
if (httpContext) {
httpContext->onHttp("GET", pattern, std::move(handler));
Expand Down
2 changes: 2 additions & 0 deletions packages/bun-uws/src/Http2App.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ struct H2App {
bool drain() { return http2Context->drain(); }

void clearRoutes() { http2Context->clearRoutes(); }
/* While on, a route is registered with its pattern as a literal path. See HttpRouter::setLiteral. */
void literalRoutes(bool on) { http2Context->router.setLiteral(on); }
/* GOAWAY + close every connection. */
void close() { http2Context->closeAll(); }
void *getNativeHandle() { return http2Context; }
Expand Down
2 changes: 2 additions & 0 deletions packages/bun-uws/src/Http3App.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ struct H3App {
void clearRoutes() {
http3Context->getContextData()->router = decltype(http3Context->getContextData()->router){};
}
/* While on, a route is registered with its pattern as a literal path. See HttpRouter::setLiteral. */
void literalRoutes(bool on) { http3Context->getContextData()->router.setLiteral(on); }
/* GOAWAY + drain. The engine itself is torn down in the destructor. */
void close() { http3Context->shutdown(); }
bool addServerNameWithOptions(const char *hostname, SocketContextOptions options) {
Expand Down
99 changes: 93 additions & 6 deletions packages/bun-uws/src/HttpRouter.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ struct HttpRouter {
/* Handler ids are 32-bit */
static constexpr uint32_t HANDLER_MASK = 0x0fffffff;

/* Name of the child of a method node that holds the literal keys: executeHandlers runs it as a wildcard, and no pattern segment has a '/' */
static constexpr std::string_view LITERAL_TOKEN = "*/";

/* List of handlers */
std::vector<MoveOnlyFunction<bool(HttpRouter *)>> handlers;

Expand All @@ -52,6 +55,9 @@ struct HttpRouter {
std::string_view urlSegmentVector[MAX_URL_SEGMENTS] = {};
int urlSegmentTop = -1;

/* See setLiteral() */
bool literalKeys = false;

/* The matching tree */
struct Node {
std::string name = {};
Expand All @@ -71,7 +77,8 @@ struct HttpRouter {
return 1;
}
if (name[0] == '*') {
return 0;
/* The literal container goes after every other high priority child (the WebSocket upgrades) */
return name == LITERAL_TOKEN ? -1 : 0;
}
return 2;
}
Expand Down Expand Up @@ -204,12 +211,74 @@ struct HttpRouter {
return false;
}

/* Whether a pattern cannot hold this key byte for byte. No call of getUrlSegment: one more caller stops its inlining into executeHandlers */
bool needsLiteralNode(std::string_view key) {
unsigned int segments = 0;
for (; !key.empty() && segments < MAX_URL_SEGMENTS; segments++) {
key.remove_prefix(1);
std::string_view segment = key.substr(0, key.find('/'));
if (lexicalOrder(segment) < 2) {
return true;
}
key.remove_prefix(segment.length());
}
/* A URL is read up to this many segments, so a pattern of this many also matches a longer URL */
return segments == MAX_URL_SEGMENTS;
}

/* The node of a literal key: a child of the LITERAL_TOKEN container, named by the whole key */
Node *literalNode(Node *methodNode, std::string_view key, bool create) {
Node *container = nullptr;
for (const std::unique_ptr<Node> &node : methodNode->children) {
if (node->name == LITERAL_TOKEN) {
container = node.get();
break;
}
}
if (!container) {
if (!create) {
return nullptr;
}
container = getNode(methodNode, LITERAL_TOKEN, true);
container->handlers.push_back(HIGH_PRIORITY | uint32_t(handlers.size()));
handlers.emplace_back([container](HttpRouter *router) {
/* Segment 0 is parsed: the URL starts one byte before it and ends where currentUrl ends */
const char *begin = router->urlSegmentVector[0].data() - 1;
std::string_view url(begin, size_t(router->currentUrl.data() + router->currentUrl.length() - begin));
for (const std::unique_ptr<Node> &node : container->children) {
if (node->name == url) {
Comment thread
robobun marked this conversation as resolved.
Resolved
for (uint32_t handler : node->handlers) {
if (router->handlers[handler & HANDLER_MASK](router)) {
return true;
}
}
break;
}
}
return false;
});
}
if (create) {
return getNode(container, key, false);
}
for (const std::unique_ptr<Node> &node : container->children) {
if (node->name == key) {
return node.get();
}
}
return nullptr;
}

/* Scans for one matching handler, returning the handler and its priority or UINT32_MAX for not found */
uint32_t findHandler(std::string_view method, std::string_view pattern, uint32_t priority) {
uint32_t findHandler(std::string_view method, std::string_view pattern, uint32_t priority, bool literal) {
for (const std::unique_ptr<Node> &node : root.children) {
if (method == node->name) {
setUrl(pattern);
Node *n = node.get();
/* A literal key is one node and has no segments to walk */
setUrl(literal ? std::string_view() : pattern);
Node *n = literal ? literalNode(node.get(), pattern, false) : node.get();
if (!n) {
return UINT32_MAX;
}
for (int i = 0; !getUrlSegment(i).second; i++) {
/* Go to next segment or quit */
std::string segment(getUrlSegment(i).first);
Expand Down Expand Up @@ -251,6 +320,11 @@ struct HttpRouter {
return userData;
}

/* While on, add() and remove() take their pattern as a literal path: ':' and '*' do not start a parameter or a wildcard */
void setLiteral(bool on) {
literalKeys = on;
}

/* Fast path */
bool route(std::string_view method, std::string_view url) {
/* Reset url parsing cache */
Expand Down Expand Up @@ -278,14 +352,27 @@ struct HttpRouter {

/* Adds the corresponding entires in matching tree and handler list */
void add(std::span<const std::string_view> methods, std::string_view pattern, MoveOnlyFunction<bool(HttpRouter *)> &&handler, uint32_t priority = MEDIUM_PRIORITY) {
/* Most literal keys are byte-exact as a pattern already */
const bool literal = literalKeys && needsLiteralNode(pattern);

/* First remove existing handler */
remove(methods[0], pattern, priority);

if (literal) {
/* A new container adds a handler of its own. Add them first: the id below has to be the same for every method. */
for (const std::string_view method : methods) {
literalNode(getNode(&root, method, false), pattern, true);
}
}

for (const std::string_view method : methods) {
/* Lookup method */
Node *node = getNode(&root, method, false);
if (literal) {
node = literalNode(node, pattern, true);
}
/* Iterate over all segments */
setUrl(pattern);
setUrl(literal ? std::string_view() : pattern);
for (int i = 0; !getUrlSegment(i).second; i++) {
std::string strippedSegment(getUrlSegment(i).first);
if (strippedSegment.length() > 1 && strippedSegment[0] == ':') {
Expand Down Expand Up @@ -359,7 +446,7 @@ struct HttpRouter {
* Removing a wildcard is done by removing ONE OF the methods the wildcard would match with.
* Example: If wildcard includes POST, GET, PUT, you can remove ALL THREE by removing GET. */
bool remove(std::string_view method, std::string_view pattern, uint32_t priority) {
uint32_t handler = findHandler(method, pattern, priority);
uint32_t handler = findHandler(method, pattern, priority, literalKeys && needsLiteralNode(pattern));
if (handler == UINT32_MAX) {
/* Not found or already removed, do nothing */
return false;
Expand Down
8 changes: 4 additions & 4 deletions src/runtime/server/HTMLBundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,21 +581,21 @@ impl Route {
continue;
}

bun_core::handle_oom(server.append_static_route(
server.append_bundled_file_route(
route_path,
AnyRoute::Static(RefPtr::new(static_route)),
MethodOptional::Any,
));
);
}

let (mut html_route, html_route_path) =
this_html_route.expect("the loop above visited html_index");
let html_route_clone = html_route.clone(global_this);
bun_core::handle_oom(server.append_static_route(
server.append_bundled_file_route(
&html_route_path,
AnyRoute::Static(RefPtr::new(html_route)),
MethodOptional::Any,
));
);
self.state.set(State::Html(html_route_clone));

if !bun_core::handle_oom(server.reload_static_routes()) {
Expand Down
Loading
Loading