@jmealo/fastify-uws 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +315 -0
- package/dist/errors.cjs +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +27 -0
- package/dist/http-socket.cjs +4 -0
- package/dist/http-socket.d.ts +67 -0
- package/dist/http-socket.js +174 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +18 -0
- package/dist/plugin-sse.cjs +10 -0
- package/dist/plugin-sse.d.ts +50 -0
- package/dist/plugin-sse.js +285 -0
- package/dist/plugin-websocket.cjs +1 -0
- package/dist/plugin-websocket.d.ts +9 -0
- package/dist/plugin-websocket.js +67 -0
- package/dist/request.cjs +1 -0
- package/dist/request.d.ts +29 -0
- package/dist/request.js +67 -0
- package/dist/response.cjs +1 -0
- package/dist/response.d.ts +40 -0
- package/dist/response.js +131 -0
- package/dist/server.cjs +1 -0
- package/dist/server.d.ts +51 -0
- package/dist/server.js +155 -0
- package/dist/symbols.cjs +1 -0
- package/dist/symbols.d.ts +27 -0
- package/dist/symbols.js +28 -0
- package/dist/websocket-server.cjs +1 -0
- package/dist/websocket-server.d.ts +82 -0
- package/dist/websocket-server.js +162 -0
- package/package.json +81 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ContextConfigDefault, FastifyBaseLogger, FastifyInstance, FastifyRequest, FastifyReply, FastifySchema, FastifyServerFactory, FastifyTypeProvider, FastifyTypeProviderDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, RequestGenericInterface } from 'fastify';
|
|
2
|
+
import { SSEContext } from './plugin-sse';
|
|
3
|
+
import { Server } from './server';
|
|
4
|
+
import { Request } from './request';
|
|
5
|
+
import { Response } from './response';
|
|
6
|
+
import { WebSocket as UwsWebSocket, WebSocketServer, WebSocketStream } from './websocket-server';
|
|
7
|
+
export type UwsServer = RawServerDefault & Server;
|
|
8
|
+
export type UwsRequest = Request;
|
|
9
|
+
export type UwsResponse = Response;
|
|
10
|
+
export type FastifyUwsInstance<TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> = FastifyInstance<any, any, any, Logger, TypeProvider>;
|
|
11
|
+
export type FastifyUwsRequest<RequestGeneric extends RequestGenericInterface = RequestGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ContextConfig = ContextConfigDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> = FastifyRequest<RequestGeneric, any, any, SchemaCompiler, TypeProvider, ContextConfig, Logger>;
|
|
12
|
+
export type FastifyUwsReply<RequestGeneric extends RequestGenericInterface = RequestGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, ContextConfig = ContextConfigDefault> = FastifyReply<RequestGeneric, any, any, any, ContextConfig, SchemaCompiler, TypeProvider>;
|
|
13
|
+
export declare const serverFactory: FastifyServerFactory<any>;
|
|
14
|
+
export type { SSEContext } from './plugin-sse';
|
|
15
|
+
export { default as sse } from './plugin-sse';
|
|
16
|
+
export { default as websocket } from './plugin-websocket';
|
|
17
|
+
export { UwsWebSocket as WebSocket, WebSocketServer, WebSocketStream, Server, Request, Response };
|
|
18
|
+
declare module 'fastify' {
|
|
19
|
+
interface FastifyReply {
|
|
20
|
+
sse: SSEContext;
|
|
21
|
+
}
|
|
22
|
+
interface RouteOptions {
|
|
23
|
+
websocket?: boolean;
|
|
24
|
+
sse?: boolean | {
|
|
25
|
+
heartbeat?: boolean;
|
|
26
|
+
serializer?: (data: any) => string;
|
|
27
|
+
};
|
|
28
|
+
ws?: {
|
|
29
|
+
topics?: string[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
interface RouteShorthandOptions<RawServer extends RawServerBase = RawServerDefault> {
|
|
33
|
+
websocket?: boolean;
|
|
34
|
+
sse?: boolean | {
|
|
35
|
+
heartbeat?: boolean;
|
|
36
|
+
serializer?: (data: any) => string;
|
|
37
|
+
};
|
|
38
|
+
ws?: {
|
|
39
|
+
topics?: string[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface RouteShorthandMethod<RawServer extends RawServerBase = RawServerDefault, RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>, RawReply extends RawReplyDefaultExpression<RawServer> = RawReplyDefaultExpression<RawServer>, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger> {
|
|
43
|
+
<RequestGeneric extends RequestGenericInterface = RequestGenericInterface, ContextConfig = ContextConfigDefault, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, Logger extends FastifyBaseLogger = FastifyBaseLogger>(path: string, opts: RouteShorthandOptions<RawServer, RawRequest, RawReply, RequestGeneric, ContextConfig, SchemaCompiler, TypeProvider, Logger> & {
|
|
44
|
+
websocket: true;
|
|
45
|
+
}, handler?: (socket: UwsWebSocket, req: FastifyRequest<RequestGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider, ContextConfig, Logger>) => void | Promise<any>): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Server as o } from "./server.js";
|
|
2
|
+
import { Request as p } from "./request.js";
|
|
3
|
+
import { Response as c } from "./response.js";
|
|
4
|
+
import { WebSocket as S, WebSocketServer as b, WebSocketStream as k } from "./websocket-server.js";
|
|
5
|
+
import { default as u } from "./plugin-sse.js";
|
|
6
|
+
import { default as W } from "./plugin-websocket.js";
|
|
7
|
+
const s = (e, r) => new o(e, r);
|
|
8
|
+
export {
|
|
9
|
+
p as Request,
|
|
10
|
+
c as Response,
|
|
11
|
+
o as Server,
|
|
12
|
+
S as WebSocket,
|
|
13
|
+
b as WebSocketServer,
|
|
14
|
+
k as WebSocketStream,
|
|
15
|
+
s as serverFactory,
|
|
16
|
+
u as sse,
|
|
17
|
+
W as websocket
|
|
18
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("node:stream"),y=require("fastify-plugin"),c=require("./symbols.cjs"),k=3e4,p=32,H=1024*1024;function d(i){return String(i).replace(/[\r\n]/g,"")}const S=/\r\n|\r|\n/;function f(i,t){let e="";if(typeof i=="string")for(const s of i.split(S))e+=`data: ${s}
|
|
2
|
+
`;else if(Buffer.isBuffer(i)){const s=i.toString("utf-8");for(const r of s.split(S))e+=`data: ${r}
|
|
3
|
+
`}else{if(i.id&&(e+=`id: ${d(i.id)}
|
|
4
|
+
`),i.event&&(e+=`event: ${d(i.event)}
|
|
5
|
+
`),i.data!==void 0){const s=t(i.data);for(const r of s.split(S))e+=`data: ${r}
|
|
6
|
+
`}i.retry&&(e+=`retry: ${d(String(i.retry))}
|
|
7
|
+
`)}return e&&(e+=`
|
|
8
|
+
`),e}const T=[{name:"Content-Type",value:"text/event-stream",isMultiValue:!1},{name:"Cache-Control",value:"no-cache",isMultiValue:!1},{name:"Connection",value:"keep-alive",isMultiValue:!1},{name:"X-Accel-Buffering",value:"no",isMultiValue:!1}];function m(i){return typeof i=="object"&&i!==null&&"data"in i}function C(i){return i!=null&&typeof i[Symbol.asyncIterator]=="function"}function I(i,t){if(t.status&&i.writeStatus(t.status),t.headers)for(const e of t.headers.values())if(e.isMultiValue)for(const s of e.value)i.writeHeader(e.name,s);else i.writeHeader(e.name,e.value)}class v{reply;serializer;heartbeatTimer=null;closeCallbacks=[];#e;#a;#t=!0;#c=!1;#f=!1;#o;#r=null;#i="";#l=0;#h=!1;#n=null;#u;#d=!0;constructor(t){this.reply=t.reply,this.#e=t.socket,this.#a=t.socket[c.kRes],this.#o=t.lastEventId||null,this.serializer=t.serializer,this.#u=this._corkFlush.bind(this);const e=()=>{if(!this.#t)return;this.#t=!1,this.#n=null;const s=this.#r;this.#r=null,s&&s(),this.cleanup()};t.socket.once("close",e),t.socket.once("aborted",e),t.heartbeatInterval>0&&this.startHeartbeat(t.heartbeatInterval)}get lastEventId(){return this.#o}get isConnected(){return this.#t}get shouldKeepAlive(){return this.#c}keepAlive(){this.#c=!0}flush(){this.#i&&this.#t&&this.#s()}close(){if(this.#t&&(this.#i&&this.#s(),this.#t=!1,this.cleanup(),!this.#e.destroyed&&!this.#e.writableEnded))try{this.#e.end(void 0,void 0)}catch{}}onClose(t){this.closeCallbacks.push(t)}async replay(t){this.#o&&await t(this.#o)}sendHeaders(){if(this.#f)return;this.#f=!0;const t=new Map;for(const e of T)t.set(e.name.toLowerCase(),e);try{const e=this.reply.getHeaders();for(const[s,r]of Object.entries(e)){const n=s.toLowerCase();t.has(n)||t.set(n,{name:s,value:String(r),isMultiValue:!1})}}catch{}this.#e[c.kHead]={status:"200 OK",headers:t}}async send(t){if(!this.#t)throw new Error("SSE connection is closed");if(typeof t=="string"||Buffer.isBuffer(t)||m(t)){const e=f(t,this.serializer);return this.writeToSocket(e)}if(t instanceof E.Readable){try{for await(const e of t){if(!this.#t){t.destroy();break}const s=f(e,this.serializer);await this.writeToSocket(s)}this.#i&&this.#t&&this.#s()}catch(e){this.#i&&this.#t&&this.#s(),this.#t=!1,this.cleanup(),e?.code!=="ECONNRESET"&&e?.code!=="EPIPE"&&this.reply.log?.error?.({err:e},"Unexpected error in SSE stream")}return}if(C(t)){try{for await(const e of t){if(!this.#t)break;const s=f(e,this.serializer);await this.writeToSocket(s)}this.#i&&this.#t&&this.#s()}catch(e){this.#i&&this.#t&&this.#s(),this.#t=!1,this.cleanup(),e?.code!=="ECONNRESET"&&e?.code!=="EPIPE"&&this.reply.log?.error?.({err:e},"Unexpected error in SSE async iterable")}return}throw new TypeError("Invalid SSE source type")}stream(){if(!this.#t)throw new Error("SSE connection is closed");const t=this;return new E.Writable({objectMode:!0,write(e,s,r){if(!t.#t){r(new Error("SSE connection is closed"));return}try{const n=f(e,t.serializer),o=t.writeToSocket(n);o===void 0?r():o.then(()=>r(),r)}catch(n){r(n)}}})}writeToSocket(t){if(!this.#t)throw new Error("SSE connection is closed");if(this.sendHeaders(),this.#h&&this.#i.length+t.length>H)throw this.close(),new Error("SSE buffer overflow: client too slow");if(this.#i+=t,this.#l++,this.#h)return this.#n;if(this.#l>=p)return this.#s()}#s(){if(!(!this.#i||!this.#t)&&(this.#a.cork(this.#u),this.#i="",this.#l=0,!this.#d))return this.#h=!0,this.#n=new Promise(t=>{this.#r=t,this.#e.writableNeedDrain=!0,this.#e._drainCb=()=>{this.#e.writableNeedDrain=!1,this.#h=!1,this.#n=null;const e=this.#r;this.#r=null,e&&e()},this.#e._drainTimer=setTimeout(this.#e._boundDrainTimeout,k)}),this.#n}_corkFlush(){const t=this.#e[c.kHead];t&&(I(this.#a,t),this.#e[c.kHead]=null),this.#d=this.#a.write(this.#i)}startHeartbeat(t){this.heartbeatTimer=setInterval(()=>{this.#t?(this.sendHeaders(),this.#i+=`: heartbeat
|
|
9
|
+
|
|
10
|
+
`,this.#l++,this.#s()):this.stopHeartbeat()},t),this.heartbeatTimer.unref()}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}cleanup(){this.stopHeartbeat();for(const t of this.closeCallbacks)try{t()}catch{}this.closeCallbacks=[]}}function _(i,t,e){const{heartbeatInterval:s=3e4,serializer:r=JSON.stringify}=t;i.addHook("onRoute",n=>{if(!n.sse)return;const o=n.handler,b=typeof n.sse=="object"?n.sse:{};n.handler=async function(l,h){if(!(l.headers.accept||"").includes("text/event-stream"))return o.call(this,l,h);h.hijack();const u=l.raw.socket;if(u.aborted||u.destroyed)return;const a=new v({reply:h,socket:u,lastEventId:l.headers["last-event-id"]||null,heartbeatInterval:b.heartbeat!==!1?s:0,serializer:b.serializer||r});h.sse=a;try{await o.call(this,l,h)}catch(w){throw a.shouldKeepAlive||a.close(),w}a.flush(),a.shouldKeepAlive||a.close()}}),e()}const M=y(_,{fastify:"5.x",name:"@fastify/sse"});exports.SSEContext=v;exports.default=M;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
2
|
+
import { HTTPSocket } from './http-socket';
|
|
3
|
+
declare class SSEContext {
|
|
4
|
+
#private;
|
|
5
|
+
reply: any;
|
|
6
|
+
serializer: (data: any) => string;
|
|
7
|
+
heartbeatTimer: ReturnType<typeof setInterval> | null;
|
|
8
|
+
closeCallbacks: (() => void)[];
|
|
9
|
+
constructor(opts: {
|
|
10
|
+
reply: any;
|
|
11
|
+
socket: HTTPSocket;
|
|
12
|
+
lastEventId: string | null;
|
|
13
|
+
heartbeatInterval: number;
|
|
14
|
+
serializer: (data: any) => string;
|
|
15
|
+
});
|
|
16
|
+
get lastEventId(): string | null;
|
|
17
|
+
get isConnected(): boolean;
|
|
18
|
+
get shouldKeepAlive(): boolean;
|
|
19
|
+
keepAlive(): void;
|
|
20
|
+
flush(): void;
|
|
21
|
+
close(): void;
|
|
22
|
+
onClose(callback: () => void): void;
|
|
23
|
+
replay(callback: (lastEventId: string) => Promise<void>): Promise<void>;
|
|
24
|
+
sendHeaders(): void;
|
|
25
|
+
send(source: any): Promise<void>;
|
|
26
|
+
stream(): Writable;
|
|
27
|
+
/**
|
|
28
|
+
* Write-combining SSE writer.
|
|
29
|
+
*
|
|
30
|
+
* Events are accumulated in a string buffer. When the buffer reaches
|
|
31
|
+
* FLUSH_BATCH_SIZE events, all events are flushed in a single corked write
|
|
32
|
+
* to the uWS HttpResponse. This reduces C++ boundary crossings from
|
|
33
|
+
* one-per-event to one-per-batch.
|
|
34
|
+
*
|
|
35
|
+
* If there's active backpressure from a previous flush, the send() caller
|
|
36
|
+
* receives a Promise that resolves when the drain completes.
|
|
37
|
+
*/
|
|
38
|
+
writeToSocket(data: string): Promise<void> | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Pre-bound cork callback. Writes headers (first call only) + buffered data.
|
|
41
|
+
*/
|
|
42
|
+
_corkFlush(): void;
|
|
43
|
+
startHeartbeat(interval: number): void;
|
|
44
|
+
stopHeartbeat(): void;
|
|
45
|
+
cleanup(): void;
|
|
46
|
+
}
|
|
47
|
+
declare function fastifySSE(fastify: any, opts: any, next: (err?: Error) => void): void;
|
|
48
|
+
export { SSEContext };
|
|
49
|
+
declare const _default: typeof fastifySSE;
|
|
50
|
+
export default _default;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { Readable as w, Writable as v } from "node:stream";
|
|
2
|
+
import y from "fastify-plugin";
|
|
3
|
+
import { kRes as p, kHead as u } from "./symbols.js";
|
|
4
|
+
const k = 3e4, m = 32, T = 1024 * 1024;
|
|
5
|
+
function d(i) {
|
|
6
|
+
return String(i).replace(/[\r\n]/g, "");
|
|
7
|
+
}
|
|
8
|
+
const S = /\r\n|\r|\n/;
|
|
9
|
+
function c(i, t) {
|
|
10
|
+
let e = "";
|
|
11
|
+
if (typeof i == "string")
|
|
12
|
+
for (const s of i.split(S))
|
|
13
|
+
e += `data: ${s}
|
|
14
|
+
`;
|
|
15
|
+
else if (Buffer.isBuffer(i)) {
|
|
16
|
+
const s = i.toString("utf-8");
|
|
17
|
+
for (const r of s.split(S))
|
|
18
|
+
e += `data: ${r}
|
|
19
|
+
`;
|
|
20
|
+
} else {
|
|
21
|
+
if (i.id && (e += `id: ${d(i.id)}
|
|
22
|
+
`), i.event && (e += `event: ${d(i.event)}
|
|
23
|
+
`), i.data !== void 0) {
|
|
24
|
+
const s = t(i.data);
|
|
25
|
+
for (const r of s.split(S))
|
|
26
|
+
e += `data: ${r}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
i.retry && (e += `retry: ${d(String(i.retry))}
|
|
30
|
+
`);
|
|
31
|
+
}
|
|
32
|
+
return e && (e += `
|
|
33
|
+
`), e;
|
|
34
|
+
}
|
|
35
|
+
const H = [
|
|
36
|
+
{ name: "Content-Type", value: "text/event-stream", isMultiValue: !1 },
|
|
37
|
+
{ name: "Cache-Control", value: "no-cache", isMultiValue: !1 },
|
|
38
|
+
{ name: "Connection", value: "keep-alive", isMultiValue: !1 },
|
|
39
|
+
{ name: "X-Accel-Buffering", value: "no", isMultiValue: !1 }
|
|
40
|
+
];
|
|
41
|
+
function I(i) {
|
|
42
|
+
return typeof i == "object" && i !== null && "data" in i;
|
|
43
|
+
}
|
|
44
|
+
function C(i) {
|
|
45
|
+
return i != null && typeof i[Symbol.asyncIterator] == "function";
|
|
46
|
+
}
|
|
47
|
+
function _(i, t) {
|
|
48
|
+
if (t.status && i.writeStatus(t.status), t.headers)
|
|
49
|
+
for (const e of t.headers.values())
|
|
50
|
+
if (e.isMultiValue)
|
|
51
|
+
for (const s of e.value)
|
|
52
|
+
i.writeHeader(e.name, s);
|
|
53
|
+
else
|
|
54
|
+
i.writeHeader(e.name, e.value);
|
|
55
|
+
}
|
|
56
|
+
class M {
|
|
57
|
+
reply;
|
|
58
|
+
serializer;
|
|
59
|
+
heartbeatTimer = null;
|
|
60
|
+
closeCallbacks = [];
|
|
61
|
+
#e;
|
|
62
|
+
#a;
|
|
63
|
+
#t = !0;
|
|
64
|
+
#c = !1;
|
|
65
|
+
#f = !1;
|
|
66
|
+
#o;
|
|
67
|
+
#r = null;
|
|
68
|
+
// Write-combining buffer: accumulate events, flush when batch is full
|
|
69
|
+
#i = "";
|
|
70
|
+
#h = 0;
|
|
71
|
+
#l = !1;
|
|
72
|
+
#n = null;
|
|
73
|
+
// Pre-bound methods
|
|
74
|
+
#u;
|
|
75
|
+
// Cork state
|
|
76
|
+
#d = !0;
|
|
77
|
+
constructor(t) {
|
|
78
|
+
this.reply = t.reply, this.#e = t.socket, this.#a = t.socket[p], this.#o = t.lastEventId || null, this.serializer = t.serializer, this.#u = this._corkFlush.bind(this);
|
|
79
|
+
const e = () => {
|
|
80
|
+
if (!this.#t) return;
|
|
81
|
+
this.#t = !1, this.#n = null;
|
|
82
|
+
const s = this.#r;
|
|
83
|
+
this.#r = null, s && s(), this.cleanup();
|
|
84
|
+
};
|
|
85
|
+
t.socket.once("close", e), t.socket.once("aborted", e), t.heartbeatInterval > 0 && this.startHeartbeat(t.heartbeatInterval);
|
|
86
|
+
}
|
|
87
|
+
get lastEventId() {
|
|
88
|
+
return this.#o;
|
|
89
|
+
}
|
|
90
|
+
get isConnected() {
|
|
91
|
+
return this.#t;
|
|
92
|
+
}
|
|
93
|
+
get shouldKeepAlive() {
|
|
94
|
+
return this.#c;
|
|
95
|
+
}
|
|
96
|
+
keepAlive() {
|
|
97
|
+
this.#c = !0;
|
|
98
|
+
}
|
|
99
|
+
flush() {
|
|
100
|
+
this.#i && this.#t && this.#s();
|
|
101
|
+
}
|
|
102
|
+
close() {
|
|
103
|
+
if (this.#t && (this.#i && this.#s(), this.#t = !1, this.cleanup(), !this.#e.destroyed && !this.#e.writableEnded))
|
|
104
|
+
try {
|
|
105
|
+
this.#e.end(void 0, void 0);
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
onClose(t) {
|
|
110
|
+
this.closeCallbacks.push(t);
|
|
111
|
+
}
|
|
112
|
+
async replay(t) {
|
|
113
|
+
this.#o && await t(this.#o);
|
|
114
|
+
}
|
|
115
|
+
sendHeaders() {
|
|
116
|
+
if (this.#f) return;
|
|
117
|
+
this.#f = !0;
|
|
118
|
+
const t = /* @__PURE__ */ new Map();
|
|
119
|
+
for (const e of H)
|
|
120
|
+
t.set(e.name.toLowerCase(), e);
|
|
121
|
+
try {
|
|
122
|
+
const e = this.reply.getHeaders();
|
|
123
|
+
for (const [s, r] of Object.entries(e)) {
|
|
124
|
+
const n = s.toLowerCase();
|
|
125
|
+
t.has(n) || t.set(n, { name: s, value: String(r), isMultiValue: !1 });
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
this.#e[u] = {
|
|
130
|
+
status: "200 OK",
|
|
131
|
+
headers: t
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async send(t) {
|
|
135
|
+
if (!this.#t) throw new Error("SSE connection is closed");
|
|
136
|
+
if (typeof t == "string" || Buffer.isBuffer(t) || I(t)) {
|
|
137
|
+
const e = c(t, this.serializer);
|
|
138
|
+
return this.writeToSocket(e);
|
|
139
|
+
}
|
|
140
|
+
if (t instanceof w) {
|
|
141
|
+
try {
|
|
142
|
+
for await (const e of t) {
|
|
143
|
+
if (!this.#t) {
|
|
144
|
+
t.destroy();
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
const s = c(e, this.serializer);
|
|
148
|
+
await this.writeToSocket(s);
|
|
149
|
+
}
|
|
150
|
+
this.#i && this.#t && this.#s();
|
|
151
|
+
} catch (e) {
|
|
152
|
+
this.#i && this.#t && this.#s(), this.#t = !1, this.cleanup(), e?.code !== "ECONNRESET" && e?.code !== "EPIPE" && this.reply.log?.error?.({ err: e }, "Unexpected error in SSE stream");
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (C(t)) {
|
|
157
|
+
try {
|
|
158
|
+
for await (const e of t) {
|
|
159
|
+
if (!this.#t) break;
|
|
160
|
+
const s = c(e, this.serializer);
|
|
161
|
+
await this.writeToSocket(s);
|
|
162
|
+
}
|
|
163
|
+
this.#i && this.#t && this.#s();
|
|
164
|
+
} catch (e) {
|
|
165
|
+
this.#i && this.#t && this.#s(), this.#t = !1, this.cleanup(), e?.code !== "ECONNRESET" && e?.code !== "EPIPE" && this.reply.log?.error?.({ err: e }, "Unexpected error in SSE async iterable");
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
throw new TypeError("Invalid SSE source type");
|
|
170
|
+
}
|
|
171
|
+
stream() {
|
|
172
|
+
if (!this.#t) throw new Error("SSE connection is closed");
|
|
173
|
+
const t = this;
|
|
174
|
+
return new v({
|
|
175
|
+
objectMode: !0,
|
|
176
|
+
write(e, s, r) {
|
|
177
|
+
if (!t.#t) {
|
|
178
|
+
r(new Error("SSE connection is closed"));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const n = c(e, t.serializer), o = t.writeToSocket(n);
|
|
183
|
+
o === void 0 ? r() : o.then(() => r(), r);
|
|
184
|
+
} catch (n) {
|
|
185
|
+
r(n);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Write-combining SSE writer.
|
|
192
|
+
*
|
|
193
|
+
* Events are accumulated in a string buffer. When the buffer reaches
|
|
194
|
+
* FLUSH_BATCH_SIZE events, all events are flushed in a single corked write
|
|
195
|
+
* to the uWS HttpResponse. This reduces C++ boundary crossings from
|
|
196
|
+
* one-per-event to one-per-batch.
|
|
197
|
+
*
|
|
198
|
+
* If there's active backpressure from a previous flush, the send() caller
|
|
199
|
+
* receives a Promise that resolves when the drain completes.
|
|
200
|
+
*/
|
|
201
|
+
writeToSocket(t) {
|
|
202
|
+
if (!this.#t) throw new Error("SSE connection is closed");
|
|
203
|
+
if (this.sendHeaders(), this.#l && this.#i.length + t.length > T)
|
|
204
|
+
throw this.close(), new Error("SSE buffer overflow: client too slow");
|
|
205
|
+
if (this.#i += t, this.#h++, this.#l)
|
|
206
|
+
return this.#n;
|
|
207
|
+
if (this.#h >= m)
|
|
208
|
+
return this.#s();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Flush buffered events to the uWS HttpResponse in a single corked write.
|
|
212
|
+
*/
|
|
213
|
+
#s() {
|
|
214
|
+
if (!(!this.#i || !this.#t) && (this.#a.cork(this.#u), this.#i = "", this.#h = 0, !this.#d))
|
|
215
|
+
return this.#l = !0, this.#n = new Promise((t) => {
|
|
216
|
+
this.#r = t, this.#e.writableNeedDrain = !0, this.#e._drainCb = () => {
|
|
217
|
+
this.#e.writableNeedDrain = !1, this.#l = !1, this.#n = null;
|
|
218
|
+
const e = this.#r;
|
|
219
|
+
this.#r = null, e && e();
|
|
220
|
+
}, this.#e._drainTimer = setTimeout(this.#e._boundDrainTimeout, k);
|
|
221
|
+
}), this.#n;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Pre-bound cork callback. Writes headers (first call only) + buffered data.
|
|
225
|
+
*/
|
|
226
|
+
_corkFlush() {
|
|
227
|
+
const t = this.#e[u];
|
|
228
|
+
t && (_(this.#a, t), this.#e[u] = null), this.#d = this.#a.write(this.#i);
|
|
229
|
+
}
|
|
230
|
+
startHeartbeat(t) {
|
|
231
|
+
this.heartbeatTimer = setInterval(() => {
|
|
232
|
+
this.#t ? (this.sendHeaders(), this.#i += `: heartbeat
|
|
233
|
+
|
|
234
|
+
`, this.#h++, this.#s()) : this.stopHeartbeat();
|
|
235
|
+
}, t), this.heartbeatTimer.unref();
|
|
236
|
+
}
|
|
237
|
+
stopHeartbeat() {
|
|
238
|
+
this.heartbeatTimer && (clearInterval(this.heartbeatTimer), this.heartbeatTimer = null);
|
|
239
|
+
}
|
|
240
|
+
cleanup() {
|
|
241
|
+
this.stopHeartbeat();
|
|
242
|
+
for (const t of this.closeCallbacks)
|
|
243
|
+
try {
|
|
244
|
+
t();
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
this.closeCallbacks = [];
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function g(i, t, e) {
|
|
251
|
+
const { heartbeatInterval: s = 3e4, serializer: r = JSON.stringify } = t;
|
|
252
|
+
i.addHook("onRoute", (n) => {
|
|
253
|
+
if (!n.sse) return;
|
|
254
|
+
const o = n.handler, E = typeof n.sse == "object" ? n.sse : {};
|
|
255
|
+
n.handler = async function(h, l) {
|
|
256
|
+
if (!(h.headers.accept || "").includes("text/event-stream"))
|
|
257
|
+
return o.call(this, h, l);
|
|
258
|
+
l.hijack();
|
|
259
|
+
const f = h.raw.socket;
|
|
260
|
+
if (f.aborted || f.destroyed) return;
|
|
261
|
+
const a = new M({
|
|
262
|
+
reply: l,
|
|
263
|
+
socket: f,
|
|
264
|
+
lastEventId: h.headers["last-event-id"] || null,
|
|
265
|
+
heartbeatInterval: E.heartbeat !== !1 ? s : 0,
|
|
266
|
+
serializer: E.serializer || r
|
|
267
|
+
});
|
|
268
|
+
l.sse = a;
|
|
269
|
+
try {
|
|
270
|
+
await o.call(this, h, l);
|
|
271
|
+
} catch (b) {
|
|
272
|
+
throw a.shouldKeepAlive || a.close(), b;
|
|
273
|
+
}
|
|
274
|
+
a.flush(), a.shouldKeepAlive || a.close();
|
|
275
|
+
};
|
|
276
|
+
}), e();
|
|
277
|
+
}
|
|
278
|
+
const B = y(g, {
|
|
279
|
+
fastify: "5.x",
|
|
280
|
+
name: "@fastify/sse"
|
|
281
|
+
});
|
|
282
|
+
export {
|
|
283
|
+
M as SSEContext,
|
|
284
|
+
B as default
|
|
285
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const E=require("fastify-plugin"),r=require("./symbols.cjs"),k=require("./websocket-server.cjs"),H=/^[A-Za-z0-9+/]{22}==$/;function y(o){return typeof o!="string"||!H.test(o)?!1:Buffer.from(o,"base64").byteLength===16}function R(o,f,n){n.log.error(o),f.close()}const T=(o,f,n)=>{const{server:W}=o,{errorHandler:a=R,options:m}=f;if(a&&typeof a!="function")return n(new Error("invalid errorHandler function"));const b=new k.WebSocketServer(m);W[r.kWs]=b,o.decorate("websocketServer",b),o.addHook("onRoute",t=>{if(!!!t.websocket||t.method==="HEAD"||t.method==="OPTIONS")return;const u=typeof t.ws=="object"?t.ws:{},h=t.handler,w=Buffer.from(t.url||"/"),S={};if(u.topics)for(const c of u.topics)S[c]=k.WebSocket.allocTopic(w,c);t.handler=function(c,i){const e=c.raw;if(e[r.kWs]){const g=e.headers["sec-websocket-key"];if(!y(g)){i.code(400).send("Invalid sec-websocket-key header");return}i.hijack();const p=e.socket[r.kRes];if(e.socket[r.kWs]=!0,e.socket.aborted||e.socket.destroyed)return;p.upgrade({req:e,handler:v=>{const s=new k.WebSocket(w,v,S);let l;try{e.once("error",()=>{s.close()}),e.once("close",()=>{s.end(1e3,"Normal Closure")}),l=h.call(this,s,c,i)}catch(d){return a.call(this,d,s,c)}l&&typeof l.catch=="function"&&l.catch(d=>a.call(this,d,s,c))}},e.headers["sec-websocket-key"],e.headers["sec-websocket-protocol"],e.headers["sec-websocket-extensions"],e[r.kWs])}else return h.call(this,c,i)}}),n()},K=E(T,{fastify:"5.x",name:"@fastify/websocket"});exports.default=K;exports.isValidWebSocketKey=y;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FastifyPluginCallback, FastifyRequest } from 'fastify';
|
|
2
|
+
import { WebSocket } from './websocket-server';
|
|
3
|
+
export declare function isValidWebSocketKey(value: unknown): value is string;
|
|
4
|
+
declare function defaultErrorHandler(err: Error, conn: WebSocket, request: FastifyRequest): void;
|
|
5
|
+
declare const _default: FastifyPluginCallback<{
|
|
6
|
+
errorHandler?: typeof defaultErrorHandler;
|
|
7
|
+
options?: any;
|
|
8
|
+
}, any>;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import E from "fastify-plugin";
|
|
2
|
+
import { kWs as i, kRes as H } from "./symbols.js";
|
|
3
|
+
import { WebSocketServer as R, WebSocket as m } from "./websocket-server.js";
|
|
4
|
+
const v = /^[A-Za-z0-9+/]{22}==$/;
|
|
5
|
+
function T(o) {
|
|
6
|
+
return typeof o != "string" || !v.test(o) ? !1 : Buffer.from(o, "base64").byteLength === 16;
|
|
7
|
+
}
|
|
8
|
+
function A(o, l, s) {
|
|
9
|
+
s.log.error(o), l.close();
|
|
10
|
+
}
|
|
11
|
+
const K = (o, l, s) => {
|
|
12
|
+
const { server: p } = o, { errorHandler: n = A, options: y } = l;
|
|
13
|
+
if (n && typeof n != "function")
|
|
14
|
+
return s(new Error("invalid errorHandler function"));
|
|
15
|
+
const k = new R(y);
|
|
16
|
+
p[i] = k, o.decorate("websocketServer", k), o.addHook("onRoute", (c) => {
|
|
17
|
+
if (!!!c.websocket || c.method === "HEAD" || c.method === "OPTIONS") return;
|
|
18
|
+
const h = typeof c.ws == "object" ? c.ws : {}, b = c.handler, w = Buffer.from(c.url || "/"), u = {};
|
|
19
|
+
if (h.topics)
|
|
20
|
+
for (const t of h.topics)
|
|
21
|
+
u[t] = m.allocTopic(w, t);
|
|
22
|
+
c.handler = function(t, a) {
|
|
23
|
+
const e = t.raw;
|
|
24
|
+
if (e[i]) {
|
|
25
|
+
const S = e.headers["sec-websocket-key"];
|
|
26
|
+
if (!T(S)) {
|
|
27
|
+
a.code(400).send("Invalid sec-websocket-key header");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
a.hijack();
|
|
31
|
+
const W = e.socket[H];
|
|
32
|
+
if (e.socket[i] = !0, e.socket.aborted || e.socket.destroyed) return;
|
|
33
|
+
W.upgrade(
|
|
34
|
+
{
|
|
35
|
+
req: e,
|
|
36
|
+
handler: (g) => {
|
|
37
|
+
const r = new m(w, g, u);
|
|
38
|
+
let f;
|
|
39
|
+
try {
|
|
40
|
+
e.once("error", () => {
|
|
41
|
+
r.close();
|
|
42
|
+
}), e.once("close", () => {
|
|
43
|
+
r.end(1e3, "Normal Closure");
|
|
44
|
+
}), f = b.call(this, r, t, a);
|
|
45
|
+
} catch (d) {
|
|
46
|
+
return n.call(this, d, r, t);
|
|
47
|
+
}
|
|
48
|
+
f && typeof f.catch == "function" && f.catch((d) => n.call(this, d, r, t));
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
e.headers["sec-websocket-key"],
|
|
52
|
+
e.headers["sec-websocket-protocol"],
|
|
53
|
+
e.headers["sec-websocket-extensions"],
|
|
54
|
+
e[i]
|
|
55
|
+
);
|
|
56
|
+
} else
|
|
57
|
+
return b.call(this, t, a);
|
|
58
|
+
};
|
|
59
|
+
}), s();
|
|
60
|
+
}, I = E(K, {
|
|
61
|
+
fastify: "5.x",
|
|
62
|
+
name: "@fastify/websocket"
|
|
63
|
+
});
|
|
64
|
+
export {
|
|
65
|
+
I as default,
|
|
66
|
+
T as isValidWebSocketKey
|
|
67
|
+
};
|
package/dist/request.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("streamx"),s=require("./symbols.cjs"),c=()=>{};class a extends u.Readable{socket;method;httpVersion="1.1";httpVersionMajor=1;httpVersionMinor=1;readableEnded;complete=!1;headersDistinct=Object.create(null);trailersDistinct=Object.create(null);trailers=Object.create(null);[s.kUrl];[s.kHeaders];[s.kWs];constructor(e,t,r){super(),this.socket=t,this.method=r,this.readableEnded=!1;const i=e.getQuery();this[s.kUrl]=e.getUrl()+(i&&i.length>0?`?${i}`:"");const o=Object.create(null);e.forEach((d,h)=>{o[d]=h,this.headersDistinct[d]=[h]}),this[s.kHeaders]=o,this.once("error",c);const n=super.destroy.bind(this);t.once("error",n),t.once("close",n),t.once("aborted",()=>{this.emit("aborted")})}get aborted(){return this.socket.aborted}get url(){return this[s.kUrl]}set url(e){this[s.kUrl]=e}get headers(){return this[s.kHeaders]}setEncoding(e){return this.socket.setEncoding(e),this}setTimeout(e,t){return this.socket.setTimeout(e),t&&this.once("timeout",t),this}destroy(e){return this.destroyed||this.destroying?this:(this.socket.destroy(e),this)}unpipe(e){return e.destroy&&e.destroy(),this}_read(e){if(this.destroyed||this.destroying||this.socket.destroyed)return e();this.socket.onRead((t,r)=>{if(t)return e(t);if(this.destroyed||this.destroying)return e();this.push(r),r||(this.complete=!0,this.readableEnded=!0,e())})}}exports.Request=a;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { default as uws } from 'uWebSockets.js';
|
|
2
|
+
import { Readable } from 'streamx';
|
|
3
|
+
import { HTTPSocket } from './http-socket';
|
|
4
|
+
import { kHeaders, kUrl, kWs } from './symbols';
|
|
5
|
+
export declare class Request extends Readable {
|
|
6
|
+
socket: HTTPSocket;
|
|
7
|
+
method: string;
|
|
8
|
+
httpVersion: string;
|
|
9
|
+
httpVersionMajor: number;
|
|
10
|
+
httpVersionMinor: number;
|
|
11
|
+
readableEnded: boolean;
|
|
12
|
+
complete: boolean;
|
|
13
|
+
headersDistinct: Record<string, string[]>;
|
|
14
|
+
trailersDistinct: Record<string, string[]>;
|
|
15
|
+
trailers: Record<string, string>;
|
|
16
|
+
[kUrl]: string;
|
|
17
|
+
[kHeaders]: Record<string, string>;
|
|
18
|
+
[kWs]?: uws.us_socket_context_t;
|
|
19
|
+
constructor(req: uws.HttpRequest, socket: HTTPSocket, method: string);
|
|
20
|
+
get aborted(): boolean;
|
|
21
|
+
get url(): string;
|
|
22
|
+
set url(url: string);
|
|
23
|
+
get headers(): Record<string, string>;
|
|
24
|
+
setEncoding(encoding: string): this;
|
|
25
|
+
setTimeout(timeout: number, cb?: () => void): this;
|
|
26
|
+
destroy(err?: Error): this;
|
|
27
|
+
unpipe(writable: any): this;
|
|
28
|
+
_read(cb: (err?: Error | null) => void): void;
|
|
29
|
+
}
|
package/dist/request.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Readable as u } from "streamx";
|
|
2
|
+
import { kUrl as r, kHeaders as o, kWs as a } from "./symbols.js";
|
|
3
|
+
const l = () => {
|
|
4
|
+
};
|
|
5
|
+
class m extends u {
|
|
6
|
+
socket;
|
|
7
|
+
method;
|
|
8
|
+
httpVersion = "1.1";
|
|
9
|
+
httpVersionMajor = 1;
|
|
10
|
+
httpVersionMinor = 1;
|
|
11
|
+
readableEnded;
|
|
12
|
+
complete = !1;
|
|
13
|
+
headersDistinct = /* @__PURE__ */ Object.create(null);
|
|
14
|
+
trailersDistinct = /* @__PURE__ */ Object.create(null);
|
|
15
|
+
trailers = /* @__PURE__ */ Object.create(null);
|
|
16
|
+
[r];
|
|
17
|
+
[o];
|
|
18
|
+
[a];
|
|
19
|
+
constructor(t, e, s) {
|
|
20
|
+
super(), this.socket = e, this.method = s, this.readableEnded = !1;
|
|
21
|
+
const i = t.getQuery();
|
|
22
|
+
this[r] = t.getUrl() + (i && i.length > 0 ? `?${i}` : "");
|
|
23
|
+
const n = /* @__PURE__ */ Object.create(null);
|
|
24
|
+
t.forEach((d, c) => {
|
|
25
|
+
n[d] = c, this.headersDistinct[d] = [c];
|
|
26
|
+
}), this[o] = n, this.once("error", l);
|
|
27
|
+
const h = super.destroy.bind(this);
|
|
28
|
+
e.once("error", h), e.once("close", h), e.once("aborted", () => {
|
|
29
|
+
this.emit("aborted");
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
get aborted() {
|
|
33
|
+
return this.socket.aborted;
|
|
34
|
+
}
|
|
35
|
+
get url() {
|
|
36
|
+
return this[r];
|
|
37
|
+
}
|
|
38
|
+
set url(t) {
|
|
39
|
+
this[r] = t;
|
|
40
|
+
}
|
|
41
|
+
get headers() {
|
|
42
|
+
return this[o];
|
|
43
|
+
}
|
|
44
|
+
setEncoding(t) {
|
|
45
|
+
return this.socket.setEncoding(t), this;
|
|
46
|
+
}
|
|
47
|
+
setTimeout(t, e) {
|
|
48
|
+
return this.socket.setTimeout(t), e && this.once("timeout", e), this;
|
|
49
|
+
}
|
|
50
|
+
destroy(t) {
|
|
51
|
+
return this.destroyed || this.destroying ? this : (this.socket.destroy(t), this);
|
|
52
|
+
}
|
|
53
|
+
unpipe(t) {
|
|
54
|
+
return t.destroy && t.destroy(), this;
|
|
55
|
+
}
|
|
56
|
+
_read(t) {
|
|
57
|
+
if (this.destroyed || this.destroying || this.socket.destroyed) return t();
|
|
58
|
+
this.socket.onRead((e, s) => {
|
|
59
|
+
if (e) return t(e);
|
|
60
|
+
if (this.destroyed || this.destroying) return t();
|
|
61
|
+
this.push(s), s || (this.complete = !0, this.readableEnded = !0, t());
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
m as Request
|
|
67
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("node:http"),u=require("streamx"),o=require("./errors.cjs"),i=require("./symbols.cjs");class f{isMultiValue;name;value;constructor(e,t){this.isMultiValue=Array.isArray(t),this.name=e,this.value=this.isMultiValue?t:String(t)}}const a=Buffer.alloc(0);function h(n,e=!1){return n?{chunk:n,empty:!1,end:e,byteLength:Buffer.isBuffer(n)?n.byteLength:Buffer.byteLength(n)}:{chunk:a,empty:!0,end:e,byteLength:0}}const c=()=>{},y={byteLength(n){return n.byteLength}};class l extends u.Writable{socket;statusCode=200;statusMessage;headersSent=!1;chunked=!1;contentLength=null;writableEnded=!1;firstChunk=!0;_boundEmitDrain;[i.kHeaders];constructor(e){super(y),this.socket=e,this._boundEmitDrain=this._emitDrain.bind(this),this[i.kHeaders]=new Map;const t=this.destroy.bind(this);this.once("error",c),e.once("error",t),e.once("close",t),e.once("aborted",()=>{this.emit("aborted")})}get aborted(){return this.socket.aborted}get finished(){return this.socket.writableEnded&&!this.socket.aborted}get status(){return`${this.statusCode} ${this.statusMessage||d.STATUS_CODES[this.statusCode]}`}get bytesWritten(){return this.socket.bytesWritten}hasHeader(e){return this[i.kHeaders].has(e.toLowerCase())}getHeader(e){return this[i.kHeaders].get(e.toLowerCase())?.value}getHeaders(){const e={};return this[i.kHeaders].forEach((t,s)=>{e[s]=t.value}),e}setHeader(e,t){if(this.headersSent)throw new o.ERR_HEAD_SET;e=e.replace(/[\r\n]/g,"");const s=e.toLowerCase();return s==="content-length"?(this.contentLength=Number(t),this):s==="transfer-encoding"?(this.chunked=String(t).includes("chunked"),this):(typeof t=="string"?t=t.replace(/[\r\n]/g,""):Array.isArray(t)&&(t=t.map(r=>typeof r=="string"?r.replace(/[\r\n]/g,""):r)),this[i.kHeaders].set(s,new f(e,t)),this)}removeHeader(e){if(this.headersSent)throw new o.ERR_HEAD_SET;this[i.kHeaders].delete(e.toLowerCase())}writeHead(e,t,s){if(this.headersSent)throw new o.ERR_HEAD_SET;if(this.statusCode=e,typeof t=="object"?s=t:t&&(this.statusMessage=typeof t=="string"?t.replace(/[\r\n]/g,""):t),s)for(const r of Object.keys(s))this.setHeader(r,s[r]);return this}end(e,t,s){if(typeof e=="function"?(s=e,e=void 0):typeof t=="function"&&(s=t),this.aborted)return s&&process.nextTick(s),this;if(this.destroyed)throw new o.ERR_STREAM_DESTROYED;return this.writableEnded=!0,s&&this.once("finish",s),super.end(h(e,!0)),this}addTrailers(){}destroy(e){return this.destroyed||this.destroying||this.aborted?this:(this.socket.destroy(e),this)}write(e,t,s){if(typeof t=="function"&&(s=t),this.aborted)return s&&process.nextTick(s),!1;if(this.destroyed)throw new o.ERR_STREAM_DESTROYED;const r=h(e);return this.firstChunk&&this.contentLength!==null&&this.contentLength===r.byteLength?(r.end=!0,this.writableEnded=!0,super.end(r),s&&process.nextTick(s),!0):(this.firstChunk=!1,this.headersSent||(this.headersSent=!0,this.socket[i.kHead]={headers:this[i.kHeaders],status:this.status}),this.socket.write(r,null,()=>{this._boundEmitDrain(),s&&s()}),!this.socket.writableNeedDrain)}_emitDrain(){this.emit("drain")}_write(e,t){if(this.aborted)return t();if(this.headersSent||(this.headersSent=!0,this.socket[i.kHead]={headers:this[i.kHeaders],status:this.status}),e.end){this.socket.end(e,null,t);return}this.socket.write(e,null,t)}_destroy(e){if(this.socket.destroyed)return e();this.socket.once("close",e)}}exports.Response=l;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Writable } from 'streamx';
|
|
2
|
+
import { HTTPSocket } from './http-socket';
|
|
3
|
+
import { kHeaders } from './symbols';
|
|
4
|
+
declare class Header {
|
|
5
|
+
isMultiValue: boolean;
|
|
6
|
+
name: string;
|
|
7
|
+
value: unknown;
|
|
8
|
+
constructor(name: string, value: unknown);
|
|
9
|
+
}
|
|
10
|
+
export declare class Response extends Writable {
|
|
11
|
+
socket: HTTPSocket;
|
|
12
|
+
statusCode: number;
|
|
13
|
+
statusMessage?: string;
|
|
14
|
+
headersSent: boolean;
|
|
15
|
+
chunked: boolean;
|
|
16
|
+
contentLength: number | null;
|
|
17
|
+
writableEnded: boolean;
|
|
18
|
+
firstChunk: boolean;
|
|
19
|
+
_boundEmitDrain: () => void;
|
|
20
|
+
[kHeaders]: Map<string, Header>;
|
|
21
|
+
constructor(socket: HTTPSocket);
|
|
22
|
+
get aborted(): boolean;
|
|
23
|
+
get finished(): boolean;
|
|
24
|
+
get status(): string;
|
|
25
|
+
get bytesWritten(): number;
|
|
26
|
+
hasHeader(name: string): boolean;
|
|
27
|
+
getHeader(name: string): unknown;
|
|
28
|
+
getHeaders(): Record<string, string>;
|
|
29
|
+
setHeader(name: string, value: string | string[] | number): this;
|
|
30
|
+
removeHeader(name: string): void;
|
|
31
|
+
writeHead(statusCode: number, statusMessage?: string | Record<string, any>, headers?: Record<string, any>): this;
|
|
32
|
+
end(data?: any, encoding?: any, callback?: () => void): this;
|
|
33
|
+
addTrailers(): void;
|
|
34
|
+
destroy(err?: Error): this;
|
|
35
|
+
write(data: any, encoding?: any, cb?: (err?: Error | null) => void): boolean;
|
|
36
|
+
_emitDrain(): void;
|
|
37
|
+
_write(data: any, cb: (err?: Error | null) => void): void;
|
|
38
|
+
_destroy(cb: (err?: Error | null) => void): void;
|
|
39
|
+
}
|
|
40
|
+
export {};
|