[core] freenow taxi is added

This commit is contained in:
Arsentiy Milchakov 2020-03-05 15:55:15 +03:00 committed by Daria Volvenkova
parent 4ae24bb3e3
commit 99c405f19d
14 changed files with 764 additions and 6 deletions

View file

@ -0,0 +1,191 @@
{
"disabled": {
"countries": [],
"mwms": []
},
"enabled": {
"countries": [
{
"cities": [
"Arezzo",
"Bologna",
"Cagliari",
"Milan",
"Naples",
"Palermo",
"Rome",
"Turin"
],
"id": "Italy"
},
{
"cities": [
"Faro",
"Lisbon",
"Porto"
],
"id": "Portugal"
},
{
"cities": [
"Gdansk",
"Katowice",
"Krakow",
"\u0141\u00f3d\u017a",
"Pozna\u0144",
"Warsaw",
"Wroc\u0142aw"
],
"id": "Poland"
},
{
"cities": [
"Stockholm"
],
"id": "Sweden"
},
{
"cities": [
"Graz",
"Innsbruck",
"Salzburg",
"Vienna"
],
"id": "Austria"
},
{
"cities": [
"Brighton",
"Edinburgh",
"Glasgow",
"London",
"Manchester",
"City of Nottingham",
"Oxford",
"Reading"
],
"id": "Great Britain"
},
{
"cities": [
"Aachen",
"Aschaffenburg",
"Augsburg",
"Berlin",
"B\u00f6blingen",
"Bochum",
"Bonn",
"Bottrop",
"Brunswick",
"Bremen",
"Dachau",
"Darmstadt",
"Dortmund",
"Dresden",
"Dusseldorf",
"Duisburg",
"Ebersberg",
"Emmendingen (Kernstadt)",
"Erding",
"Erfurt",
"Erlangen",
"Essen",
"Esslingen am Neckar",
"Flensburg",
"Frankfurt",
"Freiburg im Breisgau",
"F\u00fcrstenfeldbruck",
"F\u00fcrth",
"Gelsenkirchen",
"Gladbeck",
"G\u00f6ppingen",
"G\u00fctersloh",
"Hagen",
"Halle (Saale)",
"Hamburg",
"Hamm",
"Hanau",
"Hanover",
"Heidelberg",
"Heilbronn",
"Herne",
"Herzogenaurach",
"Hildesheim",
"Ingolstadt",
"Kaiserslautern",
"Karlsruhe",
"Kassel",
"Kiel",
"Koblenz",
"K\u00f6ln",
"Krefeld",
"Landsberg am Lech",
"Leipzig",
"Leverkusen",
"L\u00fcbeck",
"Ludwigsburg",
"Ludwigshafen am Rhein",
"Magdeburg",
"Mainz",
"Mannheim",
"Mettmann",
"M\u00f6nchengladbach",
"Munich",
"M\u00fclheim an der Ruhr",
"M\u00fcnster",
"Neuss",
"Nuremberg",
"Oberhausen",
"Offenbach am Main",
"Osnabr\u00fcck",
"Paderborn",
"Potsdam",
"Rastatt",
"Baden-Baden",
"Regensburg",
"Reutlingen",
"Rosenheim",
"Rostock",
"Saarbr\u00fccken",
"Solingen",
"Starnberg",
"Steinfurt",
"Stuttgart",
"T\u00fcbingen",
"Neu-Ulm",
"Wetzlar",
"Wiesbaden",
"Wolfsburg",
"Wuppertal"
],
"id": "Germany"
},
{
"cities": [
"Cork",
"Dublin",
"Galway",
"Limerick",
"Navan"
],
"id": "Ireland"
},
{
"cities": [
"Zurich"
],
"id": "Switzerland"
},
{
"cities": [
"Barcelona",
"Madrid",
"Seville",
"Valencia"
],
"id": "Spain"
}
],
"mwms": []
}
}

View file

