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 at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// 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 and
13// limitations under the License.
14
15/**
16 * @fileoverview Defines the {@code webdriver.http.Client} for use with
17 * NodeJS.
18 */
19
20var http = require('http'),
21 url = require('url');
22
23var base = require('../_base'),
24 HttpResponse = base.require('webdriver.http.Response');
25
26
27/**
28 * A {@link webdriver.http.Client} implementation using Node's built-in http
29 * 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 * @constructor
34 * @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 }
41
42 /** @private {http.Agent} */
43 this.agent_ = opt_agent;
44
45 /**
46 * Base options for each request.
47 * @private {!Object}
48 */
49 this.options_ = {
50 host: parsedUrl.hostname,
51 path: parsedUrl.pathname,
52 port: parsedUrl.port
53 };
54};
55
56
57/** @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 }
66
67 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 }
73
74 var options = {
75 method: httpRequest.method,
76 host: this.options_.host,
77 port: this.options_.port,
78 path: path,
79 headers: httpRequest.headers
80 };
81 if (this.agent_) {
82 options.agent = this.agent_;
83 }
84 sendRequest(options, callback, data);
85};
86
87
88/**
89 * Sends a single HTTP request.
90 * @param {!Object} options The request options.
91 * @param {function(Error, !webdriver.http.Response=)} callback The function to
92 * 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 }
107
108 if (!location.hostname) {
109 location.hostname = options.host;
110 location.port = options.port;
111 }
112
113 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 }
125
126 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 });
134
135 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 });
148
149 if (opt_data) {
150 request.write(opt_data);
151 }
152
153 request.end();
154};
155
156
157// PUBLIC API
158
159/** @type {webdriver.http.Executor.} */
160exports.Executor = base.require('webdriver.http.Executor');
161
162/** @type {webdriver.http.Request.} */
163exports.Request = base.require('webdriver.http.Request');
164
165/** @type {webdriver.http.Response.} */
166exports.Response = base.require('webdriver.http.Response');
167
168exports.HttpClient = HttpClient;