Comparing version 0.3.1 to 0.3.2
const lib = require("./lib.js"); | ||
const mime = require("mime"); | ||
module.exports.ExpressRequest = ExpressRequest; | ||
function ExpressRequest(req) { | ||
if(!(this instanceof ExpressRequest)) return new ExpressRequest(...arguments); | ||
if(!(req instanceof lib.Request)) throw new Error("ExpressRequest must be created with a Request"); | ||
module.exports.createApplication = createApplication; | ||
this.app = null; | ||
this.baseUrl = null; | ||
this.body = {}; | ||
this.cookies = req.cookies; | ||
this.fresh = true; | ||
this.hostname = req.host; | ||
this.ip = req.remote_addr.split(":")[0]; | ||
this.ips = [ this.ip ]; | ||
this.method = req.method; | ||
this.originalUrl = req.uri; | ||
this.params = req.params; | ||
this.path = req.url; | ||
this.protocol = "http"; | ||
this.query = {}; | ||
this.route = {}; | ||
this.secure = false; | ||
this.signedCookies = {}; | ||
this.stale = false; | ||
this.xhr = req.headers["x-requested-with"] == "XMLHttpRequest"; | ||
function createApplication(cfg) { | ||
let app = new lib.Application(cfg); | ||
patchApplication(app); | ||
return app; | ||
} | ||
// TODO: this.accepts | ||
// TODO: this.acceptsCharsets | ||
// TODO: this.acceptsEncodings | ||
// TODO: this.acceptsLanguages | ||
module.exports.patchApplication = patchApplication; | ||
this.header = k => req.headers[k]; | ||
this.get = this.header; | ||
function patchApplication(app) { | ||
if(!(app instanceof lib.Application)) { | ||
throw new Error("Application required"); | ||
} | ||
// TODO: this.is | ||
const _app_use = app.use.bind(app); | ||
const _app_route = app.route.bind(app); | ||
const _app_prepare = app.prepare.bind(app); | ||
const _app_listen = app.listen.bind(app); | ||
this.param = k => this.params[k] || (this.body ? this.body[k] : null) || this.query[k]; | ||
app.use = function(p, fn) { | ||
if(typeof(fn) == "function") { | ||
_app_use(p, function(req, resp, mw) { | ||
patchResponse(resp); | ||
return new Promise((cb, reject) => { | ||
fn(req, resp, function(e) { | ||
if(e) { | ||
reject(e); | ||
} else { | ||
cb(); | ||
} | ||
}); | ||
}); | ||
}); | ||
} else { | ||
_app_use(p, fn); | ||
} | ||
}; | ||
app.route = function(methods, p, fn) { | ||
_app_route(methods, p, function(req, resp) { | ||
patchResponse(resp); | ||
return fn(req, resp); | ||
}); | ||
}; | ||
// TODO: this.range | ||
app.listen = function(addr) { | ||
if(typeof(addr) == "number") { | ||
addr = "0.0.0.0:" + addr; | ||
} | ||
_app_prepare(); | ||
_app_listen(addr); | ||
}; | ||
app.setDefaultHandler((req, resp) => { | ||
resp.detach(); | ||
resp.status(404).body("Not found").send(); | ||
}); | ||
} | ||
module.exports.ExpressResponse = ExpressResponse; | ||
function ExpressResponse(resp) { | ||
if(!(this instanceof ExpressResponse)) return new ExpressResponse(...arguments); | ||
if(!(resp instanceof lib.Response)) throw new Error("ExpressResponse must be created with a Response"); | ||
function patchResponse(resp) { | ||
if(!(resp instanceof lib.Response)) { | ||
throw new Error("Response required"); | ||
} | ||
this.headers = {}; | ||
this.sent = false; | ||
if(resp.patched) return; | ||
resp.patched = true; | ||
this.app = null; | ||
this.headersSent = false; | ||
this.locals = {}; | ||
this.append = (k, v) => this.headers[k] = v; // TODO: Handle Set-Cookie and array values | ||
const _send = resp.send.bind(resp); | ||
const _status = resp.status.bind(resp); | ||
const _header = resp.header.bind(resp); | ||
const _file = resp.file.bind(resp); | ||
const _body = resp.body.bind(resp); | ||
const _renderTemplate = resp.renderTemplate.bind(resp); | ||
// TODO: Many... | ||
resp.detach(); | ||
resp.send = function(data) { | ||
if(data) _body(data); | ||
_send(); | ||
}; | ||
resp.end = function(data) { | ||
if(data) _body(data); | ||
_send(); | ||
}; | ||
resp.json = function(data) { | ||
_header("Content-Type", "application/json"); | ||
_body(JSON.stringify(data)); | ||
_send(); | ||
}; | ||
resp.sendFile = function(p) { | ||
_file(p); | ||
_send(); | ||
}; | ||
resp.sendStatus = function(code) { | ||
_status(code); | ||
_send(); | ||
}; | ||
resp.redirect = function() { | ||
let path = arguments.pop(); | ||
let code = arguments.pop() || 302; | ||
_status(code); | ||
_header("Location", path); | ||
_send(); | ||
}; | ||
resp.type = function(t) { | ||
_header("Content-Type", mime.lookup(t)); | ||
return resp; | ||
}; | ||
resp.header = function(k, v) { | ||
if(typeof(k) == "object") { | ||
let obj = k; | ||
for(const k of obj) { | ||
_header(k, obj[k]); | ||
} | ||
} else { | ||
_header(k, v);; | ||
} | ||
}; | ||
resp.set = resp.header; | ||
resp.render = function(name, data) { | ||
_renderTemplate(name, data); | ||
_send(); | ||
}; | ||
} | ||
module.exports.bodyParser = { | ||
json: parseJsonBody, | ||
urlencoded: parseUrlencodedBody | ||
} | ||
function parseJsonBody() { | ||
return function(req, resp, next) { | ||
try { | ||
req.body = req.json(); | ||
next(); | ||
} catch(e) { | ||
next(e); | ||
} | ||
} | ||
} | ||
function parseUrlencodedBody() { | ||
return function(req, resp, next) { | ||
try { | ||
req.body = req.form(); | ||
next(); | ||
} catch(e) { | ||
next(e); | ||
} | ||
} | ||
} |
338
lib.js
@@ -1,334 +0,6 @@ | ||
const core = require("./build/Release/ice_node_core"); | ||
const base = require("./base.js"); | ||
const express = require("./express_api.js"); | ||
module.exports.Application = Application; | ||
function Application(cfg) { | ||
if(!(this instanceof Application)) { | ||
return new Application(...arguments); | ||
} | ||
if(!cfg) cfg = {}; | ||
this.server = new core.Server(cfg); | ||
this.routes = {}; | ||
this.flags = []; | ||
this.middlewares = []; | ||
this.prepared = false; | ||
} | ||
Application.prototype.route = function(methods, p, fn) { | ||
if(typeof(methods) == "string") methods = [ methods ]; | ||
let param_mappings = p.split("/").filter(v => v).map(v => v.startsWith(":") ? v.substr(1) : null); | ||
let target; | ||
if(param_mappings.filter(v => v).length) { | ||
target = (req, resp) => { | ||
try { | ||
let params = {}; | ||
req.url.split("/").filter(v => v).map((v, index) => [param_mappings[index], v]).forEach(p => { | ||
if (p[0]) { | ||
params[p[0]] = p[1]; | ||
} | ||
}); | ||
req.params = params; | ||
} catch (e) { | ||
req.params = {}; | ||
} | ||
return fn(req, resp); | ||
}; | ||
} else { | ||
target = fn; | ||
} | ||
methods.map(v => v.toUpperCase()).forEach(m => { | ||
this.routes[m + " " + p] = target; | ||
}); | ||
return this; | ||
} | ||
Application.prototype.get = function(p, fn) { | ||
return this.route("GET", p, fn); | ||
} | ||
Application.prototype.post = function(p, fn) { | ||
this.use(p, new Flag("read_body")); | ||
return this.route("POST", p, fn); | ||
} | ||
Application.prototype.put = function(p, fn) { | ||
this.use(p, new Flag("read_body")); | ||
return this.route("PUT", p, fn); | ||
} | ||
Application.prototype.delete = function(p, fn) { | ||
return this.route("DELETE", p, fn); | ||
} | ||
Application.prototype.use = function(p, fn) { | ||
if(fn instanceof Flag) { | ||
this.flags.push({ | ||
prefix: p, | ||
name: fn.name | ||
}); | ||
} else { | ||
this.middlewares.push({ | ||
prefix: p, | ||
handler: fn | ||
}); | ||
} | ||
return this; | ||
} | ||
Application.prototype.addTemplate = function(name, content) { | ||
this.server.addTemplate(name, content); | ||
return this; | ||
} | ||
Application.prototype.loadCervusModule = function(name, data) { | ||
this.server.loadCervusModule(name, data); | ||
} | ||
Application.prototype.prepare = function() { | ||
if(this.prepared) { | ||
throw new Error("Application.prepare: Already prepared"); | ||
} | ||
let routes = {}; | ||
for(const k in this.routes) { | ||
const p = k.split(" ")[1]; | ||
let mws = this.middlewares.filter(v => p.startsWith(v.prefix)); | ||
if(!routes[p]) routes[p] = {}; | ||
routes[p][k.split(" ")[0]] = generateEndpointHandler(mws, this.routes[k]); | ||
} | ||
for(const p in routes) { | ||
let methodRoutes = routes[p]; | ||
let flags = this.flags.filter(v => p.startsWith(v.prefix)).map(v => v.name); | ||
this.server.route(p, function(req) { | ||
let rt = methodRoutes[req.method()]; | ||
if(rt) { | ||
rt(req); | ||
} else { | ||
let resp = req.createResponse(); | ||
resp.status(405); | ||
resp.send(); | ||
} | ||
}, flags); | ||
} | ||
this.server.route("", generateEndpointHandler(this.middlewares, (req, resp) => resp.status(404))); | ||
this.prepared = true; | ||
this.route = null; | ||
this.use = null; | ||
} | ||
Application.prototype.listen = function(addr) { | ||
if(!this.prepared) { | ||
throw new Error("Not prepared"); | ||
} | ||
this.server.listen(addr); | ||
} | ||
//module.exports.Request = Request; | ||
function Request(inst) { | ||
if(!(this instanceof Request)) { | ||
return new Request(...arguments); | ||
} | ||
this.inst = inst; | ||
this.cache = { | ||
uri: null, | ||
url: null, | ||
headers: null, | ||
cookies: null | ||
}; | ||
this.params = {}; | ||
this.session = new Proxy({}, { | ||
get: (t, k) => this.inst.sessionItem(k), | ||
set: (t, k, v) => this.inst.sessionItem(k, v) | ||
}); | ||
this.custom = new Proxy({}, { | ||
get: (t, k) => this.inst.customProperty(k) | ||
}); | ||
} | ||
Request.prototype.createResponse = function () { | ||
return new Response(this); | ||
} | ||
Request.prototype.body = function() { | ||
return this.inst.body(); | ||
} | ||
Request.prototype.json = function() { | ||
let body = this.body(); | ||
if(body) { | ||
return JSON.parse(body); | ||
} else { | ||
return null; | ||
} | ||
} | ||
Request.prototype.form = function () { | ||
let body = this.body(); | ||
if (!body) return null; | ||
let form = {}; | ||
try { | ||
body.toString().split("&").filter(v => v).map(v => v.split("=")).forEach(p => form[p[0]] = p[1]); | ||
return form; | ||
} catch (e) { | ||
throw new Error("Request body is not valid urlencoded form"); | ||
} | ||
} | ||
Object.defineProperty(Request.prototype, "uri", { | ||
get: function() { | ||
return this.cache.uri || (this.cache.uri = this.inst.uri()) | ||
} | ||
}); | ||
Object.defineProperty(Request.prototype, "url", { | ||
get: function() { | ||
return this.cache.url || (this.cache.url = this.uri.split("?")[0]) | ||
} | ||
}); | ||
Object.defineProperty(Request.prototype, "headers", { | ||
get: function() { | ||
return this.cache.headers || (this.cache.headers = this.inst.headers()) | ||
} | ||
}); | ||
Object.defineProperty(Request.prototype, "cookies", { | ||
get: function() { | ||
return this.cache.cookies || (this.cache.cookies = this.inst.cookies()) | ||
} | ||
}); | ||
//module.exports.Response = Response; | ||
function Response(req) { | ||
if(!(this instanceof Response)) { | ||
return new Response(...arguments); | ||
} | ||
if(!(req instanceof Request)) { | ||
throw new Error("Request required"); | ||
} | ||
this.inst = req.inst.createResponse(); | ||
this.detached = false; | ||
} | ||
Response.from = function(req, data) { | ||
if(typeof(data) == "string" || data instanceof Buffer) { | ||
return new Response(req).body(data); | ||
} | ||
throw new Error("Unable to convert data into Response"); | ||
} | ||
Response.prototype.body = function(data) { | ||
if(!(data instanceof Buffer)) { | ||
data = Buffer.from(data); | ||
} | ||
this.inst.body(data); | ||
return this; | ||
} | ||
Response.prototype.json = function(data) { | ||
return this.body(JSON.stringify(data)); | ||
} | ||
Response.prototype.file = function(p) { | ||
this.inst.file(p); | ||
return this; | ||
} | ||
Response.prototype.status = function(code) { | ||
this.inst.status(code); | ||
return this; | ||
} | ||
Response.prototype.header = function(k, v) { | ||
this.inst.header(k, v); | ||
return this; | ||
} | ||
Response.prototype.cookie = function(k, v) { | ||
this.inst.cookie(k, v); | ||
return this; | ||
} | ||
Response.prototype.renderTemplate = function(name, data) { | ||
this.inst.renderTemplate(name, JSON.stringify(data)); | ||
return this; | ||
} | ||
Response.prototype.stream = function(cb) { | ||
const stream = this.inst.stream(); | ||
setImmediate(() => cb(stream)); | ||
return this; | ||
} | ||
Response.prototype.send = function() { | ||
this.inst.send(); | ||
} | ||
Response.prototype.detach = function() { | ||
this.detached = true; | ||
} | ||
function generateEndpointHandler(mws, fn) { | ||
return async function(req) { | ||
req = new Request(req); | ||
let resp = req.createResponse(); | ||
for(const mw of mws) { | ||
// For dynamic dispatch | ||
if(!req.uri.startsWith(mw.prefix)) { | ||
continue; | ||
} | ||
try { | ||
// An exception from a middleware leads to a normal termination of the flow. | ||
await mw.handler(req, resp, mw); | ||
} catch(e) { | ||
resp.send(); | ||
return; | ||
} | ||
} | ||
try { | ||
// An exception from an endpoint handler leads to an abnormal termination. | ||
await fn(req, resp); | ||
} catch(e) { | ||
console.log(e); | ||
resp.status(500).send(); | ||
return; | ||
} | ||
if(!resp.detached) resp.send(); | ||
} | ||
} | ||
module.exports.Flag = Flag; | ||
function Flag(name) { | ||
if(!(this instanceof Flag)) { | ||
return new Flag(...arguments); | ||
} | ||
this.name = name; | ||
} | ||
module.exports.static = require("./static.js"); | ||
Object.assign(module.exports, base); | ||
module.exports.express = express.createApplication; | ||
Object.assign(module.exports.express, express); |
{ | ||
"name": "ice-node", | ||
"version": "0.3.1", | ||
"version": "0.3.2", | ||
"description": "Bindings for the Ice Web Framework", | ||
@@ -28,3 +28,5 @@ "main": "lib.js", | ||
"homepage": "https://github.com/losfair/ice-node#readme", | ||
"dependencies": {}, | ||
"dependencies": { | ||
"mime": "^1.3.6" | ||
}, | ||
"devDependencies": { | ||
@@ -31,0 +33,0 @@ "randomstring": "^1.1.5", |
@@ -0,1 +1,3 @@ | ||
**本文档对应的版本为 v0.2.x 。v0.3.x 的最新文档尚未完成。** | ||
Ice-node 是目前**最快的** Node Web 框架,基于 [Ice Core](https://github.com/losfair/IceCore) 核心。 | ||
@@ -11,10 +13,8 @@ | ||
对于一个 Hello world 服务, Ice-node 比 Node HTTP 库快 10% ,比 Koa 快 80% ,比 Express 快 100% 。 | ||
对于一个 Hello world 服务, Ice-node 比 Node HTTP 库快 70% 。 | ||
##### 每秒请求数,越高越好 | ||
![Benchmark Result](http://i.imgur.com/TkV8IxE.png) | ||
![Benchmark result](https://i.imgur.com/4uBIYMC.png) | ||
[原始数据](https://gist.github.com/losfair/066b04978d6a5b27418d85a6305ecd5c) | ||
对于执行数据库请求和简单逻辑的 Web 应用, Ice-node 比 Express 至少快 30% 。 | ||
@@ -21,0 +21,0 @@ 这是一个[测试应用](https://github.com/losfair/ice-node-perf-tests), |
@@ -0,1 +1,3 @@ | ||
**This documentation is for v0.2.x. The latest documentation for v0.3.x is not ready yet.** | ||
Ice-node is the **fastest** framework for building node web applications, based on [Ice Core](https://github.com/losfair/IceCore). | ||
@@ -11,14 +13,12 @@ | ||
Ice-node is based on Ice Core, which is written in Rust and C++ and provides high-performance abstractions for the Web. | ||
Ice-node is based on Ice Core, which is written in Rust and provides high-performance abstractions for the Web. | ||
When serving the "Hello world!" text, Ice-node is about 10% faster than the raw Node.js http implementation, 80% than Koa, and 100% than Express, while providing full routing support. | ||
When serving the "Hello world!" text, Ice-node is about 70% faster than the raw Node.js http implementation, while providing full routing support. | ||
##### Requests per second, higher is better | ||
![Benchmark Result](http://i.imgur.com/TkV8IxE.png) | ||
![Benchmark result](https://i.imgur.com/4uBIYMC.png) | ||
[Raw Results](https://gist.github.com/losfair/066b04978d6a5b27418d85a6305ecd5c) | ||
For practical applications that do database queries and some logic, Ice-node is also at least 30% faster than traditional Node web frameworks like Express. | ||
For practical applications that do database queries and some logic, Ice-node is also at least 30% faster than Express. | ||
We wrote a [test application](https://github.com/losfair/ice-node-perf-tests) that simulates some common API services like login and data fetching and do MongoDB queries when processing requests, | ||
@@ -40,6 +40,7 @@ and fire up 500 concurrent clients, each doing 101 requests: | ||
const ice = require("ice-node"); | ||
const app = new ice.Ice(); | ||
const app = new ice.Application(); | ||
app.get("/", req => "Hello world!"); | ||
app.get("/", (req, resp) => resp.body("Hello world!")); | ||
app.prepare(); | ||
app.listen("127.0.0.1:3535"); | ||
@@ -61,3 +62,3 @@ | ||
With `const app = new ice.Ice()`, you creates an Ice-node **Application** - an object describing how requests will be handled and how your data will be presented to users, | ||
With `const app = new ice.Application()`, you creates an Ice-node **Application** - an object describing how requests will be handled and how your data will be presented to users, | ||
containing arrays of `routes`, `middlewares`, key-value mappings of `templates`, and a `config` object. | ||
@@ -72,4 +73,5 @@ | ||
app.get("/:text", req => req.params.text); | ||
app.get("/:text", (req, resp) => resp.body(req.params.text)); | ||
app.prepare(); | ||
app.listen("127.0.0.1:3536"); | ||
@@ -86,15 +88,12 @@ | ||
- `method` (string): Request method, in upper case (`GET`, `POST` etc.) . | ||
- `host` (string): Alias for `headers.host`. | ||
- `cookies` (proxied object): Key-value mappings of cookies in the `Cookie` header. | ||
- `session` (proxied object): Key-value read and write access to the session of the request. | ||
- `params` (proxied object): Key-value mappings of params in request URL. | ||
- `cookies` (object): Key-value mappings of cookies in the `Cookie` header. | ||
- `session` (object): Key-value read and write access to the session of the request. | ||
- `params` (object): Key-value mappings of params in request URL. | ||
For example, the following code: | ||
app.get("/ip", req => req.remote_addr.split(":")[0]); | ||
app.get("/ip", (req, resp) => resp.body(req.remote_addr.split(":")[0])); | ||
will return the visitor's IP address. | ||
All of the proxied object do lazy load, bringing zero overhead if you don't use them. | ||
### Middlewares and Endpoints | ||
@@ -101,0 +100,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
1217417
65
1959
1
1
185
3
+ Addedmime@^1.3.6
+ Addedmime@1.6.0(transitive)