svelte-adapter-uws 0.4.12 → 0.4.14
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/files/handler.js +30 -30
- package/files/utils.js +34 -0
- package/package.json +1 -1
- package/testing.js +13 -9
package/files/handler.js
CHANGED
|
@@ -12,7 +12,7 @@ import { manifest, prerendered, base } from 'MANIFEST';
|
|
|
12
12
|
import { env } from 'ENV';
|
|
13
13
|
import * as wsModule from 'WS_HANDLER';
|
|
14
14
|
import { parseCookies, createCookies } from './cookies.js';
|
|
15
|
-
import { mimeLookup, parse_as_bytes, parse_origin } from './utils.js';
|
|
15
|
+
import { mimeLookup, parse_as_bytes, parse_origin, writeChunkWithBackpressure } from './utils.js';
|
|
16
16
|
|
|
17
17
|
/* global ENV_PREFIX */
|
|
18
18
|
/* global PRECOMPRESS */
|
|
@@ -1209,11 +1209,11 @@ async function writeResponse(res, response, state, acceptEncoding) {
|
|
|
1209
1209
|
return;
|
|
1210
1210
|
}
|
|
1211
1211
|
|
|
1212
|
-
// Multi-chunk streaming response
|
|
1213
|
-
// cork
|
|
1214
|
-
//
|
|
1215
|
-
//
|
|
1216
|
-
//
|
|
1212
|
+
// Multi-chunk streaming response. Headers + first two chunks share one
|
|
1213
|
+
// cork so they flush as a single syscall. Subsequent chunks are each
|
|
1214
|
+
// written inside their own cork via writeChunkWithBackpressure, which
|
|
1215
|
+
// also captures the drain signal from res.write() without tripping the
|
|
1216
|
+
// uWS "writes must be made from within a corked callback" warning.
|
|
1217
1217
|
if (state.aborted) return;
|
|
1218
1218
|
streaming = true;
|
|
1219
1219
|
res.cork(() => {
|
|
@@ -1222,17 +1222,13 @@ async function writeResponse(res, response, state, acceptEncoding) {
|
|
|
1222
1222
|
res.write(second.value);
|
|
1223
1223
|
});
|
|
1224
1224
|
|
|
1225
|
-
// Stream remaining chunks with backpressure (30s timeout per drain)
|
|
1226
1225
|
for (;;) {
|
|
1227
1226
|
const { done, value } = await reader.read();
|
|
1228
1227
|
if (done || state.aborted) break;
|
|
1229
1228
|
|
|
1230
|
-
const
|
|
1231
|
-
if (
|
|
1232
|
-
const drained = await
|
|
1233
|
-
const timer = setTimeout(() => resolve(false), 30000);
|
|
1234
|
-
res.onWritable(() => { clearTimeout(timer); resolve(true); return true; });
|
|
1235
|
-
});
|
|
1229
|
+
const result = writeChunkWithBackpressure(res, value);
|
|
1230
|
+
if (result !== true) {
|
|
1231
|
+
const drained = await result;
|
|
1236
1232
|
if (!drained) { streamTimedOut = true; break; }
|
|
1237
1233
|
if (state.aborted) break;
|
|
1238
1234
|
}
|
|
@@ -1243,7 +1239,7 @@ async function writeResponse(res, response, state, acceptEncoding) {
|
|
|
1243
1239
|
// Backpressure drained past the 30s deadline. Abruptly close the
|
|
1244
1240
|
// connection rather than sending a clean EOF on a partial body,
|
|
1245
1241
|
// which would look like a successful but truncated response.
|
|
1246
|
-
res.close();
|
|
1242
|
+
res.cork(() => res.close());
|
|
1247
1243
|
} else {
|
|
1248
1244
|
res.cork(() => res.end());
|
|
1249
1245
|
}
|
|
@@ -1657,7 +1653,9 @@ if (WS_ENABLED) {
|
|
|
1657
1653
|
// no cookie parsing). Inject remoteAddress so plugins/ratelimit can
|
|
1658
1654
|
// key on the real client IP via ws.getUserData().remoteAddress.
|
|
1659
1655
|
if (!wsModule.upgrade) {
|
|
1660
|
-
res.
|
|
1656
|
+
res.cork(() => {
|
|
1657
|
+
res.upgrade({ remoteAddress: clientIp }, secKey, secProtocol, secExtensions, context);
|
|
1658
|
+
});
|
|
1661
1659
|
return;
|
|
1662
1660
|
}
|
|
1663
1661
|
|
|
@@ -1727,23 +1725,25 @@ if (WS_ENABLED) {
|
|
|
1727
1725
|
}
|
|
1728
1726
|
const ud = userData || {};
|
|
1729
1727
|
if (!ud.remoteAddress) ud.remoteAddress = clientIp;
|
|
1730
|
-
if (responseHeaders)
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1728
|
+
if (responseHeaders) maybeWarnSetCookieOnUpgrade(responseHeaders);
|
|
1729
|
+
res.cork(() => {
|
|
1730
|
+
if (responseHeaders) {
|
|
1731
|
+
for (const [hk, hv] of Object.entries(responseHeaders)) {
|
|
1732
|
+
if (Array.isArray(hv)) {
|
|
1733
|
+
for (const v of hv) res.writeHeader(hk, v);
|
|
1734
|
+
} else {
|
|
1735
|
+
res.writeHeader(hk, hv);
|
|
1736
|
+
}
|
|
1737
1737
|
}
|
|
1738
1738
|
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
);
|
|
1739
|
+
res.upgrade(
|
|
1740
|
+
ud,
|
|
1741
|
+
secKey,
|
|
1742
|
+
secProtocol,
|
|
1743
|
+
secExtensions,
|
|
1744
|
+
context
|
|
1745
|
+
);
|
|
1746
|
+
});
|
|
1747
1747
|
})
|
|
1748
1748
|
.catch((err) => {
|
|
1749
1749
|
clearTimeout(timer);
|
package/files/utils.js
CHANGED
|
@@ -111,6 +111,40 @@ export function parse_as_bytes(value) {
|
|
|
111
111
|
return Number(multiplier !== 1 ? normalized.slice(0, -1) : normalized) * multiplier;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Write a chunk to a uWS HttpResponse inside a cork and, if backpressure
|
|
116
|
+
* builds, return a Promise that resolves when the socket drains or the
|
|
117
|
+
* timeout elapses. Returns `true` synchronously when no drain is needed.
|
|
118
|
+
*
|
|
119
|
+
* All uWS response mutations (write + onWritable registration) happen
|
|
120
|
+
* inside the cork callback, which uWS invokes synchronously, so the
|
|
121
|
+
* boolean return value of `res.write()` is captured correctly.
|
|
122
|
+
*
|
|
123
|
+
* @param {{ cork: (fn: () => void) => void, write: (value: any) => boolean, onWritable: (fn: () => boolean) => void }} res
|
|
124
|
+
* @param {any} value
|
|
125
|
+
* @param {number} [timeoutMs]
|
|
126
|
+
* @returns {true | Promise<boolean>} true if the write succeeded without drain; otherwise a promise that resolves true on drain or false on timeout.
|
|
127
|
+
*/
|
|
128
|
+
export function writeChunkWithBackpressure(res, value, timeoutMs = 30000) {
|
|
129
|
+
let ok = false;
|
|
130
|
+
/** @type {Promise<boolean> | null} */
|
|
131
|
+
let drainPromise = null;
|
|
132
|
+
res.cork(() => {
|
|
133
|
+
ok = res.write(value);
|
|
134
|
+
if (!ok) {
|
|
135
|
+
drainPromise = new Promise((resolve) => {
|
|
136
|
+
const timer = setTimeout(() => resolve(false), timeoutMs);
|
|
137
|
+
res.onWritable(() => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
resolve(true);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
return ok ? true : /** @type {Promise<boolean>} */ (drainPromise);
|
|
146
|
+
}
|
|
147
|
+
|
|
114
148
|
/**
|
|
115
149
|
* @param {string | undefined} value
|
|
116
150
|
* @returns {string | undefined}
|
package/package.json
CHANGED
package/testing.js
CHANGED
|
@@ -115,7 +115,9 @@ export async function createTestServer(options = {}) {
|
|
|
115
115
|
const rawIp = new TextDecoder().decode(res.getRemoteAddressAsText());
|
|
116
116
|
|
|
117
117
|
if (!handler.upgrade) {
|
|
118
|
-
res.
|
|
118
|
+
res.cork(() => {
|
|
119
|
+
res.upgrade({ remoteAddress: rawIp }, secKey, secProtocol, secExtensions, context);
|
|
120
|
+
});
|
|
119
121
|
return;
|
|
120
122
|
}
|
|
121
123
|
|
|
@@ -143,16 +145,18 @@ export async function createTestServer(options = {}) {
|
|
|
143
145
|
userData = result || {};
|
|
144
146
|
}
|
|
145
147
|
if (!userData.remoteAddress) userData.remoteAddress = rawIp;
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
res.cork(() => {
|
|
149
|
+
if (responseHeaders) {
|
|
150
|
+
for (const [hk, hv] of Object.entries(responseHeaders)) {
|
|
151
|
+
if (Array.isArray(hv)) {
|
|
152
|
+
for (const v of hv) res.writeHeader(hk, v);
|
|
153
|
+
} else {
|
|
154
|
+
res.writeHeader(hk, hv);
|
|
155
|
+
}
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
res.upgrade(userData, secKey, secProtocol, secExtensions, context);
|
|
159
|
+
});
|
|
156
160
|
})
|
|
157
161
|
.catch((err) => {
|
|
158
162
|
if (!aborted) {
|