Skip to content
Browse files

New api

  • Loading branch information...
1 parent d5f6f87 commit c6e97412f3256cc863e54ea6992dc19521758b4c @akaspin committed
Showing with 216 additions and 322 deletions.
  1. +2 −36 README.md
  2. +214 −0 http.js
  3. +0 −286 index.js
View
38 README.md
@@ -1,42 +1,8 @@
# kaph*
-*kaph* is router-independent request handler for node.js.
+*kaph* is loose-coupled set of tools for handle requests under node.js.
*In the Phoenician alphabet letter "kaph" indicates palm.
-## Usage
+## Design
-*kaph* does not interfere in node.js API. All that *kaph* do is two exports for
-simplify response flow control:
-
- var util = require("util");
- var http = require("http");
- var kaph = require("kaph");
-
- function Handler(request, response, context, args) {
- kaph.Handler.call(this, request, response, args);
- this.context = context;
- }
- util.inherits(Handler, kaph.Handler);
-
- Handler.prototype.GET = function(name) {
- if (name == undefined) {
- throw new kaph.HttpError(404, "Not Found, wrong url");
- } else {
- this.end("Handled! " + name + "<br />" +
- "Setting: " + this.context.setting);
- }
- };
-
- var context = {setting: "Some setting"};
-
- http.createServer(function (request, response) {
- var args = [];
- if (request.url != '/') {
- args = [request.url];
- }
-
- (new Handler(request, response, context, args)).execute();
- }).listen(8888);
-
-So what does this mean? *kaph*
View
214 http.js
@@ -0,0 +1,214 @@
+var inherits = require('util').inherits;
+var http = require('http');
+var url = require('url');
+var Buffer = require('buffer').Buffer;
+var crypto = require('crypto');
+
+/**
+ * Handler for HTTP Request
+ * @param {http.ServerRequest} request
+ * @param {http.ServerResponse} response
+ * @param {Array} stack Stack of operations. Each operation has next
+ * interface: {
+ * 'Http method* || DEFAULT': function() { ... },
+ * 'ERROR': {String} function(code, message) { ... }
+ * }
+ * @param {Array} args Optional arguments
+ * @returns {Handler}
+ */
+function HttpHandler(request, response, stack, args) {
+ this.request = request;
+ this.response = response;
+ this.args = args || [];
+
+ this.stack = stack;
+ this.level = -1; // Level in stack of operations
+ this.logger = console;
+ this.headersWritten = false;
+
+ // Initial setup
+ this.encoding = 'utf8';
+ this.statusCode = 200;
+ this.headers = {'Content-Type': "text/html; charset=UTF-8"};
+};
+exports.HttpHandler = HttpHandler;
+
+/**
+ * Executes next operation in stack. In operation selects the current
+ * request method ("GET", "POST" etc.) or "DEFAULT".
+ *
+ * If operation throws error - handles it. To generate a human readable error
+ * message uses method "ERROR" of operation.
+ */
+HttpHandler.prototype.next = function() {
+ this.level++;
+ var op = this.stack[this.level];
+ try {
+ var meth = op[this.request.method] || op['DEFAULT'];
+ meth.apply(this, this.args);
+ } catch (e) {
+ var errGen = op['ERROR'] || ERROR;
+ this._handleError(e, errGen);
+ }
+};
+
+/**
+ * Sets response status code.
+ * @param {Number} code HTTP Status code
+ */
+HttpHandler.prototype.setStatus = function(code) {
+ if (!this.headersWritten && code in http.STATUS_CODES)
+ this.statusCode = code;
+};
+
+/**
+ * Set header.
+ * @param {String} name
+ * @param value
+ */
+HttpHandler.prototype.setHeader = function(name, value) {
+ var value = value.toString();
+ var safe = value.replace(/[\x00-\x1f]/, " ").substring(0, 4000);
+ if (safe != value) throw new HttpError('Unsafe header value ' + value);
+ this.headers[name] = value;
+};
+
+HttpHandler.prototype.write = function(data, encoding) {
+ if (!data || data == null) return; // If no data - do nothing
+
+ if (!this.headersWritten) this._sendHeaders();
+ this.response.write(data, (encoding || this.encoding));
+};
+
+/**
+ *
+ * @param data
+ * @param encoding
+ */
+HttpHandler.prototype.end = function(data, encoding) {
+ if (!this.headersWritten) {
+ // if any data present - add some info in headers:
+ if (!!data && this.statusCode == 200 && this.request.method == 'GET') {
+ // ETag
+ if (!('ETag' in this.headers)) {
+ etag = '"' + crypto.createHash("sha1").
+ update(data).digest("hex") + '"';
+
+ // Check if-none-match
+ var inm = this.request.headers["if-none-match"];
+ if (inm && inm.indexOf(etag) != -1) {
+ // Not modified - just send 304
+ this.response.writeHead(304);
+ this.response.end();
+ return;
+ } else {
+ this.setHeader("ETag", etag);
+ }
+ }
+
+ // Content-Length
+ if (!("Content-Length" in this.headers)) {
+ var l = Buffer.isBuffer(data) ? data.length
+ : Buffer.byteLength(data, encoding || this._encoding);
+ this.setHeader("Content-Length", l);
+ }
+ }
+ this._sendHeaders();
+ }
+ this.response.end(data, encoding || this.encoding);
+};
+
+/**
+ * Sends a redirect to the given (optionally relative) URL.
+ * @param redirectUrl Redirect URL
+ * @param permanent Permanent. Default = false
+ * @throws HttpError
+ */
+HttpHandler.prototype.redirect = function(redirectUrl, permanent) {
+ permanent = permanent || false;
+ if (this.headersWritten) {
+ throw new Error('Cannot redirect after headers have been written');
+ }
+ this.setStatus(permanent ? 301 : 302);
+ redirectUrl = redirectUrl.replace(/[\x00-\x1f]/, "");
+ this.setHeader('Location', url.resolve(this.request.url, redirectUrl));
+ this.end();
+};
+
+/**
+ * Sends all headers to client. If headers already written - send trailers.
+ */
+HttpHandler.prototype._sendHeaders = function() {
+ this.headersWritten = true;
+ this.response.writeHead(this.statusCode, this.headers);
+};
+
+/**
+ * Handle error
+ * @param e error
+ * @param errGen Optional error message generator
+ */
+HttpHandler.prototype._handleError = function(e, errGen) {
+ errGen = errGen || ERROR;
+ var consoleMessage = '';
+ var code = 0;
+ var message = '';
+
+ if (e instanceof HttpError) {
+ consoleMessage = e.code in http.STATUS_CODES ?
+ this._summary() + " " + e : "Bad HTTP status code " + e.code;
+
+ code = e.code;
+ message = e.message;
+ } else {
+ consoleMessage = "Uncaught exception in " + this._summary() + "\n" +
+ (e.stack || e);
+ code = 500;
+ message = (e.stack || e);
+ }
+ this.logger.error(consoleMessage);
+
+ if (!this.headersWritten) {
+ this.statusCode = code;
+ this.end(errGen(code, message));
+ }
+};
+
+/**
+ * Get request summary.
+ * @returns {String}
+ */
+HttpHandler.prototype._summary = function() {
+ return this.request.method + " " + this.request.url;
+};
+
+/**
+ * Default function for genarate error html body.
+ * @param {Number} code
+ * @param {String} message
+ * @returns {String}
+ */
+function ERROR(code, message) {
+ var msg = code + ": " + message;
+ return "<html><title>" + msg + "</title>" +
+ "<body>" + msg + "</body></html>";
+};
+exports.ERROR = ERROR;
+
+/**
+ * HTTP Error
+ * @constructor
+ * @extends Error
+ * @param code Status code
+ * @param reason Reason
+ */
+function HttpError(code, reason) {
+ //Error.call(this);
+ this.name = "HttpError";
+ this.code = code || 500;
+ this.reason = reason || http.STATUS_CODES[this.code] || "Not implemented";
+ this.message = this.code + " " + this.reason;
+ Error.captureStackTrace(this);
+}
+inherits(HttpError, Error);
+exports.HttpError = HttpError;
View
286 index.js
@@ -1,286 +0,0 @@
-/**
- * Kaph. Routerless HTTP handler for node.js
- */
-
-var inherits = require('util').inherits;
-var http = require('http');
-var url = require('url');
-var Buffer = require('buffer').Buffer;
-var crypto = require('crypto');
-
-/**
- * Kaph request handler.
- *
- * @constructor
- *
- * @param request Original request
- * @param response Original clear response
- * @param args Optional Array of arguments
- * @type args Array
- * @returns {Handler}
- */
-function Handler(request, response, args) {
- // request
- this.request = request;
- //response
- this.response = response;
- // arguments
- this.args = args || [];
-
- this._headersWritten = false; // Headers not written
- this.clear(); // clear all
-}
-exports.Handler = Handler;
-
-/**
- * Cleans response.
- */
-Handler.prototype.clear = function() {
- this._headers = {
- "Server": "node.js:" + process.version,
- "Content-Type": "text/html; charset=UTF-8"
- };
- this._statusCode = 200;
- this._encoding = 'utf8';
-};
-
-// Supported methods
-Handler.prototype._supportedMethods =
- ['HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
-
-/**
- * HEAD method. Must be implemented.
- * @throws HttpError
- */
-Handler.prototype.HEAD = function() {
- throw new HttpError(405, "HEAD not implemented");
-};
-
-/**
- * GET method. Must be implemented.
- * @throws HttpError
- */
-Handler.prototype.GET = function() {
- throw new HttpError(405, "GET not implemented");
-};
-
-/**
- * POST method. Must be implemented.
- * @throws HttpError
- */
-Handler.prototype.POST = function() {
- throw new HttpError(405, "POST not implemented");
-};
-
-/**
- * PUT method. Must be implemented.
- * @throws HttpError
- */
-Handler.prototype.PUT = function() {
- throw new HttpError(405, "PUT not implemented");
-};
-
-/**
- * DELETE method. Must be implemented.
- * @throws HttpError
- */
-Handler.prototype.DELETE = function() {
- throw new HttpError(405, "DELETE not implemented");
-};
-
-/**
- * OPTIONS method. Must be implemented.
- * @throws HttpError
- *
- */
-Handler.prototype.OPTIONS = function() {
- throw new HttpError(405, "OPTIONS not implemented");
-};
-
-/**
- * Generate error HTML page.
- * @param code Status code
- * @param message Message
- * @returns {String} Error html
- */
-Handler.prototype.getErrorHtml = function(code, message) {
- var msg = code + ": " + message;
- return "<html><title>" + msg + "</title>" +
- "<body>" + msg + "</body></html>";
-};
-
-/**
- * Sets response status code.
- * @param code Status code
- */
-Handler.prototype.setStatus = function(code) {
- if (code in http.STATUS_CODES) this._statusCode = code;
-};
-
-/**
- * Sets the given response header name and value.
- * @param name Header name
- * @param value Header value
- * @throws HttpError
- */
-Handler.prototype.setHeader = function(name, value) {
- var value = value.toString();
- var safe = value.replace(/[\x00-\x1f]/, " ").substring(0, 4000);
- if (safe != value) throw new Error('Unsafe header value ' + value);
- this._headers[name] = value;
-};
-
-/**
- * Sends all headers to client.
- */
-Handler.prototype.sendHeaders = function() {
- this._headersWritten = true;
- this.response.writeHead(this._statusCode, this._headers);
-};
-
-/**
- * Writes data to client.
- * If headers not sent - sends they first.
- * @param {Buffer||String} Data chunk. If no data - nothing happens.
- * @param {String} encoding
- */
-Handler.prototype.write = function(data, encoding) {
- if (!data || data == null) return; // If no data - do nothing
-
- if (!this._headersWritten) {
- this.sendHeaders();
- }
-// if (data instanceof Object) {
-// this.setHeader("Content-Type", "text/javascript; charset=UTF-8");
-// data = JSON.stringify(data);
-// }
-
- this.response.write(data, (encoding || this._encoding));
-};
-
-/**
- * Ends response.
- * @param data
- * @param encoding
- */
-Handler.prototype.end = function(data, encoding) {
- data = data || "";
- if (!this._headersWritten) {
- // If headers not written yet - add some info in headers:
-
- // ETags
- if (this._statusCode == 200 && this.request.method == 'GET' &&
- !('ETag' in this._headers) && have_openssl) {
- etag = '"' + crypto.createHash("sha1").
- update(data).digest("hex") + '"';
-
- var inm = this.request.headers["if-none-match"];
- if (inm && inm.indexOf(etag) != -1) {
- // Not modified - just send 304
- this.response.writeHead(304);
- this.response.end("");
- return;
- } else {
- this.setHeader("ETag", etag);
- }
-
- // and content length
- if (!("Content-Length" in this._headers)) {
- var l = Buffer.isBuffer(data) ? data.length
- : Buffer.byteLength(data, encoding || this._encoding);
- this.setHeader("Content-Length", l);
- }
- }
- this.sendHeaders();
- }
- this.response.end(data, encoding || this._encoding);
-};
-
-/**
- * Sends a redirect to the given (optionally relative) URL.
- * @param redirectUrl Redirect URL
- * @param permanent Permanent. Default = false
- * @throws HttpError
- */
-Handler.prototype.redirect = function(redirectUrl, permanent) {
- permanent = permanent || false;
- if (this._headersWritten) {
- throw new Error('Cannot redirect after headers have been written');
- }
- this.setStatus(permanent ? 301 : 302);
- redirectUrl = redirectUrl.replace(/[\x00-\x1f]/, "");
- this.setHeader('Location', url.resolve(this.request.url, redirectUrl));
- this.end();
-};
-
-/**
- * Sends the given HTTP error code to the browser.
- * @param code Error code
- * @param message Error Message
- */
-Handler.prototype.sendError = function(code, message) {
- this.clear();
- this.setStatus(code);
- this.end(this.getErrorHtml(code, message));
-};
-
-/**
- * Executes handler. Catches all errors
- */
-Handler.prototype.execute = function() {
- try {
- if (this._supportedMethods.indexOf(this.request.method) == -1)
- throw new HttpError(405);
-
- this[this.request.method].apply(this, this.args);
- } catch (e) {
- this.handleError(e);
- }
-};
-
-/**
- * Handles all exceptions in request.
- * Logs it and send to client if possible.
- * @param e Exception
- */
-Handler.prototype.handleError = function(e) {
- if (e instanceof HttpError) {
- if (e.code in http.STATUS_CODES) {
- console.error(this._summary() + " " + e);
- this.sendError(e.code, e.reason);
- } else {
- console.error("Bad HTTP status code %d", e.code);
- this.sendError(e.code, e.reason);
- }
- } else {
- console.error("Uncaught exception in " + this._summary() + "\n" +
- (e.stack || e));
- this.sendError(500, (e.stack || e));
- }
-};
-
-/**
- * Get request summary.
- * @returns {String}
- */
-Handler.prototype._summary = function() {
- return this.request.method + " " + this.request.url;
-};
-
-/**
- * HTTP Error
- * @constructor
- * @extends Error
- * @param code Status code
- * @param reason Reason
- */
-function HttpError(code, reason) {
- //Error.call(this);
- this.name = "HttpError";
- this.code = code || 500;
- this.reason = reason || http.STATUS_CODES[this.code] || "Not implemented";
- this.message = this.code + " " + this.reason;
- Error.captureStackTrace(this);
-}
-inherits(HttpError, Error);
-exports.HttpError = HttpError;

0 comments on commit c6e9741

Please sign in to comment.
Something went wrong with that request. Please try again.