Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjusting API request to POST if too much data #180

Merged
merged 13 commits into from
Dec 2, 2024
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ webpagetest --help
- **-s, --server** _\<server\>_: the WPT server URL [https://www.webpagetest.org]
- **-d, --dryrun**: just return the RESTful API URL
- **-o, --out** _\<file\>_: place the output into \<file\>. Defaults to stdout
- **--http_method** _\<method\>_: the HTTP method to use (GET, POST) [GET]

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

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

- **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
- **server**: _String_, if specified, overrides the WebPageTest server informed in the constructor only for that method call
- **http_method**: _String_, if specified, overrides the HTTP method in the constructor only for that method call (GET, POST) [GET]

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

Expand Down
2 changes: 2 additions & 0 deletions bin/webpagetest
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ function cleanupProgram() {
delete program.server;
delete program.dryrun;
delete program.out;
delete program.http_method;
}

function execCommand() {
Expand All @@ -277,6 +278,7 @@ function execCommand() {
// options
if (options) {
options.dryRun = program.dryrun;
options.http_method = program.http_method;
args.push(options);
}

Expand Down
18 changes: 16 additions & 2 deletions lib/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ var xml2js = require('xml2js'),
url = require('url'),
os = require('os'),
csv = require('csv'),
entities = require('entities');
entities = require('entities')
qs = require('querystring');

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

Expand Down Expand Up @@ -155,9 +156,22 @@ function scriptToString(data) {
}

// Build the RESTful API url call only
function dryRun(config, path) {
function dryRun(config, path, form) {
path = url.parse(path, true);

if (config.method == "POST") {
return {
url: url.format({
protocol: config.protocol,
hostname: config.hostname,
port: (config.port !== 80 && config.port !== 443 ?
config.port : undefined),
pathname: path.pathname,
}),
form: qs.stringify(form)
};
}

return {
url: url.format({
protocol: config.protocol,
Expand Down
5 changes: 5 additions & 0 deletions lib/mapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ var options = {
bool: true,
info: "just return the RESTful API URL",
},
http_method: {
name: "http_method",
param: "string",
info: "HTTP method to use for submitting the test (GET or POST) [GET]",
}
},
test: {
location: {
Expand Down
51 changes: 40 additions & 11 deletions lib/webpagetest.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ var http = require("http"),
specs = require("./specs"),
helper = require("./helper"),
server = require("./server"),
mapping = require("./mapping");
mapping = require("./mapping"),
qs = require('querystring');

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

var paths = {
testStatus: "testStatus.php",
Expand Down Expand Up @@ -57,8 +59,8 @@ var filenames = {
cached: "_Cached",
};

// GET helper function
function get(config, pathname, proxy, agent, callback, encoding) {
// GET/POST helper function
function get(config, pathname, data, proxy, agent, callback, encoding) {
var protocol, options;

if (proxy) {
Expand All @@ -79,6 +81,7 @@ function get(config, pathname, proxy, agent, callback, encoding) {
headers: {
Host: config.hostname,
},
method: config.method
};
} else {
protocol = config.protocol === "https:" ? https : http;
Expand All @@ -87,10 +90,15 @@ function get(config, pathname, proxy, agent, callback, encoding) {
host: config.hostname,
auth: config.auth,
port: config.port,
method: config.method,
headers: {},
};
}

if (options.method == "POST") {
options.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}

// api key always required
options.headers["X-WPT-API-KEY"] = this.config.key;
options.headers["accept-encoding"] = "gzip,deflate";
Expand All @@ -100,8 +108,8 @@ function get(config, pathname, proxy, agent, callback, encoding) {
options.agent = agent;
}

return protocol
.get(options, function getResponse(res) {
var request = protocol
.request(options, function getResponse(res) {
var data,
length,
statusCode = res.statusCode;
Expand Down Expand Up @@ -158,6 +166,13 @@ function get(config, pathname, proxy, agent, callback, encoding) {
.on("error", function onError(err) {
callback(err);
});

if (options.method == "POST") {
return request.end(qs.stringify(data));
}

return request.end();

}

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

pathname = url.format({
pathname: url.resolve(config.pathname, pathname),
query: query,
});
pathname = url.resolve(config.pathname, pathname);

if (reHTTPmethods.test(options.http_method)) {
config.method = options.http_method;
} else {
config.method = WebPageTest.defaultHTTPMethod;
}


if (config.method == "GET") {
pathname = url.format({
pathname: pathname,
query: query,
});
query = undefined;
}

if (options.dryRun) {
// dry run: return the API url (string) only
if (typeof callback === "function") {
callback.apply(callback, [undefined, helper.dryRun(config, pathname)]);
callback.apply(callback, [undefined, helper.dryRun(config, pathname, query)]);
}
} else {
// make the real API call
get.call(
this,
config,
pathname,
query,
options.proxy,
options.agent,
function apiCallback(err, data, info) {
Expand Down Expand Up @@ -931,6 +959,7 @@ WebPageTest.filenames = filenames;
WebPageTest.defaultServer = "https://www.webpagetest.org";
WebPageTest.defaultListenPort = 7791;
WebPageTest.defaultWaitResultsPort = 8000;
WebPageTest.defaultHTTPMethod = "GET";

// Version
Object.defineProperty(WebPageTest, "version", {
Expand Down
12 changes: 12 additions & 0 deletions test/command-line-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,16 @@ describe('WebPageTest Command Line', function() {
});
});

it('gets a test with long custom metrics script then returns API url and payload with custom metrics data', function (done) {
let script = '"[example]\n\\\\' + 'X'.repeat(6000) + '\nreturn 1;"'

exec(mock('test http://foobar.com --http_method POST --custom ' + script), function (err, data) {
if (err) return done(err);
data = JSON.parse(data);
assert.equal(data.url, wptServer + 'runtest.php');
assert.equal(data.form.length, 6077);
done();
});
});

});
14 changes: 14 additions & 0 deletions test/edge-cases-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,20 @@ describe('Edge Cases of', function() {
});
});

it('gets a test with long custom metrics script then returns API url and payload with custom metrics data', function (done) {
wpt.runTest('http://foobar.com', {
dryRun: true,
mobile: 1,
http_method: 'POST',
custom: '[example]\n\\\\' + 'X'.repeat(10000) + '\nreturn 1;'
}, function (err, data) {
if (err) return done(err);
assert.equal(data.url, wptServer + 'runtest.php');
assert.equal(data.form.length, 10089);
done();
});
});

});

describe('WebPageTest localhost helper', function() {
Expand Down
54 changes: 54 additions & 0 deletions test/fixtures/command-line/help.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Usage: webpagetest [options] [command]

Options:
-V, --version output the version number
-s, --server <server> the WPT server URL
[https://www.webpagetest.org]
-d, --dryrun just return the RESTful API URL
--http_method <string> HTTP method to use for submitting the test
(GET or POST) [GET]
-o, --out <file> place the output into <file>. Defaults to
stdout
-h, --help display help for command

Commands:
status [options] <id> check test status
results [options] <id> get test results
locations [options] list locations and the number of pending
tests
testBalance [options] get remaining tests for the account
testers [options] list testers status and details
test [options] <url_or_script> run test
testAndWait [options] <url_or_script> run test and waits for the result
restart [options] <id> restart test
cancel [options] <id> cancel running/pending test
har <id> get the HTTP Archive (HAR) from test
pagespeed [options] <id> get the Google Page Speed results (if
available) from test
utilization [options] <id> get the CPU, bandwidth and memory
utilization data from test
request [options] <id> get the request data from test
timeline [options] <id> get the Chrome Developer Tools Timeline
data (if available) from test
netlog [options] <id> get the Chrome Developer Tools Net log
data (if available) from test
chrometrace [options] <id> get the Chrome Trace data (if available)
from test
console [options] <id> get the browser console log data (if
available) from test
testinfo <id> get test request info/details
history [days] get history of previously run tests
googlecsi [options] <id> get Google CSI data (Client Side
Instrumentation)
response [options] <id> get response body for text resources
waterfall [options] <id> get the waterfall PNG image
screenshot [options] <id> get the fully loaded page screenshot in
JPG format (PNG if in full resolution)
video [options] <tests> create a video from <tests> (comma
separated test ids)
player <id> get a html5 player for a video <id>
listen [options] [hostname:port] start webpagetest-api proxy server on
<hostname>:<port> [hostname:7791]
batch <file> run commands in batch, i.e. one command
per line from <file> in parallel
help [command] display help for command