« index
Coverage for /Users/yunong/workspace/node-restify/lib/upgrade.js : 88%
179 lines |
158 run |
21 missing |
0 partial |
29 blocks |
16 blocks run |
13 blocks missing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | // Copyright (c) 2013, Joyent, Inc. All rights reserved. var EventEmitter = require('events').EventEmitter; var util = require('util'); var assert = require('assert-plus'); function InvalidUpgradeStateError(msg) { if (Error.captureStackTrace) Error.captureStackTrace(this, InvalidUpgradeStateError); this.message = msg; this.name = 'InvalidUpgradeStateError'; } util.inherits(InvalidUpgradeStateError, Error); // // The Node HTTP Server will, if we handle the 'upgrade' event, swallow any // Request with the 'Connection: upgrade' header set. While doing this it // detaches from the 'data' events on the Socket and passes the socket to // us, so that we may take over handling for the connection. // // Unfortunately, the API does not presently provide a http.ServerResponse // for us to use in the event that we do not wish to upgrade the connection. // This factory method provides a skeletal implementation of a // restify-compatible response that is sufficient to allow the existing // request handling path to work, while allowing us to perform _at most_ one // of either: // // - Return a basic HTTP Response with a provided Status Code and // close the socket. // - Upgrade the connection and stop further processing. // // To determine if an upgrade is requested, a route handler would check for // the 'claimUpgrade' method on the Response. The object this method // returns will have the 'socket' and 'head' Buffer emitted with the // 'upgrade' event by the http.Server. If the upgrade is not possible, such // as when the HTTP head (or a full request) has already been sent by some // other handler, this method will throw. // function createServerUpgradeResponse(req, socket, head) { return (new ServerUpgradeResponse(socket, head)); } function ServerUpgradeResponse(socket, head) { assert.object(socket, 'socket'); assert.buffer(head, 'head'); EventEmitter.call(this); this.sendDate = true; this.statusCode = 400; this._upgrade = { socket: socket, head: head }; this._headWritten = false; this._upgradeClaimed = false; } util.inherits(ServerUpgradeResponse, EventEmitter); function notImplemented(method) { if (!method.throws) { return function () { return (method.returns); }; } else { return function () { throw (new Error('Method ' + method.name + ' is not ' + 'implemented!')); }; } } var NOT_IMPLEMENTED = [ { name: 'writeContinue', throws: true }, { name: 'setHeader', throws: false, returns: null }, { name: 'getHeader', throws: false, returns: null }, { name: 'getHeaders', throws: false, returns: {} }, { name: 'removeHeader', throws: false, returns: null }, { name: 'addTrailer', throws: false, returns: null }, { name: 'cache', throws: false, returns: 'public' }, { name: 'format', throws: true }, { name: 'set', throws: false, returns: null }, { name: 'get', throws: false, returns: null }, { name: 'headers', throws: false, returns: {} }, { name: 'header', throws: false, returns: null }, { name: 'json', throws: false, returns: null }, { name: 'link', throws: false, returns: null } ]; NOT_IMPLEMENTED.forEach(function (method) { ServerUpgradeResponse.prototype[method.name] = notImplemented(method); }); ServerUpgradeResponse.prototype._writeHeadImpl = function _writeHeadImpl(statusCode, reason) { if (this._headWritten) return; this._headWritten = true; if (this._upgradeClaimed) throw new InvalidUpgradeStateError('Upgrade already claimed!'); var head = [ 'HTTP/1.1 ' + statusCode + ' ' + reason, 'Connection: close' ]; if (this.sendDate) head.push('Date: ' + new Date().toUTCString()); this._upgrade.socket.write(head.join('\r\n') + '\r\n'); }; ServerUpgradeResponse.prototype.status = function status(code) { assert.number(code, 'code'); this.statusCode = code; return (code); }; ServerUpgradeResponse.prototype.send = function send(code, body) { if (typeof (code) === 'number') this.statusCode = code; else body = code; if (typeof (body) === 'object') { if (typeof (body.statusCode) === 'number') this.statusCode = body.statusCode; if (typeof (body.message) === 'string') this.statusReason = body.message; } return (this.end()); }; ServerUpgradeResponse.prototype.end = function end() { this._writeHeadImpl(this.statusCode, 'Connection Not Upgraded'); this._upgrade.socket.end('\r\n'); return (true); }; ServerUpgradeResponse.prototype.write = function write() { this._writeHeadImpl(this.statusCode, 'Connection Not Upgraded'); return (true); }; ServerUpgradeResponse.prototype.writeHead = function writeHead(statusCode, reason) { assert.number(statusCode, 'statusCode'); assert.optionalString(reason, 'reason'); this.statusCode = statusCode; if (!reason) reason = 'Connection Not Upgraded'; if (this._headWritten) throw new Error('Head already written!'); return (this._writeHeadImpl(statusCode, reason)); }; ServerUpgradeResponse.prototype.claimUpgrade = function claimUpgrade() { if (this._upgradeClaimed) throw new InvalidUpgradeStateError('Upgrade already claimed!'); if (this._headWritten) throw new InvalidUpgradeStateError('Upgrade already aborted!'); this._upgradeClaimed = true; return (this._upgrade); }; module.exports = { createResponse: createServerUpgradeResponse, InvalidUpgradeStateError: InvalidUpgradeStateError }; |