mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
[bun.js] Add a Server.stop function
This commit is contained in:
12
examples/http-stop.ts
Normal file
12
examples/http-stop.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
const server = Bun.serve({
|
||||
fetch(req: Request) {
|
||||
return new Response(`Pending requests: ${this?.pendingRequests ?? 0}`);
|
||||
},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
// stop the server after the first request
|
||||
// when the server is stopped, this becomes undefined
|
||||
server?.stop();
|
||||
console.log("Stopping the server...");
|
||||
}, 1000);
|
||||
@@ -21,7 +21,6 @@ Bun.serve({
|
||||
|
||||
port: 3000, // number or string
|
||||
});
|
||||
|
||||
// Start a fast HTTP server from the main file's export
|
||||
// export default {
|
||||
// fetch(req) {
|
||||
|
||||
147
packages/bun-types/types.d.ts
vendored
147
packages/bun-types/types.d.ts
vendored
@@ -66,7 +66,7 @@ declare module "bun" {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function serve(options: Serve): void;
|
||||
export function serve(options: Serve): Server;
|
||||
|
||||
/**
|
||||
* Synchronously resolve a `moduleId` as though it were imported from `parent`
|
||||
@@ -521,9 +521,10 @@ declare module "bun" {
|
||||
* Respond to {@link Request} objects with a {@link Response} object.
|
||||
*
|
||||
*/
|
||||
fetch(request: Request): Response | Promise<Response>;
|
||||
fetch(this: Server, request: Request): Response | Promise<Response>;
|
||||
|
||||
error?: (
|
||||
this: Server,
|
||||
request: Errorlike
|
||||
) => Response | Promise<Response> | undefined | Promise<undefined>;
|
||||
}
|
||||
@@ -571,6 +572,23 @@ declare module "bun" {
|
||||
serverNames: Record<string, SSLOptions & SSLAdvancedOptions>;
|
||||
};
|
||||
|
||||
interface Server {
|
||||
/**
|
||||
* Stop listening to prevent new connections from being accepted.
|
||||
*
|
||||
* It does not close existing connections.
|
||||
*/
|
||||
stop(): void;
|
||||
|
||||
/**
|
||||
* How many requests are in-flight right now?
|
||||
*/
|
||||
readonly pendingRequests: number;
|
||||
readonly port: number;
|
||||
readonly hostname: string;
|
||||
readonly development: boolean;
|
||||
}
|
||||
|
||||
export type Serve = SSLServeOptions | ServeOptions;
|
||||
|
||||
/**
|
||||
@@ -4724,7 +4742,7 @@ interface Process {
|
||||
setuid(id: number | string): void;
|
||||
}
|
||||
|
||||
declare let process: Process;
|
||||
declare var process: Process;
|
||||
|
||||
interface BlobInterface {
|
||||
text(): Promise<string>;
|
||||
@@ -4765,7 +4783,7 @@ interface Headers {
|
||||
): void;
|
||||
}
|
||||
|
||||
declare let Headers: {
|
||||
declare var Headers: {
|
||||
prototype: Headers;
|
||||
new (init?: HeadersInit): Headers;
|
||||
};
|
||||
@@ -5223,7 +5241,7 @@ interface Crypto {
|
||||
randomUUID(): string;
|
||||
}
|
||||
|
||||
declare let crypto: Crypto;
|
||||
declare var crypto: Crypto;
|
||||
|
||||
/**
|
||||
* [`atob`](https://developer.mozilla.org/en-US/docs/Web/API/atob) converts ascii text into base64.
|
||||
@@ -5603,7 +5621,7 @@ interface EventTarget {
|
||||
): void;
|
||||
}
|
||||
|
||||
declare let EventTarget: {
|
||||
declare var EventTarget: {
|
||||
prototype: EventTarget;
|
||||
new (): EventTarget;
|
||||
};
|
||||
@@ -5707,7 +5725,7 @@ interface Event {
|
||||
readonly NONE: number;
|
||||
}
|
||||
|
||||
declare let Event: {
|
||||
declare var Event: {
|
||||
prototype: Event;
|
||||
new (type: string, eventInitDict?: EventInit): Event;
|
||||
readonly AT_TARGET: number;
|
||||
@@ -5727,7 +5745,7 @@ interface ErrorEvent extends Event {
|
||||
readonly message: string;
|
||||
}
|
||||
|
||||
declare let ErrorEvent: {
|
||||
declare var ErrorEvent: {
|
||||
prototype: ErrorEvent;
|
||||
new (type: string, eventInitDict?: ErrorEventInit): ErrorEvent;
|
||||
};
|
||||
@@ -5775,7 +5793,7 @@ interface URLSearchParams {
|
||||
): void;
|
||||
}
|
||||
|
||||
declare let URLSearchParams: {
|
||||
declare var URLSearchParams: {
|
||||
prototype: URLSearchParams;
|
||||
new (
|
||||
init?: string[][] | Record<string, string> | string | URLSearchParams
|
||||
@@ -5783,7 +5801,7 @@ declare let URLSearchParams: {
|
||||
toString(): string;
|
||||
};
|
||||
|
||||
declare let URL: {
|
||||
declare var URL: {
|
||||
prototype: URL;
|
||||
new (url: string | URL, base?: string | URL): URL;
|
||||
/** Not implemented yet */
|
||||
@@ -5802,7 +5820,7 @@ interface EventListenerObject {
|
||||
handleEvent(object: Event): void;
|
||||
}
|
||||
|
||||
declare let AbortController: {
|
||||
declare var AbortController: {
|
||||
prototype: AbortController;
|
||||
new (): AbortController;
|
||||
};
|
||||
@@ -5859,7 +5877,7 @@ interface AbortSignal extends EventTarget {
|
||||
): void;
|
||||
}
|
||||
|
||||
declare let AbortSignal: {
|
||||
declare var AbortSignal: {
|
||||
prototype: AbortSignal;
|
||||
new (): AbortSignal;
|
||||
};
|
||||
@@ -5878,6 +5896,111 @@ type DOMHighResTimeStamp = number;
|
||||
// type EpochTimeStamp = number;
|
||||
type EventListenerOrEventListenerObject = EventListener | EventListenerObject;
|
||||
|
||||
/**
|
||||
* Low-level JavaScriptCore API for accessing the native ES Module loader (not a Bun API)
|
||||
*
|
||||
* Before using this, be aware of a few things:
|
||||
*
|
||||
* **Using this incorrectly will crash your application**.
|
||||
*
|
||||
* This API may change any time JavaScriptCore is updated.
|
||||
*
|
||||
* Bun may rewrite ESM import specifiers to point to bundled code. This will
|
||||
* be confusing when using this API, as it will return a string like
|
||||
* "/node_modules.server.bun".
|
||||
*
|
||||
* Bun may inject additional imports into your code. This usually has a `bun:` prefix.
|
||||
*
|
||||
*/
|
||||
declare var Loader: {
|
||||
/**
|
||||
* ESM module registry
|
||||
*
|
||||
* This lets you implement live reload in Bun. If you
|
||||
* delete a module specifier from this map, the next time it's imported, it
|
||||
* will be re-transpiled and loaded again.
|
||||
*
|
||||
* The keys are the module specifiers and the
|
||||
* values are metadata about the module.
|
||||
*
|
||||
* The keys are an implementation detail for Bun that will change between
|
||||
* versions.
|
||||
*
|
||||
* - Userland modules are an absolute file path
|
||||
* - Virtual modules have a `bun:` prefix or `node:` prefix
|
||||
* - JS polyfills start with `"/bun-vfs/"`. `"buffer"` is an example of a JS polyfill
|
||||
* - If you have a `node_modules.bun` file, many modules will point to that file
|
||||
*
|
||||
* Virtual modules and JS polyfills are embedded in bun's binary. They don't
|
||||
* point to anywhere in your local filesystem.
|
||||
*
|
||||
*
|
||||
*/
|
||||
registry: Map<
|
||||
string,
|
||||
{
|
||||
/**
|
||||
* This refers to the state the ESM module is in
|
||||
*
|
||||
* TODO: make an enum for this number
|
||||
*
|
||||
*
|
||||
*/
|
||||
state: number;
|
||||
dependencies: string[];
|
||||
/**
|
||||
* Your application will probably crash if you mess with this.
|
||||
*/
|
||||
module: any;
|
||||
}
|
||||
>;
|
||||
/**
|
||||
* For an already-evaluated module, return the dependencies as module specifiers
|
||||
*
|
||||
* This list is already sorted and uniqued.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* For this code:
|
||||
* ```js
|
||||
* // /foo.js
|
||||
* import classNames from 'classnames';
|
||||
* import React from 'react';
|
||||
* import {createElement} from 'react';
|
||||
* ```
|
||||
*
|
||||
* This would return:
|
||||
* ```js
|
||||
* Loader.dependencyKeysIfEvaluated("/foo.js")
|
||||
* ["bun:wrap", "/path/to/node_modules/classnames/index.js", "/path/to/node_modules/react/index.js"]
|
||||
* ```
|
||||
*
|
||||
* @param specifier - module specifier as it appears in transpiled source code
|
||||
*
|
||||
*/
|
||||
dependencyKeysIfEvaluated: (specifier: string) => string[];
|
||||
/**
|
||||
* The function JavaScriptCore internally calls when you use an import statement.
|
||||
*
|
||||
* This may return a path to `node_modules.server.bun`, which will be confusing.
|
||||
*
|
||||
* Consider {@link Bun.resolve} or {@link ImportMeta.resolve}
|
||||
* instead.
|
||||
*
|
||||
* @param specifier - module specifier as it appears in transpiled source code
|
||||
*/
|
||||
resolve: (specifier: string) => Promise<string>;
|
||||
/**
|
||||
* Synchronously resolve a module specifier
|
||||
*
|
||||
* This may return a path to `node_modules.server.bun`, which will be confusing.
|
||||
*
|
||||
* Consider {@link Bun.resolveSync}
|
||||
* instead.
|
||||
*/
|
||||
resolveSync: (specifier: string, from: string) => string;
|
||||
};
|
||||
|
||||
|
||||
// ./path.d.ts
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ pub const Loop = opaque {
|
||||
pub fn nextTick(this: *Loop, comptime UserType: type, user_data: UserType, comptime deferCallback: fn (ctx: UserType) void) void {
|
||||
const Handler = struct {
|
||||
pub fn callback(data: *anyopaque) callconv(.C) void {
|
||||
deferCallback(@ptrCast(UserType, @alignCast(@alignOf(UserType), data)));
|
||||
const std = @import("std");
|
||||
deferCallback(@ptrCast(UserType, @alignCast(@alignOf(std.meta.Child(UserType)), data)));
|
||||
}
|
||||
};
|
||||
uws_loop_defer(this, user_data, Handler.callback);
|
||||
|
||||
@@ -675,8 +675,6 @@ pub const RequestContext = struct {
|
||||
|
||||
const AsyncIO = @import("io");
|
||||
pub fn writeSocket(ctx: *RequestContext, buf: anytype, _: anytype) !usize {
|
||||
// ctx.conn.client.setWriteBufferSize(@intCast(u32, buf.len)) catch {};
|
||||
|
||||
switch (Syscall.send(ctx.conn.client.socket.fd, buf, SOCKET_FLAGS)) {
|
||||
.err => |err| {
|
||||
const erro = AsyncIO.asError(err.getErrno());
|
||||
|
||||
@@ -456,6 +456,7 @@ pub const AsyncHTTP = struct {
|
||||
outer: {
|
||||
this.err = null;
|
||||
this.state.store(.sending, .Monotonic);
|
||||
|
||||
var timer = std.time.Timer.start() catch @panic("Timer failure");
|
||||
defer this.elapsed = timer.read();
|
||||
|
||||
|
||||
@@ -1191,25 +1191,67 @@ pub fn serve(
|
||||
return null;
|
||||
}
|
||||
|
||||
// Listen happens on the next tick!
|
||||
// This is so we can return a Server object
|
||||
if (config.ssl_config != null) {
|
||||
if (config.development) {
|
||||
var server = JSC.API.DebugSSLServer.init(config, ctx.ptr());
|
||||
server.listen();
|
||||
if (!server.thisObject.isEmpty()) {
|
||||
exception.* = server.thisObject.asObjectRef();
|
||||
server.thisObject = JSC.JSValue.zero;
|
||||
server.deinit();
|
||||
return null;
|
||||
}
|
||||
var obj = JSC.API.DebugSSLServer.Class.make(ctx, server);
|
||||
JSC.C.JSValueProtect(ctx, obj);
|
||||
server.thisObject = JSValue.c(obj);
|
||||
return obj;
|
||||
} else {
|
||||
var server = JSC.API.SSLServer.init(config, ctx.ptr());
|
||||
server.listen();
|
||||
if (!server.thisObject.isEmpty()) {
|
||||
exception.* = server.thisObject.asObjectRef();
|
||||
server.thisObject = JSC.JSValue.zero;
|
||||
server.deinit();
|
||||
return null;
|
||||
}
|
||||
var obj = JSC.API.SSLServer.Class.make(ctx, server);
|
||||
JSC.C.JSValueProtect(ctx, obj);
|
||||
server.thisObject = JSValue.c(obj);
|
||||
return obj;
|
||||
}
|
||||
} else {
|
||||
if (config.development) {
|
||||
var server = JSC.API.DebugServer.init(config, ctx.ptr());
|
||||
server.listen();
|
||||
if (!server.thisObject.isEmpty()) {
|
||||
exception.* = server.thisObject.asObjectRef();
|
||||
server.thisObject = JSC.JSValue.zero;
|
||||
server.deinit();
|
||||
return null;
|
||||
}
|
||||
var obj = JSC.API.DebugServer.Class.make(ctx, server);
|
||||
JSC.C.JSValueProtect(ctx, obj);
|
||||
server.thisObject = JSValue.c(obj);
|
||||
return obj;
|
||||
} else {
|
||||
var server = JSC.API.Server.init(config, ctx.ptr());
|
||||
server.listen();
|
||||
if (!server.thisObject.isEmpty()) {
|
||||
exception.* = server.thisObject.asObjectRef();
|
||||
server.thisObject = JSC.JSValue.zero;
|
||||
server.deinit();
|
||||
return null;
|
||||
}
|
||||
var obj = JSC.API.Server.Class.make(ctx, server);
|
||||
JSC.C.JSValueProtect(ctx, obj);
|
||||
server.thisObject = JSValue.c(obj);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return JSC.JSValue.jsUndefined().asObjectRef();
|
||||
unreachable;
|
||||
}
|
||||
|
||||
pub fn allocUnsafe(
|
||||
|
||||
@@ -477,12 +477,21 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
return;
|
||||
}
|
||||
|
||||
if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) {
|
||||
if (arguments.len == 0) {
|
||||
ctx.renderMissing();
|
||||
return;
|
||||
}
|
||||
|
||||
var response = arguments[0].as(JSC.WebCore.Response) orelse {
|
||||
handleResolve(ctx, arguments[0]);
|
||||
}
|
||||
|
||||
fn handleResolve(ctx: *RequestContext, value: JSC.JSValue) void {
|
||||
if (value.isEmptyOrUndefinedOrNull()) {
|
||||
ctx.renderMissing();
|
||||
return;
|
||||
}
|
||||
|
||||
var response = value.as(JSC.WebCore.Response) orelse {
|
||||
Output.prettyErrorln("Expected a Response object", .{});
|
||||
Output.flush();
|
||||
ctx.renderMissing();
|
||||
@@ -501,8 +510,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
return;
|
||||
}
|
||||
|
||||
handleReject(ctx, if (arguments.len > 0) arguments[0] else JSC.JSValue.jsUndefined());
|
||||
}
|
||||
|
||||
fn handleReject(ctx: *RequestContext, value: JSC.JSValue) void {
|
||||
ctx.runErrorHandler(
|
||||
if (arguments.len > 0) arguments[0] else JSC.JSValue.jsUndefined(),
|
||||
value,
|
||||
);
|
||||
|
||||
if (ctx.aborted) {
|
||||
@@ -516,7 +529,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
}
|
||||
|
||||
pub fn renderMissing(ctx: *RequestContext) void {
|
||||
if (debug_mode) {
|
||||
if (comptime !debug_mode) {
|
||||
ctx.resp.writeStatus("204 No Content");
|
||||
ctx.resp.endWithoutBody();
|
||||
ctx.finalize();
|
||||
@@ -646,9 +659,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
this.response_buf_owned.clearAndFree(bun.default_allocator);
|
||||
}
|
||||
pub fn finalize(this: *RequestContext) void {
|
||||
var server = this.server;
|
||||
this.finalizeWithoutDeinit();
|
||||
|
||||
this.server.request_pool_allocator.destroy(this);
|
||||
std.debug.assert(server.pending_requests > 0);
|
||||
server.request_pool_allocator.destroy(this);
|
||||
server.onRequestComplete();
|
||||
}
|
||||
|
||||
fn writeHeaders(
|
||||
@@ -671,7 +686,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanupAfterSendfile(this: *RequestContext) void {
|
||||
fn cleanupAndFinalizeAfterSendfile(this: *RequestContext) void {
|
||||
this.resp.setWriteOffset(this.sendfile.offset);
|
||||
this.resp.endWithoutBody();
|
||||
// use node syscall so that we don't segfault on BADF
|
||||
@@ -686,6 +701,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
}};
|
||||
|
||||
pub fn onSendfile(this: *RequestContext) bool {
|
||||
if (this.aborted) {
|
||||
this.cleanupAndFinalizeAfterSendfile();
|
||||
return false;
|
||||
}
|
||||
|
||||
const adjusted_count_temporary = @minimum(@as(u64, this.sendfile.remain), @as(u63, std.math.maxInt(u63)));
|
||||
// TODO we should not need this int cast; improve the return type of `@minimum`
|
||||
const adjusted_count = @intCast(u63, adjusted_count_temporary);
|
||||
@@ -707,7 +727,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
|
||||
Output.flush();
|
||||
}
|
||||
this.cleanupAfterSendfile();
|
||||
this.cleanupAndFinalizeAfterSendfile();
|
||||
return errcode != .SUCCESS;
|
||||
}
|
||||
} else {
|
||||
@@ -731,7 +751,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
|
||||
Output.flush();
|
||||
}
|
||||
this.cleanupAfterSendfile();
|
||||
this.cleanupAndFinalizeAfterSendfile();
|
||||
return errcode == .SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -774,7 +794,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
}
|
||||
|
||||
fn onPrepareSendfile(this: *RequestContext, fd: i32, size: Blob.SizeType, err: ?JSC.SystemError, globalThis: *JSGlobalObject) void {
|
||||
this.setAbortHandler();
|
||||
if (this.aborted) {
|
||||
this.finalize();
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) |system_error| {
|
||||
if (system_error.errno == @enumToInt(std.os.E.NOENT)) {
|
||||
@@ -796,7 +819,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
};
|
||||
|
||||
if (this.aborted) {
|
||||
_ = JSC.Node.Syscall.close(this.sendfile.fd);
|
||||
this.finalize();
|
||||
return;
|
||||
}
|
||||
@@ -804,12 +826,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
this.resp.runCorked(*RequestContext, renderMetadata, this);
|
||||
|
||||
if (size == 0) {
|
||||
this.cleanupAfterSendfile();
|
||||
this.finalize();
|
||||
|
||||
this.cleanupAndFinalizeAfterSendfile();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setAbortHandler();
|
||||
|
||||
// TODO: fix this to be MSGHDR
|
||||
_ = std.os.write(this.sendfile.socket_fd, "\r\n") catch 0;
|
||||
|
||||
@@ -828,6 +850,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
|
||||
pub fn doSendfile(this: *RequestContext, blob: Blob) void {
|
||||
if (this.has_sendfile_ctx) return;
|
||||
if (this.aborted) {
|
||||
this.finalize();
|
||||
return;
|
||||
}
|
||||
this.has_sendfile_ctx = true;
|
||||
this.setAbortHandler();
|
||||
|
||||
@@ -947,7 +973,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp
|
||||
if (!this.server.config.onError.isEmpty() and !this.has_called_error_handler) {
|
||||
this.has_called_error_handler = true;
|
||||
var args = [_]JSC.C.JSValueRef{value.asObjectRef()};
|
||||
const result = JSC.C.JSObjectCallAsFunctionReturnValue(this.server.globalThis.ref(), this.server.config.onError.asObjectRef(), null, 1, &args);
|
||||
const result = JSC.C.JSObjectCallAsFunctionReturnValue(this.server.globalThis.ref(), this.server.config.onError.asObjectRef(), this.server.thisObject.asObjectRef(), 1, &args);
|
||||
|
||||
if (!result.isEmptyOrUndefinedOrNull()) {
|
||||
if (result.isError() or result.isAggregateError(this.server.globalThis)) {
|
||||
@@ -1152,13 +1178,124 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
pub const App = uws.NewApp(ssl_enabled);
|
||||
|
||||
listener: ?*App.ListenSocket = null,
|
||||
|
||||
thisObject: JSC.JSValue = JSC.JSValue.zero,
|
||||
app: *App = undefined,
|
||||
vm: *JSC.VirtualMachine = undefined,
|
||||
globalThis: *JSGlobalObject,
|
||||
base_url_string_for_joining: string = "",
|
||||
response_objects_pool: JSC.WebCore.Response.Pool = JSC.WebCore.Response.Pool{},
|
||||
config: ServerConfig = ServerConfig{},
|
||||
next_tick_pending: bool = false,
|
||||
pending_requests: usize = 0,
|
||||
request_pool_allocator: std.mem.Allocator = undefined,
|
||||
has_js_deinited: bool = false,
|
||||
listen_callback: JSC.AnyTask = undefined,
|
||||
|
||||
pub const Class = JSC.NewClass(
|
||||
ThisServer,
|
||||
.{ .name = "Server" },
|
||||
.{
|
||||
.stop = .{
|
||||
.rfn = JSC.wrapSync(ThisServer, "stopFromJS"),
|
||||
},
|
||||
.finalize = .{
|
||||
.rfn = finalize,
|
||||
},
|
||||
},
|
||||
.{
|
||||
.port = .{
|
||||
.get = JSC.getterWrap(ThisServer, "getPort"),
|
||||
},
|
||||
.hostname = .{
|
||||
.get = JSC.getterWrap(ThisServer, "getHostname"),
|
||||
},
|
||||
.development = .{
|
||||
.get = JSC.getterWrap(ThisServer, "getDevelopment"),
|
||||
},
|
||||
.pendingRequests = .{
|
||||
.get = JSC.getterWrap(ThisServer, "getPendingRequests"),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
pub fn stopFromJS(this: *ThisServer) JSC.JSValue {
|
||||
if (this.listener != null) {
|
||||
JSC.C.JSValueUnprotect(this.globalThis.ref(), this.thisObject.asObjectRef());
|
||||
this.thisObject = JSC.JSValue.jsUndefined();
|
||||
this.stop();
|
||||
}
|
||||
|
||||
return JSC.JSValue.jsUndefined();
|
||||
}
|
||||
|
||||
pub fn getPort(this: *ThisServer) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(this.config.port);
|
||||
}
|
||||
|
||||
pub fn getPendingRequests(this: *ThisServer) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(@intCast(i32, @truncate(u31, this.pending_requests)));
|
||||
}
|
||||
|
||||
pub fn getHostname(this: *ThisServer, globalThis: *JSGlobalObject) JSC.JSValue {
|
||||
return ZigString.init(this.config.base_uri).toValue(globalThis);
|
||||
}
|
||||
|
||||
pub fn getDevelopment(
|
||||
_: *ThisServer,
|
||||
) JSC.JSValue {
|
||||
return JSC.JSValue.jsBoolean(debug_mode);
|
||||
}
|
||||
|
||||
pub fn onRequestComplete(this: *ThisServer) void {
|
||||
this.pending_requests -= 1;
|
||||
this.deinitIfWeCan();
|
||||
}
|
||||
|
||||
pub fn finalize(this: *ThisServer) void {
|
||||
this.has_js_deinited = true;
|
||||
this.deinitIfWeCan();
|
||||
}
|
||||
|
||||
pub fn deinitIfWeCan(this: *ThisServer) void {
|
||||
if (this.pending_requests == 0 and this.listener == null and this.has_js_deinited)
|
||||
this.deinit();
|
||||
}
|
||||
|
||||
pub fn stop(this: *ThisServer) void {
|
||||
this.next_tick_pending = true;
|
||||
|
||||
if (this.listener) |listener| {
|
||||
listener.close();
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
this.deinitIfWeCan();
|
||||
}
|
||||
|
||||
pub fn deinit(this: *ThisServer) void {
|
||||
if (this.vm.response_objects_pool) |pool| {
|
||||
if (pool == &this.response_objects_pool) {
|
||||
this.vm.response_objects_pool = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.app.destroy();
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn nextTick(this: *ThisServer) void {
|
||||
std.debug.assert(this.next_tick_pending);
|
||||
|
||||
this.next_tick_pending = false;
|
||||
this.vm.tick();
|
||||
}
|
||||
|
||||
pub fn queueNextTick(this: *ThisServer) void {
|
||||
std.debug.assert(!this.next_tick_pending);
|
||||
|
||||
this.next_tick_pending = true;
|
||||
uws.Loop.get().?.nextTick(*ThisServer, this, nextTick);
|
||||
}
|
||||
|
||||
pub fn init(config: ServerConfig, globalThis: *JSGlobalObject) *ThisServer {
|
||||
var server = bun.default_allocator.create(ThisServer) catch @panic("Out of memory!");
|
||||
@@ -1166,6 +1303,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
.globalThis = globalThis,
|
||||
.config = config,
|
||||
.base_url_string_for_joining = strings.trim(config.base_url.href, "/"),
|
||||
.vm = JSC.VirtualMachine.vm,
|
||||
};
|
||||
RequestContext.pool = bun.default_allocator.create(RequestContext.RequestContextStackAllocator) catch @panic("Out of memory!");
|
||||
server.request_pool_allocator = RequestContext.pool.get();
|
||||
@@ -1227,7 +1365,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
zig_str.withEncoding().mark();
|
||||
}
|
||||
}
|
||||
JSC.VirtualMachine.vm.defaultErrorHandler(zig_str.toErrorInstance(this.globalThis), null);
|
||||
// store the exception in here
|
||||
this.thisObject = zig_str.toErrorInstance(this.globalThis);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1237,14 +1376,20 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
}
|
||||
|
||||
this.listener = socket;
|
||||
VirtualMachine.vm.uws_event_loop = uws.Loop.get();
|
||||
VirtualMachine.vm.response_objects_pool = &this.response_objects_pool;
|
||||
this.vm.uws_event_loop = uws.Loop.get();
|
||||
this.vm.response_objects_pool = &this.response_objects_pool;
|
||||
this.listen_callback = JSC.AnyTask.New(ThisServer, run).init(this);
|
||||
this.vm.eventLoop().enqueueTask(JSC.Task.init(&this.listen_callback));
|
||||
}
|
||||
|
||||
pub fn run(this: *ThisServer) void {
|
||||
this.app.run();
|
||||
}
|
||||
|
||||
pub fn onBunInfoRequest(_: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
||||
pub fn onBunInfoRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
||||
if (comptime JSC.is_bindgen) return undefined;
|
||||
this.pending_requests += 1;
|
||||
defer this.pending_requests -= 1;
|
||||
req.setYield(false);
|
||||
var stack_fallback = std.heap.stackFallback(8096, bun.default_allocator);
|
||||
var allocator = stack_fallback.get();
|
||||
@@ -1268,8 +1413,10 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
resp.end(buffer, false);
|
||||
}
|
||||
|
||||
pub fn onSrcRequest(_: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
||||
pub fn onSrcRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
||||
if (comptime JSC.is_bindgen) return undefined;
|
||||
this.pending_requests += 1;
|
||||
defer this.pending_requests -= 1;
|
||||
req.setYield(false);
|
||||
if (req.header("open-in-editor") == null) {
|
||||
resp.writeStatus("501 Not Implemented");
|
||||
@@ -1298,7 +1445,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
|
||||
pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
|
||||
if (comptime JSC.is_bindgen) return undefined;
|
||||
|
||||
this.pending_requests += 1;
|
||||
var vm = this.vm;
|
||||
req.setYield(false);
|
||||
var ctx = this.request_pool_allocator.create(RequestContext) catch @panic("ran out of memory");
|
||||
ctx.create(this, req, resp);
|
||||
@@ -1321,11 +1469,16 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
var args = [_]JSC.C.JSValueRef{JSC.WebCore.Request.Class.make(this.globalThis.ref(), request_object)};
|
||||
ctx.request_js_object = args[0];
|
||||
JSC.C.JSValueProtect(this.globalThis.ref(), args[0]);
|
||||
ctx.response_jsvalue = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), null, 1, &args);
|
||||
defer JSC.VirtualMachine.vm.tick();
|
||||
ctx.response_jsvalue = JSC.C.JSObjectCallAsFunctionReturnValue(this.globalThis.ref(), this.config.onRequest.asObjectRef(), this.thisObject.asObjectRef(), 1, &args);
|
||||
var needs_tick = false;
|
||||
|
||||
defer if (!this.next_tick_pending and (needs_tick or
|
||||
// this is evaluated _after_ this function call
|
||||
vm.eventLoop().pending_tasks_count.value > 0))
|
||||
this.queueNextTick();
|
||||
|
||||
if (ctx.aborted) {
|
||||
ctx.finalize();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1340,17 +1493,48 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
}
|
||||
|
||||
JSC.C.JSValueProtect(this.globalThis.ref(), ctx.response_jsvalue.asObjectRef());
|
||||
|
||||
if (ctx.response_jsvalue.as(JSC.WebCore.Response)) |response| {
|
||||
ctx.render(response);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.response_jsvalue.jsTypeLoose() == .JSPromise) {
|
||||
ctx.setAbortHandler();
|
||||
JSC.VirtualMachine.vm.tick();
|
||||
var wait_for_promise = false;
|
||||
|
||||
if (ctx.response_jsvalue.asPromise()) |promise| {
|
||||
// If we immediately have the value available, we can skip the extra event loop tick
|
||||
switch (promise.status(vm.global.vm())) {
|
||||
.Pending => {},
|
||||
.Fulfilled => {
|
||||
ctx.handleResolve(promise.result(vm.global.vm()));
|
||||
return;
|
||||
},
|
||||
.Rejected => {
|
||||
ctx.handleReject(promise.result(vm.global.vm()));
|
||||
return;
|
||||
},
|
||||
}
|
||||
wait_for_promise = true;
|
||||
needs_tick = true;
|
||||
// I don't think this case should happen
|
||||
// But I'm uncertain
|
||||
} else if (ctx.response_jsvalue.asInternalPromise()) |promise| {
|
||||
switch (promise.status(vm.global.vm())) {
|
||||
.Pending => {},
|
||||
.Fulfilled => {
|
||||
ctx.handleResolve(promise.result(vm.global.vm()));
|
||||
return;
|
||||
},
|
||||
.Rejected => {
|
||||
ctx.handleReject(promise.result(vm.global.vm()));
|
||||
return;
|
||||
},
|
||||
}
|
||||
wait_for_promise = true;
|
||||
needs_tick = true;
|
||||
}
|
||||
|
||||
if (wait_for_promise) {
|
||||
ctx.setAbortHandler();
|
||||
ctx.response_jsvalue.then(
|
||||
this.globalThis,
|
||||
RequestContext,
|
||||
@@ -1361,11 +1545,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
return;
|
||||
}
|
||||
|
||||
// switch (ctx.response_jsvalue.jsTypeLoose()) {
|
||||
// .JSPromise => {
|
||||
// JSPromise.
|
||||
// },
|
||||
// }
|
||||
// The user returned something that wasn't a promise or a promise with a response
|
||||
if (!ctx.resp.hasResponded()) ctx.renderMissing();
|
||||
}
|
||||
|
||||
pub fn listen(this: *ThisServer) void {
|
||||
@@ -1404,8 +1585,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type {
|
||||
};
|
||||
}
|
||||
|
||||
pub const Server = NewServer(false, true);
|
||||
pub const SSLServer = NewServer(true, true);
|
||||
|
||||
pub const Server = NewServer(false, false);
|
||||
pub const SSLServer = NewServer(true, false);
|
||||
pub const DebugServer = NewServer(false, true);
|
||||
pub const DebugSSLServer = NewServer(true, true);
|
||||
|
||||
@@ -2548,6 +2548,10 @@ const EndTag = JSC.Cloudflare.EndTag;
|
||||
const DocEnd = JSC.Cloudflare.DocEnd;
|
||||
const AttributeIterator = JSC.Cloudflare.AttributeIterator;
|
||||
const Blob = JSC.WebCore.Blob;
|
||||
const Server = JSC.API.Server;
|
||||
const SSLServer = JSC.API.SSLServer;
|
||||
const DebugServer = JSC.API.DebugServer;
|
||||
const DebugSSLServer = JSC.API.DebugSSLServer;
|
||||
|
||||
pub const JSPrivateDataPtr = TaggedPointerUnion(.{
|
||||
AttributeIterator,
|
||||
@@ -2556,6 +2560,8 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
|
||||
Body,
|
||||
BuildError,
|
||||
Comment,
|
||||
DebugServer,
|
||||
DebugSSLServer,
|
||||
DescribeScope,
|
||||
DirEnt,
|
||||
DocEnd,
|
||||
@@ -2575,6 +2581,8 @@ pub const JSPrivateDataPtr = TaggedPointerUnion(.{
|
||||
ResolveError,
|
||||
Response,
|
||||
Router,
|
||||
Server,
|
||||
SSLServer,
|
||||
Stats,
|
||||
TextChunk,
|
||||
TextDecoder,
|
||||
@@ -2605,6 +2613,7 @@ pub fn getterWrap(comptime Container: type, comptime name: string) GetterType(Co
|
||||
return struct {
|
||||
const FunctionType = @TypeOf(@field(Container, name));
|
||||
const FunctionTypeInfo: std.builtin.TypeInfo.Fn = @typeInfo(FunctionType).Fn;
|
||||
const ArgsTuple = std.meta.ArgsTuple(FunctionType);
|
||||
|
||||
pub fn callback(
|
||||
this: *Container,
|
||||
@@ -2613,7 +2622,12 @@ pub fn getterWrap(comptime Container: type, comptime name: string) GetterType(Co
|
||||
_: js.JSStringRef,
|
||||
exception: js.ExceptionRef,
|
||||
) js.JSObjectRef {
|
||||
const result: JSValue = @call(.{}, @field(Container, name), .{ this, ctx.ptr() });
|
||||
const result: JSValue = if (comptime std.meta.fields(ArgsTuple).len == 1)
|
||||
@call(.{}, @field(Container, name), .{
|
||||
this,
|
||||
})
|
||||
else
|
||||
@call(.{}, @field(Container, name), .{ this, ctx.ptr() });
|
||||
if (!result.isUndefinedOrNull() and result.isError()) {
|
||||
exception.* = result.asObjectRef();
|
||||
return null;
|
||||
|
||||
@@ -312,6 +312,7 @@ pub const Task = TaggedPointerUnion(.{
|
||||
OpenAndStatFileTask,
|
||||
CopyFilePromiseTask,
|
||||
WriteFileTask,
|
||||
AnyTask,
|
||||
// PromiseTask,
|
||||
// TimeoutTasklet,
|
||||
});
|
||||
@@ -433,6 +434,30 @@ pub const SavedSourceMap = struct {
|
||||
};
|
||||
const uws = @import("uws");
|
||||
|
||||
pub const AnyTask = struct {
|
||||
ctx: *anyopaque,
|
||||
callback: fn (*anyopaque) void,
|
||||
|
||||
pub fn run(this: *AnyTask) void {
|
||||
this.callback(this.ctx);
|
||||
}
|
||||
|
||||
pub fn New(comptime Type: type, comptime Callback: anytype) type {
|
||||
return struct {
|
||||
pub fn init(ctx: *Type) AnyTask {
|
||||
return AnyTask{
|
||||
.callback = wrap,
|
||||
.ctx = ctx,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn wrap(this: *anyopaque) void {
|
||||
Callback(@ptrCast(*Type, @alignCast(@alignOf(Type), this)));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// If you read JavascriptCore/API/JSVirtualMachine.mm - https://github.com/WebKit/WebKit/blob/acff93fb303baa670c055cb24c2bad08691a01a0/Source/JavaScriptCore/API/JSVirtualMachine.mm#L101
|
||||
// We can see that it's sort of like std.mem.Allocator but for JSGlobalContextRef, to support Automatic Reference Counting
|
||||
// Its unavailable on Linux
|
||||
@@ -583,6 +608,12 @@ pub const VirtualMachine = struct {
|
||||
finished += 1;
|
||||
vm_.active_tasks -|= 1;
|
||||
},
|
||||
@field(Task.Tag, @typeName(AnyTask)) => {
|
||||
var any: *AnyTask = task.get(AnyTask).?;
|
||||
any.run();
|
||||
finished += 1;
|
||||
vm_.active_tasks -|= 1;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
@@ -1677,12 +1708,7 @@ pub const VirtualMachine = struct {
|
||||
var promise: *JSInternalPromise = undefined;
|
||||
|
||||
promise = JSModuleLoader.loadAndEvaluateModule(this.global, &ZigString.init(entry_path));
|
||||
|
||||
this.tick();
|
||||
|
||||
while (promise.status(this.global.vm()) == JSPromise.Status.Pending) {
|
||||
this.tick();
|
||||
}
|
||||
this.waitForPromise(promise);
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
22
types/bun/bun.d.ts
vendored
22
types/bun/bun.d.ts
vendored
@@ -56,7 +56,7 @@ declare module "bun" {
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function serve(options: Serve): void;
|
||||
export function serve(options: Serve): Server;
|
||||
|
||||
/**
|
||||
* Synchronously resolve a `moduleId` as though it were imported from `parent`
|
||||
@@ -511,9 +511,10 @@ declare module "bun" {
|
||||
* Respond to {@link Request} objects with a {@link Response} object.
|
||||
*
|
||||
*/
|
||||
fetch(request: Request): Response | Promise<Response>;
|
||||
fetch(this: Server, request: Request): Response | Promise<Response>;
|
||||
|
||||
error?: (
|
||||
this: Server,
|
||||
request: Errorlike
|
||||
) => Response | Promise<Response> | undefined | Promise<undefined>;
|
||||
}
|
||||
@@ -561,6 +562,23 @@ declare module "bun" {
|
||||
serverNames: Record<string, SSLOptions & SSLAdvancedOptions>;
|
||||
};
|
||||
|
||||
interface Server {
|
||||
/**
|
||||
* Stop listening to prevent new connections from being accepted.
|
||||
*
|
||||
* It does not close existing connections.
|
||||
*/
|
||||
stop(): void;
|
||||
|
||||
/**
|
||||
* How many requests are in-flight right now?
|
||||
*/
|
||||
readonly pendingRequests: number;
|
||||
readonly port: number;
|
||||
readonly hostname: string;
|
||||
readonly development: boolean;
|
||||
}
|
||||
|
||||
export type Serve = SSLServeOptions | ServeOptions;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user