Skip to content

Commit

Permalink
Merge pull request #2 from fyrejet/v2.0.0
Browse files Browse the repository at this point in the history
v2.0.0
  • Loading branch information
schamberg97 committed Sep 9, 2020
2 parents 27e3365 + 03f4c98 commit 7c7edb5
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 97 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ lerna-debug.log*

package-lock.json

performance/test.js

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

Expand Down
71 changes: 51 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@


# Fyrejet 1.1
# Fyrejet 2

<img src="./fyre.png" alt="logo" height="150" width="150" />

Expand All @@ -10,11 +10,19 @@ Fyrejet is a web-framework that is designed for speed and ease-of-use. After wor

Unfortunately, that comes at a cost. While Express brings the speed of development, its performance is just okay-ish. Other frameworks either provide different APIs, are incompatible with Express middlewares or provide less functionality. For instance, Restana, a great API-oriented framework by jkybernees provides incredible performance, but only a subset of Express APIs, making it not suitable as an Express replacement.

Fyrejet does not strive to be the fastest framework. However, Fyrejet seeks to be faster than Express, while providing most of the original Express API. In fact, Fyrejet uses slightly modified<sup>[1](#footnote1)</sup> Express automated unit tests to verify the codebase. Additionally, Fyrejet is flexible enough to offer you additional abilities to increase your route performance, such as disabling Express API for certain routes or for the whole project. You choose.
Fyrejet does not strive to be the fastest framework. However, Fyrejet seeks to be faster than Express, while providing most of the original Express API. In fact, Fyrejet uses slightly modified<sup>[1](#footnote1)</sup> Express automated unit tests to verify the codebase. Moreover, Fyrejet offers you to use Express APIs with uWebSockets.js, enabling even greater performance at the cost of minor incompatibilities.

Finally, Fyrejet is flexible enough to offer you additional abilities to increase your route performance, such as disabling Express API for certain routes or for the whole project. You choose.


<a name="footnote1">[1]</a>: 35 tests removed, because they are irrelevant, ~6 tests modified to test a replacement API instead. 1100 out of 1135 Express tests are used.

<a name="footnote1">[1]</a>:

* `50` tests removed, because they are arguably irrelevant (`test/Route.js` and `test/Router.js`)
* `~6` tests modified to test a replacement API instead (`req.currentUrl`)
* `1` test removed, because deprecated functionality was too much time to implement.
* `1091` out of `1147` Express tests are used, including aforementioned modified tests. Total number of tests used by Fyrejet: `1114`
* Tests passed by uWebSockets.js implementation: `1113` out of `1114` (minor inconvenience at best)



Expand All @@ -40,9 +48,15 @@ Fyrejet is shared with the community under MIT License.



## Installation
## Breaking changes from `1.x` to `2.0`

* `uWebSockets.js` compatibility implementation has been rewritten from scratch and it no longer relies on 0http's `low` library, at least until it has the same improvements as we do have. Moreover, the tests are now shared between `Fyrejet` and `Fyrejet + uWebSockets.js`. As such, certain dirty hacks are no longer used, which means that projects using `Fyrejet 1.x` and uWebSockets can be slightly incompatible with `Fyrejet 2`. Thus, according to Semantic Versioning, it has to versioned as `Fyrejet 2`.
* When using `uWebSockets.js`, `serverType` has to be `uWebSockets` and not `uWebSocket`, as the former is a more correct project name. `uWebSocket` will be silently changed into `uWebSockets`. Next release will show deprecation messages.
* That's it :)



## Installation

In order to install and test Fyrejet, you can issue the following commands:

Expand Down Expand Up @@ -228,7 +242,7 @@ hi, sweetheart!* Closing connection 0

##### How to avoid rerouting?

Sometimes, rerouting is not acceptable. In this cases, you can change the method or url with these helper functions:
Sometimes, rerouting is not acceptable. In these cases, you can change the method or url with these helper functions:

```req.setUrl(url)``` and ```req.setMethod(method)```. Both return req object, so they are chainable.

Expand Down Expand Up @@ -312,28 +326,26 @@ No known caveats yet.

## uWebSockets

Fyrejet includes some preliminary support for uWebSockets.

Please do note that uWebSockets was a lesser priority at the time of this initial release, so uWebSockets are not covered with tests at this point. Additionally, only version 17.5.0 was used, so there is no guarantee it will work with newer versions. This feature is thus subject to change and is to be considered alpha-version feature.

However, uWebSockets offers promising performance dividends that can be reaped in the future to accelerate existing Express apps. For specific performance results, please check the [Benchmarks](#Benchmarks).
Fyrejet includes BETA support for uWebSockets.

Versions 17.5.0 and 18.5.0 have been tested and do seem to work. Despite this, minor incompatibilities are expected. Please refer to Known problems section.

Despite, uWebSockets offers promising performance dividends for existing Express apps. For specific performance results, please check the [Benchmarks](#Benchmarks).

### How to use

```js
'use strict'

// preliminary testing done with uWS 17.5.0, but it is NOT covered with tests yet
const low = require('0http/lib/server/low')
/* you will need Rolando Santamaria Maso's (jkyberneees) excellent 0http.
you are responsible for installing it yourself. BYOB kind of situation.
const low = require('../index').uwsCompat
/* Based on low library from Rolando Santamaria Maso's (jkyberneees) excellent 0http.
However, many aspects of the library had to be reworked to make it more compatible with native node.js HTTP module. It is likely that the uwsCompat module could be made more efficient.
*/
const app = require('../index')({
prioRequestsProcessing: false, // without this option set to 'false' uWS is going to be extremely sluggish
server: low(),
serverType: 'uWebSocket' // also required, or there will always be errors
serverType: 'uWebSockets' // also required, or there will always be errors
})

app.get('/hi', (req, res) => {
Expand All @@ -344,10 +356,19 @@ app.start(3001, (socket) => {
if (socket) {
console.log('HTTP server running at http://localhost:3001')
}
}) // please be aware that you need to start the server with uWS that way. That's a limitation of 0http/lib/server/low
}) // in Fyrejet 1.x, you needed to provide a callback for this to work. This is no longer the case.

setTimeout(() => {server.close()}, 10000) // closes server in approximately 10 seconds

```

### Known problems

At this time, there are 2 known problems with uWebSockets.js implementation used in Fyrejet.

* uWebSockets.js is unable to pass `test/app.listen.js`, because `server.close()` works a bit differently. **SEVERITY:** Low
* uWebSockets.js passes the last test (see `test/acceptance/web-service.js`), but ends up with segmentation failure upon attempt to close server. **SEVERITY:** Low-Medium?

## Benchmarks

It is a pseudo-scientific benchmark, but whatevs :)
Expand Down Expand Up @@ -376,17 +397,27 @@ Second-best result out of a series of 5 is used.

Results:

1) 26015.02 req/s (95.5% faster than express)
1) 24536.90 req/s (76.1% faster than express)

2) 31900.48 req/s (139.8% faster than express)
2) 32633.49 req/s (134.3% faster than express)

