A web framework based purely on require('http')
require('http')
is a web framework. All you need are a few
small modules that do one thing well when you are building your
web application route handlers.
This module aggregates and documents (with examples) a selection of small modules that can be used to achieve this goal.
It's recommended you check out the:
I do not recommend you use this "framework". You should check out the small modules and use them directly. Use the list of examples here for inspiration.
http-framework is an OPEN Open Source Project, see the Contributing section to find out what this means.
express
inspired examples
These examples are clones of express
examples demonstrating
how to author web apps without frameworks.
- auth An example demonstrating how to login and authenticate users
- auth-helpers An example demonstrating how you can restrict certain routes in your apps to only be accessed by certain types of users
- content-negotiation An example demonstrating how you can return different types of content based on what the user asks for in his Accept header.
- cookie-sessions An example of storing session information in a users cookie
- cookies An example of setting a cookie to track a user
- cors An example of adding cors support to your HTTP server
- downloads An example of allowing users to download files from your server
- error An example of handling errors in your HTTP server
- error-pages An example of rendering custom 500 and 404 pages in your web server
- expose-data-to-client An example of putting server side state into your template so that it can be accessed from browser javascript
- hello-world A simple hello world example
- multipart An example demonstrating file uploads and saving them to disk temporarily.
- mvc An over engineered example of how to structure a slightly larger web application. Includes configuration, modularity and databases. Take ideas from it, please do not copy paste it
- online
An example of using redis and the
online
module to track user presence - route-map
An example of a
map
utility function that can be used to structure your routes differently. This demonstrates you can do whatever you want, if you like it, do it. - route-seperation An example of spreading your route handlers over multiple files.
- search An example of doing a database query over XHR with a web server backed by redis
- session An example of storing information in a session. The session is either backed by redis or memory.
- static-files An example of serving static files for your web server
- vhost An example of handling multiple sub domains as seperate applications in a singlue web server
- web-service An example JSON REST api web server. Contains API key authentication and error handling
Credit for the application's goes to
- @visionmedia & express maintainers
hapi
inspired examples
These examples are clones of hapi
examples demonstrating
how to author web apps without frameworks.
- tail An example of handling async errors in your applications after you have ended the HTTP response
- validate-api An example of how to add validation logic to your HTTP route handlers. Both validating query params & the HTTP body
Credit for the application's goes to
- @spumko & hapi maintainers
connect
inspired examples
These examples are clonse of connect
examples demonstrating
how to author web apps without frameworks.
- basic-auth An example of handling basic authorization style authentication of a web server.
- csrf An example of adding csrf security to a form in a web application
Credit for the applicatiosn` goes to
- @visionmedia & connect maintainers
Check out the modules folder for an example of small modules you can combine to build your own custom "framework"
See the package.json
dependencies hash for an example of
many small modules used in the examples folder of this project.
For a complete list of Modules check out the wiki
I want to load multiple cascading configuration files
Using config-chain
you can load the configuration for your web
application from multiple locations. It also cascades the
loading so you can load general configuration and overwrite
it with more specific configuration.
var path = require("path")
var http = require("http")
var cc = require("config-chain")
var optimist = require("optimist")
var NODE_ENV = process.env.NODE_ENV
// optimist is a useful module to parse command line arguments
// if you run `node example.js --port 8000 --host localhost` then
// argv is { port: 8000, host: 'localhost' }
var argv = optimist.argv
// config loads host & port from your config.json file in the
// config folder. The host & port are overwritten by NODE_ENV
// specific config. This is useful for running a test & local
// server without conflicting ports.
// You can also overwrite the configuration with a hardcoded
// path. You can then just have secret configuration files on
// the production box. Lastly we pass in the argv which allows
// us to overwrite configuration values by passing command line
// arguments to our process!
var config = cc(
argv,
'/usr/config/my-app/config.json',
path.join(__dirname, "config", "config." + NODE_ENV + ".json"),
path.join(__dirname, "config", "config.json")
).store
// port & host are pretty flexibly defined.
http.createServer(function (req, res) {
res.end("hello world")
}).listen(config.port, config.host)
Alternatively by hand:
var path = require("path")
var http = require("http")
var fs = require("fs")
var NODE_ENV = process.env.NODE_ENV
var configOpts = [
'/usr/config/my-app/config.json',
path.join(__dirname, "config", "config." + NODE_ENV + ".json"),
path.join(__dirname, "config", "config.json")
]
var config = {}
// loop trick. Asynchronous for loop.
(function loop(callback) {
// if we have read all files then done
if (configOpts.length === 0) {
return callback()
}
// do a thing
var uri = configOpts.pop()
fs.readFile(uri, function (err, file) {
if (err) {
return callback(err)
}
var value = JSON.parse(String(file))
extend(config, value)
// when we done the thing get the next value of array
loop(callback)
})
}(startServer))
function startServer(err) {
extend(config, parseArguments(process.argv))
http.createServer(function (req, res) {
res.end("hello world")
}).listen(config.port, config.host)
}
function extend(dest, src) {
for (var k in src) {
dest[k] = src[k]
}
}
function parseArguments(args) {
var opts = {}, key
args = args.slice(2)
args.forEach(function (arg) {
if (arg.substr(0, 2) === '--') {
key = arg
} else {
opts[key] = arg
}
})
return opts
}
I want to persist data between req/res pairs from the same client
Using cookies
you cam get()
and set()
HTTP cookies on a req / res
pair. cookies
also allows you to handle options like signed cookies
and setting the domain / http only flag. Cookies will stick in the
http client and will be send again with the next request by that
client.
var http = require("http")
var cookies = require("cookies")
http.createServer(function (req, res) {
var cookies = Cookies(req, res)
// fetch value out of cookie
var count = Number(cookies.get("count")) || 0
count++
// store value in cookie
cookies.set("count", String(count))
res.end("you have watched this page " + count + " times")
}).listen(8080)
Alternatively by hand:
var http = require("http")
http.createServer(function (req, res) {
var cookie = req.headers.cookie || ""
// parse cookie out
var pairs = cookie.split(";")
var obj = {}
pairs.forEach(function (pair) {
var parts = pair.split("=")
obj[parts[0].trim()] = parts[1].trim()
})
var count = Number(obj.count) || 0
count++
// serialize cookie
res.setHeader("Set-Cookie", "count=" + count)
res.end("you have watched this page " + count + " times")
}).listen(8080)
Note that using cookies
gives you a more forgiving parser &
serializer as well as it handling all options & signed
cookies.
I want to be able to make cross domain ajax requests to my web server
You can set CORS headers on your res
that allow browsers to make
http ajax requests from other domains bypassing the same
domain restriction.
This is useful if you have a public HTTP API server that other developers & clients use or if your API server is on a different domain / port then your web server.
var http = require("http")
var Corsify = require("corsify")
http.createServer(Corsify({
// you should not allow all origins! That's a security risk
// you can use getOrigin to whitelist the origin.
// whatever you return becomes the Access-Control-Allow-Origin
// HTTP header.
getOrigin: function (req, res) {
var origin = req.headers.origin
// manually white list allowed CORS servers
if (origin === "http://web.my-server.com") {
return "http://web.my-server-.com"
}
}
}, function () {
res.end("API server! CORS me.")
})).listen(8080)
Alternatively by hand:
var http = require("http")
http.createServer(function (req, res) {
var origin = req.headers.origin
if (origin === "http://web.my-server.com") {
res.setHeader("Access-Control-Allow-Origin", origin)
}
res.setHeader("Access-Control-Allow-Methods",
"POST, GET, PUT, DELETE, OPTIONS")
res.setHeader("Access-Control-Allow-Credentials", "true")
res.setHeader("Access-Control-Max-Age", "86400")
res.setHeader("Access-Control-Allow-Headers",
"X-Requested-With, Content-Type, Accept")
// CORS in browsers send a preflight OPTIONS HTTP request
// for POSTs to see whether making a HTTP request to this
// end point is allowed. You should handle `OPTIONS` seperately
// from `GET` / `POST` / other headers.
if (req.method === "OPTIONS") {
return res.end()
}
res.end("API Server! CORS me.")
}).listen(8080)
I want to secure a HTML form post against cross site attack vectors
If you do not protect your HTML forms then other websites can submit your forms on other users behalf and thus hijack their sessions.
This is based on the fact they can just guess the field names and values. To circumvent this we can CSRF to put an effectively random field in the form that can't be guessed by malicious third party websites
var http = require("http")
var querystring = require("querystring")
var csrf = require("csrf-lite")
var Cookies = require("cookies")
http.createServer(function (req, res) {
var token = getToken(req, res)
// render a HTML form. put the csrf token in there as
// a html hidden input field
if (req.method === "GET") {
res.setHeader("Content-Type", "text/html")
res.end("<html><form method=post>" +
"<label>Name <input name=name></label>" +
csrf.html(token) +
"<input type=submit value=submit>" +
"</form></html>");
} else if (req.method === "POST") {
var body = ""
req.on("data", function (chunk) {
body += chunk
})
req.on("end", function () {
var payload = querystring.parse(body)
var valid = csrf.validate(payload, token)
if (!valid) {
res.statusCode = 403
return res.end("invalid csrf")
}
res.end("ok! " + payload.name)
})
}
}).listen(8080)
function getToken(req, res) {
// use the sessionId as the token
var token = cookies.get("ssid")
if (!token) {
token = crypto.randomBytes(24).toString("base64")
res.setHeader("Set-Cookie", "ssid=" + token)
}
return token
}
Alternatively by hand:
var http = require("http")
var querystring = require("querystring")
var crypto = require("crypto")
http.createServer(function (req, res) {
var token = getToken(req, res)
// render a HTML form. put the csrf token in there as
// a html hidden input field
if (req.method === "GET") {
// HTML encode the token so it can be inserted into a form
token = token.replace(/"/g, """)
res.setHeader("Content-Type", "text/html")
res.end("<html><form method=post>" +
"<label>Name <input name=name></label>" +
"<input type=hidden name=x-csrf-token value=\"" + token + "\">" +
"<input type=submit value=submit>" +
"</form></html>");
} else if (req.method === "POST") {
var body = ""
req.on("data", function (chunk) {
body += chunk
})
req.on("end", function () {
var payload = querystring.parse(body)
var csrfToken = payload["x-csrf-token"]
if (csrfToken !== token) {
res.statusCode = 403
return res.end("invalid csrf")
}
res.end("ok! " + payload.name)
})
}
}).listen(8080)
function getToken(req, res) {
var cookie = req.headers.cookie || ""
// parse cookie out
var pairs = cookie.split(";")
var obj = {}
pairs.forEach(function (pair) {
var parts = pair.split("=")
obj[parts[0].trim()] = parts[1].trim()
})
// use the sessionId as the token
var token = obj.ssid
if (!token) {
token = csrf()
cookies.set("ssid", token)
}
return token
}
I want to send a file down a response
Using filed
you will stream a file down the HttpResponse
. The usage
of streaming the payload is nice for efficiency and memory usage.
var http = require("http")
var filed = require("filed")
http.createServer(function (req, res) {
filed(__dirname + "/static/index.html").pipe(res)
}).listen(8080)
Alternatively by hand:
var http = require("http")
var fs = require("fs")
http.createServer(function (req, res) {
var filePath = __dirname + "/static/index.html"
// optionally stat the file your streaming. This allows you
// to set the Content-Length header.
fs.stat(filePath, function (err, stat) {
if (err) {
// the ENOENT code means no such file or directory
if (err.code === "ENOENT") {
res.statusCode = 404
return res.end("Not Found")
}
res.statusCode = 500
return res.end(err.message)
}
// you can skip the fs.stat call and just stream the content directly
var stream = fs.createReadStream(__dirname + "/static/index.html")
res.statusCode = 200
res.setHeader("Content-Type", "text/html")
res.setHeader("Content-Type", stat.size)
stream.pipe(res)
})
}).listen(8080)
I want to read the body of a HTML <form> submission.
var http = require("http")
var formBody = require("body/form")
http.createServer(function (req, res) {
formBody(req, res, function (err, body) {
if (err) {
res.statusCode = 500
res.end(err.message)
}
res.end("you submitted " + JSON.stringify(body))
})
}).listen(8080)
Alternatively by hand:
var http = require("http")
var querystring = require("qs")
var StringDecoder = require("string_decoder").StringDecoder
http.createServer(function (req, res) {
var requestBody = ""
// use a StringDecoder to parse two byte UTF8 characters.
// if you use string concatenation you might get half of
// a two byte character and your body will be wrong
var stringDecoder = new StringDecoder()
req.on("data", onData)
req.once("end", onEnd)
req.once("error", onError)
function onData(chunk) {
requestBody += stringDecoder.write(chunk)
}
function onEnd() {
// remove listeners to avoid leaks
req.removeListener("data", onData)
req.removeListener("error", onError)
requestBody += stringDecoder.end()
var body = querystring.parse(requestBody)
res.end("you submitted " + JSON.stringify(body))
}
function onError(err) {
// // remove listeners to avoid leaks
req.removeListener("data", onData)
req.removeListener("end", onEnd)
res.statusCode = 500
res.end(err.message)
}
}).listen(8080)
Note that this hand written alternative has a buffer overflow
attack in it. It never keeps appending to requestBody
and
will crash the process if someone sends you a chunked encoded
body of 100s of GBs.
I want to send a JSON payload to my user
var http = require("http")
var sendJson = require("send-data/json")
http.createServer(function (req, res) {
sendJson(req, res, {
sending: "json"
})
}).listen(8080)
Alternatively by hand:
var http = require("http")
http.createServer(function (req, res) {
// notice creating a buffer from the string
var payload = new Buffer(JSON.stringify({
sending: "json"
}))
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
// ensure Content-Length is number of bytes, not number of characters
res.setHeader("Content-Length", payload.length)
res.end(payload)
}).listen(8080)
I want to send a HTML payload to my user
var http = require("http")
var fs = require("fs")
var sendHtml = require("send-data/html")
var page = fs.readFileSync(__dirname + "/static/index.html")
http.createServer(function (req, res) {
sendHtml(req, res, page)
}).listen(8080)
Alternatively by hand:
var http = require("http")
var fs = require("fs")
// note that fs.readFileSync returns a buffer
var page = fs.readFileSync(__dirname + "/static/index.html")
http.createServer(function (req, res) {
res.statusCode = 200
res.setHeader("Content-Type", "text/html")
// ensure Content-Length is number of bytes, not number of characters
res.setHeader("Content-Length", page.length)
res.end(page)
}).listen(8080)
// TODO
For now see the examples folder
- Finish porting express examples
- continue porting hapi examples
- continue connect examples
- Port koajs examples
- Port restify examples
- Port partial.js examples
- Finish documentation
- Get community feedback
- Author new fresh examples
npm install http-framework
- Raynos
- Matt-Esch
- maxogden