From bf01bb84b6b96fe2cba529f588e29a9038c0daf6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Mon, 14 Mar 2011 00:57:05 +0900 Subject: [PATCH] Added JSONP support. Callback query parameter is "jsoncallback". --- src/HttpServer.cc | 5 +++ src/HttpServer.h | 2 ++ src/HttpServerBodyCommand.cc | 10 ++++-- src/json.cc | 49 ++++++++++++++++++++++++++++ src/json.h | 62 ++++++++++++++++++++++++------------ test/JsonTest.cc | 31 ++++++++++++++++++ 6 files changed, 135 insertions(+), 24 deletions(-) diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 15016501..31893c95 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -137,6 +137,11 @@ std::string HttpServer::getBody() const return lastBody_.str(); } +const std::string& HttpServer::getMethod() const +{ + return lastRequestHeader_->getMethod(); +} + const std::string& HttpServer::getRequestPath() const { return lastRequestHeader_->getRequestPath(); diff --git a/src/HttpServer.h b/src/HttpServer.h index cdd1e40e..2f8d6329 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -79,6 +79,8 @@ public: std::string getBody() const; + const std::string& getMethod() const; + const std::string& getRequestPath() const; void feedResponse(const std::string& text, const std::string& contentType); diff --git a/src/HttpServerBodyCommand.cc b/src/HttpServerBodyCommand.cc index c60502b1..7577d3eb 100644 --- a/src/HttpServerBodyCommand.cc +++ b/src/HttpServerBodyCommand.cc @@ -213,12 +213,16 @@ bool HttpServerBodyCommand::execute() addHttpServerResponseCommand(); return true; } else if(reqPath == "/jsonrpc") { - // TODO handle query parameter std::string callback; - SharedHandle json; try { - json = json::decode(httpServer_->getBody()); + if(httpServer_->getMethod() == "GET") { + json::JsonGetParam param = json::decodeGetParams(query); + callback = param.callback; + json = json::decode(param.request); + } else { + json = json::decode(httpServer_->getBody()); + } } catch(RecoverableException& e) { A2_LOG_INFO_EX (fmt("CUID#%lld - Failed to parse JSON-RPC request", diff --git a/src/json.cc b/src/json.cc index 05d4e618..f1b84d40 100644 --- a/src/json.cc +++ b/src/json.cc @@ -42,6 +42,7 @@ #include "a2functional.h" #include "util.h" #include "fmt.h" +#include "Base64.h" namespace aria2 { @@ -607,6 +608,54 @@ std::string encode(const SharedHandle& json) return encode(out, json.get()).str(); } +JsonGetParam::JsonGetParam +(const std::string& request, const std::string& callback) + : request(request), callback(callback) +{} + +JsonGetParam +decodeGetParams(const std::string& query) +{ + std::string jsonRequest; + std::string callback; + if(!query.empty() && query[0] == '?') { + std::string method; + std::string id; + std::string params; + std::vector getParams; + util::split(query.substr(1), std::back_inserter(getParams), "&"); + for(std::vector::const_iterator i = + getParams.begin(), eoi = getParams.end(); i != eoi; ++i) { + if(util::startsWith(*i, "method=")) { + method = (*i).substr(7); + } else if(util::startsWith(*i, "id=")) { + id = (*i).substr(3); + } else if(util::startsWith(*i, "params=")) { + params = (*i).substr(7); + } else if(util::startsWith(*i, "jsoncallback=")) { + callback = (*i).substr(13); + } + } + std::string jsonParam = + Base64::decode(util::percentDecode(params)); + if(method.empty() && id.empty()) { + // Assume batch call. + jsonRequest = jsonParam; + } else { + jsonRequest = '{'; + if(!method.empty()) { + strappend(jsonRequest, "\"method\":\"", method, "\","); + } + if(!id.empty()) { + strappend(jsonRequest, "\"id\":\"", id, "\","); + } + strappend(jsonRequest, "\"params\":", jsonParam); + jsonRequest += '}'; + } + } + return JsonGetParam(jsonRequest, callback); +} + } // namespace json } // namespace aria2 diff --git a/src/json.h b/src/json.h index 186f3a4c..97d00e94 100644 --- a/src/json.h +++ b/src/json.h @@ -58,9 +58,9 @@ OutputStream& encode(OutputStream& out, const ValueBase* vlb) { const std::string& s = string.s(); std::string t = jsonEscape(s); - out_ << '"'; + out_ << "\""; out_.write(t.data(), t.size()); - out_ << '"'; + out_ << "\""; } virtual void visit(const Integer& integer) @@ -80,40 +80,40 @@ OutputStream& encode(OutputStream& out, const ValueBase* vlb) virtual void visit(const List& list) { - out_ << '['; + out_ << "["; List::ValueType::const_iterator i = list.begin(); if(!list.empty()) { (*i)->accept(*this); + ++i; + for(List::ValueType::const_iterator eoi = list.end(); i != eoi; ++i){ + out_ << ","; + (*i)->accept(*this); + } } - ++i; - for(List::ValueType::const_iterator eoi = list.end(); i != eoi; ++i){ - out_ << ','; - (*i)->accept(*this); - } - out_ << ']'; + out_ << "]"; } virtual void visit(const Dict& dict) { - out_ << '{'; + out_ << "{"; Dict::ValueType::const_iterator i = dict.begin(); if(!dict.empty()) { std::string key = jsonEscape((*i).first); - out_ << '"'; + out_ << "\""; out_.write(key.data(), key.size()); out_ << "\":"; (*i).second->accept(*this); + ++i; + for(Dict::ValueType::const_iterator eoi = dict.end(); i != eoi; ++i){ + out_ << ","; + std::string key = jsonEscape((*i).first); + out_ << "\""; + out_.write(key.data(), key.size()); + out_ << "\":"; + (*i).second->accept(*this); + } } - ++i; - for(Dict::ValueType::const_iterator eoi = dict.end(); i != eoi; ++i){ - out_ << ','; - std::string key = jsonEscape((*i).first); - out_ << '"'; - out_.write(key.data(), key.size()); - out_ << "\":"; - (*i).second->accept(*this); - } - out_ << '}'; + out_ << "}"; } private: OutputStream& out_; @@ -133,6 +133,26 @@ OutputStream& encode(OutputStream& out, const SharedHandle& vlb) std::string encode(const ValueBase* json); std::string encode(const SharedHandle& json); +struct JsonGetParam { + std::string request; + std::string callback; + JsonGetParam(const std::string& request, const std::string& callback); +}; + +// Decodes JSON-RPC request from GET query parameter query. query must +// starts with "?". This function identifies method name, id, +// parameters and jsonp callback. For method name, it searches +// "method" query parameter. For id, it searches "id" query +// parameter. The id is always treated as string. For parameters, it +// searches "params" query parameter. The params is Base64 encoded +// JSON string normally associated "params" key in POST request. For +// jsonp callback, it searches "jsoncallback". For example, calling +// remote method, sum([1,2,3]) with id=300 looks like this: +// ?method=sum&id=300¶ms=WzEsMiwzXQ%3D%3D +// +// If both method and id are missing, params is treated as batch call. +JsonGetParam decodeGetParams(const std::string& query); + } // namespace json } // namespace aria2 diff --git a/test/JsonTest.cc b/test/JsonTest.cc index 856b4ea2..3f18bd2a 100644 --- a/test/JsonTest.cc +++ b/test/JsonTest.cc @@ -5,6 +5,7 @@ #include "RecoverableException.h" #include "util.h" #include "array_fun.h" +#include "Base64.h" namespace aria2 { @@ -14,6 +15,7 @@ class JsonTest:public CppUnit::TestFixture { CPPUNIT_TEST(testDecode); CPPUNIT_TEST(testDecode_error); CPPUNIT_TEST(testEncode); + CPPUNIT_TEST(testDecodeGetParams); CPPUNIT_TEST_SUITE_END(); private: @@ -21,6 +23,7 @@ public: void testDecode(); void testDecode_error(); void testEncode(); + void testDecodeGetParams(); }; CPPUNIT_TEST_SUITE_REGISTRATION( JsonTest ); @@ -443,4 +446,32 @@ void JsonTest::testEncode() } } +void JsonTest::testDecodeGetParams() +{ + { + std::string param = util::percentEncode(Base64::encode("[1,2,3]")); + std::string query = "?params="; + query += param; + query += '&'; + query += "method=sum&"; + query += "id=300&"; + query += "jsoncallback=cb"; + json::JsonGetParam gparam = json::decodeGetParams(query); + CPPUNIT_ASSERT_EQUAL(std::string("{\"method\":\"sum\"," + "\"id\":\"300\"," + "\"params\":[1,2,3]}"), + gparam.request); + CPPUNIT_ASSERT_EQUAL(std::string("cb"), gparam.callback); + } + { + std::string query = "?params="; + query += util::percentEncode(Base64::encode("[{}]")); + query += '&'; + query += "jsoncallback=cb"; + json::JsonGetParam gparam = json::decodeGetParams(query); + CPPUNIT_ASSERT_EQUAL(std::string("[{}]"), gparam.request); + CPPUNIT_ASSERT_EQUAL(std::string("cb"), gparam.callback); + } +} + } // namespace aria2