'use strict';
const http = require('http'),
https = require('https'),
url = require('url'),
path = require('path'),
fs = require('fs'),
querystring=require('querystring'),
Config = require(`${__dirname}/Config.js`);
'use strict';
const http = require('http'),
https = require('https'),
url = require('url'),
path = require('path'),
fs = require('fs'),
querystring=require('querystring'),
Config = require(`${__dirname}/Config.js`);
You can pass a userConfig
object to shallow merge and/or decorate the server.config
when instatiating if you desire.
const server=require('node-http-server');
// server is a Server instation already so to modify or decorate its config, you
// must call the server.deploy method.
server.deploy({verbose:true})
const Server=server.Server;
// Server is a refrence to the Server class so you can extend or instatiate it.
const myServer=new Server({verbose:true});
// this is the instatiated server which now contains your desired defaults.
// like the first example, it too can still be modified on deployment
myServer.deploy({port:8888},(server)=>{console.log(server)});
class Server{
server.config
is where the servers configuration will reside.
It is a new instance of the Config class which will be shallow merged and/or decorated by with
the passed userConfig
if one is passed upon construction of the Server class, or to the server.deploy
method.
constructor(userConfig){
this.config=new this.Config(userConfig);
}
server.deploy
starts the server.
server.deploy(userConfig,readyCallback)
method | returns |
---|---|
deploy | void |
parameter | required | description |
---|---|---|
userConfig | no | if a userConfig object is passed it will decorate the Config class |
readyCallback | no | called once the server is started |
const server=require('node-http-server');
server.deploy(
{
port:8000,
root:'~/myApp/'
},
serverReady
);
server.deploy(
{
port:8888,
root:'~/myOtherApp/'
},
serverReady
);
function serverReady(server){
console.log( `Server on port ${server.config.port} is now up`);
}
See the example folder for more detailed examples, or check the node-http-server readme for some Quickstart Examples for deploying a node server
get deploy(){
return deploy;
}
server.onRawRequest
server.onRawRequest(request,response,serve)
method | should return |
---|---|
onRawRequest | bool/void |
parameter | description |
---|---|
request | http(s) request obj |
response | http(s) response obj |
serve | ref to server.serve |
const server=require('node-http-server');
const config=new server.Config;
config.port=8000;
server.onRawRequest=gotRequest;
server.deploy(config);
function gotRequest(request,response,serve){
console.log(request.uri,request.headers);
serve(
request,
response,
JSON.stringify(
{
uri:request.uri,
headers:request.headers
}
)
);
return true;
}
onRawRequest(request,response,serve){
}
server.onRequest
server.onRequest(request,response,serve)
method | should return |
---|---|
onRequest | bool/void |
parameter | description |
---|---|
request | raw http(s) request obj |
response | http(s) response obj |
serve | ref to server.serve |
const server=require('node-http-server');
const config=new server.Config;
config.port=8099;
config.verbose=true;
server.onRequest=gotRequest;
server.deploy(config);
function gotRequest(request,response,serve){
//at this point the request is decorated with helper members lets take a look at the query params if there are any.
console.log(request.query,request.uri,request.headers);
//lets only let the requests with a query param of hello go through
if(request.query.hello){
// remember returning false means do not inturrupt the response lifecycle
// and that you will not be manually serving
return false;
}
serve(
request,
response,
JSON.stringify(
{
success:false,
message:'you must have a query param of hello to access the server i.e. /index.html?hello'
uri:request.uri,
query:request.query
}
)
);
//now we let the server know we want it to kill the normal request lifecycle
//because we just completed it by serving above. we could also handle it async style
//and request a meme or something from the web and put that on the page (or something...)
return true;
}
onRequest(request,response,serve){
}
server.beforeServe
server.beforeServe(request,response,body,encoding,serve)
method | should return |
---|---|
beforeServe | bool/void |
parameter | description |
---|---|
request | decorated http(s) request obj |
response | http(s) response obj |
body | response content body RefString |
encoding | response body encoding RefString |
serve | ref to server.serve |
type RefString
type | keys | description |
---|---|---|
RefString | value |
a way to allow modifying a string by refrence. |
const server=require('node-http-server');
server.beforeServe=beforeServe;
function beforeServe(request,response,body,encoding){
//only parsing html files for this example
if(response.getHeader('Content-Type')!=server.config.contentType.html){
//return void||false to allow response lifecycle to continue as normal
return;
}
const someVariable='this is some variable value';
body.value=body.value.replace('{{someVariable}}',someVariable);
//return void||false to allow response lifecycle to continue as normal
//with modified body content
return;
}
server.deploy(
{
port:8000,
root:`${__dirname}/appRoot/`
}
);
beforeServe(request,response,body,encoding,serve){
}
server.afterServe
server.afterServe(request)
method | should return |
---|---|
afterServe | n/a |
parameter | description |
---|---|
request | decorated http(s) request obj |
const server=require('node-http-server');
server.afterServe=afterServe;
function afterServe(request){
console.log(`just served ${request.uri}`);
}
server.deploy(
{
port:8075,
root:`${__dirname}/appRoot/`
}
);
afterServe(request){
}
get serve(){
return serve;
}
get serveFile(){
return serveFile;
}
get Config(){
return Config
}
get Server(){
return Server
}
}
function deploy(userConfig, readyCallback=function(){}){
Object.defineProperty(
this,
'server',
{
value:http.createServer(
requestRecieved.bind(this)
),
writable:false,
enumerable:true
}
);
if(userConfig){
Object.assign(this.config,userConfig);
}
if(this.config.https && this.config.https.privateKey && this.config.https.certificate){
if(!this.config.https.port){
this.config.https.port=443;
}
this.config.httpsOptions = {
key: fs.readFileSync(this.config.https.privateKey),
cert: fs.readFileSync(this.config.https.certificate),
passphrase: this.config.https.passphrase
};
if(this.config.https.ca){
this.config.httpsOptions.ca=fs.readFileSync(this.config.https.ca);
}
Object.defineProperty(
this,
'secureServer',
{
value:https.createServer(
this.config.httpsOptions,
requestRecieved.bind(this)
),
writable:false,
enumerable:true
}
);
}else{
this.config.https={
only:false
};
}
this.config.logID=`### ${this.config.domain} server`;
if(this.config.verbose){
console.log(
`${this.config.logID} configured with ###\n\n`,this.config);
}
this.server.timeout=this.config.server.timeout;
if(!this.config.https.only){
this.server.listen(
this.config.port,
function() {
if(this.config.verbose){
console.log(`${this.config.logID} listening on port ${this.config.port} ###\n\n`);
}
The readyCallback
is passed the full server instance
so that you may use the same callback to handle multiple
server instances in the same code instead of writing an inline
callback… think about it ;)
readyCallback(this);
}.bind(this)
);
}
if(this.config.httpsOptions){
this.secureServer.listen(
this.config.https.port,
function() {
if(this.config.verbose){
console.log(`HTTPS: ${this.config.logID} listening on port ${this.config.https.port} ###\n\n`);
}
for example the same ready callback could handle both the https and http servers with a simple test for the servers port
readyCallback(this);
}.bind(this)
);
}
}
function setHeaders(response,headers){
const keys=Object.keys(headers);
for(const i in keys){
response.setHeader(
keys[i],
headers[keys[i]]
);
}
}
function serveFile(filename,exists,request,response) {
if(!exists) {
if(this.config.verbose){
console.log(`${this.config.logID} 404 ###\n\n`);
}
if(!response){
return false;
}
response.statusCode=404;
setHeaders(response, this.config.errors.headers);
this.serve(
request,
response,
this.config.errors['404']
);
return;
}
const contentType = path.extname(filename).slice(1);
if(!this.config.contentType[contentType]){
if(this.config.verbose){
console.log(`${this.config.logID} 415 ###\n\n`);
}
response.statusCode=415;
setHeaders(response, this.config.errors.headers);
this.serve(
request,
response,
this.config.errors['415']
);
return;
}
if (
fs.statSync(filename).isDirectory()
){
filename+=`/${this.config.server.index}`;
}
if (
this.config.restrictedType[contentType]
){
if(this.config.verbose){
console.log(`${this.config.logID} 403 ###\n\n`);
}
response.statusCode=403;
setHeaders(response, this.config.errors.headers);
this.serve(
request,
response,
this.config.errors['403']
);
return;
}
fs.readFile(
filename,
'binary',
function(err, file) {
if(err) {
if(this.config.verbose){
console.log(`${this.config.logID} 500 ###\n\n`,err,'\n\n');
}
response.statusCode=500;
setHeaders(response, this.config.errors.headers);
this.serve(
request,
response,
this.config.errors['500'].replace(/\{\{err\}\}/g,err)
);
return;
}
response.setHeader(
'Content-Type',
this.config.contentType[contentType]
);
if(this.config.server.noCache){
response.setHeader(
'Cache-Control',
'no-cache, no-store, must-revalidate'
);
}
response.statusCode=200;
this.serve(
request,
response,
file,
'binary'
);
if(this.config.verbose){
console.log(`${this.config.logID} 200 ###\n\n`);
}
return;
}.bind(this)
);
}
function serve(request,response,body,encoding){
if(!response.statusCode){
response.statusCode=200;
}
if(!response.getHeader('Content-Type')){
response.setHeader(
'Content-Type',
'text/plain'
);
if(this.config.verbose){
console.log(`${this.config.logID} response content-type header not specified ###\n\nContent-Type set to: text/plain\n\n`);
}
}
if(!encoding){
encoding='utf8';
if(this.config.verbose){
console.log(`${this.config.logID} encoding not specified ###\nencoding set to:\n`,encoding,'\n\n');
}
}
const refBody=new RefString;
const refEncoding=new RefString;
refBody.value=body;
refEncoding.value=encoding;
return any value to force or specify delayed or manual serving
if(
this.beforeServe(
request,
response,
refBody,
refEncoding,
completeServing.bind(this)
)
){
return;
};
completeServing.bind(this)(request,response,refBody,encoding);
return;
}
function completeServing(request,response,refBody,refEncoding){
if(!(refBody instanceof RefString)){
refBody=new RefString(refBody);
}
if(!(refEncoding instanceof RefString)){
refEncoding=new RefString(refEncoding||'binary');
}
if(response.finished){
this.afterServe(request);
return;
}
response.end(
refBody.value,
refEncoding.value,
this.afterServe.bind(this,request)
);
}
class RefString{
constructor(value){
if(value){
this._string=value;
}
}
get value(){
return this._string;
}
set value(value){
this._string=value;
return this._string;
}
}
function requestRecieved(request,response){
if(this.config.log){
const logData={
method : request.method,
url : request.url,
headers : request.headers
};
this.config.logFunction(
logData
);
}
let uri = url.parse(request.url);
uri.protocol='http';
uri.host=uri.hostname=request.headers.host;
uri.port=80;
uri.query=querystring.parse(uri.query);
if(request.connection.encrypted){
uri.protocol='https';
uri.port=443;
}
(
function(){
if(!uri.host){
return;
}
const host=uri.host.split(':');
if(!host[1]){
return;
}
uri.host=uri.hostname=host[0];
uri.port=host[1];
}
)();
for(let key in uri){
if(uri[key]!==null){
continue;
}
uri[key]='';
}
request.uri=uri;
if(
this.onRawRequest(
request,
response,
completeServing.bind(this)
)
){
return;
};
uri=uri.pathname;
if (uri=='/'){
uri=`/${this.config.server.index}`;
}
let hostname= [];
if (request.headers.host !== undefined){
hostname = request.headers.host.split(':');
}
let root = this.config.root;
if(this.config.verbose){
console.log(`${this.config.logID} REQUEST ###\n\n`,
request.headers,'\n',
uri,'\n\n',
hostname,'\n\n'
);
}
if(this.config.domain!='0.0.0.0' && hostname.length > 0 && hostname[0]!=this.config.domain){
if(!this.config.domains[hostname[0]]){
if(this.config.verbose){
console.log(`${this.config.logID} INVALID HOST ###\n\n`);
}
this.serveFile(hostname[0],false,response);
return;
}
root=this.config.domains[hostname[0]];
}
if(this.config.verbose){
console.log(`${this.config.logID} USING ROOT : ${root}###\n\n`);
}
if(uri.slice(-1)=='/'){
uri+=this.config.server.index;
}
request.url=uri;
request.serverRoot=root;
return any value to force or specify delayed or manual serving
if(
this.onRequest(
request,
response,
completeServing.bind(this)
)
){
return;
};
const filename = path.join(
request.serverRoot,
request.url
);
fs.exists(
filename,
function fileExists(exists){
this.serveFile(filename,exists,request,response);
}.bind(this)
);
}
module.exports=new Server;