# http/index.js

    1// Copyright 2011 Software Freedom Conservancy. All Rights Reserved.2//3// Licensed under the Apache License, Version 2.0 (the "License");4// you may not use this file except in compliance with the License.5// You may obtain a copy of the License at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// distributed under the License is distributed on an "AS IS" BASIS,11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.12// See the License for the specific language governing permissions and13// limitations under the License.1415/**16 * @fileoverview Defines a the {@code webdriver.http.Client} for use with17 * NodeJS.18 */1920var http = require('http'),21    url = require('url');2223var base = require('../_base'),24    HttpResponse = base.require('webdriver.http.Response');252627/**28 * A {@link webdriver.http.Client} implementation using Node's built-in http29 * module.30 * @param {string} serverUrl URL for the WebDriver server to send commands to.31 * @param {http.Agent=} opt_agent The agent to use for each request.32 *     Defaults to {@code http.globalAgent}.33 * @constructor34 * @implements {webdriver.http.Client}35 */36var HttpClient = function(serverUrl, opt_agent) {37  var parsedUrl = url.parse(serverUrl);38  if (!parsedUrl.hostname) {39    throw new Error('Invalid server URL: ' + serverUrl);40  }4142  /** @private {http.Agent} */43  this.agent_ = opt_agent;4445  /**46   * Base options for each request.47   * @private {!Object}48   */49  this.options_ = {50    host: parsedUrl.hostname,51    path: parsedUrl.pathname,52    port: parsedUrl.port53  };54};555657/** @override */58HttpClient.prototype.send = function(httpRequest, callback) {59  var data;60  httpRequest.headers['Content-Length'] = 0;61  if (httpRequest.method == 'POST' || httpRequest.method == 'PUT') {62    data = JSON.stringify(httpRequest.data);63    httpRequest.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');64    httpRequest.headers['Content-Type'] = 'application/json;charset=UTF-8';65  }6667  var path = this.options_.path;68  if (path[path.length - 1] === '/' && httpRequest.path[0] === '/') {69    path += httpRequest.path.substring(1);70  } else {71    path += httpRequest.path;72  }7374  var options = {75    method: httpRequest.method,76    host: this.options_.host,77    port: this.options_.port,78    path: path,79    headers: httpRequest.headers80  };81  if (this.agent_) {82    options.agent = this.agent_;83  }84  sendRequest(options, callback, data);85};868788/**89 * Sends a single HTTP request.90 * @param {!Object} options The request options.91 * @param {function(Error, !webdriver.http.Response=)} callback The function to92 *     invoke with the server's response.93 * @param {string=} opt_data The data to send with the request.94 */95var sendRequest = function(options, callback, opt_data) {96  var request = http.request(options, function(response) {97    if (response.statusCode == 302 || response.statusCode == 303) {98      try {99        var location = url.parse(response.headers['location']);100      } catch (ex) {101        callback(Error(102            'Failed to parse "Location" header for server redirect: ' +103            ex.message + '\nResponse was: \n' +104            new HttpResponse(response.statusCode, response.headers, '')));105        return;106      }107108      if (!location.hostname) {109        location.hostname = options.host;110        location.port = options.port;111      }112113      request.abort();114      sendRequest({115        method: 'GET',116        host: location.hostname,117        path: location.pathname + (location.search || ''),118        port: location.port,119        headers: {120          'Accept': 'application/json; charset=utf-8'121        }122      }, callback);123      return;124    }125126    var body = [];127    response.on('data', body.push.bind(body));128    response.on('end', function() {129      var resp = new HttpResponse(response.statusCode,130          response.headers, body.join('').replace(/\0/g, ''));131      callback(null, resp);132    });133  });134135  request.on('error', function(e) {136    if (e.code === 'ECONNRESET') {137      setTimeout(function() {138        sendRequest(options, callback, opt_data);139      }, 15);140    } else {141      var message = e.message;142      if (e.code) {143        message = e.code + ' ' + message;144      }145      callback(new Error(message));146    }147  });148149  if (opt_data) {150    request.write(opt_data);151  }152153  request.end();154};155156157// PUBLIC API158159/** @type {webdriver.http.Executor.} */160exports.Executor = base.require('webdriver.http.Executor');161162/** @type {webdriver.http.Request.} */163exports.Request = base.require('webdriver.http.Request');164165/** @type {webdriver.http.Response.} */166exports.Response = base.require('webdriver.http.Response');167168exports.HttpClient = HttpClient;