forked from restify/node-restify
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathupgrade.js
181 lines (149 loc) · 6.05 KB
/
upgrade.js
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
180
181
// 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
};
// vim: set et ts=8 sts=8 sw=8: