http: cleanup setHeader()
[platform/upstream/nodejs.git] / lib / _http_outgoing.js
1 // Copyright Joyent, Inc. and other Node contributors.
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining a
4 // copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to permit
8 // persons to whom the Software is furnished to do so, subject to the
9 // following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17 // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18 // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20 // USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 var assert = require('assert').ok;
23 var Stream = require('stream');
24 var timers = require('timers');
25 var util = require('util');
26
27 var common = require('_http_common');
28
29 var CRLF = common.CRLF;
30 var chunkExpression = common.chunkExpression;
31 var debug = common.debug;
32
33
34 var connectionExpression = /Connection/i;
35 var transferEncodingExpression = /Transfer-Encoding/i;
36 var closeExpression = /close/i;
37 var contentLengthExpression = /Content-Length/i;
38 var dateExpression = /Date/i;
39 var expectExpression = /Expect/i;
40
41 var automaticHeaders = {
42   connection: true,
43   'content-length': true,
44   'transfer-encoding': true,
45   date: true
46 };
47
48
49 var dateCache;
50 function utcDate() {
51   if (!dateCache) {
52     var d = new Date();
53     dateCache = d.toUTCString();
54     timers.enroll(utcDate, 1000 - d.getMilliseconds());
55     timers._unrefActive(utcDate);
56   }
57   return dateCache;
58 }
59 utcDate._onTimeout = function() {
60   dateCache = undefined;
61 };
62
63
64 function OutgoingMessage() {
65   Stream.call(this);
66
67   this.output = [];
68   this.outputEncodings = [];
69   this.outputCallbacks = [];
70
71   this.writable = true;
72
73   this._last = false;
74   this.chunkedEncoding = false;
75   this.shouldKeepAlive = true;
76   this.useChunkedEncodingByDefault = true;
77   this.sendDate = false;
78   this._removedHeader = {};
79
80   this._hasBody = true;
81   this._trailer = '';
82
83   this.finished = false;
84   this._hangupClose = false;
85   this._headerSent = false;
86
87   this.socket = null;
88   this.connection = null;
89   this._header = null;
90   this._headers = null;
91   this._headerNames = {};
92 }
93 util.inherits(OutgoingMessage, Stream);
94
95
96 exports.OutgoingMessage = OutgoingMessage;
97
98
99 OutgoingMessage.prototype.setTimeout = function(msecs, callback) {
100   if (callback)
101     this.on('timeout', callback);
102   if (!this.socket) {
103     this.once('socket', function(socket) {
104       socket.setTimeout(msecs);
105     });
106   } else
107     this.socket.setTimeout(msecs);
108 };
109
110
111 // It's possible that the socket will be destroyed, and removed from
112 // any messages, before ever calling this.  In that case, just skip
113 // it, since something else is destroying this connection anyway.
114 OutgoingMessage.prototype.destroy = function(error) {
115   if (this.socket)
116     this.socket.destroy(error);
117   else
118     this.once('socket', function(socket) {
119       socket.destroy(error);
120     });
121 };
122
123
124 // This abstract either writing directly to the socket or buffering it.
125 OutgoingMessage.prototype._send = function(data, encoding, callback) {
126   // This is a shameful hack to get the headers and first body chunk onto
127   // the same packet. Future versions of Node are going to take care of
128   // this at a lower level and in a more general way.
129   if (!this._headerSent) {
130     if (util.isString(data) &&
131         encoding !== 'hex' &&
132         encoding !== 'base64') {
133       data = this._header + data;
134     } else {
135       this.output.unshift(this._header);
136       this.outputEncodings.unshift('binary');
137       this.outputCallbacks.unshift(null);
138     }
139     this._headerSent = true;
140   }
141   return this._writeRaw(data, encoding, callback);
142 };
143
144
145 OutgoingMessage.prototype._writeRaw = function(data, encoding, callback) {
146   if (util.isFunction(encoding)) {
147     callback = encoding;
148     encoding = null;
149   }
150
151   if (data.length === 0) {
152     if (util.isFunction(callback))
153       process.nextTick(callback);
154     return true;
155   }
156
157   if (this.connection &&
158       this.connection._httpMessage === this &&
159       this.connection.writable &&
160       !this.connection.destroyed) {
161     // There might be pending data in the this.output buffer.
162     while (this.output.length) {
163       if (!this.connection.writable) {
164         this._buffer(data, encoding, callback);
165         return false;
166       }
167       var c = this.output.shift();
168       var e = this.outputEncodings.shift();
169       var cb = this.outputCallbacks.shift();
170       this.connection.write(c, e, cb);
171     }
172
173     // Directly write to socket.
174     return this.connection.write(data, encoding, callback);
175   } else if (this.connection && this.connection.destroyed) {
176     // The socket was destroyed.  If we're still trying to write to it,
177     // then we haven't gotten the 'close' event yet.
178     return false;
179   } else {
180     // buffer, as long as we're not destroyed.
181     this._buffer(data, encoding, callback);
182     return false;
183   }
184 };
185
186
187 OutgoingMessage.prototype._buffer = function(data, encoding, callback) {
188   this.output.push(data);
189   this.outputEncodings.push(encoding);
190   this.outputCallbacks.push(callback);
191   return false;
192 };
193
194
195 OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
196   // firstLine in the case of request is: 'GET /index.html HTTP/1.1\r\n'
197   // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
198   var state = {
199     sentConnectionHeader: false,
200     sentContentLengthHeader: false,
201     sentTransferEncodingHeader: false,
202     sentDateHeader: false,
203     sentExpect: false,
204     messageHeader: firstLine
205   };
206
207   var field, value;
208
209   if (headers) {
210     var keys = Object.keys(headers);
211     var isArray = util.isArray(headers);
212     var field, value;
213
214     for (var i = 0, l = keys.length; i < l; i++) {
215       var key = keys[i];
216       if (isArray) {
217         field = headers[key][0];
218         value = headers[key][1];
219       } else {
220         field = key;
221         value = headers[key];
222       }
223
224       if (util.isArray(value)) {
225         for (var j = 0; j < value.length; j++) {
226           storeHeader(this, state, field, value[j]);
227         }
228       } else {
229         storeHeader(this, state, field, value);
230       }
231     }
232   }
233
234   // Date header
235   if (this.sendDate === true && state.sentDateHeader === false) {
236     state.messageHeader += 'Date: ' + utcDate() + CRLF;
237   }
238
239   // Force the connection to close when the response is a 204 No Content or
240   // a 304 Not Modified and the user has set a "Transfer-Encoding: chunked"
241   // header.
242   //
243   // RFC 2616 mandates that 204 and 304 responses MUST NOT have a body but
244   // node.js used to send out a zero chunk anyway to accommodate clients
245   // that don't have special handling for those responses.
246   //
247   // It was pointed out that this might confuse reverse proxies to the point
248   // of creating security liabilities, so suppress the zero chunk and force
249   // the connection to close.
250   var statusCode = this.statusCode;
251   if ((statusCode === 204 || statusCode === 304) &&
252       this.chunkedEncoding === true) {
253     debug(statusCode + ' response should not use chunked encoding,' +
254           ' closing connection.');
255     this.chunkedEncoding = false;
256     this.shouldKeepAlive = false;
257   }
258
259   // keep-alive logic
260   if (this._removedHeader.connection) {
261     this._last = true;
262     this.shouldKeepAlive = false;
263   } else if (state.sentConnectionHeader === false) {
264     var shouldSendKeepAlive = this.shouldKeepAlive &&
265         (state.sentContentLengthHeader ||
266          this.useChunkedEncodingByDefault ||
267          this.agent);
268     if (shouldSendKeepAlive) {
269       state.messageHeader += 'Connection: keep-alive\r\n';
270     } else {
271       this._last = true;
272       state.messageHeader += 'Connection: close\r\n';
273     }
274   }
275
276   if (state.sentContentLengthHeader === false &&
277       state.sentTransferEncodingHeader === false) {
278     if (this._hasBody && !this._removedHeader['transfer-encoding']) {
279       if (this.useChunkedEncodingByDefault) {
280         state.messageHeader += 'Transfer-Encoding: chunked\r\n';
281         this.chunkedEncoding = true;
282       } else {
283         this._last = true;
284       }
285     } else {
286       // Make sure we don't end the 0\r\n\r\n at the end of the message.
287       this.chunkedEncoding = false;
288     }
289   }
290
291   this._header = state.messageHeader + CRLF;
292   this._headerSent = false;
293
294   // wait until the first body chunk, or close(), is sent to flush,
295   // UNLESS we're sending Expect: 100-continue.
296   if (state.sentExpect) this._send('');
297 };
298
299 function storeHeader(self, state, field, value) {
300   // Protect against response splitting. The if statement is there to
301   // minimize the performance impact in the common case.
302   if (/[\r\n]/.test(value))
303     value = value.replace(/[\r\n]+[ \t]*/g, '');
304
305   state.messageHeader += field + ': ' + value + CRLF;
306
307   if (connectionExpression.test(field)) {
308     state.sentConnectionHeader = true;
309     if (closeExpression.test(value)) {
310       self._last = true;
311     } else {
312       self.shouldKeepAlive = true;
313     }
314
315   } else if (transferEncodingExpression.test(field)) {
316     state.sentTransferEncodingHeader = true;
317     if (chunkExpression.test(value)) self.chunkedEncoding = true;
318
319   } else if (contentLengthExpression.test(field)) {
320     state.sentContentLengthHeader = true;
321   } else if (dateExpression.test(field)) {
322     state.sentDateHeader = true;
323   } else if (expectExpression.test(field)) {
324     state.sentExpect = true;
325   }
326 }
327
328
329 OutgoingMessage.prototype.setHeader = function(name, value) {
330   if (typeof name !== 'string')
331     throw new TypeError('"name" should be a string');
332   if (value === undefined)
333     throw new Error('"name" and "value" are required for setHeader().');
334   if (this._header)
335     throw new Error('Can\'t set headers after they are sent.');
336
337   if (this._headers === null)
338     this._headers = {};
339
340   var key = name.toLowerCase();
341   this._headers[key] = value;
342   this._headerNames[key] = name;
343
344   if (automaticHeaders[key])
345     this._removedHeader[key] = false;
346 };
347
348
349 OutgoingMessage.prototype.getHeader = function(name) {
350   if (arguments.length < 1) {
351     throw new Error('`name` is required for getHeader().');
352   }
353
354   if (!this._headers) return;
355
356   var key = name.toLowerCase();
357   return this._headers[key];
358 };
359
360
361 OutgoingMessage.prototype.removeHeader = function(name) {
362   if (arguments.length < 1) {
363     throw new Error('`name` is required for removeHeader().');
364   }
365
366   if (this._header) {
367     throw new Error('Can\'t remove headers after they are sent.');
368   }
369
370   var key = name.toLowerCase();
371
372   if (key === 'date')
373     this.sendDate = false;
374   else if (automaticHeaders[key])
375     this._removedHeader[key] = true;
376
377   if (this._headers) {
378     delete this._headers[key];
379     delete this._headerNames[key];
380   }
381 };
382
383
384 OutgoingMessage.prototype._renderHeaders = function() {
385   if (this._header) {
386     throw new Error('Can\'t render headers after they are sent to the client.');
387   }
388
389   if (!this._headers) return {};
390
391   var headers = {};
392   var keys = Object.keys(this._headers);
393
394   for (var i = 0, l = keys.length; i < l; i++) {
395     var key = keys[i];
396     headers[this._headerNames[key]] = this._headers[key];
397   }
398   return headers;
399 };
400
401
402 Object.defineProperty(OutgoingMessage.prototype, 'headersSent', {
403   configurable: true,
404   enumerable: true,
405   get: function() { return !!this._header; }
406 });
407
408
409 OutgoingMessage.prototype.write = function(chunk, encoding, callback) {
410   var self = this;
411
412   if (this.finished) {
413     var err = new Error('write after end');
414     process.nextTick(function() {
415       self.emit('error', err);
416       if (callback) callback(err);
417     });
418
419     return true;
420   }
421
422   if (!this._header) {
423     this._implicitHeader();
424   }
425
426   if (!this._hasBody) {
427     debug('This type of response MUST NOT have a body. ' +
428           'Ignoring write() calls.');
429     return true;
430   }
431
432   if (!util.isString(chunk) && !util.isBuffer(chunk)) {
433     throw new TypeError('first argument must be a string or Buffer');
434   }
435
436
437   // If we get an empty string or buffer, then just do nothing, and
438   // signal the user to keep writing.
439   if (chunk.length === 0) return true;
440
441   var len, ret;
442   if (this.chunkedEncoding) {
443     if (util.isString(chunk) &&
444         encoding !== 'hex' &&
445         encoding !== 'base64' &&
446         encoding !== 'binary') {
447       len = Buffer.byteLength(chunk, encoding);
448       chunk = len.toString(16) + CRLF + chunk + CRLF;
449       ret = this._send(chunk, encoding, callback);
450     } else {
451       // buffer, or a non-toString-friendly encoding
452       if (util.isString(chunk))
453         len = Buffer.byteLength(chunk, encoding);
454       else
455         len = chunk.length;
456
457       if (this.connection && !this.connection.corked) {
458         this.connection.cork();
459         var conn = this.connection;
460         process.nextTick(function connectionCork() {
461           if (conn)
462             conn.uncork();
463         });
464       }
465       this._send(len.toString(16), 'binary', null);
466       this._send(crlf_buf, null, null);
467       this._send(chunk, encoding, null);
468       ret = this._send(crlf_buf, null, callback);
469     }
470   } else {
471     ret = this._send(chunk, encoding, callback);
472   }
473
474   debug('write ret = ' + ret);
475   return ret;
476 };
477
478
479 OutgoingMessage.prototype.addTrailers = function(headers) {
480   this._trailer = '';
481   var keys = Object.keys(headers);
482   var isArray = util.isArray(headers);
483   var field, value;
484   for (var i = 0, l = keys.length; i < l; i++) {
485     var key = keys[i];
486     if (isArray) {
487       field = headers[key][0];
488       value = headers[key][1];
489     } else {
490       field = key;
491       value = headers[key];
492     }
493
494     this._trailer += field + ': ' + value + CRLF;
495   }
496 };
497
498
499 var crlf_buf = new Buffer('\r\n');
500
501
502 OutgoingMessage.prototype.end = function(data, encoding, callback) {
503   if (util.isFunction(data)) {
504     callback = data;
505     data = null;
506   } else if (util.isFunction(encoding)) {
507     callback = encoding;
508     encoding = null;
509   }
510
511   if (data && !util.isString(data) && !util.isBuffer(data)) {
512     throw new TypeError('first argument must be a string or Buffer');
513   }
514
515   if (this.finished) {
516     return false;
517   }
518
519   var self = this;
520   function finish() {
521     self.emit('finish');
522   }
523
524   if (util.isFunction(callback))
525     this.once('finish', callback);
526
527
528   if (!this._header) {
529     this._implicitHeader();
530   }
531
532   if (data && !this._hasBody) {
533     debug('This type of response MUST NOT have a body. ' +
534           'Ignoring data passed to end().');
535     data = null;
536   }
537
538   if (this.connection && data)
539     this.connection.cork();
540
541   var ret;
542   if (data) {
543     // Normal body write.
544     ret = this.write(data, encoding);
545   }
546
547   if (this._hasBody && this.chunkedEncoding) {
548     ret = this._send('0\r\n' + this._trailer + '\r\n', 'binary', finish);
549   } else {
550     // Force a flush, HACK.
551     ret = this._send('', 'binary', finish);
552   }
553
554   if (this.connection && data)
555     this.connection.uncork();
556
557   this.finished = true;
558
559   // There is the first message on the outgoing queue, and we've sent
560   // everything to the socket.
561   debug('outgoing message end.');
562   if (this.output.length === 0 && this.connection._httpMessage === this) {
563     this._finish();
564   }
565
566   return ret;
567 };
568
569
570 OutgoingMessage.prototype._finish = function() {
571   assert(this.connection);
572   this.emit('prefinish');
573 };
574
575
576 // This logic is probably a bit confusing. Let me explain a bit:
577 //
578 // In both HTTP servers and clients it is possible to queue up several
579 // outgoing messages. This is easiest to imagine in the case of a client.
580 // Take the following situation:
581 //
582 //    req1 = client.request('GET', '/');
583 //    req2 = client.request('POST', '/');
584 //
585 // When the user does
586 //
587 //   req2.write('hello world\n');
588 //
589 // it's possible that the first request has not been completely flushed to
590 // the socket yet. Thus the outgoing messages need to be prepared to queue
591 // up data internally before sending it on further to the socket's queue.
592 //
593 // This function, outgoingFlush(), is called by both the Server and Client
594 // to attempt to flush any pending messages out to the socket.
595 OutgoingMessage.prototype._flush = function() {
596   if (this.socket && this.socket.writable) {
597     var ret;
598     while (this.output.length) {
599       var data = this.output.shift();
600       var encoding = this.outputEncodings.shift();
601       var cb = this.outputCallbacks.shift();
602       ret = this.socket.write(data, encoding, cb);
603     }
604
605     if (this.finished) {
606       // This is a queue to the server or client to bring in the next this.
607       this._finish();
608     } else if (ret) {
609       // This is necessary to prevent https from breaking
610       this.emit('drain');
611     }
612   }
613 };
614
615
616 OutgoingMessage.prototype.flush = function() {
617   if (!this._header) {
618     // Force-flush the headers.
619     this._implicitHeader();
620     this._send('');
621   }
622 };