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 a 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 * @constructor
32 * @implements {webdriver.http.Client}
33 */
34var HttpClient = function(serverUrl) {
35 var parsedUrl = url.parse(serverUrl);
36 if (!parsedUrl.hostname) {
37 throw new Error('Invalid server URL: ' + serverUrl);
38 }
39
40 /**
41 * Base options for each request.
42 * @private {!Object}
43 */
44 this.options_ = {
45 host: parsedUrl.hostname,
46 path: parsedUrl.pathname,
47 port: parsedUrl.port
48 };
49};
50
51
52/** @override */
53HttpClient.prototype.send = function(httpRequest, callback) {
54 var data;
55 httpRequest.headers['Content-Length'] = 0;
56 if (httpRequest.method == 'POST' || httpRequest.method == 'PUT') {
57 data = JSON.stringify(httpRequest.data);
58 httpRequest.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
59 httpRequest.headers['Content-Type'] = 'application/json;charset=UTF-8';
60 }
61
62 var path = this.options_.path;
63 if (path[path.length - 1] === '/' && httpRequest.path[0] === '/') {
64 path += httpRequest.path.substring(1);
65 } else {
66 path += httpRequest.path;
67 }
68
69 sendRequest({
70 method: httpRequest.method,
71 host: this.options_.host,
72 port: this.options_.port,
73 path: path,
74 headers: httpRequest.headers
75 }, callback, data);
76};
77
78
79/**
80 * Sends a single HTTP request.
81 * @param {!Object} options The request options.
82 * @param {function(Error, !webdriver.http.Response=)} callback The function to
83 * invoke with the server's response.
84 * @param {string=} opt_data The data to send with the request.
85 */
86var sendRequest = function(options, callback, opt_data) {
87 var request = http.request(options, function(response) {
88 if (response.statusCode == 302 || response.statusCode == 303) {
89 try {
90 var location = url.parse(response.headers['location']);
91 } catch (ex) {
92 callback(Error(
93 'Failed to parse "Location" header for server redirect: ' +
94 ex.message + '\nResponse was: \n' +
95 new HttpResponse(response.statusCode, response.headers, '')));
96 return;
97 }
98
99 if (!location.hostname) {
100 location.hostname = options.host;
101 location.port = options.port;
102 }
103
104 request.abort();
105 sendRequest({
106 method: 'GET',
107 host: location.hostname,
108 path: location.pathname + (location.search || ''),
109 port: location.port,
110 headers: {
111 'Accept': 'application/json; charset=utf-8'
112 }
113 }, callback);
114 return;
115 }
116
117 var body = [];
118 response.on('data', body.push.bind(body));
119 response.on('end', function() {
120 var resp = new HttpResponse(response.statusCode,
121 response.headers, body.join('').replace(/\0/g, ''));
122 callback(null, resp);
123 });
124 });
125
126 request.on('error', function(e) {
127 if (e.code === 'ECONNRESET') {
128 setTimeout(function() {
129 sendRequest(options, callback, opt_data);
130 }, 15);
131 } else {
132 var message = e.message;
133 if (e.code) {
134 message = e.code + ' ' + message;
135 }
136 callback(new Error(message));
137 }
138 });
139
140 if (opt_data) {
141 request.write(opt_data);
142 }
143
144 request.end();
145};
146
147
148// PUBLIC API
149
150/** @type {webdriver.http.Executor.} */
151exports.Executor = base.require('webdriver.http.Executor');
152
153/** @type {webdriver.http.Request.} */
154exports.Request = base.require('webdriver.http.Request');
155
156/** @type {webdriver.http.Response.} */
157exports.Response = base.require('webdriver.http.Response');
158
159exports.HttpClient = HttpClient;