diff --git a/.gitignore b/.gitignore index 2952bbdc9..02f698ec0 100755 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ /ui/.module-cache /windows/WebUIWrapper/bin /windows/WebUIWrapper/obj +node_modules diff --git a/controller/SqliteNetworkController.cpp b/controller/SqliteNetworkController.cpp index 9b8b84dac..74ec8fddd 100644 --- a/controller/SqliteNetworkController.cpp +++ b/controller/SqliteNetworkController.cpp @@ -560,12 +560,9 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( std::string &responseContentType) { char json[16384]; - - if (path.empty()) - return 404; Mutex::Lock _l(_lock); - if (path[0] == "network") { + if ((path.size() > 0)&&(path[0] == "network")) { if ((path.size() >= 2)&&(path[1].length() == 16)) { uint64_t nwid = Utils::hexStrToU64(path[1].c_str()); @@ -584,15 +581,15 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( if (sqlite3_step(_sGetMember2) == SQLITE_ROW) { Utils::snprintf(json,sizeof(json), "{\n" - "\tnwid: \"%s\",\n" - "\taddress: \"%s\",\n" - "\tauthorized: %s,\n" - "\tactiveBridge: %s,\n" - "\tlastAt: \"%s\",\n" - "\tlastSeen: %llu,\n" - "\tfirstSeen: %llu,\n" - "\tidentity: \"%s\",\n" - "\tipAssignments: [", + "\t\"nwid\": \"%s\",\n" + "\t\"address\": \"%s\",\n" + "\t\"authorized\": %s,\n" + "\t\"activeBridge\": %s,\n" + "\t\"lastAt\": \"%s\",\n" + "\t\"lastSeen\": %llu,\n" + "\t\"firstSeen\": %llu,\n" + "\t\"identity\": \"%s\",\n" + "\t\"ipAssignments\": [", nwids, addrs, (sqlite3_column_int(_sGetMember2,0) > 0) ? "true" : "false", @@ -651,18 +648,18 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( case NetworkController::NETCONF_QUERY_INTERNAL_SERVER_ERROR: result = "INTERNAL_SERVER_ERROR"; break; default: result = "(unrecognized result code)"; break; } - responseBody.append(",\n\tnetconf: \""); + responseBody.append(",\n\t\"netconf\": \""); responseBody.append(_jsonEscape(netconf.toString().c_str())); - responseBody.append("\",\n\tnetconfResult: \""); + responseBody.append("\",\n\t\"netconfResult\": \""); responseBody.append(result); - responseBody.append("\",\n\tnetconfResultMessage: \""); + responseBody.append("\",\n\t\"netconfResultMessage\": \""); responseBody.append(_jsonEscape(netconf["error"].c_str())); responseBody.append("\""); } else { - responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"invalid member or signing identity\""); + responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"invalid member or signing identity\""); } } catch ( ... ) { - responseBody.append(",\n\tnetconf: \"\",\n\tnetconfResult: \"INTERNAL_SERVER_ERROR\",\n\tnetconfResultMessage: \"unexpected exception\""); + responseBody.append(",\n\t\"netconf\": \"\",\n\t\"netconfResult\": \"INTERNAL_SERVER_ERROR\",\n\t\"netconfResultMessage\": \"unexpected exception\""); } } @@ -679,17 +676,17 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( if (sqlite3_step(_sGetNetworkById) == SQLITE_ROW) { Utils::snprintf(json,sizeof(json), "{\n" - "\tnwid: \"%s\",\n" - "\tname: \"%s\",\n" - "\tprivate: %s,\n" - "\tenableBroadcast: %s,\n" - "\tallowPassiveBridging: %s,\n" - "\tv4AssignMode: \"%s\",\n" - "\tv6AssignMode: \"%s\",\n" - "\tmulticastLimit: %d,\n" - "\tcreationTime: %llu,\n", - "\trevision: %llu,\n" - "\amembers: [", + "\t\"nwid\": \"%s\",\n" + "\t\"name\": \"%s\",\n" + "\t\"private\": %s,\n" + "\t\"enableBroadcast\": %s,\n" + "\t\"allowPassiveBridging\": %s,\n" + "\t\"v4AssignMode\": \"%s\",\n" + "\t\"v6AssignMode\": \"%s\",\n" + "\t\"multicastLimit\": %d,\n" + "\t\"creationTime\": %llu,\n", + "\t\"revision\": %llu,\n" + "\a\"members\": [", nwids, _jsonEscape((const char *)sqlite3_column_text(_sGetNetworkById,0)).c_str(), (sqlite3_column_int(_sGetNetworkById,1) > 0) ? "true" : "false", @@ -713,7 +710,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( responseBody.push_back('"'); firstMember = false; } - responseBody.append("],\n\trelays: ["); + responseBody.append("],\n\t\"relays\": ["); sqlite3_reset(_sGetRelays); sqlite3_bind_text(_sGetRelays,1,nwids,16,SQLITE_STATIC); @@ -721,13 +718,13 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( while (sqlite3_step(_sGetRelays) == SQLITE_ROW) { responseBody.append(firstRelay ? "\n\t\t" : ",\n\t\t"); firstRelay = false; - responseBody.append("{address:\""); + responseBody.append("{\"address\":\""); responseBody.append((const char *)sqlite3_column_text(_sGetRelays,0)); - responseBody.append("\",phyAddress:\""); + responseBody.append("\",\"phyAddress\":\""); responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sGetRelays,1))); responseBody.append("\"}"); } - responseBody.append("],\n\tipAssignmentPools: ["); + responseBody.append("],\n\t\"ipAssignmentPools\": ["); sqlite3_reset(_sGetIpAssignmentPools2); sqlite3_bind_text(_sGetIpAssignmentPools2,1,nwids,16,SQLITE_STATIC); @@ -736,69 +733,69 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( responseBody.append(firstIpAssignmentPool ? "\n\t\t" : ",\n\t\t"); firstIpAssignmentPool = false; InetAddress ipp((const void *)sqlite3_column_blob(_sGetIpAssignmentPools2,0),(sqlite3_column_int(_sGetIpAssignmentPools2,2) == 6) ? 16 : 4,(unsigned int)sqlite3_column_int(_sGetIpAssignmentPools2,1)); - Utils::snprintf(json,sizeof(json),"{network:\"%s\",netmaskBits:%u}", + Utils::snprintf(json,sizeof(json),"{\"network\":\"%s\",\"netmaskBits\":%u}", _jsonEscape(ipp.toIpString()).c_str(), ipp.netmaskBits()); responseBody.append(json); } - responseBody.append("],\n\trules: ["); + responseBody.append("],\n\t\"rules\": ["); sqlite3_reset(_sListRules); sqlite3_bind_text(_sListRules,1,nwids,16,SQLITE_STATIC); bool firstRule = true; while (sqlite3_step(_sListRules) == SQLITE_ROW) { responseBody.append(firstRule ? "\n\t{\n" : ",{\n"); - Utils::snprintf(json,sizeof(json),"\t\truleId: %lld,\n",sqlite3_column_int64(_sListRules,0)); + Utils::snprintf(json,sizeof(json),"\t\t\"ruleId\": %lld,\n",sqlite3_column_int64(_sListRules,0)); responseBody.append(json); if (sqlite3_column_type(_sListRules,1) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tnodeId: \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1)); + Utils::snprintf(json,sizeof(json),"\t\t\"nodeId\": \"%s\",\n",(const char *)sqlite3_column_text(_sListRules,1)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,2) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tvlanId: %d,\n",sqlite3_column_int(_sListRules,2)); + Utils::snprintf(json,sizeof(json),"\t\t\"vlanId\": %d,\n",sqlite3_column_int(_sListRules,2)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,3) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tvlanPcp: %d,\n",sqlite3_column_int(_sListRules,3)); + Utils::snprintf(json,sizeof(json),"\t\t\"vlanPcp\": %d,\n",sqlite3_column_int(_sListRules,3)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,4) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tetherType: %d,\n",sqlite3_column_int(_sListRules,4)); + Utils::snprintf(json,sizeof(json),"\t\t\"etherType\": %d,\n",sqlite3_column_int(_sListRules,4)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,5) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tmacSource: \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,5)).toString().c_str()); + Utils::snprintf(json,sizeof(json),"\t\t\"macSource\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,5)).toString().c_str()); responseBody.append(json); } if (sqlite3_column_type(_sListRules,6) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tmacDest: \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,6)).toString().c_str()); + Utils::snprintf(json,sizeof(json),"\t\t\"macDest\": \"%s\",\n",MAC((const char *)sqlite3_column_text(_sListRules,6)).toString().c_str()); responseBody.append(json); } if (sqlite3_column_type(_sListRules,7) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipSource: \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,7)).c_str()); + Utils::snprintf(json,sizeof(json),"\t\t\"ipSource\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,7)).c_str()); responseBody.append(json); } if (sqlite3_column_type(_sListRules,8) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipDest: \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,8)).c_str()); + Utils::snprintf(json,sizeof(json),"\t\t\"ipDest\": \"%s\",\n",_jsonEscape((const char *)sqlite3_column_text(_sListRules,8)).c_str()); responseBody.append(json); } if (sqlite3_column_type(_sListRules,9) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipTos: %d,\n",sqlite3_column_int(_sListRules,9)); + Utils::snprintf(json,sizeof(json),"\t\t\"ipTos\": %d,\n",sqlite3_column_int(_sListRules,9)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,10) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipProtocol: %d,\n",sqlite3_column_int(_sListRules,10)); + Utils::snprintf(json,sizeof(json),"\t\t\"ipProtocol\": %d,\n",sqlite3_column_int(_sListRules,10)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,11) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipSourcePort: %d,\n",sqlite3_column_int(_sListRules,11)); + Utils::snprintf(json,sizeof(json),"\t\t\"ipSourcePort\": %d,\n",sqlite3_column_int(_sListRules,11)); responseBody.append(json); } if (sqlite3_column_type(_sListRules,12) != SQLITE_NULL) { - Utils::snprintf(json,sizeof(json),"\t\tipDestPort: %d,\n",sqlite3_column_int(_sListRules,12)); + Utils::snprintf(json,sizeof(json),"\t\t\"ipDestPort\": %d,\n",sqlite3_column_int(_sListRules,12)); responseBody.append(json); } - responseBody.append("\t\taction: \""); + responseBody.append("\t\t\"action\": \""); responseBody.append(_jsonEscape((const char *)sqlite3_column_text(_sListRules,13))); responseBody.append("\"\n\t}"); } @@ -828,7 +825,7 @@ unsigned int SqliteNetworkController::handleControlPlaneHttpGET( } else { // GET /controller returns status and API version if controller is supported - Utils::snprintf(json,sizeof(json),"{\n\tcontroller: true,\n\tapiVersion: %d\n\tclock: %llu\n}",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); + Utils::snprintf(json,sizeof(json),"{\n\t\"controller\": true,\n\t\"apiVersion\": %d,\n\t\"clock\": %llu\n}",ZT_NETCONF_CONTROLLER_API_VERSION,(unsigned long long)OSUtils::now()); responseBody = json; responseContentType = "applicaiton/json"; return 200; diff --git a/controller/SqliteNetworkController.hpp b/controller/SqliteNetworkController.hpp index b5c83a491..a58741b5a 100644 --- a/controller/SqliteNetworkController.hpp +++ b/controller/SqliteNetworkController.hpp @@ -40,17 +40,14 @@ #include "../node/NetworkController.hpp" #include "../node/Mutex.hpp" -#include "../service/ControlPlaneSubsystem.hpp" - namespace ZeroTier { -class SqliteNetworkController : public NetworkController,public ControlPlaneSubsystem +class SqliteNetworkController : public NetworkController { public: SqliteNetworkController(const char *dbPath); virtual ~SqliteNetworkController(); - // NetworkController virtual NetworkController::ResultCode doNetworkConfigRequest( const InetAddress &fromAddr, const Identity &signingId, @@ -60,22 +57,21 @@ public: uint64_t haveRevision, Dictionary &netconf); - // ControlPlaneSubsystem - virtual unsigned int handleControlPlaneHttpGET( + unsigned int handleControlPlaneHttpGET( const std::vector &path, const std::map &urlArgs, const std::map &headers, const std::string &body, std::string &responseBody, std::string &responseContentType); - virtual unsigned int handleControlPlaneHttpPOST( + unsigned int handleControlPlaneHttpPOST( const std::vector &path, const std::map &urlArgs, const std::map &headers, const std::string &body, std::string &responseBody, std::string &responseContentType); - virtual unsigned int handleControlPlaneHttpDELETE( + unsigned int handleControlPlaneHttpDELETE( const std::vector &path, const std::map &urlArgs, const std::map &headers, diff --git a/controller/zt1-controller-client/index.js b/controller/zt1-controller-client/index.js new file mode 100644 index 000000000..b1e07e81d --- /dev/null +++ b/controller/zt1-controller-client/index.js @@ -0,0 +1,26 @@ +'use strict' + +var request = require('request'); + +function ZT1ControllerClient(url,authToken) +{ + this.url = url; + this.authToken = authToken; +} + +ZT1ControllerClient.prototype.status = function(callback) +{ + request({ + url: this.url + 'controller', + method: 'GET', + headers: { + 'X-ZT1-Auth': this.authToken + } + },function(error,response,body) { + if ((error)||(response.statusCode !== 200)) + return callback(error,{}); + return callback(null,JSON.parse(body)); + }); +}; + +exports.ZT1ControllerClient = ZT1ControllerClient; diff --git a/controller/zt1-controller-client/package.json b/controller/zt1-controller-client/package.json new file mode 100644 index 000000000..8eda13ad4 --- /dev/null +++ b/controller/zt1-controller-client/package.json @@ -0,0 +1,14 @@ +{ + "name": "zt1-controller-client", + "version": "1.0.0", + "description": "ZeroTier One network controller client for NodeJS", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "ZeroTier, Inc.", + "license": "BSD", + "dependencies": { + "request": "^2.55.0" + } +} diff --git a/controller/zt1-controller-client/test-controller.js b/controller/zt1-controller-client/test-controller.js new file mode 100644 index 000000000..d76bad8f0 --- /dev/null +++ b/controller/zt1-controller-client/test-controller.js @@ -0,0 +1,7 @@ +var ZT1ControllerClient = require('./index.js').ZT1ControllerClient; + +var zt1c = new ZT1ControllerClient('http://127.0.0.1:9993/','5d6181b71fae2684f9cc64ed'); + +zt1c.status(function(err,status) { + console.log(status); +}); \ No newline at end of file diff --git a/service/ControlPlane.cpp b/service/ControlPlane.cpp index 69c5d48d0..1d9f9b4a4 100644 --- a/service/ControlPlane.cpp +++ b/service/ControlPlane.cpp @@ -26,7 +26,6 @@ */ #include "ControlPlane.hpp" -#include "ControlPlaneSubsystem.hpp" #include "OneService.hpp" #include "../version.h" @@ -34,6 +33,8 @@ #include "../ext/http-parser/http_parser.h" +#include "../controller/SqliteNetworkController.hpp" + #include "../node/InetAddress.hpp" #include "../node/Node.hpp" #include "../node/Utils.hpp" @@ -444,7 +445,7 @@ unsigned int ControlPlane::handleRequest( responseContentType = "text/plain"; scode = 200; } else { - std::map::const_iterator ss(_subsystems.find(ps[0])); + std::map::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) scode = ss->second->handleControlPlaneHttpGET(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); else scode = 404; @@ -477,7 +478,7 @@ unsigned int ControlPlane::handleRequest( } else scode = 500; } } else { - std::map::const_iterator ss(_subsystems.find(ps[0])); + std::map::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) scode = ss->second->handleControlPlaneHttpPOST(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); else scode = 404; @@ -509,7 +510,7 @@ unsigned int ControlPlane::handleRequest( _node->freeQueryResult((void *)nws); } else scode = 500; } else { - std::map::const_iterator ss(_subsystems.find(ps[0])); + std::map::const_iterator ss(_subsystems.find(ps[0])); if (ss != _subsystems.end()) scode = ss->second->handleControlPlaneHttpDELETE(std::vector(ps.begin()+1,ps.end()),urlArgs,headers,body,responseBody,responseContentType); else scode = 404; diff --git a/service/ControlPlane.hpp b/service/ControlPlane.hpp index b7b0a8576..fc8a01827 100644 --- a/service/ControlPlane.hpp +++ b/service/ControlPlane.hpp @@ -40,7 +40,7 @@ namespace ZeroTier { class OneService; class Node; -class ControlPlaneSubsystem; +class SqliteNetworkController; struct InetAddress; /** @@ -72,7 +72,7 @@ public: * @param prefix First element in URI path * @param subsys Object to call for results of GET and POST/PUT operations */ - inline void mount(const char *prefix,ControlPlaneSubsystem *subsys) + inline void mount(const char *prefix,SqliteNetworkController *subsys) { Mutex::Lock _l(_lock); _subsystems[std::string(prefix)] = subsys; @@ -104,7 +104,7 @@ private: Node *const _node; std::string _uiStaticPath; std::set _authTokens; - std::map _subsystems; + std::map _subsystems; Mutex _lock; }; diff --git a/service/ControlPlaneSubsystem.hpp b/service/ControlPlaneSubsystem.hpp deleted file mode 100644 index 9de875cac..000000000 --- a/service/ControlPlaneSubsystem.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * -- - * - * ZeroTier may be used and distributed under the terms of the GPLv3, which - * are available at: http://www.gnu.org/licenses/gpl-3.0.html - * - * If you would like to embed ZeroTier into a commercial application or - * redistribute it in a modified binary form, please contact ZeroTier Networks - * LLC. Start here: http://www.zerotier.com/ - */ - -#ifndef ZT_CONTROLPLANESUBSYSTEM_HPP -#define ZT_CONTROLPLANESUBSYSTEM_HPP - -#include -#include -#include - -namespace ZeroTier { - -/** - * Base class for subsystems that can be mounted under the HTTP control plane - * - * Handlers should fill in responseBody and responseContentType and return - * a HTTP status code or 0 on other errors. - */ -class ControlPlaneSubsystem -{ -public: - ControlPlaneSubsystem() {} - virtual ~ControlPlaneSubsystem() {} - - virtual unsigned int handleControlPlaneHttpGET( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) = 0; - - virtual unsigned int handleControlPlaneHttpPOST( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) = 0; - - virtual unsigned int handleControlPlaneHttpDELETE( - const std::vector &path, - const std::map &urlArgs, - const std::map &headers, - const std::string &body, - std::string &responseBody, - std::string &responseContentType) = 0; -}; - -} // namespace ZeroTier - -#endif diff --git a/service/OneService.cpp b/service/OneService.cpp index 99aaf77d2..13108aa1c 100644 --- a/service/OneService.cpp +++ b/service/OneService.cpp @@ -233,7 +233,7 @@ public: _controlPlane = new ControlPlane(this,_node,(_homePath + ZT_PATH_SEPARATOR_S + "ui").c_str()); _controlPlane->addAuthToken(authToken.c_str()); if (_master) - _controlPlane->mount("controller",reinterpret_cast(_master)); + _controlPlane->mount("controller",reinterpret_cast(_master)); { // Remember networks from previous session std::vector networksDotD(OSUtils::listDirectory((_homePath + ZT_PATH_SEPARATOR_S + "networks.d").c_str())); diff --git a/service/README.md b/service/README.md index c347931f8..ecab6e75d 100644 --- a/service/README.md +++ b/service/README.md @@ -11,7 +11,7 @@ The JSON API supports GET, POST/PUT, and DELETE. PUT is treated as a synonym for Values POSTed to the JSON API are *extremely* type sensitive. Things *must* be of the indicated type, otherwise they will be ignored or will generate an error. Anything quoted is a string so booleans and integers must lack quotes. Booleans must be *true* or *false* and nothing else. Integers cannot contain decimal points or they are floats (and vice versa). If something seems to be getting ignored or set to a strange value, or if you receive errors, check the type of all JSON fields you are submitting against the types listed below. Unrecognized fields in JSON objects are also ignored. -API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *authToken* URL parameter (e.g. '?authToken=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication. +API requests must be authenticated via an authentication token. ZeroTier One saves this token in the *authtoken.secret* file in its working directory. This token may be supplied via the *auth* URL parameter (e.g. '?auth=...') or via the *X-ZT1-Auth* HTTP request header. Static UI pages are the only thing the server will allow without authentication. A *jsonp* URL argument may be supplied to request JSONP encapsulation. A JSONP response is sent as a script with its JSON response payload wrapped in a call to the function name supplied as the argument to *jsonp*.