3) 20473.71 req/s (53.9% faster than express)
3) 21125.14 req/s (51.7% faster than express)

4) 18401.97 req/s (38.3% faster than express)
4) 18994.90 req/s (36.4% faster than express)

5) 13301.73 req/s (baseline)
5) 13929.94 req/s (baseline)



Be aware that uWS generally doesn't perform on MacOS as well as on Linux.



## Run tests

```
npm install
npm run test
npm run test-uWS
```

Binary file modified benchmark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 57 additions & 30 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,50 @@ var res = require('./lib/response')
var bodyParser = require('body-parser')
var finalhandler = require('finalhandler')
var mixin = require('merge-descriptors')
var uwsCompat = require('./lib/uwsCompat')

const requestRouter = require('./lib/routing/request-router')

var initMiddleware = require('./lib/middleware/init')
const { logerror } = require('./lib/utils')

var appCore = function (options, server, mounted) {
if (options.serverType === 'uWebSocket') {
req.listeners = function () {
return []
}
req.resume = function () {}
res.serverType = 'uWebSocket'
}

var appCore = function (options, server, app) {
const startFn = (...args) => {
if (!args || !args.length) args = [3000]

if (options.serverType === "uWebSockets") {
var __address = {}
server.__address = __address
if (typeof args[args.length - 1] !== 'function') {
args.push((socket) => {
// stub function
})
}
var ipFamily = app.get('ipFamily')
if (ipFamily !== 'IPv6' && ipFamily !== 'IPv4') ipFamily = 'IPv6'

switch (args.length >= 3) {
case true:
server.__address.address = args[0]
server.__address.port = args[1]
server.__address.family = ipFamily
break;
case false:
server.__address.port = args[0]
server.__address.family = ipFamily
if (ipFamily === 'IPv6') server.__address.address = '::'
else server.__address.address = '127.0.0.1'
break
}
server.address = () => {

return __address
}
}
server.listen(...args)
return server
}
const core = {
var core = {
errorHandler: options.errorHandler,
fyrejetApp: true,
newRouter () {
Expand All @@ -51,8 +74,12 @@ var appCore = function (options, server, mounted) {
getRouter () {
return this.getRouter()
},

uWebSockets: function() {
if (server.keepAliveTimeout) return false
return true
},
handle: function handle (req, res, step) {
res.__serverType = options.serverType
res.defaultErrHandler = finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
Expand All @@ -75,16 +102,16 @@ var appCore = function (options, server, mounted) {
start: startFn,
listen: startFn,
address: function () {
const addr = server.address()
return addr
if (server.address && typeof server.address === 'function') {
return server.address()
}
return server.__address
},
close: () => new Promise((resolve, reject) => {
server.close((err) => {
if (err) reject(err)
resolve()
})
})
close: (cb) => {
return server.close(cb)
}
}

return core
}

Expand All @@ -96,6 +123,11 @@ var defaultErrorHandler = (err, req, res) => {

function createApplication (options = {}) {
options.errorHandler = options.errorHandler || defaultErrorHandler
if (options.serverType === 'uWebSocket' || process.env.UWS_SERVER_ENABLED_FOR_TEST === 'TRUE') {
options.serverType = 'uWebSockets'
options.prioRequestsProcessing = false
options.server = uwsCompat()
}

var server = options.server || require('http').createServer()

Expand All @@ -121,9 +153,10 @@ function createApplication (options = {}) {
app.handle(req, res, next)
}

mixin(app, appCore(options, server))
mixin(app, EventEmitter.prototype)
mixin(app, proto)
mixin(app, appCore(options, server, app))
mixin(app, EventEmitter.prototype)


app.request = Object.assign({}, req)

Expand Down Expand Up @@ -186,14 +219,6 @@ exports.static = require('./lib/additions/static.js')
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded

/**
* Helper function for creating a getter on an object.
*
* @param {Object} obj
* @param {String} name
* @param {Function} getter
* @private
*/

/**
* Expose the prototypes.
Expand All @@ -210,6 +235,8 @@ exports.response = res
// exports.Route = Route;
exports.Router = require('./lib/routing/request-router-constructor')

exports.uwsCompat = uwsCompat

/**
* Replace Express removed middleware with an appropriate error message. We are not express, but we will imitate it precisely
*/
Expand Down Expand Up @@ -237,7 +264,7 @@ var removedMiddlewares = [
removedMiddlewares.forEach(function (name) {
Object.defineProperty(exports, name, {
get: function () {
throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.')
throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express (and thus Fyrejet) and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.')
},
configurable: true
})
Expand Down
2 changes: 1 addition & 1 deletion lib/middleware/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var init = function (options, reqProperties, reqPropertiesEssential, router, sla
req.rData_internal.initDone = true
await next()
} catch (err) {
console.log(err)
if (process.env.NODE_ENV !== 'production') console.log(err)
return options.errorHandler(err, req, res)
}
}
Expand Down
4 changes: 1 addition & 3 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,7 @@ res.send = function send (body) {
encoding = undefined
len = chunk.length
}
if (!this.serverType === 'uWebSocket') {
this.set('Content-Length', len)
}
if (!this.__serverType === 'uWebSocket') this.set('Content-Length', len)
}

// populate ETag
Expand Down
Loading

0 comments on commit 7c7edb5

Please sign in to comment.