Skip to content

Lightweight WSGI framework, inspired by ROR & Laravel frameworks

License

Notifications You must be signed in to change notification settings

TeaEntityLab/wsgiLite.js

Folders and files

NameName
Last commit message
Last commit date
Aug 21, 2021
Dec 8, 2020
Jun 13, 2018
Jun 13, 2018
Dec 8, 2020
Jun 25, 2018
Jun 25, 2018
Dec 8, 2020
Jun 22, 2018
Nov 9, 2022
Nov 9, 2022
Jun 22, 2018
Dec 10, 2020

Repository files navigation

wsgiLite.js

npm download npm version

license stars forks

Lightweight WSGI framework, inspired by ROR & Laravel frameworks

Why

Though there're many frameworks have done WSGI server features, however they are somehow a little heavy;

Thus I make this project, and make it simple & almost just a pure nodejs http server.

Features

  • Built-in
    • Execution Timeout Customizing for each route
    • Exception-Handling(Exceptions in Middleware & Route handlers will be caught)
    • CSRF Checking
    • Path parameters parsing, routing path & methods matching
    • Query parameters(built-in module url)
    • Form fields parsing
    • FastCGI style multiple processes serving(depending on CPU cores by default), reducing response time
      • (built-in module cluster)
  • Optional extension
    • Static files serving(built-in module fs)
    • Template

Installation

npm i wsgilite

Demo

# Get this repo ready
git clone https://github.com/TeaEntityLab/wsgiLite.js.git
cd wsgiLite.js/demo
npm install

# The demo server will listen on port 3333
npm run serve

# WebSocket demo(tests by `wscat` cli tools)
npm run serve-websocket
npm install -g wscat
wscat -c "ws://localhost:3333/wss1"
wscat -c "ws://localhost:3333/wss2"

Examples

Simple Routing cases with WebSocket

In this example, we combined wsgilite server and WebSocket server(built by ws(npm github))

WSGILite & WebSocket work perfectly together(WSGILite acts just like a native built-in http(s) server)

Example

Simple Routing cases

const {
  WSGILite,
} = require('wsgilite/wsgilite');
const {
  Template,
} = require('wsgilite/template');
const {
  mimeMap,
  defMiddlewareServeFileStatic,
  defMiddlewareServeFileStaticWithDirList,
} = require('wsgilite/file');
const {
  defFormCsrfCheckRoutes,
  defHeaderCsrfCheckRoutes,
  getCSRF_token,
  generateCSRFFormInput,
} = require('wsgilite/csrf');

// Settings

const wsgilite = new WSGILite({
  secret: 'abcdefg', // The secret key for CSRF
  logProcessMessage: true, // Log the multiple processes starting/ending messages.
  debug: true, // Debug mode, it will show stacktrace of errors in the responses
  isHttps: false, // Is it a https server?

  processNum: require('os').cpus().length, // N + 1 processes (cluster: 1 * master + n * fastcgi style http/https serving)
  // processNum: 0, // Single process
  // createServerOptions: {}, // Additional createServer options for http/https.createServer(options)
  onServerCreated: function (httpServer) {
    httpServer.maxHeadersCount = 0;
    console.log('server.maxHeadersCount set');
  }, // Callback on one of servers created(this will be called in Multiple processes)
  onMessageMaster: function (worker, msg, handle) {
    console.log(`master got message: ${msg.event}`);
  }, // Optional: We could monitor and handle communications between cluster master & workers
  onMessageWorker: function (msg, handle) {
    console.log(`worker got message: ${msg.event}`);
  }, // Optional: We could monitor and handle communications between cluster master & workers

  workerServeTimesToRestart: 500, // Each child worker will auto-restart after it served 500 requests
});
const redirect = wsgilite.redirectAsFunction(); // Define redirect() in simple & shorter function style.
const template = new Template({
  baseDir: "demo/template", // Define the base dir of template files
});
const render = template.renderAsFunction(); // Define render() in simple & shorter function style.

// Middlewares

wsgilite.addMiddleware(async (request, response, meta)=>{
  meta.msg3 = 'I got it3';
});
wsgilite.addMiddleware(function * (request, response, meta) {
  return Promise.resolve(0).then(()=>meta.msg2 = 'I got it2');
});
wsgilite.addMiddleware((request, response, meta)=>{
  meta.msg = 'I got it';
});
// CSRF routes defining
wsgilite.addMiddleware(defFormCsrfCheckRoutes([
  '/upload', // Your can define more paths here
], wsgilite));
wsgilite.addMiddleware(defHeaderCsrfCheckRoutes([
  '/upload2', // Your can define more paths here
], wsgilite));

// Routes

// Print the `meta` object
wsgilite.GET('/', (request, response, meta)=>{
  return JSON.stringify(meta); // {"_skip404":true,"_url_path":"/","msg3":"I got it3","msg2":"I got it2","msg":"I got it"}
});

