blob: 5863017f2158275b110a9a3ff488d36674a8b271 [file] [log] [blame]
/*
Copyright 2007-2010 WebDriver committers
Copyright 2007-2010 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
goog.provide('Response');
/**
* Encapsulates the information for a HTTP response.
* @param {?Request} request The request this is a response to. May be null if
* this response is to a malformed request that could not be fully parsed.
* @param {nsIOutputStream} outputStream The stream to write responses to.
* @constructor
*/
Response = function(response, outputStream) {
/**
* The request this is a response to.
* @type {?Request}
* @private
*/
this.response_ = response;
/**
* This response's message body.
* @type {string}
* @private
*/
this.body_ = '';
this.committed_ = false;
};
Response.CONTINUE = 100;
Response.OK = 200;
Response.NO_CONTENT = 204;
Response.SEE_OTHER = 303;
Response.BAD_REQUEST = 400;
Response.NOT_FOUND = 404;
Response.METHOD_NOT_ALLOWED = 405;
Response.LENGTH_REQUIRED = 411;
Response.INTERNAL_ERROR = 500;
Response.NOT_IMPLEMENTED = 501;
Response.HTTP_VERSION_NOT_SUPPORTED = 505;
Response.TEXT_PLAIN = 'text/plain';
Response.TEXT_HTML = 'text/html';
Response.APPLICATION_JSON = 'application/json';
/**
* Internal map of supported status messages.
* @enum {string}
*/
Response.StatusMessage_ = {
100: 'Continue',
200: 'OK',
204: 'No Content',
303: 'See Other',
400: 'Bad Request',
404: 'Not Found',
405: 'Method Not Allowed',
411: 'Length Required',
500: 'Internal Server Error',
501: 'Not Implemented',
505: 'HTTP Version Not Supported'
};
/**
* Constant for carriage return line feed.
* @type {string}
* @const
*/
Response.CRLF = '\r\n';
/**
* This response's HTTP stauts code.
* @type {number}
* @private
*/
Response.prototype.status_ = Response.OK;
/**
* Set this response's HTTP status code.
*/
Response.prototype.setStatus = function(status) {
this.status_ = status;
this.response_.setStatusLine(null, status, Response.StatusMessage_[status]);
};
/**
* Sets the value of a header field.
* @param {string} name The name of the header.
* @param {?string} value The header value, or null to delete the field.
*/
Response.prototype.setHeader = function(name, value) {
this.response_.setHeader(name, value, false);
};
/**
* Sets the value of this response's Content-Type header. Responses are always
* sent as UTF-8, so the content type value need not specify a charset.
* @param {string} type The new content type.
*/
Response.prototype.setContentType = function(type) {
this.setHeader('Content-Type', type + '; charset=UTF-8');
};
/**
* @param {string} name The name of the header to get.
* @return {string} The value of the named header if it has been specified.
*/
Response.prototype.getHeader = function(name) {
return this.response_.getHeader(name);
};
/**
* Sets this response's message body, overwriting any previously saved content.
* The message body will be converted to UTF-8 before the response is committed.
* @param {string} body The new message body.
* @see Response.prototype.commit
*/
Response.prototype.setBody = function(body) {
this.body_ = body.toString();
};
/** @return {string} The response message body. */
Response.prototype.getBody = function() {
return this.body_;
};
/**
* Convenience function for sending a 303 redirect.
* @param {string} location The location to redirect to.
*/
Response.prototype.sendRedirect = function(location) {
this.setStatus(Response.SEE_OTHER);
this.setHeader('Location', location);
this.commit();
};
/**
* Convenience function for sending an error message.
* @param {number} code The HTTP status code to use. Must be either a 4xx or
* 5xx value.
* @param {string} opt_message An optional error message to include.
* @param {string} opt_contentType An optional content type to send with the
* error response.
*/
Response.prototype.sendError = function(code, opt_message, opt_contentType) {
this.setStatus(code);
this.setBody(opt_message || '');
if (opt_contentType) {
this.setContentType(opt_contentType);
}
this.commit();
};
Response.prototype.send = function() {
this.commit();
};
/**
* Commits this response. This function is a no-op if the response has already
* been committed.
*/
Response.prototype.commit = function() {
if (this.committed_) {
var info = ['Response already committed'];
info.push('response: ' + this.status_ + ' ' +
Response.StatusMessage_[this.status_]);
info.push(' ' + this.body_);
Components.utils.reportError(info.join('\n '));
return;
}
this.committed_ = true;
var statusCanHaveBody = (this.status_ < 100 || this.status_ > 199) &&
this.status_ != 204 && this.status_ != 304;
var converter = Components.
classes['@mozilla.org/intl/scriptableunicodeconverter'].
createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = 'UTF-8';
var bytes = converter.convertToByteArray(this.body_, {});
if (this.status_ < 100 || this.status_ > 199) {
this.setHeader('Connection', 'close');
}
if (statusCanHaveBody) {
this.setHeader('Content-Length', bytes.length);
}
// If necessary, send the body.
if (statusCanHaveBody) {
var byteStream = converter.convertToInputStream(this.body_);
this.response_.bodyOutputStream.writeFrom(byteStream, bytes.length);
this.response_.bodyOutputStream.flush();
}
this.response_.finish();
};