ultimate-express 1.0.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.
Files changed (200) hide show
  1. package/.github/workflows/test.yml +27 -0
  2. package/EXPRESS_LICENSE +26 -0
  3. package/README.md +269 -0
  4. package/package.json +76 -0
  5. package/src/application.js +295 -0
  6. package/src/index.js +23 -0
  7. package/src/middlewares.js +95 -0
  8. package/src/request.js +324 -0
  9. package/src/response.js +686 -0
  10. package/src/router.js +490 -0
  11. package/src/utils.js +285 -0
  12. package/src/view.js +103 -0
  13. package/src/workers/fs.js +14 -0
  14. package/tests/index.js +64 -0
  15. package/tests/parts/.test/index.html +11 -0
  16. package/tests/parts/big.jpg +0 -0
  17. package/tests/parts/index.art +12 -0
  18. package/tests/parts/index.dot +12 -0
  19. package/tests/parts/index.ejs +12 -0
  20. package/tests/parts/index.handlebars +2 -0
  21. package/tests/parts/index.html +12 -0
  22. package/tests/parts/index.mustache +12 -0
  23. package/tests/parts/index.pug +6 -0
  24. package/tests/parts/index.swig +12 -0
  25. package/tests/parts/layouts/main.handlebars +12 -0
  26. package/tests/preload.cjs +5 -0
  27. package/tests/singular.js +41 -0
  28. package/tests/tests/app/app-engine.js +8 -0
  29. package/tests/tests/app/app-on-mount.js +24 -0
  30. package/tests/tests/app/app-path.js +21 -0
  31. package/tests/tests/app/app-route.js +30 -0
  32. package/tests/tests/app/options.js +16 -0
  33. package/tests/tests/app/setting-inheritance.js +16 -0
  34. package/tests/tests/engines/art-template.js +33 -0
  35. package/tests/tests/engines/dot.js +28 -0
  36. package/tests/tests/engines/ejs.js +25 -0
  37. package/tests/tests/engines/handlebars.js +28 -0
  38. package/tests/tests/engines/mustache.js +28 -0
  39. package/tests/tests/engines/pug.js +25 -0
  40. package/tests/tests/engines/swig.js +28 -0
  41. package/tests/tests/errors/error-handling-middleware.js +22 -0
  42. package/tests/tests/errors/next-error-optimized.js +26 -0
  43. package/tests/tests/errors/next-error.js +26 -0
  44. package/tests/tests/errors/unexpected-error-handling.js +18 -0
  45. package/tests/tests/listen/listen-random.js +11 -0
  46. package/tests/tests/listen/listen-specific.js +17 -0
  47. package/tests/tests/middlewares/body-json.js +31 -0
  48. package/tests/tests/middlewares/body-raw-deflate.js +43 -0
  49. package/tests/tests/middlewares/body-raw-gzip.js +43 -0
  50. package/tests/tests/middlewares/body-raw.js +41 -0
  51. package/tests/tests/middlewares/body-text.js +27 -0
  52. package/tests/tests/middlewares/body-urlencoded.js +30 -0
  53. package/tests/tests/middlewares/cookie-parser-signed.js +31 -0
  54. package/tests/tests/middlewares/cookie-parser.js +28 -0
  55. package/tests/tests/middlewares/cookie-session.js +40 -0
  56. package/tests/tests/middlewares/cors.js +29 -0
  57. package/tests/tests/middlewares/errorhandler.js +26 -0
  58. package/tests/tests/middlewares/express-fileupload-temp.js +46 -0
  59. package/tests/tests/middlewares/express-fileupload.js +28 -0
  60. package/tests/tests/middlewares/express-rate-limit.js +33 -0
  61. package/tests/tests/middlewares/express-session.js +37 -0
  62. package/tests/tests/middlewares/express-static-options.js +72 -0
  63. package/tests/tests/middlewares/express-static.js +40 -0
  64. package/tests/tests/middlewares/method-override.js +33 -0
  65. package/tests/tests/middlewares/multer.js +43 -0
  66. package/tests/tests/middlewares/multiple-middlewares.js +37 -0
  67. package/tests/tests/middlewares/response-time.js +29 -0
  68. package/tests/tests/middlewares/serve-index.js +38 -0
  69. package/tests/tests/middlewares/serve-static.js +38 -0
  70. package/tests/tests/middlewares/vhost.js +50 -0
  71. package/tests/tests/params/array-param.js +30 -0
  72. package/tests/tests/params/nested-params.js +24 -0
  73. package/tests/tests/params/param-errors.js +56 -0
  74. package/tests/tests/params/param-function.js +49 -0
  75. package/tests/tests/params/param-next-route.js +48 -0
  76. package/tests/tests/params/param-optimized.js +38 -0
  77. package/tests/tests/params/param-use.js +39 -0
  78. package/tests/tests/params/param.js +68 -0
  79. package/tests/tests/params/params-regex.js +26 -0
  80. package/tests/tests/params/params-use.js +20 -0
  81. package/tests/tests/params/params.js +35 -0
  82. package/tests/tests/req/req-accepts-charsets.js +40 -0
  83. package/tests/tests/req/req-accepts-encodings.js +36 -0
  84. package/tests/tests/req/req-accepts-languages.js +41 -0
  85. package/tests/tests/req/req-accepts.js +41 -0
  86. package/tests/tests/req/req-app.js +17 -0
  87. package/tests/tests/req/req-baseurl.js +38 -0
  88. package/tests/tests/req/req-connection.js +19 -0
  89. package/tests/tests/req/req-fresh.js +59 -0
  90. package/tests/tests/req/req-get.js +78 -0
  91. package/tests/tests/req/req-headers-distinct.js +72 -0
  92. package/tests/tests/req/req-headers.js +72 -0
  93. package/tests/tests/req/req-host.js +45 -0
  94. package/tests/tests/req/req-hostname.js +45 -0
  95. package/tests/tests/req/req-ip.js +19 -0
  96. package/tests/tests/req/req-is.js +44 -0
  97. package/tests/tests/req/req-original-url.js +29 -0
  98. package/tests/tests/req/req-param.js +29 -0
  99. package/tests/tests/req/req-protocol.js +20 -0
  100. package/tests/tests/req/req-query.js +23 -0
  101. package/tests/tests/req/req-range.js +48 -0
  102. package/tests/tests/req/req-raw-headers.js +72 -0
  103. package/tests/tests/req/req-subdomains.js +48 -0
  104. package/tests/tests/req/req-url-nested.js +27 -0
  105. package/tests/tests/req/req-url-optimized-router.js +26 -0
  106. package/tests/tests/req/req-url-optimized.js +23 -0
  107. package/tests/tests/req/req-url.js +36 -0
  108. package/tests/tests/req/req-xhr.js +23 -0
  109. package/tests/tests/res/head-content-length.js +18 -0
  110. package/tests/tests/res/head.js +47 -0
  111. package/tests/tests/res/injecting.js +25 -0
  112. package/tests/tests/res/piping.js +23 -0
  113. package/tests/tests/res/res-app.js +17 -0
  114. package/tests/tests/res/res-append.js +24 -0
  115. package/tests/tests/res/res-attachment.js +19 -0
  116. package/tests/tests/res/res-clear-cookie.js +18 -0
  117. package/tests/tests/res/res-cookie.js +22 -0
  118. package/tests/tests/res/res-download.js +36 -0
  119. package/tests/tests/res/res-format.js +57 -0
  120. package/tests/tests/res/res-get.js +18 -0
  121. package/tests/tests/res/res-headers-sent.js +18 -0
  122. package/tests/tests/res/res-json.js +17 -0
  123. package/tests/tests/res/res-jsonp.js +25 -0
  124. package/tests/tests/res/res-links.js +21 -0
  125. package/tests/tests/res/res-location.js +34 -0
  126. package/tests/tests/res/res-redirect.js +46 -0
  127. package/tests/tests/res/res-remove-header.js +19 -0
  128. package/tests/tests/res/res-send-file.js +17 -0
  129. package/tests/tests/res/res-send-status.js +17 -0
  130. package/tests/tests/res/res-send.js +69 -0
  131. package/tests/tests/res/res-set.js +28 -0
  132. package/tests/tests/res/res-status.js +18 -0
  133. package/tests/tests/res/res-type.js +19 -0
  134. package/tests/tests/res/res-vary.js +19 -0
  135. package/tests/tests/res/res-write.js +29 -0
  136. package/tests/tests/routers/complex-routers.js +34 -0
  137. package/tests/tests/routers/empty-router.js +25 -0
  138. package/tests/tests/routers/lot-of-routes.js +38 -0
  139. package/tests/tests/routers/mergeparams.js +42 -0
  140. package/tests/tests/routers/nested-routers.js +52 -0
  141. package/tests/tests/routers/router-options.js +68 -0
  142. package/tests/tests/routers/routers.js +45 -0
  143. package/tests/tests/routers/simple-routers.js +35 -0
  144. package/tests/tests/routing/all.js +47 -0
  145. package/tests/tests/routing/array-arguments.js +35 -0
  146. package/tests/tests/routing/array-use.js +33 -0
  147. package/tests/tests/routing/async-use.js +25 -0
  148. package/tests/tests/routing/complex-routes.js +50 -0
  149. package/tests/tests/routing/lot-of-param-routes.js +26 -0
  150. package/tests/tests/routing/lot-of-routes.js +59 -0
  151. package/tests/tests/routing/next-existent-optimized-route.js +29 -0
  152. package/tests/tests/routing/next-existent-route.js +29 -0
  153. package/tests/tests/routing/next-nonexistent-optimized-route.js +19 -0
  154. package/tests/tests/routing/next-nonexistent-route.js +19 -0
  155. package/tests/tests/routing/next-special-cases.js +54 -0
  156. package/tests/tests/routing/next-unoptimized.js +39 -0
  157. package/tests/tests/routing/no-path-use.js +29 -0
  158. package/tests/tests/routing/non-string-routes.js +27 -0
  159. package/tests/tests/routing/req-multiple-mountpaths.js +34 -0
  160. package/tests/tests/routing/simple-routes.js +29 -0
  161. package/tests/tests/routing/simple-use.js +52 -0
  162. package/tests/tests/routing/some-middlewares.js +27 -0
  163. package/tests/tests/routing/special-characters.js +28 -0
  164. package/tests/tests/routing/star.js +31 -0
  165. package/tests/tests/routing/sub-apps.js +32 -0
  166. package/tests/tests/routing/trailing-slash.js +37 -0
  167. package/tests/tests/routing/weird-route-start.js +18 -0
  168. package/tests/tests/send-file/accept-ranges.js +26 -0
  169. package/tests/tests/send-file/callback.js +23 -0
  170. package/tests/tests/send-file/default-error-routing.js +21 -0
  171. package/tests/tests/send-file/dotfiles.js +24 -0
  172. package/tests/tests/send-file/etag.js +19 -0
  173. package/tests/tests/send-file/fs-threads.js +39 -0
  174. package/tests/tests/send-file/head.js +49 -0
  175. package/tests/tests/send-file/headers.js +23 -0
  176. package/tests/tests/send-file/if-match.js +31 -0
  177. package/tests/tests/send-file/if-range.js +37 -0
  178. package/tests/tests/send-file/if-unmodified-since.js +32 -0
  179. package/tests/tests/send-file/immutable.js +22 -0
  180. package/tests/tests/send-file/large-file.js +19 -0
  181. package/tests/tests/send-file/last-modified.js +21 -0
  182. package/tests/tests/send-file/max-age.js +21 -0
  183. package/tests/tests/send-file/path-traversal.js +35 -0
  184. package/tests/tests/send-file/range.js +57 -0
  185. package/tests/tests/send-file/simple.js +18 -0
  186. package/tests/tests/settings/case-sensitive-routing.js +48 -0
  187. package/tests/tests/settings/env-errors.js +38 -0
  188. package/tests/tests/settings/etag.js +36 -0
  189. package/tests/tests/settings/json-escape.js +45 -0
  190. package/tests/tests/settings/json-replacer.js +50 -0
  191. package/tests/tests/settings/json-spaces.js +44 -0
  192. package/tests/tests/settings/query-parser.js +45 -0
  193. package/tests/tests/settings/strict-routing.js +64 -0
  194. package/tests/tests/settings/subdomain-offset.js +38 -0
  195. package/tests/tests/settings/trust-proxy-host.js +58 -0
  196. package/tests/tests/settings/trust-proxy-ip.js +58 -0
  197. package/tests/tests/settings/trust-proxy-ips.js +58 -0
  198. package/tests/tests/settings/trust-proxy-protocol.js +46 -0
  199. package/tests/tests/settings/x-powered-by.js +32 -0
  200. package/tests/uws.js +14 -0