// The server will terminate
wsgilite.GET('/terminate', (request, response, meta)=>{
  response.end("terminate");
  wsgilite.terminate();
});

// Path parameters(until `/` or `?` or the end)
wsgilite.GET('/user/:id', function *(request, response, meta) {
  return yield Promise.resolve(meta); // {"_skip404":true,"_url_path":"/user/theID","msg3":"I got it3","msg2":"I got it2","msg":"I got it","id":"theID"}
});
// Path parameters(until `?` or the end)
wsgilite.GET('/file*relativePath', (request, response, meta)=>{
  defMiddlewareServeFileStatic('demo')(request, response, meta);
});

// Show the CSRF usages (token only & a form hidden input with the token)
wsgilite.GET('/csrf', function *(request, response, meta) {
  return getCSRF_token(request, response) + ` ${generateCSRFFormInput(request, response)}`; // CSRF_token
});
// CSRF checking (form)
wsgilite.POST('/upload', (request, response, meta)=>{
  return 'CSRF_token ok'; // CSRF_token ok
});
// CSRF checking (header)
wsgilite.POST('/upload2', (request, response, meta)=>{
  return 'x-csrf-token ok'; // x-csrf-token ok
});
wsgilite.GET('/heartbeat', async (request, response, meta)=>{
  return "ok"; // ok
});

// Redirect to an existing route
wsgilite.GET('/heartbeat2', async (request, response, meta)=>{
  redirect('/heartbeat')(request, response, meta); // ok
});
// Redirect to a non-existing route(will 404)
wsgilite.GET('/heartbeat3', async (request, response, meta)=>{
  redirect('/heartbeat999')(request, response, meta); // 404 File not found.
});

// Nested routing definitions
wsgilite.defSubRoute('test', function (defSub) {
  defSub.defSubRoute('change', function (defSub) {
    defSub.defSubRoute('for', function (defSub) {
      defSub.GET('it', async (request, response, meta)=>{
        return "Here we go"; // Here we go
      })
    });
  });
});

// Template usages
wsgilite.GET('/template', async (request, response, meta)=>{
  return render("features", {
            "title": "JavaScript Templates",
            "url": "https://github.com/blueimp/JavaScript-Templates",
            "features": [
                "lightweight & fast",
                "powerful",
                "zero dependencies"
            ]
        }).catch((e)=>{
          response.statusCode = 500;
          return e.stack;
        })
        ; // ok
});

// Post jobs to cluster master(if wsgilite.config.processNum > 0)
wsgilite.addClusterMasterRequestHandler(async (worker, msg, handle) => {
  // throw new Error("There's an exception");
  if (msg && msg.data && msg.data.action === 'readrecord') {
    return {data: 'user01'};
  }
});
wsgilite.GET('/requestActionOnClusterMaster', async function (request, response, meta) {
  // {"event":"MSG_WSGILITE_DO_THINGS_WORKER_SUCCESS","result":[{"data":"user01"}]}
  return wsgilite.requestActionOnClusterMaster({action: 'readrecord'}).catch((e)=>{
    console.log(`I got error: ${e.errorMessage}`);
    return e.errorMessage;
  });
});
// Post jobs to cluster master(Timeout cases)
wsgilite.addClusterMasterRequestHandler(async (worker, msg, handle) => {
  if (msg && msg.data && msg.data.action === 'streaming_timeout') {
    const snooze = require('fpEs/fp').snooze;
    let startTimestamp = Date.now();
    while(Date.now() - startTimestamp < 5*1000) {
      await snooze(500);

      if (msg.cancel) {
        throw new Error('cluster master 5secs job: cancelled');
      }
    }
    console.log(msg);

    return 'ok';
  }
});
wsgilite.GET('/requestActionOnClusterMasterTimeout', async function (request, response, meta) {
  // Timeout for 3000 ms
  return wsgilite.requestActionOnClusterMaster({action: 'streaming_timeout'}, 3000).catch((e)=>{
    console.log(`I got error: ${e.errorStacktrace}`);
    return e.errorStacktrace;
  });
});
// if (require('cluster').isMaster) {
//   // It could be called on cluster master
//   wsgilite.requestActionOnClusterMaster({action: 'readrecord'}).catch((e)=>{
//     return e.errorMessage;
//   }).then((msg)=>console.log(msg));
// }

// Exception
wsgilite.GET('/exception', async function (request, response, meta) {
  throw new Error("There's an exception"); // Error: There's an exception
});

// Timeout
let routeTimeout = wsgilite.GET('/timeout', async function (request, response, meta) {
  var fetch = require('node-fetch');
  await fetch('https://www.sample-videos.com/video/mp4/240/big_buck_bunny_240p_30mb.mp4');
  return "ok";
});
routeTimeout.timeout = 5000;

wsgilite.listen(3333, 'localhost', function () {
  console.log('Server up');
});