@ -20,6 +20,8 @@ set(
downloader_promo.hpp
facebook_ads.cpp
facebook_ads.hpp
freenow_api.cpp
freenow_api.hpp
google_ads.cpp
google_ads.hpp
locals_api.cpp

View file

@ -0,0 +1,219 @@
#include "partners_api/freenow_api.hpp"
#include "platform/http_client.hpp"
#include "platform/platform.hpp"
#include "platform/preferred_languages.hpp"
#include "coding/url.hpp"
#include "geometry/latlon.hpp"
#include "base/logging.hpp"
#include "std/target_os.hpp"
#include <iomanip>
#include <limits>
#include <sstream>
#include "3party/jansson/myjansson.hpp"
#include "private.h"
namespace
{
auto const kTimeoutSec = 15;
bool CheckResponse(json_t const * answer)
{
if (answer == nullptr)
return false;
if (!json_is_array(answer))
return false;
if (json_array_size(answer) <= 0)
return false;
return true;
}
} // namespace
namespace taxi
{
namespace freenow
{
std::string const kTaxiEndpoint = "https://api.live.free-now.com/publicapigatewayservice/v1";
bool RawApi::GetAccessToken(std::string & result, std::string const & baseUrl /* = kTaxiEndpoint */)
{
platform::HttpClient request(url::Join(baseUrl, "oauth/token"));
request.SetTimeout(kTimeoutSec);
request.SetBodyData("grant_type=client_credentials", "application/x-www-form-urlencoded");
request.SetUserAndPassword(FREENOW_CLIENT_ID, FREENOW_CLIENT_SECRET);
return request.RunHttpRequest(result);
}
bool RawApi::GetServiceTypes(ms::LatLon const & from, ms::LatLon const & to,
std::string const & token, std::string & result,
const std::string & baseUrl /* = kTaxiEndpoint */)
{
std::ostringstream url;
url << std::fixed << std::setprecision(6) << baseUrl << "/service-types?pickupLatitude="
<< from.m_lat << "&pickupLongitude=" << from.m_lon << "&destinationLatitude=" << to.m_lat
<< "&destinationLongitude=" << to.m_lon;
platform::HttpClient request(url.str());
request.SetTimeout(kTimeoutSec);
request.SetRawHeader("Authorization", "Bearer " + token);
request.SetRawHeader("Accept", "application/json");
request.SetRawHeader("Accept-Language", languages::GetCurrentOrig());
return request.RunHttpRequest(result);
}
SafeToken::SafeToken(Token const & token)
: m_token(token)
{
}
void SafeToken::Set(Token const & token)
{
std::lock_guard<std::mutex> lock(m_mutex);
m_token = token;
}
SafeToken::Token SafeToken::Get() const
{
std::lock_guard<std::mutex> lock(m_mutex);
return m_token;
}
/// Requests list of available products from Freenow.
void Api::GetAvailableProducts(ms::LatLon const & from, ms::LatLon const & to,
ProductsCallback const & successFn,
ErrorProviderCallback const & errorFn)
{
ASSERT(successFn, ());
ASSERT(errorFn, ());
// TODO(a): Add ErrorCode::FarDistance and provide this error code.
if (!IsDistanceSupported(from, to))
{
errorFn(ErrorCode::NoProducts);
return;
}
static_assert(std::is_const<decltype(m_baseUrl)>::value, "");
GetPlatform().RunTask(Platform::Thread::Network, [this, from, to, successFn, errorFn]()
{
auto token = m_accessToken.Get();
if (token.m_expiredTime <= std::chrono::steady_clock::now() - std::chrono::seconds(kTimeoutSec))
{
std::string tokenSource;
if (!RawApi::GetAccessToken(tokenSource, m_baseUrl))
{
errorFn(ErrorCode::RemoteError);
return;
}
token = MakeTokenFromJson(tokenSource);
m_accessToken.Set(token);
}
std::string httpResult;
if (!RawApi::GetServiceTypes(from, to, token.m_token, httpResult, m_baseUrl))
{
errorFn(ErrorCode::RemoteError);
return;
}
std::vector<Product> products;
try
{
products = MakeProductsFromJson(httpResult);
}
catch (base::Json::Exception const & e)
{
LOG(LERROR, (e.Msg()));
products.clear();
}
if (products.empty())
errorFn(ErrorCode::NoProducts);
else
successFn(products);
});
}
/// Returns link which allows you to launch the Freenow app.
RideRequestLinks Api::GetRideRequestLinks(std::string const & productId, ms::LatLon const & from,
ms::LatLon const & to) const
{
std::ostringstream deepLink;
deepLink << std::fixed << std::setprecision(6)
<< "mytaxi://de.mytaxi.passenger/order?pickup_coordinate=" << from.m_lat << ","
<< from.m_lon << "&destination_coordinate=" << to.m_lat << "," << to.m_lon;
std::string const universalLink = "https://mytaxi.onelink.me/HySP?pid=maps.me&c=in-app-referral-"
"link_030320_my_pa_in_0_gl_gl_-_mx_mo_co_mx_af_-_ge_-_-_-_-_-"
"&is_retargeting=true&af_dp=mytaxi%3A%2F%2Fde.mytaxi.passenger";
return {deepLink.str(), universalLink};
}
std::vector<taxi::Product> MakeProductsFromJson(std::string const & src)
{
std::vector<taxi::Product> products;
base::Json root(src.c_str());
auto const serviceTypesArray = json_object_get(root.get(), "serviceTypes");
if (!CheckResponse(serviceTypesArray))
return {};
auto const count = json_array_size(serviceTypesArray);
for (size_t i = 0; i < count; ++i)
{
taxi::Product product;
uint64_t time = 0;
uint64_t price = 0;
auto const item = json_array_get(serviceTypesArray, i);
FromJSONObjectOptionalField(item, "id", product.m_productId);
FromJSONObjectOptionalField(item, "displayName", product.m_name);
auto const eta = json_object_get(item, "eta");
FromJSONObjectOptionalField(eta, "value", time);
product.m_time = strings::to_string(time);
auto const fare = json_object_get(item, "fare");
FromJSONObjectOptionalField(fare, "displayValue", product.m_price);
FromJSONObjectOptionalField(fare, "currencyCode", product.m_currency);
if (product.m_name.empty() || product.m_time.empty() || product.m_price.empty())
continue;
products.push_back(std::move(product));
}
return products;
}
SafeToken::Token MakeTokenFromJson(std::string const & src)
{
SafeToken::Token result;
base::Json root(src.c_str());
FromJSONObject(root.get(), "access_token", result.m_token);
uint64_t expiresInSeconds = 0;
FromJSONObject(root.get(), "expires_in", expiresInSeconds);
result.m_expiredTime = std::chrono::steady_clock::now() + std::chrono::seconds(expiresInSeconds);
return result;
}
} // namespace freenow
} // namespace taxi

View file

@ -0,0 +1,70 @@
#pragma once
#include "partners_api/taxi_base.hpp"
#include <mutex>
#include <string>
namespace ms
{
class LatLon;
}
namespace taxi
{
namespace freenow
{
extern std::string const kTaxiEndpoint;
/// "Free now" api wrapper based on synchronous http requests.
class RawApi
{
public:
static bool GetAccessToken(std::string & result, std::string const & url = kTaxiEndpoint);
static bool GetServiceTypes(ms::LatLon const & from, ms::LatLon const & to,
std::string const & token, std::string & result,
std::string const & url = kTaxiEndpoint);
};
class SafeToken
{
public:
struct Token
{
std::string m_token;
std::chrono::steady_clock::time_point m_expiredTime;
};
SafeToken() = default;
void Set(Token const & token);
Token Get() const;
private:
SafeToken(Token const & token);
mutable std::mutex m_mutex;
Token m_token;
};
/// Class which used for making products from http requests results.
class Api : public ApiBase
{
public:
explicit Api(std::string const & baseUrl = kTaxiEndpoint) : ApiBase(baseUrl) {}
// ApiBase overrides:
/// Requests list of available products from "Free now".
void GetAvailableProducts(ms::LatLon const & from, ms::LatLon const & to,
ProductsCallback const & successFn,
ErrorProviderCallback const & errorFn) override;
/// Returns link which allows you to launch the "Free now" app.
RideRequestLinks GetRideRequestLinks(std::string const & productId, ms::LatLon const & from,
ms::LatLon const & to) const override;
private:
SafeToken m_accessToken;
};
SafeToken::Token MakeTokenFromJson(std::string const & src);
std::vector<taxi::Product> MakeProductsFromJson(std::string const & src);
} // namespace freenow
} // namespace taxi

View file

@ -7,8 +7,10 @@ set(
ads_engine_tests.cpp
booking_tests.cpp
facebook_tests.cpp
freenow_tests.cpp
google_tests.cpp
maxim_tests.cpp
# Maxim taxi project is disabled.
# maxim_tests.cpp
# Megafon project is disabled until the contract is renewed or extended.
# megafon_countries_tests.cpp
mopub_tests.cpp
@ -17,7 +19,8 @@ set(
rutaxi_tests.cpp
taxi_engine_tests.cpp
taxi_places_tests.cpp
uber_tests.cpp
# Uber taxi project is disabled.
# uber_tests.cpp
yandex_tests.cpp
)

View file

@ -0,0 +1,150 @@
#include "testing/testing.hpp"
#include "partners_api/freenow_api.hpp"
#include "geometry/latlon.hpp"
#include "platform/platform.hpp"
#include <string>
namespace
{
using Runner = Platform::ThreadRunner;
std::string const kTokenResponse = R"(
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV",
"token_type": "bearer",
"expires_in": 600,
"scope": "service-types"
})";
std::string const kServiceTypesResponse = R"(
{
"serviceTypes": [
{
"id": "TAXI",
"type": "TAXI",
"displayName": "Taxi",
"eta": {
"value": 0,
"displayValue": "0 Minutes"
},
"fare": {
"type": "FIXED",
"value": 5000,
"currencyCode": "GBP",
"displayValue": "5000GBP"
},
"availablePaymentMethodTypes": [
"BUSINESS_ACCOUNT",
"CREDIT_CARD",
"PAYPAL",
"CASH"
],
"seats": {
"max": 4,
"values": [],
"displayValue": "4"
},
"availableBookingOptions": [
{
"name": "COMMENT",
"displayName": "COMMENT",
"type": "TEXT"
},
{
"name": "MERCEDES",
"displayName": "MERCEDES",
"type": "BOOLEAN"
},
{
"name": "FAVORITE_DRIVER",
"displayName": "FAVORITE_DRIVER",
"type": "BOOLEAN"
},
{
"name": "FIVE_STARS",
"displayName": "FIVE_STARS",
"type": "BOOLEAN"
},
{
"name": "SMALL_ANIMAL",
"displayName": "SMALL_ANIMAL",
"type": "BOOLEAN"
}
]
}
]
})";
UNIT_TEST(Freenow_GetAccessToken)
{
ms::LatLon const from(55.796918, 37.537859);
ms::LatLon const to(55.758213, 37.616093);
std::string result;
taxi::freenow::RawApi::GetAccessToken(result);
TEST(!result.empty(), ());
auto const token = taxi::freenow::MakeTokenFromJson(result);
TEST(!token.m_token.empty(), ());
}
UNIT_TEST(Freenow_MakeTokenFromJson)
{
auto const token = taxi::freenow::MakeTokenFromJson(kTokenResponse);
TEST(!token.m_token.empty(), ());
TEST_NOT_EQUAL(token.m_expiredTime.time_since_epoch().count(), 0, ());
}
UNIT_TEST(Freenow_MakeProductsFromJson)
{
auto const products = taxi::freenow::MakeProductsFromJson(kServiceTypesResponse);
TEST_EQUAL(products.size(), 1, ());
TEST_EQUAL(products.back().m_name, "Taxi", ());
TEST_EQUAL(products.back().m_time, "0", ());
TEST_EQUAL(products.back().m_price, "5000GBP", ());
TEST_EQUAL(products.back().m_currency, "GBP", ());
}
UNIT_CLASS_TEST(Runner, Freenow_GetAvailableProducts)
{
taxi::freenow::Api api("http://localhost:34568/partners");
ms::LatLon const from(55.796918, 37.537859);
ms::LatLon const to(55.758213, 37.616093);
std::vector<taxi::Product> resultProducts;
api.GetAvailableProducts(from, to,
[&resultProducts](std::vector<taxi::Product> const & products) {
resultProducts = products;
testing::Notify();
},
[](taxi::ErrorCode const code) {
TEST(false, (code));
});
testing::Wait();
TEST(!resultProducts.empty(), ());
taxi::ErrorCode errorCode = taxi::ErrorCode::RemoteError;
ms::LatLon const farPos(56.838197, 35.908507);
api.GetAvailableProducts(from, farPos,
[](std::vector<taxi::Product> const & products) {
TEST(false, ());
},
[&errorCode](taxi::ErrorCode const code) {
errorCode = code;
testing::Notify();
});
testing::Wait();
TEST_EQUAL(errorCode, taxi::ErrorCode::NoProducts, ());
}
} // namespace

