Note that when this event is emitted and handled, the `request` event will
not be emitted.
+### Event: 'connect'
+
+`function (request, socket, head) { }`
+
+Emitted each time a client requests a http CONNECT method. If this event isn't
+listened for, then clients requesting a CONNECT method will have their
+connections closed.
+
+* `request` is the arguments for the http request, as it is in the request
+ event.
+* `socket` is the network socket between the server and client.
+* `head` is an instance of Buffer, the first packet of the tunneling stream,
+ this may be empty.
+
+After this event is emitted, the request's socket will not have a `data`
+event listener, meaning you will need to bind to it in order to handle data
+sent to the server on that socket.
+
### Event: 'upgrade'
`function (request, socket, head) { }`
listened for, then clients requesting an upgrade will have their connections
closed.
-* `request` is the arguments for the http request, as it is in the request event.
+* `request` is the arguments for the http request, as it is in the request
+ event.
* `socket` is the network socket between the server and client.
-* `head` is an instance of Buffer, the first packet of the upgraded stream, this may be empty.
+* `head` is an instance of Buffer, the first packet of the upgraded stream,
+ this may be empty.
After this event is emitted, the request's socket will not have a `data`
event listener, meaning you will need to bind to it in order to handle data
Emitted after a socket is assigned to this request.
+### Event: 'connect'
+
+`function (response, socket, head) { }`
+
+Emitted each time a server responds to a request with a CONNECT method. If this
+event isn't being listened for, clients receiving a CONNECT method will have
+their connections closed.
+
+A client server pair that show you how to listen for the `connect` event.
+
+ var http = require('http');
+ var net = require('net');
+ var url = require('url');
+
+ // Create an HTTP tunneling proxy
+ var proxy = http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end('okay');
+ });
+ proxy.on('connect', function(req, cltSocket, head) {
+ // connect to an origin server
+ var srvUrl = url.parse('http://' + req.url);
+ var srvSocket = net.connect(srvUrl.port, srvUrl.hostname, function() {
+ cltSocket.write('HTTP/1.1 200 Connection Established\r\n' +
+ 'Proxy-agent: Node-Proxy\r\n' +
+ '\r\n');
+ srvSocket.write(head);
+ srvSocket.pipe(cltSocket);
+ cltSocket.pipe(srvSocket);
+ });
+ });
+
+ // now that proxy is running
+ proxy.listen(1337, '127.0.0.1', function() {
+
+ // make a request to a tunneling proxy
+ var options = {
+ port: 1337,
+ host: '127.0.0.1',
+ method: 'CONNECT',
+ path: 'www.google.com:80'
+ };
+
+ var req = http.request(options);
+ req.end();
+
+ req.on('connect', function(res, socket, head) {
+ console.log('got connected!');
+
+ // make a request over an HTTP tunnel
+ socket.write('GET / HTTP/1.1\r\n' +
+ 'Host: www.google.com:80\r\n' +
+ 'Connection: close\r\n' +
+ '\r\n');
+ socket.on('data', function(chunk) {
+ console.log(chunk.toString());
+ });
+ socket.on('end', function() {
+ proxy.close();
+ });
+ });
+ });
+
### Event: 'upgrade'
`function (response, socket, head) { }`
event isn't being listened for, clients receiving an upgrade header will have
their connections closed.
-A client server pair that show you how to listen for the `upgrade` event using `http.getAgent`:
+A client server pair that show you how to listen for the `upgrade` event.
var http = require('http');
- var net = require('net');
// Create an HTTP server
var srv = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('okay');
});
- srv.on('upgrade', function(req, socket, upgradeHead) {
+ srv.on('upgrade', function(req, socket, head) {
socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' +
'Upgrade: WebSocket\r\n' +
'Connection: Upgrade\r\n' +
- '\r\n\r\n');
+ '\r\n');
- socket.ondata = function(data, start, end) {
- socket.write(data.toString('utf8', start, end), 'utf8'); // echo back
- };
+ socket.pipe(socket); // echo back
});
// now that server is running
parser.incoming.upgrade = info.upgrade;
- var isHeadResponse = false;
+ var skipBody = false; // response to HEAD or CONNECT
if (!info.upgrade) {
- // For upgraded connections, we'll emit this after parser.execute
+ // For upgraded connections and CONNECT method request,
+ // we'll emit this after parser.execute
// so that we can capture the first part of the new protocol
- isHeadResponse = parser.onIncoming(parser.incoming, info.shouldKeepAlive);
+ skipBody = parser.onIncoming(parser.incoming, info.shouldKeepAlive);
}
- return isHeadResponse;
+ return skipBody;
};
parser.onBody = function(b, start, len) {
new Buffer(options.auth).toString('base64'));
}
- if (method === 'GET' || method === 'HEAD') {
+ if (method === 'GET' || method === 'HEAD' || method === 'CONNECT') {
self.useChunkedEncodingByDefault = false;
} else {
self.useChunkedEncodingByDefault = true;
debug('parse error');
socket.destroy(ret);
} else if (parser.incoming && parser.incoming.upgrade) {
+ // Upgrade or CONNECT
var bytesParsed = ret;
- socket.ondata = null;
- socket.onend = null;
-
var res = parser.incoming;
req.res = res;
+ socket.ondata = null;
+ socket.onend = null;
+ parser.finish();
+ parsers.free(parser);
+
// This is start + byteParsed
- var upgradeHead = d.slice(start + bytesParsed, end);
- if (req.listeners('upgrade').length) {
- // Emit 'upgrade' on the Agent.
- req.upgraded = true;
- req.emit('upgrade', res, socket, upgradeHead);
+ var bodyHead = d.slice(start + bytesParsed, end);
+
+ var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
+ if (req.listeners(eventName).length) {
+ req.upgradeOrConnect = true;
+ req.emit(eventName, res, socket, bodyHead);
socket.emit('agentRemove');
} else {
- // Got upgrade header, but have no handler.
+ // Got Upgrade header or CONNECT method, but have no handler.
socket.destroy();
}
}
}
req.res = res;
+ // Responses to CONNECT request is handled as Upgrade.
+ if (req.method === 'CONNECT') {
+ res.upgrade = true;
+ return true; // skip body
+ }
+
// Responses to HEAD requests are crazy.
// HEAD responses aren't allowed to have an entity-body
// but *can* have a content-length which actually corresponds
return true;
}
- if (req.shouldKeepAlive && res.headers.connection !== 'keep-alive' && !req.upgraded) {
+ if (req.shouldKeepAlive && res.headers.connection !== 'keep-alive' &&
+ !req.upgradeOrConnect) {
// Server MUST respond with Connection:keep-alive for us to enable it.
// If we've been upgraded (via WebSockets) we also shouldn't try to
// keep the connection open.
// abort socket._httpMessage ?
}
+ function serverSocketCloseListener() {
+ debug('server socket close');
+ // unref the parser for easy gc
+ parsers.free(parser);
+
+ abortIncoming();
+ }
+
debug('SERVER new http connection');
httpSocketSetup(socket);
debug('parse error');
socket.destroy(ret);
} else if (parser.incoming && parser.incoming.upgrade) {
+ // Upgrade or CONNECT
var bytesParsed = ret;
+ var req = parser.incoming;
+
socket.ondata = null;
socket.onend = null;
-
- var req = parser.incoming;
+ socket.removeListener('close', serverSocketCloseListener);
+ parser.finish();
+ parsers.free(parser);
// This is start + byteParsed
- var upgradeHead = d.slice(start + bytesParsed, end);
+ var bodyHead = d.slice(start + bytesParsed, end);
- if (self.listeners('upgrade').length) {
- self.emit('upgrade', req, req.socket, upgradeHead);
+ var eventName = req.method === 'CONNECT' ? 'connect' : 'upgrade';
+ if (self.listeners(eventName).length) {
+ self.emit(eventName, req, req.socket, bodyHead);
} else {
- // Got upgrade header, but have no handler.
+ // Got upgrade header or CONNECT method, but have no handler.
socket.destroy();
}
}
}
};
- socket.addListener('close', function() {
- debug('server socket close');
- // unref the parser for easy gc
- parsers.free(parser);
-
- abortIncoming();
- });
+ socket.addListener('close', serverSocketCloseListener);
// The following callback is issued after the headers have been read on a
// new message. In this callback we setup the response object and pass it
--- /dev/null
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+var common = require('../common');
+var assert = require('assert');
+var http = require('http');
+
+var serverGotConnect = false;
+var clientGotConnect = false;
+
+var server = http.createServer(function(req, res) {
+ assert(false);
+});
+server.on('connect', function(req, socket, firstBodyChunk) {
+ assert.equal(req.method, 'CONNECT');
+ assert.equal(req.url, 'google.com:443');
+ common.debug('Server got CONNECT request');
+ serverGotConnect = true;
+
+ socket.write('HTTP/1.1 200 Connection established\r\n\r\n');
+
+ var data = firstBodyChunk.toString();
+ socket.on('data', function(buf) {
+ data += buf.toString();
+ });
+ socket.on('end', function() {
+ socket.end(data);
+ });
+});
+server.listen(common.PORT, function() {
+ var req = http.request({
+ port: common.PORT,
+ method: 'CONNECT',
+ path: 'google.com:443'
+ }, function(res) {
+ assert(false);
+ });
+ req.on('connect', function(res, socket, firstBodyChunk) {
+ common.debug('Client got CONNECT request');
+ clientGotConnect = true;
+
+ var data = firstBodyChunk.toString();
+ socket.on('data', function(buf) {
+ data += buf.toString();
+ });
+ socket.on('end', function() {
+ assert.equal(data, 'HeadBody');
+ server.close();
+ });
+ socket.write('Body');
+ socket.end();
+ });
+
+ // It is legal for the client to send some data intended for the server
+ // before the "200 Connection established" (or any other success or
+ // error code) is received.
+ req.write('Head');
+ req.end();
+});
+
+process.on('exit', function() {
+ assert.ok(serverGotConnect);
+ assert.ok(clientGotConnect);
+
+ // Make sure this request got removed from the pool.
+ var name = 'localhost:' + common.PORT;
+ assert(!http.globalAgent.sockets.hasOwnProperty(name));
+ assert(!http.globalAgent.requests.hasOwnProperty(name));
+});