Skip to content

Commit 2d6727e

Browse files
committed
Added support for method specific requests.
1 parent 2fd43da commit 2d6727e

3 files changed

Lines changed: 261 additions & 10 deletions

File tree

index.js

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"use strict";
22

3+
// @ts-check
4+
/// <reference path="./index.d.ts>"
5+
36
const fs = require('fs');
47
const querystring = require('querystring');
58
const formidable = require('formidable');
@@ -18,14 +21,17 @@ module.exports = (http) => {
1821
* Add route callback code to routes object
1922
* @param {string} path
2023
* @param {function} cb
24+
* @param {method} method
2125
* @return {bool}
2226
*/
23-
addRoute(route, cb) {
27+
addRoute(route, cb, method) {
28+
method = method ? method : "*"; // old school default param
29+
2430
let success = false;
2531

2632
if (Array.isArray(route)) {
2733
route.forEach((route) => {
28-
this.addRoute(route, cb);
34+
this.addRoute(route, cb, method);
2935
});
3036
}
3137
else {
@@ -34,7 +40,10 @@ module.exports = (http) => {
3440
}
3541

3642
if (!this.routes.hasOwnProperty(route)) { // Check if the route already exists
37-
this.routes[route] = cb;
43+
this.routes[route] = {
44+
method: method,
45+
cb: cb
46+
};
3847
success = true;
3948
}
4049
else {
@@ -116,12 +125,16 @@ module.exports = (http) => {
116125
return this.renderAsset(assetType, file, assetType, res);
117126
}
118127
}
119-
128+
120129
this.pageNotFound(res, rawUrl);
121130
}
122131
else {
123132
if (routes.indexOf(urlPathname) !== -1) { // If the url is for a page route
124-
this.routes[urlPathname](req, res, rawUrl, queryString);
133+
const {method, cb} = this.routes[urlPathname];
134+
if (!this.checkMethod(req, res ,method)) {
135+
return;
136+
}
137+
cb(req, res, rawUrl, queryString);
125138
}
126139
else {
127140
let routeMatch = false;
@@ -149,21 +162,36 @@ module.exports = (http) => {
149162

150163
if (param) {
151164
routeMatch = true;
152-
return this.routes[route](req, res, rawUrl, queryString);
165+
const {method, cb} = this.routes[route];
166+
167+
if (!this.checkMethod(req, res ,method)) {
168+
return;
169+
}
170+
return cb(req, res, rawUrl, queryString);
153171
}
154172
}
155173
else if (routeParts.length === urlParts.length) {
156174
if (routeParts[0] === rawUrl) {
157175
routeMatch = true;
158-
return this.routes[route](req, res, rawUrl, queryString);
176+
177+
const {method, cb} = this.routes[route];
178+
if (!this.checkMethod(req, res ,method)) {
179+
return
180+
}
181+
return cb(req, res, rawUrl, queryString);
159182
}
160183
else {
161184
let param = this.parseURLParameter(urlParts[0], routeParts[0]);
162185

163186
if (param) {
164187
routeMatch = true;
165188
req.parameters = Object.assign(req.parameters, param);
166-
return this.routes[route](req, res, rawUrl, queryString);
189+
190+
const {method, cb} = this.routes[route];
191+
if (!!this.checkMethod(req, res ,method)) {
192+
return;
193+
}
194+
return cb (req, res, rawUrl, queryString);
167195
}
168196
else {
169197
routeMatch = true;
@@ -252,6 +280,9 @@ module.exports = (http) => {
252280
if (routePart.indexOf(':') === 0) {
253281
let param = {};
254282
let parameterSections = routePart.split('(');
283+
if (parameterSections.length < 2) {
284+
throw new Error("Regex is missing from " + parameterSections[0] + ". Url parameters request a regex in \"/:param(regex)\" format.")
285+
}
255286
let paramName = parameterSections[0].replace(':', '');
256287
let regex = new RegExp(`(${parameterSections[1]}`);
257288

@@ -280,8 +311,45 @@ module.exports = (http) => {
280311
close() {
281312
this.server.close();
282313
}
283-
}
284314

315+
get(route, cb) {
316+
this.addRoute(route, cb, "GET");
317+
}
318+
319+
post(route, cb) {
320+
this.addRoute(route, cb, "POST")
321+
}
322+
323+
put(route, cb) {
324+
this.addRoute(route, cb, "PUT")
325+
}
326+
327+
delete(route, cb) {
328+
this.addRoute(route, cb, "DELETE")
329+
}
330+
331+
/**
332+
* Check if the request method is what was expected for the route
333+
*
334+
* @param {http.IncomingMessage} req
335+
* @param {http.ServerResponse} res
336+
* @param {string} expected
337+
* @return {boolean}
338+
*/
339+
checkMethod(req, res, expected) {
340+
if (expected === "*") {
341+
return true;
342+
}
343+
344+
if (req.method.toLowerCase() === expected.toLowerCase()) {
345+
return true;
346+
}
347+
348+
res.statusCode = 405;
349+
res.end(`Method not allowed for ${req.url}`);
350+
return false;
351+
}
352+
}
285353

286354

287355
return new Router();

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "small-router",
3-
"version": "1.1.15",
3+
"version": "1.2.0",
44
"description": "Small router module that creates http server and runs callbacks that have have been added for a route via the api",
55
"main": "index.js",
66
"scripts": {
@@ -26,5 +26,8 @@
2626
},
2727
"dependencies": {
2828
"formidable": "^1.0.17"
29+
},
30+
"prettier": {
31+
"tabWidth": 4
2932
}
3033
}

tests/index.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,26 @@ describe('server', () => {
4949
res.end(JSON.stringify(queryString));
5050
});
5151

52+
router.get("/test/get/method", (req, res ) => {
53+
res.writeHead(200, { 'Content-Type': 'text/html' });
54+
res.end("test text");
55+
})
56+
57+
router.post("/test/post/method", (req, res ) => {
58+
res.writeHead(200, { 'Content-Type': 'text/html' });
59+
res.end("test text");
60+
})
61+
62+
router.put("/test/put/method", (req, res ) => {
63+
res.writeHead(200, { 'Content-Type': 'text/html' });
64+
res.end("test text");
65+
})
66+
67+
router.delete("/test/delete/method", (req, res ) => {
68+
res.writeHead(200, { 'Content-Type': 'text/html' });
69+
res.end("test text");
70+
})
71+
5272
router.addAssetPath('images', 'tests/images/');
5373
router.addAssetPath('js', 'tests/js/');
5474

@@ -361,6 +381,166 @@ describe('server', () => {
361381
});
362382
});
363383

384+
describe("Routes with specific request methods", function() {
385+
it("it should only accept get method requests", (done) => {
386+
Promise.all([
387+
new Promise((resolve, reject) => {
388+
http.get(`${SERVER_URL}/test/get/method`, (res) => {
389+
res.statusCode.should.equal(200);
390+
391+
let body = '';
392+
res.on('data', (chunk) => body += chunk)
393+
.on("end", () => {
394+
body.should.equal("test text")
395+
resolve();
396+
});
397+
});
398+
}),
399+
new Promise((resolve, reject) => {
400+
const req = http.request({
401+
url: `http://localhost/`,
402+
port: '8000',
403+
method: 'POST',
404+
path: "/test/get/method",
405+
}, (res) => {
406+
res.statusCode.should.equal(405);
407+
let body = "";
408+
res.on("data", (chunk) => body += chunk)
409+
.on("end", () => {
410+
body.should.equal("Method not allowed for /test/get/method")
411+
resolve();
412+
});
413+
});
414+
415+
req.on('error', (err) => {
416+
throw err;
417+
});
418+
419+
req.end();
420+
})
421+
]).then(() => done());
422+
});
423+
424+
it("it should only accept post requests", (done) => {
425+
const path = "/test/post/method";
426+
Promise.all([
427+
new Promise((resolve, reject) => {
428+
const req = http.request({
429+
url: "http://locahost",
430+
port: "8000",
431+
method: "POST",
432+
path,
433+
}, res => {
434+
res.statusCode.should.equal(200);
435+
let body = "";
436+
res.on("data", chunk => body += chunk)
437+
.on("end", () => {
438+
body.should.equal("test text");
439+
resolve();
440+
});
441+
});
442+
443+
req.on("error", err => {
444+
throw err
445+
});
446+
req.end();
447+
}),
448+
new Promise((resolve, reject) => {
449+
http.get(`${SERVER_URL}${path}`, res => {
450+
res.statusCode.should.equal(405);
451+
452+
let body = "";
453+
res.on("data", chunk => body += chunk)
454+
.on("end", () => {
455+
body.should.equal(`Method not allowed for ${path}`)
456+
resolve();
457+
});
458+
});
459+
})
460+
]).then(() => done());
461+
});
462+
463+
it("it should only accept put method requests", (done) => {
464+
const path = "/test/put/method";
465+
466+
Promise.all([
467+
new Promise((resolve, reject) => {
468+
const req = http.request({
469+
url: "http://locahost",
470+
port: 8000,
471+
path,
472+
method: "PUT",
473+
}, res => {
474+
res.statusCode.should.equal(200);
475+
476+
let body = "";
477+
res.on("data", chunk => body += chunk)
478+
.on("end", () => {
479+
body.should.equal("test text");
480+
resolve()
481+
});
482+
});
483+
484+
req.on("error", (err) => {
485+
throw err;
486+
}) .end();
487+
}),
488+
new Promise((resolve, reject) => {
489+
http.get(`${SERVER_URL}${path}`, res => {
490+
res.statusCode.should.equal(405);
491+
492+
let body = "";
493+
res.on("data", chunk => body += chunk)
494+
.on("end", () => {
495+
body.should.equal(`Method not allowed for ${path}`)
496+
resolve() ;
497+
});
498+
});
499+
})
500+
]).then(() => done());
501+
});
502+
503+
it("it should only accept delete method requests", (done) => {
504+
const path = "/test/delete/method";
505+
506+
Promise.all([
507+
new Promise((resolve, reject) => {
508+
const req = http.request({
509+
url: "http://locahost",
510+
port: 8000,
511+
path,
512+
method: "DELETE",
513+
}, res => {
514+
res.statusCode.should.equal(200);
515+
516+
let body = "";
517+
res.on("data", chunk => body += chunk)
518+
.on("end", () => {
519+
body.should.equal("test text");
520+
resolve()
521+
});
522+
});
523+
524+
req.on("error", (err) => {
525+
throw err;
526+
}) .end();
527+
}),
528+
new Promise((resolve, reject) => {
529+
http.get(`${SERVER_URL}${path}`, res => {
530+
res.statusCode.should.equal(405);
531+
532+
let body = "";
533+
res.on("data", chunk => body += chunk)
534+
.on("end", () => {
535+
body.should.equal(`Method not allowed for ${path}`)
536+
resolve() ;
537+
});
538+
});
539+
})
540+
]).then(() => done());
541+
})
542+
});
543+
364544
after(() => {
365545
router.close();
366546
});

0 commit comments

Comments
 (0)