View file

@ -1,5 +1,6 @@
#include "testing/testing.hpp"
#include "partners_api/freenow_api.hpp"
#include "partners_api/maxim_api.hpp"
#include "partners_api/rutaxi_api.hpp"
#include "partners_api/taxi_engine.hpp"
@ -97,6 +98,18 @@ public:
storage::CountryId GetMwmId(m2::PointD const & point) override { return {}; }
};
class IrelandDublinDelegate : public taxi::Delegate
{
public:
storage::CountriesVec GetCountryIds(m2::PointD const & point) override
{
return {"Ireland"};
}
std::string GetCityName(m2::PointD const & point) override { return "Dublin"; }
storage::CountryId GetMwmId(m2::PointD const & point) override { return {}; }
};
std::vector<taxi::Product> GetUberSynchronous(ms::LatLon const & from, ms::LatLon const & to,
std::string const & url)
{
@ -169,6 +182,15 @@ std::vector<taxi::Product> GetRutaxiSynchronous(ms::LatLon const & from, ms::Lat
return rutaxiProducts;
}
std::vector<taxi::Product> GetFreenowSynchronous(ms::LatLon const & from, ms::LatLon const & to,
std::string const & url)
{
std::string freenowAnswer;
TEST(taxi::freenow::RawApi::GetServiceTypes(from, to, "-" /* token */, freenowAnswer, url), ());
return taxi::freenow::MakeProductsFromJson(freenowAnswer);
}
taxi::ProvidersContainer GetProvidersSynchronous(taxi::Engine const & engine,
ms::LatLon const & from, ms::LatLon const & to,
taxi::Delegate & delegate, std::string const & url)
@ -192,6 +214,9 @@ taxi::ProvidersContainer GetProvidersSynchronous(taxi::Engine const & engine,
providers.emplace_back(taxi::Provider::Type::Rutaxi,
GetRutaxiSynchronous(from, to, delegate, url));
break;
case taxi::Provider::Type::Freenow:
providers.emplace_back(taxi::Provider::Type::Freenow, GetFreenowSynchronous(from, to, url));
break;
case taxi::Provider::Type::Count:
LOG(LERROR, ());
break;
@ -445,7 +470,8 @@ UNIT_CLASS_TEST(AsyncGuiThread, TaxiEngine_Smoke)
taxi::Engine engine({{taxi::Provider::Type::Uber, kTesturl},
{taxi::Provider::Type::Yandex, kTesturl},
{taxi::Provider::Type::Maxim, kTesturl},
{taxi::Provider::Type::Rutaxi, kTesturl}});
{taxi::Provider::Type::Rutaxi, kTesturl},
{taxi::Provider::Type::Freenow, kTesturl}});
engine.SetDelegate(std::make_unique<BelarusMinskDelegate>());
BelarusMinskDelegate delegate;
@ -523,5 +549,10 @@ UNIT_TEST(TaxiEngine_GetProvidersAtPos)
engine.SetDelegate(std::make_unique<RussiaKonetsDelegate>());
providers = engine.GetProvidersAtPos(latlon);
TEST(providers.empty(), (providers));
engine.SetDelegate(std::make_unique<IrelandDublinDelegate>());
providers = engine.GetProvidersAtPos(latlon);
TEST_EQUAL(providers.size(), 1, ());
TEST_EQUAL(providers[0], taxi::Provider::Type::Freenow, ());
}
} // namespace