@@ -0,0 +1,27 @@
1
+ name: Run tests
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+ pull_request:
7
+ branches: [ "main" ]
8
+
9
+ jobs:
10
+ test:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout Source Tree
16
+ uses: actions/checkout@v3
17
+
18
+ - name: Setup Node.js environment
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 20
22
+
23
+ - name: Install dependencies
24
+ run: npm install --include=dev
25
+
26
+ - name: Run tests
27
+ run: npm run test
@@ -0,0 +1,26 @@
1
+ Express.js License (only Express.js code)
2
+
3
+ (The MIT License)
4
+
5
+ Copyright (c) 2009-2014 TJ Holowaychuk <tj@vision-media.ca>
6
+ Copyright (c) 2013-2014 Roman Shtylman <shtylman+expressjs@gmail.com>
7
+ Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining
10
+ a copy of this software and associated documentation files (the
11
+ 'Software'), to deal in the Software without restriction, including
12
+ without limitation the rights to use, copy, modify, merge, publish,
13
+ distribute, sublicense, and/or sell copies of the Software, and to
14
+ permit persons to whom the Software is furnished to do so, subject to
15
+ the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be
18
+ included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
21
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
25
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
26
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,269 @@
1
+ # µExpress
2
+
3
+ The *Ultimate* Express. Fastest http server with **full** Express compatibility, based on µWebSockets.
4
+
5
+ This library is a very fast re-implementation of Express.js 4.
6
+ It is designed to be a drop-in replacement for Express.js, with the same API and functionality, while being much faster. It is not a fork of Express.js.
7
+ To make sure µExpress matches behavior of Express in all cases, we run all tests with Express first, and then with µExpress and compare results to make sure they match.
8
+
9
+ `npm install ultimate-express`
10
+
11
+ ![Node.js >= 16.0.0](https://img.shields.io/badge/Node.js-%3E=16.0.0-green)
12
+
13
+ ## Difference from similar projects
14
+
15
+ Similar projects based on uWebSockets:
16
+
17
+ - `express` on Bun - since Bun uses uWS for its HTTP module, Express is about 2.5 times faster than on Node.js with 25k req/sec instead of 10k req/sec normally, but still slower than µExpress at 60k req/sec because it doesn't do uWS-specific optimizations.
18
+ - `hyper-express` - while having a similar API to Express, it's very far from being a drop-in replacement, and implements most of the functionality differently. This creates a lot of random quirks and issues, making the switch quite difficult. Built in middlewares are also very different.
19
+ - `uwebsockets-express` - this library is closer to being a drop-in replacement, but misses a lot of APIs, depends on Express by calling it's methods under the hood and doesn't try to optimize routing by using native uWS router.
20
+
21
+ ## Differences from Express
22
+
23
+ In a lot of cases, you can just replace `require("express")` with `require("ultimate-express")` and everything works the same. But there are some differences:
24
+
25
+ - `case sensitive routing` is enabled by default.
26
+ - Depending on how you send response, `Content-Length` header may be overwritten or not sent at all:
27
+ - - on simple responses with res.send(), res.json(), etc. it's set automatically (any value you set with res.set() is overwritten)
28
+ - - on streaming responses (piping, res.sendFile()) it's not sent because uWS uses chunked transfer encoding instead
29
+ - - on responses without body, it *is* sent (useful for HEAD requests)
30
+ - request body is only read for POST, PUT and PATCH requests by default. You can add additional methods by setting `body methods` to array with uppercased methods.
31
+ - For HTTPS, instead of doing this:
32
+
33
+ ```js
34
+ const https = require("https");
35
+ const express = require("express");
36
+
37
+ const app = express();
38
+
39
+ https.createServer({
40
+ key: fs.readFileSync('path/to/key.pem'),
41
+ cert: fs.readFileSync('path/to/cert.pem')
42
+ }, app).listen(3000, () => {
43
+ console.log('Server is running on port 3000');
44
+ });
45
+ ```
46
+
47
+ You have to pass `uwsOptions` to the `express()` constructor:
48
+ ```js
49
+ const express = require("u-express");
50
+
51
+ const app = express({
52
+ uwsOptions: {
53
+ // https://unetworking.github.io/uWebSockets.js/generated/interfaces/AppOptions.html
54
+ key_file_name: 'path/to/key.pem',
55
+ cert_file_name: 'path/to/cert.pem'
56
+ }
57
+ });
58
+
59
+ app.listen(3000, () => {
60
+ console.log('Server is running on port 3000');
61
+ });
62
+ ```
63
+
64
+ - This also applies to non-SSL HTTP too. Do not create http server manually, use `app.listen()` instead.
65
+
66
+ ## Performance tips
67
+
68
+ 1. µExpress tries to optimize routing as much as possible, but it's only possible if:
69
+ - `case sensitive routing` is enabled (it is by default, unlike in normal Express).
70
+ - only string paths without regex characters like *, +, (), {}, :param, etc. can be optimized.
71
+ - only 1-level deep routers can be optimized.
72
+
73
+ Optimized routes can be up to 10 times faster than normal routes, as they're using native uWS router and have pre-calculated path.
74
+
75
+ 2. Do not use external `serve-static` module. Instead use built-in `express.static()` middleware, which is optimized for uExpress.
76
+
77
+ 3. Do not set `body methods` to read body of requests with GET method or other methods that don't need a body. Reading body makes server about 10k req/sec slower.
78
+
79
+ 4. By default, µExpress creates 1 worker thread for reading files (or 0 if your CPU has only 1 core). You can change this number by setting `fsThreads` to a different number in `express()`, or set to 0 to disable thread pool for file reading (`express({ fsThreads: 0 })`). Threads are shared between all express() instances, with largest fsThreads number being used. Using more threads will not necessarily improve performance. Sometimes not using threads at all is faster, please [test](https://github.com/wg/wrk/) both options.
80
+
81
+ ## Compatibility
82
+
83
+ ✅ - Full support (all features and options are supported)
84
+ 🚧 - Partial support (some options are not supported)
85
+ ❌ - Not supported
86
+
87
+ ### express
88
+
89
+ - ✅ express()
90
+ - ✅ express.Router()
91
+ - ✅ express.json()
92
+ - ✅ express.urlencoded()
93
+ - ✅ express.static()
94
+ - ✅ express.text()
95
+ - ✅ express.raw()
96
+ - 🚧 express.request (this is not a constructor but a prototype for replacing methods)
97
+ - 🚧 express.response (this is not a constructor but a prototype for replacing methods)
98
+
99
+ ### Application
100
+
101
+ - ✅ app.listen()
102
+ - ✅ app.METHOD() (app.get, app.post, etc.)
103
+ - ✅ app.route()
104
+ - ✅ app.all()
105
+ - ✅ app.use()
106
+ - ✅ app.mountpath
107
+ - ✅ app.set()
108
+ - ✅ app.get()
109
+ - ✅ app.enable()
110
+ - ✅ app.disable()
111
+ - ✅ app.enabled()
112
+ - ✅ app.disabled()
113
+ - ✅ app.path()
114
+ - ✅ app.param(name, callback)
115
+ - ✅ app.param(callback)
116
+ - ✅ app.engine()
117
+ - ✅ app.render()
118
+ - ✅ app.locals
119
+ - ✅ app.settings
120
+ - ✅ app.engines
121
+ - ✅ app.on("mount")
122
+ - ✅ HEAD method
123
+
124
+ ### Application settings
125
+
126
+ - ✅ case sensitive routing
127
+ - ✅ env
128
+ - ✅ etag
129
+ - ✅ jsonp callback name
130
+ - ✅ json escape
131
+ - ✅ json replacer
132
+ - ✅ json spaces
133
+ - ✅ query parser
134
+ - ✅ strict routing
135
+ - ✅ subdomain offset
136
+ - ✅ trust proxy
137
+ - ✅ views
138
+ - ✅ view cache
139
+ - ✅ view engine
140
+ - ✅ x-powered-by
141
+
142
+ ### Request
143
+ - ✅ implements Readable stream
144
+ - ✅ req.app
145
+ - ✅ req.baseUrl
146
+ - ✅ req.body
147
+ - ✅ req.cookies
148
+ - ✅ req.fresh
149
+ - ✅ req.hostname
150
+ - ✅ req.headers
151
+ - ✅ req.headersDistinct
152
+ - ✅ req.rawHeaders
153
+ - ✅ req.ip
154
+ - ✅ req.ips
155
+ - ✅ req.method
156
+ - ✅ req.url
157
+ - ✅ req.originalUrl
158
+ - ✅ req.params
159
+ - ✅ req.path
160
+ - ✅ req.protocol
161
+ - ✅ req.query
162
+ - ✅ req.res
163
+ - ✅ req.secure
164
+ - ✅ req.signedCookies
165
+ - ✅ req.stale
166
+ - ✅ req.subdomains
167
+ - ✅ req.xhr
168
+ - 🚧 req.route (route implementation is different from Express)
169
+ - 🚧 req.connection, req.socket (only `encrypted`, `remoteAddress`, `localPort` and `remotePort` are supported)
170
+ - ✅ req.accepts()
171
+ - ✅ req.acceptsCharsets()
172
+ - ✅ req.acceptsEncodings()
173
+ - ✅ req.acceptsLanguages()
174
+ - ✅ req.get()
175
+ - ✅ req.is()
176
+ - ✅ req.param()
177
+ - ✅ req.range()
178
+
179
+ ### Response
180
+
181
+ - ✅ implements Writable stream
182
+ - ✅ res.app
183
+ - ✅ res.headersSent
184
+ - ✅ res.req
185
+ - ✅ res.locals
186
+ - ✅ res.append()
187
+ - ✅ res.attachment()
188
+ - ✅ res.cookie()
189
+ - ✅ res.clearCookie()
190
+ - ✅ res.download()
191
+ - ✅ res.end()
192
+ - ✅ res.format()
193
+ - ✅ res.getHeader(), res.get()
194
+ - ✅ res.json()
195
+ - ✅ res.jsonp()
196
+ - ✅ res.links()
197
+ - ✅ res.location()
198
+ - ✅ res.redirect()
199
+ - ✅ res.render()
200
+ - ✅ res.send()
201
+ - ✅ res.sendFile()
202
+ - - ✅ options.maxAge
203
+ - - ✅ options.root
204
+ - - ✅ options.lastModified
205
+ - - ✅ options.headers
206
+ - - ✅ options.dotfiles
207
+ - - ✅ options.acceptRanges
208
+ - - ✅ options.cacheControl
209
+ - - ✅ options.immutable
210
+ - - ✅ Range header
211
+ - - ✅ Setting ETag header
212
+ - - ✅ If-Match header
213
+ - - ✅ If-Modified-Since header
214
+ - - ✅ If-Unmodified-Since header
215
+ - - ✅ If-Range header
216
+ - ✅ res.sendStatus()
217
+ - ✅ res.header(), res.setHeader(), res.set()
218
+ - ✅ res.status()
219
+ - ✅ res.type()
220
+ - ✅ res.vary()
221
+ - ✅ res.removeHeader()
222
+ - ✅ res.write()
223
+ - ✅ res.writeHead()
224
+
225
+ ### Router
226
+
227
+ - ✅ router.all()
228
+ - ✅ router.METHOD() (router.get, router.post, etc.)
229
+ - ✅ router.route()
230
+ - ✅ router.use()
231
+ - ✅ router.param(name, callback)
232
+ - ✅ router.param(callback)
233
+ - ✅ options.caseSensitive
234
+ - ✅ options.strict
235
+ - ✅ options.mergeParams
236
+
237
+ ## Tested middlewares
238
+
239
+ Most of the middlewares that are compatible with Express are compatible with µExpress. Here's list of middlewares that we test for compatibility:
240
+
241
+ - ✅ [body-parser](https://npmjs.com/package/body-parser)
242
+ - ✅ [cookie-parser](https://npmjs.com/package/cookie-parser)
243
+ - ✅ [cookie-session](https://npmjs.com/package/cookie-session)
244
+ - ✅ [serve-static](https://npmjs.com/package/serve-static) (use `express.static()` instead for better performance)
245
+ - ✅ [serve-index](https://npmjs.com/package/serve-index)
246
+ - ✅ [cors](https://npmjs.com/package/cors)
247
+ - ✅ [errorhandler](https://npmjs.com/package/errorhandler)
248
+ - ✅ [method-override](https://npmjs.com/package/method-override)
249
+ - ✅ [multer](https://npmjs.com/package/multer)
250
+ - ✅ [response-time](https://npmjs.com/package/response-time)
251
+ - ✅ [express-fileupload](https://npmjs.com/package/express-fileupload)
252
+ - ✅ [express-session](https://npmjs.com/package/express-session)
253
+ - ✅ [express-rate-limit](https://npmjs.com/package/express-rate-limit)
254
+ - ✅ [vhost](https://npmjs.com/package/vhost)
255
+
256
+ Middlewares that are confirmed to not work:
257
+
258
+ - ❌ [compression](https://npmjs.com/package/compression) (doesn't error, but doesn't compress)
259
+
260
+ ## Tested view engines
261
+
262
+ Any Express view engine should work. Here's list of engines we include in our test suite:
263
+
264
+ - ✅ [ejs](https://npmjs.com/package/ejs)
265
+ - ✅ [pug](https://npmjs.com/package/pug)
266
+ - ✅ [express-dot-engine](https://npmjs.com/package/express-dot-engine)
267
+ - ✅ [express-art-template](https://npmjs.com/package/express-art-template)
268
+ - ✅ [express-handlebars](https://npmjs.com/package/express-handlebars)
269
+ - ✅ [swig](https://npmjs.com/package/swig)
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "ultimate-express",
3
+ "version": "1.0.0",
4
+ "description": "The Ultimate Express. Fastest http server with full Express compatibility, based on uWebSockets.",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "node tests/index.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/dimdenGD/u-express.git"
12
+ },
13
+ "keywords": [
14
+ "express",
15
+ "http",
16
+ "fast",
17
+ "server",
18
+ "http server",
19
+ "https",
20
+ "https server",
21
+ "ws",
22
+ "websocket",
23
+ "websockets",
24
+ "uwebsockets",
25
+ "uws"
26
+ ],
27
+ "author": "dimden.dev",
28
+ "license": "UNLICENSED",
29
+ "bugs": {
30
+ "url": "https://github.com/dimdenGD/u-express/issues"
31
+ },
32
+ "homepage": "https://github.com/dimdenGD/u-express#readme",
33
+ "dependencies": {
34
+ "accepts": "^1.3.8",
35
+ "body-parser": "^1.20.3",
36
+ "cookie": "^0.6.0",
37
+ "cookie-session": "^2.1.0",
38
+ "cookie-signature": "^1.2.1",
39
+ "etag": "^1.8.1",
40
+ "express-rate-limit": "^7.4.0",
41
+ "fresh": "^0.5.2",
42
+ "mime-types": "^2.1.35",
43
+ "ms": "^2.1.3",
44
+ "proxy-addr": "^2.0.7",
45
+ "qs": "^6.13.0",
46
+ "range-parser": "^1.2.1",
47
+ "statuses": "^2.0.1",
48
+ "tseep": "^1.2.2",
49
+ "type-is": "^1.6.18",
50
+ "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.48.0",
51
+ "vary": "^1.1.2"
52
+ },
53
+ "devDependencies": {
54
+ "cookie-parser": "^1.4.6",
55
+ "cors": "^2.8.5",
56
+ "ejs": "^3.1.10",
57
+ "errorhandler": "^1.5.1",
58
+ "exit-hook": "^2.2.1",
59
+ "express": "^4.19.2",
60
+ "express-art-template": "^1.0.1",
61
+ "express-dot-engine": "^1.0.8",
62
+ "express-fileupload": "^1.5.1",
63
+ "express-handlebars": "^8.0.1",
64
+ "express-session": "^1.18.0",
65
+ "method-override": "^3.0.0",
66
+ "multer": "^1.4.5-lts.1",
67
+ "mustache-express": "^1.3.2",
68
+ "pako": "^2.1.0",
69
+ "pug": "^3.0.3",
70
+ "response-time": "^2.3.2",
71
+ "serve-index": "^1.9.1",
72
+ "serve-static": "^1.16.2",
73
+ "swig": "^1.4.2",
74
+ "vhost": "^3.0.2"
75
+ }
76
+ }
@@ -0,0 +1,295 @@
1
+ const uWS = require("uWebSockets.js");
2
+ const Router = require("./router.js");
3
+ const { removeDuplicateSlashes, defaultSettings, compileTrust, createETagGenerator } = require("./utils.js");
4
+ const querystring = require("querystring");
5
+ const qs = require("qs");
6
+ const ViewClass = require("./view.js");
7
+ const path = require("path");
8
+ const os = require("os");
9
+ const { Worker } = require("worker_threads");
10
+
11
+ const cpuCount = os.cpus().length;
12
+
13
+ let fsWorkers = [];
14
+ let fsKey = 0;
15
+ const fsCache = {};
16
+
17
+ function createFsWorker() {
18
+ const fsWorker = new Worker(path.join(__dirname, 'workers/fs.js'));
19
+ fsWorkers.push(fsWorker);
20
+
21
+ fsWorker.on('message', (message) => {
22
+ if(message.err) {
23
+ fsCache[message.key].reject(new Error(message.err));
24
+ } else {
25
+ fsCache[message.key].resolve(message.data);
26
+ }
27
+ delete fsCache[message.key];
28
+ });
29
+ fsWorker.unref();
30
+
31
+ return fsWorker;
32
+ }
33
+
34
+ class Application extends Router {
35
+ constructor(settings = {}) {
36
+ super(settings);
37
+ if(!settings?.uwsOptions) {
38
+ settings.uwsOptions = {};
39
+ }
40
+ if(typeof settings.fsThreads !== 'number') {
41
+ settings.fsThreads = cpuCount > 1 ? 1 : 0;
42
+ }
43
+ if(settings.uwsOptions.key_file_name && settings.uwsOptions.cert_file_name) {
44
+ this.uwsApp = uWS.SSLApp(settings.uwsOptions);
45
+ this.ssl = true;
46
+ } else {
47
+ this.uwsApp = uWS.App(settings.uwsOptions);
48
+ this.ssl = false;
49
+ }
50
+ this.cache = {};
51
+ this.engines = {};
52
+ this.locals = {
53
+ settings: this.settings
54
+ };
55
+ this.listenCalled = false;
56
+ this.fsWorkers = [];
57
+ for(let i = 0; i < settings.fsThreads; i++) {
58
+ if(fsWorkers[i]) {
59
+ this.fsWorkers[i] = fsWorkers[i];
60
+ } else {
61
+ this.fsWorkers[i] = createFsWorker();
62
+ }
63
+ }
64
+ this.port = undefined;
65
+ for(const key in defaultSettings) {
66
+ if(typeof this.settings[key] === 'undefined') {
67
+ if(typeof defaultSettings[key] === 'function') {
68
+ this.settings[key] = defaultSettings[key]();
69
+ } else {
70
+ this.settings[key] = defaultSettings[key];
71
+ }
72
+ }
73
+ }
74
+ this.set('view', ViewClass);
75
+ this.set('views', path.resolve('views'));
76
+ }
77
+
78
+ readFileWithWorker(path) {
79
+ return new Promise((resolve, reject) => {
80
+ const fsWorker = this.fsWorkers[Math.floor(Math.random() * this.fsWorkers.length)];
81
+ const key = fsKey++;
82
+ fsWorker.postMessage({ key, type: 'readFile', path });
83
+ fsCache[key] = { resolve, reject };
84
+ if(key > 1000000) {
85
+ fsKey = 0;
86
+ }
87
+ });
88
+ }
89
+
90
+
91
+ set(key, value) {
92
+ if(key === 'trust proxy') {
93
+ if(!value) {
94
+ delete this.settings['trust proxy fn'];
95
+ } else {
96
+ this.settings['trust proxy fn'] = compileTrust(value);
97
+ }
98
+ } else if(key === 'query parser') {
99
+ if(value === 'extended') {
100
+ this.settings['query parser fn'] = qs.parse;
101
+ } else if(value === 'simple') {
102
+ this.settings['query parser fn'] = querystring.parse;
103
+ } else if(typeof value === 'function') {
104
+ this.settings['query parser fn'] = value;
105
+ } else {
106
+ this.settings['query parser fn'] = undefined;
107
+ }
108
+ } else if(key === 'env') {
109
+ if(value === 'production') {
110
+ this.settings['view cache'] = true;
111
+ } else {
112
+ this.settings['view cache'] = undefined;
113
+ }
114
+ } else if(key === 'views') {
115
+ this.settings[key] = path.resolve(value);
116
+ return this;
117
+ } else if(key === 'etag') {
118
+ if(typeof value === 'function') {
119
+ this.settings['etag fn'] = value;
120
+ } else {
121
+ switch(value) {
122
+ case true:
123
+ case 'weak':
124
+ this.settings['etag fn'] = createETagGenerator({ weak: true });
125
+ break;
126
+ case 'strong':
127
+ this.settings['etag fn'] = createETagGenerator({ weak: false });
128
+ break;
129
+ case false:
130
+ delete this.settings['etag fn'];
131
+ break;
132
+ default:
133
+ throw new Error(`Invalid etag mode: ${value}`);
134
+ }
135
+ }
136
+ }
137
+
138
+ this.settings[key] = value;
139
+ return this;
140
+ }
141
+
142
+ enable(key) {
143
+ this.settings[key] = true;
144
+ return this;
145
+ }
146
+
147
+ disable(key) {
148
+ this.settings[key] = false;
149
+ return this;
150
+ }
151
+
152
+ enabled(key) {
153
+ return !!this.settings[key];
154
+ }
155
+
156
+ disabled(key) {
157
+ return !this.settings[key];
158
+ }
159
+
160
+ #createRequestHandler() {
161
+ this.uwsApp.any('/*', async (res, req) => {
162
+
163
+ const request = new this._request(req, res, this);
164
+ const response = new this._response(res, request, this);
165
+ request.res = response;
166
+ response.req = request;
167
+ res.onAborted(() => {
168
+ const err = new Error('Request aborted');
169
+ err.code = 'ECONNABORTED';
170
+ response.aborted = true;
171
+ response.socket.emit('error', err);
172
+ });
173
+
174
+ let matchedRoute = await this._routeRequest(request, response);
175
+
176
+ if(!matchedRoute && !res.aborted && !response.headersSent) {
177
+ response.status(404);
178
+ response.send(this._generateErrorPage(`Cannot ${request.method} ${request.path}`));
179
+ }
180
+ });
181
+ }
182
+
183
+ listen(port, callback) {
184
+ this.#createRequestHandler();
185
+ if(!callback && typeof port === 'function') {
186
+ callback = port;
187
+ port = 0;
188
+ }
189
+ let fn = 'listen';
190
+ if(typeof port !== 'number') {
191
+ if(!isNaN(Number(port))) {
192
+ port = Number(port);
193
+ } else {
194
+ fn = 'listen_unix';
195
+ }
196
+ }
197
+ this.listenCalled = true;
198
+ this.uwsApp[fn](port, socket => {
199
+ if(!socket) {
200
+ let err = new Error('Failed to listen on port ' + port + '. No permission or address already in use.');
201
+ throw err;
202
+ }
203
+ this.port = uWS.us_socket_local_port(socket);
204
+ callback(this.port);
205
+ });
206
+ }
207
+
208
+ address() {
209
+ return { port: this.port };
210
+ }
211
+
212
+ path() {
213
+ let paths = [this.mountpath];
214
+ let parent = this.parent;
215
+ while(parent) {
216
+ paths.unshift(parent.mountpath);
217
+ parent = parent.parent;
218
+ }
219
+ let path = removeDuplicateSlashes(paths.join(''));
220
+ return path === '/' ? '' : path;
221
+ }
222
+
223
+ engine(ext, fn) {
224
+ if (typeof fn !== 'function') {
225
+ throw new Error('callback function required');
226
+ }
227
+ const extension = ext[0] !== '.'
228
+ ? '.' + ext
229
+ : ext;
230
+ this.engines[extension] = fn;
231
+ return this;
232
+ }
233
+
234
+ render(name, options, callback) {
235
+ if(typeof options === 'function') {
236
+ callback = options;
237
+ options = {};
238
+ }
239
+ if(!options) {
240
+ options = {};
241
+ } else {
242
+ options = Object.assign({}, options);
243
+ }
244
+ for(let key in this.locals) {
245
+ options[key] = this.locals[key];
246
+ }
247
+
248
+ if(options._locals) {
249
+ for(let key in options._locals) {
250
+ options[key] = options._locals[key];
251
+ }
252
+ }
253
+
254
+ if(options.cache == null) {
255
+ options.cache = this.enabled('view cache');
256
+ }
257
+
258
+ let view;
259
+ if(options.cache) {
260
+ view = this.cache[name];
261
+ }
262
+
263
+ if(!view) {
264
+ const View = this.get('view');
265
+ view = new View(name, {
266
+ defaultEngine: this.get('view engine'),
267
+ root: this.get('views'),
268
+ engines: this.engines
269
+ });
270
+ if(!view.path) {
271
+ const dirs = Array.isArray(view.root) && view.root.length > 1
272
+ ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
273
+ : 'directory "' + view.root + '"';
274
+
275
+ const err = new Error(`Failed to lookup view "${name}" in views ${dirs}`);
276
+ err.view = view;
277
+ return callback(err);
278
+ }
279
+
280
+ if(options.cache) {
281
+ this.cache[name] = view;
282
+ }
283
+ }
284
+
285
+ try {
286
+ view.render(options, callback);
287
+ } catch(err) {
288
+ callback(err);
289
+ }
290
+ }
291
+ }
292
+
293
+ module.exports = function(options) {
294
+ return new Application(options);
295
+ }