@server/next 0.15.2 → 0.16.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "description": "An experimental reimplementation of server.js focused on the DX",
5
5
  "homepage": "https://node-server.com/",
6
6
  "repository": "https://github.com/franciscop/server-next.git",
package/readme.md CHANGED
@@ -21,7 +21,7 @@ app([
21
21
  ]);
22
22
  ```
23
23
 
24
- It includes all the things you would expect from a modern Server framework, like routing, static file serving, body+file parsing, gzip+brotli\*, streaming, etc.
24
+ It includes all the things you would expect from a modern Server framework, like routing, static file serving, body+file parsing, gzip+brotli, streaming, server-timing, plugins\*, etc.
25
25
 
26
26
  > \* not yet available
27
27
 
@@ -17,13 +17,6 @@ const format = (n, [below, above], limit = Infinity) => {
17
17
  }
18
18
  };
19
19
 
20
- const findSize = ({ headers, body, size }, res) => {
21
- if (size) return size;
22
- if (body) return body.length;
23
- if (headers["content-length"]) return +headers["content-length"];
24
- return 0;
25
- };
26
-
27
20
  function simpleType(type) {
28
21
  const simpler = {
29
22
  "text/html": "html",
@@ -59,7 +52,7 @@ export default function RequestLogger(ctx) {
59
52
  const statColor =
60
53
  status < 300 ? "green" : status < 500 ? "yellow" : "red";
61
54
  const statusBlock = `{${statColor}}[${ctx.res.status}]{/}`;
62
- const resSize = format(findSize(ctx.res), ["b", "kb"], 100000);
55
+ const resSize = format(ctx.res.size || 0, ["b", "kb"], 100000);
63
56
  const t = Math.round(ctx.time._total - ctx.time._init);
64
57
  const resTime = format(t, ["ms", "s"], 1000);
65
58
 
package/src/ServerUrl.js CHANGED
@@ -36,7 +36,11 @@ export default class ServerUrl extends URL {
36
36
 
37
37
  for (let key of properties) {
38
38
  const value = key in custom ? custom[key] : this[key];
39
- Object.defineProperty(this, key, { value, enumerable: true });
39
+ Object.defineProperty(this, key, {
40
+ value,
41
+ enumerable: true,
42
+ writable: true,
43
+ });
40
44
  }
41
45
  }
42
46
 
package/src/index.js CHANGED
@@ -2,7 +2,9 @@ import http from "node:http";
2
2
  import fs from "node:fs";
3
3
  import fsp from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import zlib from "node:zlib";
5
6
  import { pipeline } from "node:stream/promises";
7
+ import { Readable, PassThrough } from "node:stream";
6
8
 
7
9
  import "dotenv/config";
8
10
 
@@ -29,6 +31,35 @@ const exists = (file) => {
29
31
  );
30
32
  };
31
33
 
34
+ const measure = (ctx) => {
35
+ const sizeUp = new PassThrough();
36
+ ctx.res.size = 0;
37
+ sizeUp.on("data", (chunk) => {
38
+ ctx.res.size += chunk.length;
39
+ });
40
+ return sizeUp;
41
+ };
42
+
43
+ const findEncoding = (acceptEncoding) => {
44
+ let encoding;
45
+ if (/\bbr\b/.test(acceptEncoding)) {
46
+ encoding = "br";
47
+ } else if (/\bgzip\b/.test(acceptEncoding)) {
48
+ encoding = "gzip";
49
+ } else if (/\bdeflate\b/.test(acceptEncoding)) {
50
+ encoding = "deflate";
51
+ }
52
+
53
+ const methods = {
54
+ deflate: () => zlib.createDeflate(),
55
+ gzip: () => zlib.createGzip(),
56
+ br: () => zlib.createBrotliCompress(),
57
+ };
58
+
59
+ const compress = methods[encoding];
60
+ return [encoding, compress];
61
+ };
62
+
32
63
  const api = {
33
64
  get: [],
34
65
  post: [],
@@ -119,7 +150,6 @@ export default function (options = {}, plugins) {
119
150
  ctx.res.body = out;
120
151
  const isHtml = out.trim().startsWith("<");
121
152
  ctx.res.headers["content-type"] = isHtml ? "text/html" : "text/plain";
122
- ctx.res.headers["content-length"] = Buffer.byteLength(out);
123
153
  } else {
124
154
  if (out.pipe) {
125
155
  ctx.res.body = out;
@@ -127,7 +157,6 @@ export default function (options = {}, plugins) {
127
157
  } else {
128
158
  // Plain object
129
159
  if (out.type) ctx.res.headers["content-type"] = out.type;
130
- if (out.length) ctx.res.headers["content-length"] = out.length;
131
160
  ctx.res.headers = { ...ctx.res.headers, ...(out.headers || {}) };
132
161
  ctx.res.body = out.body || "";
133
162
  ctx.res.status = out.status || 200;
@@ -147,21 +176,30 @@ export default function (options = {}, plugins) {
147
176
  ctx.res.headers["server-timing"] += ", ";
148
177
  }
149
178
  });
179
+
180
+ const [encoding, compress] = findEncoding(ctx.headers["accept-encoding"]);
181
+ if (encoding) {
182
+ ctx.res.headers["content-encoding"] = encoding;
183
+ }
184
+
150
185
  res.writeHead(ctx.res.status, ctx.res.headers);
151
- if (ctx.res.body.pipe) {
152
- if (ctx.res.body.path) {
153
- ctx.res.type = ctx.res.body.path.split(".").pop();
154
- ctx.res.size = await fsp.stat(ctx.res.body.path).then((s) => s.size);
155
- } else {
156
- ctx.res.size = 0;
157
- ctx.res.body.on("data", function (chunk) {
158
- ctx.res.size += chunk.length;
159
- });
160
- }
161
- await pipeline(ctx.res.body, res);
186
+
187
+ // If it's not a pipe, e.g. a String, make it a pipe
188
+ if (!ctx.res.body.pipe) {
189
+ ctx.res.body = Readable.from([ctx.res.body]);
190
+ }
191
+
192
+ if (ctx.res.body.path) {
193
+ ctx.res.type = ctx.res.body.path.split(".").pop();
194
+ }
195
+
196
+ if (compress) {
197
+ await pipeline(ctx.res.body, compress(), measure(ctx), res);
162
198
  } else {
163
- res.end(ctx.res.body);
199
+ await pipeline(ctx.res.body, measure(ctx), res);
164
200
  }
201
+ res.end();
202
+
165
203
  // The actual sent headers, as seen by the response
166
204
  ctx.res.headers = Object.fromEntries(
167
205
  res._header