View file

@ -1,4 +1,6 @@
#include "partners_api/taxi_engine.hpp"
#include "partners_api/freenow_api.hpp"
#include "partners_api/maxim_api.hpp"
#include "partners_api/rutaxi_api.hpp"
#include "partners_api/taxi_places_loader.hpp"
@ -118,6 +120,7 @@ Engine::Engine(std::vector<ProviderUrl> urls /* = {} */)
{
AddApi<yandex::Api>(urls, Provider::Type::Yandex);
AddApi<rutaxi::Api>(urls, Provider::Type::Rutaxi);
AddApi<freenow::Api>(urls, Provider::Type::Freenow);
}
void Engine::SetDelegate(std::unique_ptr<Delegate> delegate)

View file

@ -54,6 +54,7 @@ std::string Loader::GetFileNameByProvider(Provider::Type const type)
case Provider::Type::Rutaxi: return "taxi_places/rutaxi.json";
case Provider::Type::Uber: return "taxi_places/uber.json";
case Provider::Type::Yandex: return "taxi_places/yandex.json";
case Provider::Type::Freenow: return "taxi_places/freenow.json";
case Provider::Type::Count: LOG(LERROR, ("Incorrect taxi provider")); return "";
}
UNREACHABLE();

View file

@ -25,6 +25,7 @@ public:
Yandex,
Maxim,
Rutaxi,
Freenow,
Count
};
@ -82,6 +83,7 @@ inline std::string DebugPrint(Provider::Type type)
case Provider::Type::Yandex: return "Yandex";
case Provider::Type::Maxim: return "Maxim";
case Provider::Type::Rutaxi: return "Rutaxi";
case Provider::Type::Freenow: return "Freenow";
case Provider::Type::Count: ASSERT(false, ()); return "";
}
UNREACHABLE();

