Skip to content

Commit 0415e87

Browse files
authored
Merge pull request #180 from max-ostapenko/gentle-mite
Adjusting API request to POST if too much data
2 parents f6dba94 + 5b475b5 commit 0415e87

File tree

8 files changed

+145
-13
lines changed

8 files changed

+145
-13
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ webpagetest --help
9898
- **-s, --server** _\<server\>_: the WPT server URL [https://www.webpagetest.org]
9999
- **-d, --dryrun**: just return the RESTful API URL
100100
- **-o, --out** _\<file\>_: place the output into \<file\>. Defaults to stdout
101+
- **--http_method** _\<method\>_: the HTTP method to use (GET, POST) [GET]
101102

102103
_The default WPT server can also be specified via environment variable `WEBPAGETEST_SERVER`_
103104

@@ -480,6 +481,7 @@ wpt.runTest(script, (err, data) => {
480481

481482
- **dryRun**: _Boolean_, if `true`, method does not make an actual request to the API Server but rather returns an object with `url` which contains the actual URL to make the GET request to WebPageTest API Server
482483
- **server**: _String_, if specified, overrides the WebPageTest server informed in the constructor only for that method call
484+
- **http_method**: _String_, if specified, overrides the HTTP method in the constructor only for that method call (GET, POST) [GET]
483485

484486
#### Test (works with `runTest` and `runTestAndWait`)
485487

bin/webpagetest

+2
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ function cleanupProgram() {
269269
delete program.server;
270270
delete program.dryrun;
271271
delete program.out;
272+
delete program.http_method;
272273
}
273274

274275
function execCommand() {
@@ -277,6 +278,7 @@ function execCommand() {
277278
// options
278279
if (options) {
279280
options.dryRun = program.dryrun;
281+
options.http_method = program.http_method;
280282
args.push(options);
281283
}
282284

lib/helper.js

+16-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ var xml2js = require('xml2js'),
99
url = require('url'),
1010
os = require('os'),
1111
csv = require('csv'),
12-
entities = require('entities');
12+
entities = require('entities')
13+
qs = require('querystring');
1314

1415
var parser = new xml2js.Parser({explicitArray: false, mergeAttrs: true});
1516

@@ -155,9 +156,22 @@ function scriptToString(data) {
155156
}
156157

157158
// Build the RESTful API url call only
158-
function dryRun(config, path) {
159+
function dryRun(config, path, form) {
159160
path = url.parse(path, true);
160161

162+
if (config.method == "POST") {
163+
return {
164+
url: url.format({
165+
protocol: config.protocol,
166+
hostname: config.hostname,
167+
port: (config.port !== 80 && config.port !== 443 ?
168+
config.port : undefined),
169+
pathname: path.pathname,
170+
}),
171+
form: qs.stringify(form)
172+
};
173+
}
174+
161175
return {
162176
url: url.format({
163177
protocol: config.protocol,

lib/mapping.js

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ var options = {
2121
bool: true,
2222
info: "just return the RESTful API URL",
2323
},
24+
http_method: {
25+
name: "http_method",
26+
param: "string",
27+
info: "HTTP method to use for submitting the test (GET or POST) [GET]",
28+
}
2429
},
2530
test: {
2631
location: {

lib/webpagetest.js

+40-11
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ var http = require("http"),
1313
specs = require("./specs"),
1414
helper = require("./helper"),
1515
server = require("./server"),
16-
mapping = require("./mapping");
16+
mapping = require("./mapping"),
17+
qs = require('querystring');
1718

1819
var reSpace = /\s/,
1920
reConnectivity =
2021
/^(?:Cable|DSL|3GSlow|3G|3GFast|4G|LTE|Edge|2G|Dial|FIOS|Native|custom)$/,
21-
reHTMLOutput = /<h\d[^<]*>([^<]+)<\/h\d>/; // for H3 on cancelTest.php
22+
reHTMLOutput = /<h\d[^<]*>([^<]+)<\/h\d>/, // for H3 on cancelTest.php
23+
reHTTPmethods = /^(?:GET|POST)$/;
2224

2325
var paths = {
2426
testStatus: "testStatus.php",
@@ -57,8 +59,8 @@ var filenames = {
5759
cached: "_Cached",
5860
};
5961

60-
// GET helper function
61-
function get(config, pathname, proxy, agent, callback, encoding) {
62+
// GET/POST helper function
63+
function get(config, pathname, data, proxy, agent, callback, encoding) {
6264
var protocol, options;
6365

6466
if (proxy) {
@@ -79,6 +81,7 @@ function get(config, pathname, proxy, agent, callback, encoding) {
7981
headers: {
8082
Host: config.hostname,
8183
},
84+
method: config.method
8285
};
8386
} else {
8487
protocol = config.protocol === "https:" ? https : http;
@@ -87,10 +90,15 @@ function get(config, pathname, proxy, agent, callback, encoding) {
8790
host: config.hostname,
8891
auth: config.auth,
8992
port: config.port,
93+
method: config.method,
9094
headers: {},
9195
};
9296
}
9397

98+
if (options.method == "POST") {
99+
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
100+
}
101+
94102
// api key always required
95103
options.headers["X-WPT-API-KEY"] = this.config.key;
96104
options.headers["accept-encoding"] = "gzip,deflate";
@@ -100,8 +108,8 @@ function get(config, pathname, proxy, agent, callback, encoding) {
100108
options.agent = agent;
101109
}
102110

103-
return protocol
104-
.get(options, function getResponse(res) {
111+
var request = protocol
112+
.request(options, function getResponse(res) {
105113
var data,
106114
length,
107115
statusCode = res.statusCode;
@@ -158,6 +166,13 @@ function get(config, pathname, proxy, agent, callback, encoding) {
158166
.on("error", function onError(err) {
159167
callback(err);
160168
});
169+
170+
if (options.method == "POST") {
171+
return request.end(qs.stringify(data));
172+
}
173+
174+
return request.end();
175+
161176
}
162177

163178
// execute callback properly normalizing optional args
@@ -185,22 +200,35 @@ function api(pathname, callback, query, options) {
185200
config = this.config;
186201
}
187202

188-
pathname = url.format({
189-
pathname: url.resolve(config.pathname, pathname),
190-
query: query,
191-
});
203+
pathname = url.resolve(config.pathname, pathname);
204+
205+
if (reHTTPmethods.test(options.http_method)) {
206+
config.method = options.http_method;
207+
} else {
208+
config.method = WebPageTest.defaultHTTPMethod;
209+
}
210+
211+
212+
if (config.method == "GET") {
213+
pathname = url.format({
214+
pathname: pathname,
215+
query: query,
216+
});
217+
query = undefined;
218+
}
192219

193220
if (options.dryRun) {
194221
// dry run: return the API url (string) only
195222
if (typeof callback === "function") {
196-
callback.apply(callback, [undefined, helper.dryRun(config, pathname)]);
223+
callback.apply(callback, [undefined, helper.dryRun(config, pathname, query)]);
197224
}
198225
} else {
199226
// make the real API call
200227
get.call(
201228
this,
202229
config,
203230
pathname,
231+
query,
204232
options.proxy,
205233
options.agent,
206234
function apiCallback(err, data, info) {
@@ -931,6 +959,7 @@ WebPageTest.filenames = filenames;
931959
WebPageTest.defaultServer = "https://www.webpagetest.org";
932960
WebPageTest.defaultListenPort = 7791;
933961
WebPageTest.defaultWaitResultsPort = 8000;
962+
WebPageTest.defaultHTTPMethod = "GET";
934963

935964
// Version
936965
Object.defineProperty(WebPageTest, "version", {

test/command-line-test.js

+12
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,16 @@ describe('WebPageTest Command Line', function() {
333333
});
334334
});
335335

336+
it('gets a test with long custom metrics script then returns API url and payload with custom metrics data', function (done) {
337+
let script = '"[example]\n\\\\' + 'X'.repeat(6000) + '\nreturn 1;"'
338+
339+
exec(mock('test http://foobar.com --http_method POST --custom ' + script), function (err, data) {
340+
if (err) return done(err);
341+
data = JSON.parse(data);
342+
assert.equal(data.url, wptServer + 'runtest.php');
343+
assert.equal(data.form.length, 6077);
344+
done();
345+
});
346+
});
347+
336348
});

test/edge-cases-test.js

+14
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ describe('Edge Cases of', function() {
8585
});
8686
});
8787

88+
it('gets a test with long custom metrics script then returns API url and payload with custom metrics data', function (done) {
89+
wpt.runTest('http://foobar.com', {
90+
dryRun: true,
91+
mobile: 1,
92+
http_method: 'POST',
93+
custom: '[example]\n\\\\' + 'X'.repeat(10000) + '\nreturn 1;'
94+
}, function (err, data) {
95+
if (err) return done(err);
96+
assert.equal(data.url, wptServer + 'runtest.php');
97+
assert.equal(data.form.length, 10089);
98+
done();
99+
});
100+
});
101+
88102
});
89103

90104
describe('WebPageTest localhost helper', function() {

test/fixtures/command-line/help.txt

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Usage: webpagetest [options] [command]
2+
3+
Options:
4+
-V, --version output the version number
5+
-s, --server <server> the WPT server URL
6+
[https://www.webpagetest.org]
7+
-d, --dryrun just return the RESTful API URL
8+
--http_method <string> HTTP method to use for submitting the test
9+
(GET or POST) [GET]
10+
-o, --out <file> place the output into <file>. Defaults to
11+
stdout
12+
-h, --help display help for command
13+
14+
Commands:
15+
status [options] <id> check test status
16+
results [options] <id> get test results
17+
locations [options] list locations and the number of pending
18+
tests
19+
testBalance [options] get remaining tests for the account
20+
testers [options] list testers status and details
21+
test [options] <url_or_script> run test
22+
testAndWait [options] <url_or_script> run test and waits for the result
23+
restart [options] <id> restart test
24+
cancel [options] <id> cancel running/pending test
25+
har <id> get the HTTP Archive (HAR) from test
26+
pagespeed [options] <id> get the Google Page Speed results (if
27+
available) from test
28+
utilization [options] <id> get the CPU, bandwidth and memory
29+
utilization data from test
30+
request [options] <id> get the request data from test
31+
timeline [options] <id> get the Chrome Developer Tools Timeline
32+
data (if available) from test
33+
netlog [options] <id> get the Chrome Developer Tools Net log
34+
data (if available) from test
35+
chrometrace [options] <id> get the Chrome Trace data (if available)
36+
from test
37+
console [options] <id> get the browser console log data (if
38+
available) from test
39+
testinfo <id> get test request info/details
40+
history [days] get history of previously run tests
41+
googlecsi [options] <id> get Google CSI data (Client Side
42+
Instrumentation)
43+
response [options] <id> get response body for text resources
44+
waterfall [options] <id> get the waterfall PNG image
45+
screenshot [options] <id> get the fully loaded page screenshot in
46+
JPG format (PNG if in full resolution)
47+
video [options] <tests> create a video from <tests> (comma
48+
separated test ids)
49+
player <id> get a html5 player for a video <id>
50+
listen [options] [hostname:port] start webpagetest-api proxy server on
51+
<hostname>:<port> [hostname:7791]
52+
batch <file> run commands in batch, i.e. one command
53+
per line from <file> in parallel
54+
help [command] display help for command

0 commit comments

Comments
 (0)