Security News
Input Validation Vulnerabilities Dominate MITRE's 2024 CWE Top 25 List
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
A simple http/2-enabled server framework with an API similar to Express
Fluvial was born from an attempt to understand and use http/2 for static file sharing and wanting a similar request lifecycle as Express for other such requests. It's as much a learning project as a project that attempts to be production-ready. If you're familiar with Express, much of the API might seem familiar to you. However, there are a few key differences you may notice and it will be mentioned below.
Fluvial is compatible with both http/1.x and http/2. The type of the raw request/response will depend on which http version you're using.
As of the writing of this document, this project is in alpha mode and will need many tweaks and adjustments to make it even more robust and complete prior to a full version (v1). Feel free to make an issue discussing your need.
These are the current aims of this project:
Install fluvial:
npm install fluvial
And inside your main file:
import { fluvial } from 'fluvial';
// create the main app
const app = fluvial();
// register a route
app.get('/anything', (req, res) => {
res.send('a response via http/2');
});
// listen to the port of your choice
app.listen(8090, () => {
console.log('server up and running on port 8090');
});
And then you should be good to send a request to the /anything
route and it will respond with the message you added in the route handler.
There is an extended example below in the "Comparisons with Express" section.
The documentation here is simplified for the scope of the readme. Type annotations are included which may help with the documentation while developing your application.
fluvial(options)
Call this to create a Fluvial application
Arguments:
options
: An optional object with the following optional properties
server
: An already-created Http2Server
or Http2SecureServer
as you can get from http2.createServer
or http2.createSecureServer
(this might be needed if you have other libraries that handle the raw Http2Server
instance and also need to provide the same instance to Fluvial)ssl
: An object to set up and configure a Http2SecureServer
's SSL with the following properties
certificate
(the raw string/Buffer
of the certificate) or certificatePath
(the path to the certificate file), andkey
(the raw string/Buffer
of the key) or keyPath
(the path to the key file)Return value:
Application
, which is an object whose properties and methods are described belowApplication
The main entrypoint and listener that contains the underlying Http2Server
instance. It is itself a Router
and has all properties and methods as can be found on a router plus those mentioned here:
application.component
A property that reports the string of 'application'
(for possible use in identifying this Fluvial object)
application.listen(port, cb?)
A method used to start the server in the case that the server itself wasn't started previously
Router()
A function to create a router for the application. It is the main way to split up your Fluvial application and a way to register and manage routes.
Each of the following HTTP methods/verbs are available as methods on each router instance.
get
post
patch
put
delete
options
head
Each router method have the same arguments but the handlers provided to them will only be called if the HTTP method and at least one of the provided paths match the requested path. For example, registering a get
request handler would look like:
router.get('/path-matcher', (req, res) => {
// handle the GET request to /path-matcher
});
The arguments for each of these methods are as follows:
pathMatcher
: a string
, string[]
, or RegExp
describing a request path it should match, e.g.:
'/some-path'
or '/some-path-with/:id'
['/one-path', '/another-path-with/:id']
/\/a-path|another-path|foo\/described-by\/(?<named>param)/
handler
s which are functions with:
Request
and Response
objects as arguments, andNEXT
constant (which is just the plain value of 'next'
), which tells fluvial to move on to the next matching request handlerNEXT_ROUTE
constant (which is just the plain value of 'route'
), which tells fluvial to skip any other handlers inside of this specificPromise
that resolves to any of the above valuesThere are a few other methods that have special meanings and can either be likewise constrained to specific path(s) or be used without any paths.
all
, for handling any kind of HTTP methoduse
, for registering other routers and middleware regardless of the HTTP method, andcatch
, for catching any errors that are thrown in any of the handlers prior to being registeredThere is one last method on a router that can be used which might be easier if preferred:
route(pathMatcher)
which returns a Route
A Route
has all the same methods as a Router
with two differences:
use
and route
don't exist, andpathMatcher
argument and only handlersAn example of a Route
is as follows:
router.route('/users/:id')
.get(async (req, res) => {
// handle a get request
})
.patch(requiresAuth(), async (req, res) => {
// handle a patch request
});
Request
objectmethod
: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD'
path
: string
headers
: { [key: string]: string }
payload
: any
'body'
)rawRequest
: http2.Http2ServerRequest | http.IncomingMessage
Http2ServerRequest
, whereas if the request is done via http/1.x, it will be an IncomingMessage
params
: { [key: string]: string }
/users/:id
matches the path /users/3
and the params
on the request would be { id: '3' }
)query
: { [key: string]: string }
/users?limit=10
would result in the query
to be { limit: '10' }
)hash
: string
/users#someHash
would be someHash
)httpVersion
: '2.0' | '1.1'
'2.0'
or '1.1'
depending under which version the request was maderesponse
: Response
Response
object related to this Request
Response
objectsResponse Modes:
There are two "modes" used forResponse
s: Default and event stream. The "default" mode allows only one response to be sent with the headers. The "event stream" mode, however, prepares the connection to persist until the client asks for it to close and allows you to send multiple events. The available properties change based on which mode is active. Below will specify which of the properties apply to which mode; any property that doesn't specify this is available on both.
status
: one of status(code: number): void
or status(): number
(read-only)httpVersion
: '2.0' | '1.1'
(read-only)headers
: { [key: string]: string | string[] }
responseSent
: boolean
(read-only)asEventSource
: one of asEventSource(value: boolean): void
or asEventSource(): boolean
(read-only)
a getter or setter to change this mode from default to event stream or vice versasend(data?: any): void
(default mode only)send
, it will automatically serialize it into JSONjson(data: object): void
(default mode only)write(data: string | Buffer): void
write
methodend(data?: string | Buffer): void
(default mode only)end
methodsendEvent(data?: object | string): void
(event stream mode only)stream(stream: Readable): void
Functions provided to any of the Router
's methods meant to handle requests from a client can return (or resolve) a few different values that will signal to fluvial what it should do next with the request:
'next'
(or using the built-in constant NEXT
), which will tell fluvial to continue to the next-matching handler'route'
(or using the built-in constant NEXT_ROUTE
), which will tell fluvial to skip any other functions registered at the same time as the current handler (such as the first function passed to a router.get
call returning 'route'
will skip the second, third, etc., functions passed in at the same time) and pass the request on to the next-matching route.catch
handlerIf you are familiar with the back-end framework of Express, you will find many aspects of this framework familiar, but there are some very key differences, too. Below you'll find an Express application and the same thing inside of Fluvial.
In Express:
// import the default export from `express`, which is the `express` application factory function
import express from 'express';
// create the express application
const app = express();
// a simple middleware function
app.use((req, res, next) => {
// ... that logs minimal data about the request and the time it was received
console.log(`${Date.time()}: ${req.method} to ${req.url}`);
// ... and then passes the request on to the next handler
next();
});
// built-in middleware to read JSON request payloads and put them onto `req.body`
app.use(express.json());
// registering a handler for a `GET` request to `/users`
app.get('/users', (req, res, next) => {
// get the current users, and
Users.find()
.then((users) => {
// set the status code for the response
res.status(200);
res.send(users);
})
// catch any error, and ...
.catch((err) => {
// ... send it to an error handler
next(err);
});
});
// registering an error handler for the route above
app.use((err, req, res, next) => {
if (err.message.includes('not found')) {
res.status(404);
res.send('Not found');
return;
}
if (err.message.includes('unauthorized')) {
res.status(401);
res.send('Not authorized');
return;
}
// ... etc.
});
// start the app listening
app.listen(3000, () => {
console.log('listening to port 3000');
});
And in Fluvial:
// import the named export of `fluvial` (and the signal keyword `NEXT`)
import { fluvial, NEXT } from 'fluvial';
// import the JSON middleware function
import { deserializeJsonPayload } from 'fluvial/middleware';
// create the fluvial application
const app = fluvial();
// a simple middleware function
app.use((req, res) => {
// ... that logs minimal data about the request and the time it was received
console.log(`${Date.time()}: ${req.method} to ${req.path}`);
// ... and then passes the request on to the next handler
return NEXT;
});
// use the deserialize JSON payload function
app.use(deserializeJsonPayload());
// registering a handler for a `GET` request to `/users`
app.get('/users', async (req, res) => {
// get the current users, and
const users = await Users.find();
// send the users as the payload in the response
res.send(users);
// ... but no need to catch here. Instead, ...
});
// register a catch handler
app.catch((err, req, res) => {
// ... and any errors from above will trickle in here
if (err.message.includes('not found')) {
res.status(404);
res.send('Not found');
return;
}
if (err.message.includes('unauthorized')) {
res.status(401);
res.send('Not authorized');
return;
}
// ... etc.
});
// start the app listening
app.listen(3000, () => {
console.log('listening to port 300');
});
FAQs
Fluvial: A light http/2 server framework, similar to Express
The npm package fluvial receives a total of 1 weekly downloads. As such, fluvial popularity was classified as not popular.
We found that fluvial demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.