View file

@ -150,6 +150,8 @@ class ResponseProvider:
"/gallery/v2/search/": self.promo_gallery_city,
"/single/empty/gallery/v2/search/": self.promo_gallery_city_single_empty,
"/single/gallery/v2/search/": self.promo_gallery_city_single,
"/partners/oauth/token": self.freenow_auth_token,
"/partners/service-types": self.freenow_service_types,
}[url]()
except:
return self.test_404()
@ -251,6 +253,12 @@ class ResponseProvider:
def promo_gallery_city_single(self):
return Payload(jsons.PROMO_GALLERY_CITY_SINGLE)
def freenow_auth_token(self):
return Payload(jsons.FREENOW_AUTH_TOKEN)
def freenow_service_types(self):
return Payload(jsons.FREENOW_SERVICE_TYPES)
def kill(self):
logging.debug("Kill called in ResponseProvider")
self.delegate.kill()

View file

@ -607,3 +607,72 @@ PROMO_GALLERY_CITY_SINGLE = """
}
}
"""
FREENOW_AUTH_TOKEN = """
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXV",
"token_type": "bearer",
"expires_in": 600,
"scope": "service-types"
}
"""
FREENOW_SERVICE_TYPES = """
{
"serviceTypes": [
{
"id": "TAXI",
"type": "TAXI",
"displayName": "Taxi",
"eta": {
"value": 0,
"displayValue": "0 Minutes"
},
"fare": {
"type": "FIXED",
"value": 5000,
"currencyCode": "GBP",
"displayValue": "5000GBP"
},
"availablePaymentMethodTypes": [
"BUSINESS_ACCOUNT",
"CREDIT_CARD",
"PAYPAL",
"CASH"
],
"seats": {
"max": 4,
"values": [],
"displayValue": "4"
},
"availableBookingOptions": [
{
"name": "COMMENT",
"displayName": "COMMENT",
"type": "TEXT"
},
{
"name": "MERCEDES",
"displayName": "MERCEDES",
"type": "BOOLEAN"
},
{
"name": "FAVORITE_DRIVER",
"displayName": "FAVORITE_DRIVER",
"type": "BOOLEAN"
},
{
"name": "FIVE_STARS",
"displayName": "FIVE_STARS",
"type": "BOOLEAN"
},
{
"name": "SMALL_ANIMAL",
"displayName": "SMALL_ANIMAL",
"type": "BOOLEAN"
}
]
}
]
}
"""

View file

@ -181,8 +181,13 @@ class PostHandler(BaseHTTPRequestHandler, ResponseProviderMixin):
def do_POST(self):
self.init_vars()
self.server.reset_selfdestruct_timer()
length = int(self.headers.getheader('content-length'))
self.dispatch_response(Payload(self.rfile.read(length)))
headers = self.prepare_headers()
payload = self.response_provider.response_for_url_and_headers(self.path, headers)
if payload.response_code() >= 300:
length = int(self.headers.getheader('content-length'))
self.dispatch_response(Payload(self.rfile.read(length)))
else:
self.dispatch_response(payload)
def do_GET(self):

View file

@ -59,7 +59,11 @@ class MainHandler(tornado.web.RequestHandler, ResponseProviderMixin):
def post(self, param):
self.dispatch_response(Payload(self.request.body))
payload = self.response_provider.response_for_url_and_headers(self.request.uri, self.headers)
if payload.response_code() >= 300:
self.dispatch_response(Payload(self.request.body))
else:
self.dispatch_response(payload)
@staticmethod