Commit 48a0e323 authored by gejun's avatar gejun

patch svn r34953

Change-Id: I5cf6ae18d3cf0826da2d5d4efa2939e0d6e7d6b7
parent 4be6565c
...@@ -390,7 +390,8 @@ void SerializeHttpRequest(base::IOBuf* /*not used*/, ...@@ -390,7 +390,8 @@ void SerializeHttpRequest(base::IOBuf* /*not used*/,
// Serialize content as json // Serialize content as json
std::string err; std::string err;
json2pb::Pb2JsonOptions opt; json2pb::Pb2JsonOptions opt;
opt.enum_option = (FLAGS_pb_enum_as_number ? json2pb::OUTPUT_ENUM_BY_NUMBER opt.enum_option = (FLAGS_pb_enum_as_number
? json2pb::OUTPUT_ENUM_BY_NUMBER
: json2pb::OUTPUT_ENUM_BY_NAME); : json2pb::OUTPUT_ENUM_BY_NAME);
if (!json2pb::ProtoMessageToJson(*request, &wrapper, opt, &err)) { if (!json2pb::ProtoMessageToJson(*request, &wrapper, opt, &err)) {
cntl->request_attachment().clear(); cntl->request_attachment().clear();
...@@ -574,49 +575,43 @@ static void SendHttpResponse(Controller *cntl, ...@@ -574,49 +575,43 @@ static void SendHttpResponse(Controller *cntl,
// Notice: Not check res->IsInitialized() which should be checked in the // Notice: Not check res->IsInitialized() which should be checked in the
// conversion function. // conversion function.
if (res != NULL && if (res != NULL &&
cntl->response_attachment().empty() &&
// ^ user did not fill the body yet.
res->GetDescriptor()->field_count() > 0 && res->GetDescriptor()->field_count() > 0 &&
// ^ a pb service must have fields in response. // ^ a pb service
!cntl->Failed()) { !cntl->Failed()) {
// ^ pb response in failed RPC is undefined, no need to convert. // ^ pb response in failed RPC is undefined, no need to convert.
if (!cntl->response_attachment().empty()) { base::IOBufAsZeroCopyOutputStream wrapper(&cntl->response_attachment());
if (res->ByteSize() != 0) { // fields in `res' were set. const std::string* content_type_str = &res_header->content_type();
LOG(ERROR) << "Service on " << req_header->uri().path() if (content_type_str->empty()) {
<< " sets both response_attachment and response(pb)" content_type_str = &req_header->content_type();
", you can set only one of them."; }
} // else no fields in `res' were set, user is intended to fill const HttpContentType content_type = ParseContentType(*content_type_str);
// the http body by him/herself. if (content_type == HTTP_CONTENT_PROTO) {
} else { if (res->SerializeToZeroCopyStream(&wrapper)) {
base::IOBufAsZeroCopyOutputStream wrapper(&cntl->response_attachment()); // Set content-type if user did not
const std::string* content_type_str = &res_header->content_type(); if (res_header->content_type().empty()) {
if (content_type_str->empty()) { res_header->set_content_type(common->CONTENT_TYPE_PROTO);
content_type_str = &req_header->content_type();
}
const HttpContentType content_type = ParseContentType(*content_type_str);
if (content_type == HTTP_CONTENT_PROTO) {
if (res->SerializeToZeroCopyStream(&wrapper)) {
// Set content-type if user did not
if (res_header->content_type().empty()) {
res_header->set_content_type(common->CONTENT_TYPE_PROTO);
}
} else {
cntl->SetFailed(ERESPONSE, "Fail to serialize %s",
res->GetTypeName().c_str());
} }
} else { } else {
std::string err; cntl->SetFailed(ERESPONSE, "Fail to serialize %s",
json2pb::Pb2JsonOptions opt; res->GetTypeName().c_str());
opt.enum_option = (FLAGS_pb_enum_as_number ? json2pb::OUTPUT_ENUM_BY_NUMBER }
: json2pb::OUTPUT_ENUM_BY_NAME); } else {
if (json2pb::ProtoMessageToJson(*res, &wrapper, opt, &err)) { std::string err;
// Set content-type if user did not json2pb::Pb2JsonOptions opt;
if (res_header->content_type().empty()) { opt.enum_option = (FLAGS_pb_enum_as_number
res_header->set_content_type(common->CONTENT_TYPE_JSON); ? json2pb::OUTPUT_ENUM_BY_NUMBER
} : json2pb::OUTPUT_ENUM_BY_NAME);
} else { if (json2pb::ProtoMessageToJson(*res, &wrapper, opt, &err)) {
cntl->SetFailed(ERESPONSE, "Fail to convert response to json, %s", // Set content-type if user did not
err.c_str()); if (res_header->content_type().empty()) {
res_header->set_content_type(common->CONTENT_TYPE_JSON);
} }
} else {
cntl->SetFailed(ERESPONSE, "Fail to convert response to json, %s",
err.c_str());
} }
} }
} }
...@@ -1220,7 +1215,8 @@ void ProcessHttpRequest(InputMessageBase *msg) { ...@@ -1220,7 +1215,8 @@ void ProcessHttpRequest(InputMessageBase *msg) {
cntl->SetFailed("Fail to new req or res"); cntl->SetFailed("Fail to new req or res");
return SendHttpResponse(cntl.release(), server, method_status); return SendHttpResponse(cntl.release(), server, method_status);
} }
if (method->input_type()->field_count() > 0) { if (sp->allow_http_body_to_pb &&
method->input_type()->field_count() > 0) {
// A protobuf service. No matter if Content-type is set to // A protobuf service. No matter if Content-type is set to
// applcation/json or body is empty, we have to treat body as a json // applcation/json or body is empty, we have to treat body as a json
// and try to convert it to pb, which guarantees that a protobuf // and try to convert it to pb, which guarantees that a protobuf
......
...@@ -247,6 +247,7 @@ RestfulMap::~RestfulMap() { ...@@ -247,6 +247,7 @@ RestfulMap::~RestfulMap() {
bool RestfulMap::AddMethod(const RestfulMethodPath& path, bool RestfulMap::AddMethod(const RestfulMethodPath& path,
google::protobuf::Service* service, google::protobuf::Service* service,
bool is_tabbed, bool is_tabbed,
bool allow_http_body_to_pb,
const std::string& method_name, const std::string& method_name,
MethodStatus* status) { MethodStatus* status) {
if (service == NULL) { if (service == NULL) {
...@@ -278,6 +279,7 @@ bool RestfulMap::AddMethod(const RestfulMethodPath& path, ...@@ -278,6 +279,7 @@ bool RestfulMap::AddMethod(const RestfulMethodPath& path,
info.is_builtin_service = false; info.is_builtin_service = false;
info.own_method_status = false; info.own_method_status = false;
info.is_tabbed = is_tabbed; info.is_tabbed = is_tabbed;
info.allow_http_body_to_pb = allow_http_body_to_pb;
info.service = service; info.service = service;
info.method = md; info.method = md;
info.status = status; info.status = status;
......
...@@ -61,6 +61,7 @@ public: ...@@ -61,6 +61,7 @@ public:
bool AddMethod(const RestfulMethodPath& path, bool AddMethod(const RestfulMethodPath& path,
google::protobuf::Service* service, google::protobuf::Service* service,
bool is_tabbed, bool is_tabbed,
bool allow_http_body_to_pb,
const std::string& method_name, const std::string& method_name,
MethodStatus* status); MethodStatus* status);
......
...@@ -140,6 +140,7 @@ Server::MethodProperty::MethodProperty() ...@@ -140,6 +140,7 @@ Server::MethodProperty::MethodProperty()
: is_builtin_service(false) : is_builtin_service(false)
, own_method_status(false) , own_method_status(false)
, is_tabbed(false) , is_tabbed(false)
, allow_http_body_to_pb(true)
, http_url(NULL) , http_url(NULL)
, service(NULL) , service(NULL)
, method(NULL) , method(NULL)
...@@ -1061,9 +1062,8 @@ int Server::Join() { ...@@ -1061,9 +1062,8 @@ int Server::Join() {
} }
int Server::AddServiceInternal(google::protobuf::Service* service, int Server::AddServiceInternal(google::protobuf::Service* service,
ServiceOwnership ownership,
bool is_builtin_service, bool is_builtin_service,
base::StringPiece restful_mappings) { const ServiceOptions& svc_opt) {
if (NULL == service) { if (NULL == service) {
LOG(ERROR) << "Parameter[service] is NULL!"; LOG(ERROR) << "Parameter[service] is NULL!";
return -1; return -1;
...@@ -1108,6 +1108,7 @@ int Server::AddServiceInternal(google::protobuf::Service* service, ...@@ -1108,6 +1108,7 @@ int Server::AddServiceInternal(google::protobuf::Service* service,
mp.is_builtin_service = is_builtin_service; mp.is_builtin_service = is_builtin_service;
mp.own_method_status = true; mp.own_method_status = true;
mp.is_tabbed = !!tabbed; mp.is_tabbed = !!tabbed;
mp.allow_http_body_to_pb = svc_opt.allow_http_body_to_pb;
mp.service = service; mp.service = service;
mp.method = md; mp.method = md;
mp.status = new MethodStatus; mp.status = new MethodStatus;
...@@ -1132,7 +1133,8 @@ int Server::AddServiceInternal(google::protobuf::Service* service, ...@@ -1132,7 +1133,8 @@ int Server::AddServiceInternal(google::protobuf::Service* service,
} }
} }
const ServiceProperty ss = { is_builtin_service, ownership, service, NULL }; const ServiceProperty ss = {
is_builtin_service, svc_opt.ownership, service, NULL };
_fullname_service_map[sd->full_name()] = ss; _fullname_service_map[sd->full_name()] = ss;
_service_map[sd->name()] = ss; _service_map[sd->name()] = ss;
if (is_builtin_service) { if (is_builtin_service) {
...@@ -1142,7 +1144,8 @@ int Server::AddServiceInternal(google::protobuf::Service* service, ...@@ -1142,7 +1144,8 @@ int Server::AddServiceInternal(google::protobuf::Service* service,
_first_service = service; _first_service = service;
} }
} }
base::StringPiece restful_mappings = svc_opt.restful_mappings;
restful_mappings.trim_spaces(); restful_mappings.trim_spaces();
if (!restful_mappings.empty()) { if (!restful_mappings.empty()) {
// Parse the mappings. // Parse the mappings.
...@@ -1190,6 +1193,7 @@ int Server::AddServiceInternal(google::protobuf::Service* service, ...@@ -1190,6 +1193,7 @@ int Server::AddServiceInternal(google::protobuf::Service* service,
} }
if (!_global_restful_map->AddMethod( if (!_global_restful_map->AddMethod(
mappings[i].path, service, !!tabbed, mappings[i].path, service, !!tabbed,
svc_opt.allow_http_body_to_pb,
mappings[i].method_name, mp->status)) { mappings[i].method_name, mp->status)) {
LOG(ERROR) << "Fail to map `" << mappings[i].path LOG(ERROR) << "Fail to map `" << mappings[i].path
<< "' to `" << full_method_name << '\''; << "' to `" << full_method_name << '\'';
...@@ -1222,6 +1226,7 @@ int Server::AddServiceInternal(google::protobuf::Service* service, ...@@ -1222,6 +1226,7 @@ int Server::AddServiceInternal(google::protobuf::Service* service,
m = sp->restful_map; m = sp->restful_map;
} }
if (!m->AddMethod(mappings[i].path, service, !!tabbed, if (!m->AddMethod(mappings[i].path, service, !!tabbed,
svc_opt.allow_http_body_to_pb,
mappings[i].method_name, mp->status)) { mappings[i].method_name, mp->status)) {
LOG(ERROR) << "Fail to map `" << mappings[i].path << "' to `" LOG(ERROR) << "Fail to map `" << mappings[i].path << "' to `"
<< sd->full_name() << '.' << mappings[i].method_name << sd->full_name() << '.' << mappings[i].method_name
...@@ -1271,16 +1276,37 @@ int Server::AddServiceInternal(google::protobuf::Service* service, ...@@ -1271,16 +1276,37 @@ int Server::AddServiceInternal(google::protobuf::Service* service,
return 0; return 0;
} }
ServiceOptions::ServiceOptions()
: ownership(SERVER_DOESNT_OWN_SERVICE)
, allow_http_body_to_pb(true) {
}
int Server::AddService(google::protobuf::Service* service, int Server::AddService(google::protobuf::Service* service,
ServiceOwnership ownership) { ServiceOwnership ownership) {
return AddServiceInternal(service, ownership, false/*non-builtin*/, ""); ServiceOptions options;
options.ownership = ownership;
return AddServiceInternal(service, false, options);
} }
int Server::AddService(google::protobuf::Service* service, int Server::AddService(google::protobuf::Service* service,
ServiceOwnership ownership, ServiceOwnership ownership,
const base::StringPiece& restful_mappings) { const base::StringPiece& restful_mappings) {
return AddServiceInternal(service, ownership, false/*non-builtin*/, ServiceOptions options;
restful_mappings); options.ownership = ownership;
// TODO: This is weird
options.restful_mappings = restful_mappings.as_string();
return AddServiceInternal(service, false, options);
}
int Server::AddService(google::protobuf::Service* service,
const ServiceOptions& options) {
return AddServiceInternal(service, false, options);
}
int Server::AddBuiltinService(google::protobuf::Service* service) {
ServiceOptions options;
options.ownership = SERVER_OWNS_SERVICE;
return AddServiceInternal(service, true, options);
} }
void Server::RemoveMethodsOf(google::protobuf::Service* service) { void Server::RemoveMethodsOf(google::protobuf::Service* service) {
......
...@@ -295,6 +295,34 @@ enum ServiceOwnership { ...@@ -295,6 +295,34 @@ enum ServiceOwnership {
SERVER_DOESNT_OWN_SERVICE SERVER_DOESNT_OWN_SERVICE
}; };
struct ServiceOptions {
ServiceOptions(); // constructed with default options.
// SERVER_OWNS_SERVICE: the service will be deleted by the server.
// SERVER_DOESNT_OWN_SERVICE: the service shall be deleted by user after
// stopping the server.
// Default: SERVER_DOESNT_OWN_SERVICE
ServiceOwnership ownership;
// If this option is non-empty, methods in the service will be exposed
// on specified paths instead of default "/SERVICE/METHOD".
// Mappings are in form of: "PATH1 => NAME1, PATH2 => NAME2 ..." where
// PATHs are valid http paths, NAMEs are method names in the service.
// Default: empty
std::string restful_mappings;
// [ Not recommended to change this option ]
// If this flag is true, the service will convert http body to protobuf
// when the pb schema is non-empty in http servings. The body must be
// valid json or protobuf(wire-format) otherwise the request is rejected.
// This option does not affect pure-http services (pb schema is empty).
// Services that use older versions of baidu-rpc may need to turn this
// conversion off and handle http requests by their own to keep compatible
// with existing clients.
// Default: true
bool allow_http_body_to_pb;
};
// Represent ports inside [min_port, max_port] // Represent ports inside [min_port, max_port]
struct PortRange { struct PortRange {
int min_port; int min_port;
...@@ -336,6 +364,7 @@ public: ...@@ -336,6 +364,7 @@ public:
bool is_builtin_service; bool is_builtin_service;
bool own_method_status; bool own_method_status;
bool is_tabbed; bool is_tabbed;
bool allow_http_body_to_pb;
// NULL if service of the method was never added as restful. // NULL if service of the method was never added as restful.
// "@path1 @path2 ..." if the method was mapped from paths. // "@path1 @path2 ..." if the method was mapped from paths.
std::string* http_url; std::string* http_url;
...@@ -402,14 +431,7 @@ public: ...@@ -402,14 +431,7 @@ public:
// function may block indefinitely. // function may block indefinitely.
void RunUntilAskedToQuit(); void RunUntilAskedToQuit();
// Add a service. // Add a service. Arguments are explained in ServiceOptions above.
// If `ownership' is SERVER_OWNS_SERVICE, the service will be deleted along
// with this server, otherwise user shall delete the service after stopping
// this server.
// If `restful_mappings' is not empty, the methods in the service will be
// exposed on the specified paths instead of fixed "/SERVICE/METHOD". The
// mapping should be in form of: "PATH1 => NAME1, PATH2 => NAME2 ..." where
// PATHs are valid http URI paths, NAMEs are method names in the service.
// NOTE: Adding a service while server is running is forbidden. // NOTE: Adding a service while server is running is forbidden.
// Returns 0 on success, -1 otherwise. // Returns 0 on success, -1 otherwise.
int AddService(google::protobuf::Service* service, int AddService(google::protobuf::Service* service,
...@@ -417,6 +439,8 @@ public: ...@@ -417,6 +439,8 @@ public:
int AddService(google::protobuf::Service* service, int AddService(google::protobuf::Service* service,
ServiceOwnership ownership, ServiceOwnership ownership,
const base::StringPiece& restful_mappings); const base::StringPiece& restful_mappings);
int AddService(google::protobuf::Service* service,
const ServiceOptions& options);
// Remove a service from this server. // Remove a service from this server.
// NOTE: removing a service while server is running is forbidden. // NOTE: removing a service while server is running is forbidden.
...@@ -533,13 +557,10 @@ friend class ServerPrivateAccessor; ...@@ -533,13 +557,10 @@ friend class ServerPrivateAccessor;
friend class Controller; friend class Controller;
int AddServiceInternal(google::protobuf::Service* service, int AddServiceInternal(google::protobuf::Service* service,
ServiceOwnership ownership,
bool is_builtin_service, bool is_builtin_service,
base::StringPiece restful_mappings); const ServiceOptions& options);
int AddBuiltinService(google::protobuf::Service* service) { int AddBuiltinService(google::protobuf::Service* service);
return AddServiceInternal(service, SERVER_OWNS_SERVICE, true, "");
}
// Remove all methods of `service' from internal structures. // Remove all methods of `service' from internal structures.
void RemoveMethodsOf(google::protobuf::Service* service); void RemoveMethodsOf(google::protobuf::Service* service);
......
...@@ -130,7 +130,7 @@ protected: ...@@ -130,7 +130,7 @@ protected:
brpc::Server server; brpc::Server server;
EvilService evil(conflict_sd); EvilService evil(conflict_sd);
EXPECT_EQ(0, server.AddServiceInternal( EXPECT_EQ(0, server.AddServiceInternal(
&evil, brpc::SERVER_DOESNT_OWN_SERVICE, false, "")); &evil, false, brpc::ServiceOptions()));
EXPECT_EQ(-1, server.AddBuiltinServices()); EXPECT_EQ(-1, server.AddBuiltinServices());
} }
}; };
...@@ -206,12 +206,18 @@ public: ...@@ -206,12 +206,18 @@ public:
, ncalled_echo5(0) , ncalled_echo5(0)
{} {}
virtual ~EchoServiceV1() {} virtual ~EchoServiceV1() {}
virtual void Echo(google::protobuf::RpcController*, virtual void Echo(google::protobuf::RpcController* cntl_base,
const v1::EchoRequest* request, const v1::EchoRequest* request,
v1::EchoResponse* response, v1::EchoResponse* response,
google::protobuf::Closure* done) { google::protobuf::Closure* done) {
brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);
brpc::ClosureGuard done_guard(done); brpc::ClosureGuard done_guard(done);
response->set_message(request->message() + "_v1"); if (request->has_message()) {
response->set_message(request->message() + "_v1");
} else {
CHECK_EQ(brpc::PROTOCOL_HTTP, cntl->request_protocol());
cntl->response_attachment() = cntl->request_attachment();
}
ncalled.fetch_add(1); ncalled.fetch_add(1);
} }
virtual void Echo2(google::protobuf::RpcController*, virtual void Echo2(google::protobuf::RpcController*,
...@@ -441,6 +447,76 @@ TEST_F(ServerTest, various_forms_of_uri_paths) { ...@@ -441,6 +447,76 @@ TEST_F(ServerTest, various_forms_of_uri_paths) {
server1.Join(); server1.Join();
} }
TEST_F(ServerTest, missing_required_fields) {
const int port = 9200;
brpc::Server server1;
EchoServiceV1 service_v1;
ASSERT_EQ(0, server1.AddService(&service_v1, brpc::SERVER_DOESNT_OWN_SERVICE));
ASSERT_EQ(0, server1.Start(port, NULL));
brpc::Channel http_channel;
brpc::ChannelOptions chan_options;
chan_options.protocol = "http";
ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options));
brpc::Controller cntl;
cntl.http_request().uri() = "/EchoService/Echo";
http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
ASSERT_TRUE(cntl.Failed());
ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode());
ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code());
ASSERT_EQ(0, service_v1.ncalled.load());
cntl.Reset();
cntl.http_request().uri() = "/EchoService/Echo";
cntl.http_request().set_method(brpc::HTTP_METHOD_POST);
http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
ASSERT_TRUE(cntl.Failed());
ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode());
ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code());
ASSERT_EQ(0, service_v1.ncalled.load());
cntl.Reset();
cntl.http_request().uri() = "/EchoService/Echo";
cntl.http_request().set_method(brpc::HTTP_METHOD_POST);
cntl.request_attachment().append("{\"message2\":\"foo\"}");
http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
ASSERT_TRUE(cntl.Failed());
ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode());
ASSERT_EQ(brpc::HTTP_STATUS_BAD_REQUEST, cntl.http_response().status_code());
ASSERT_EQ(0, service_v1.ncalled.load());
}
TEST_F(ServerTest, disallow_http_body_to_pb) {
const int port = 9200;
brpc::Server server1;
EchoServiceV1 service_v1;
brpc::ServiceOptions svc_opt;
svc_opt.allow_http_body_to_pb = false;
svc_opt.restful_mappings = "/access_echo1=>Echo";
ASSERT_EQ(0, server1.AddService(&service_v1, svc_opt));
ASSERT_EQ(0, server1.Start(port, NULL));
brpc::Channel http_channel;
brpc::ChannelOptions chan_options;
chan_options.protocol = "http";
ASSERT_EQ(0, http_channel.Init("0.0.0.0", port, &chan_options));
brpc::Controller cntl;
cntl.http_request().uri() = "/access_echo1";
http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
ASSERT_TRUE(cntl.Failed());
ASSERT_EQ(brpc::EHTTP, cntl.ErrorCode());
ASSERT_EQ(brpc::HTTP_STATUS_INTERNAL_SERVER_ERROR,
cntl.http_response().status_code());
ASSERT_EQ(1, service_v1.ncalled.load());
cntl.Reset();
cntl.http_request().uri() = "/access_echo1";
cntl.http_request().set_method(brpc::HTTP_METHOD_POST);
cntl.request_attachment().append("heheda");
http_channel.CallMethod(NULL, &cntl, NULL, NULL, NULL);
ASSERT_FALSE(cntl.Failed()) << cntl.ErrorText();
ASSERT_EQ("heheda", cntl.response_attachment());
ASSERT_EQ(2, service_v1.ncalled.load());
}
TEST_F(ServerTest, restful_mapping) { TEST_F(ServerTest, restful_mapping) {
const int port = 9200; const int port = 9200;
EchoServiceV1 service_v1; EchoServiceV1 service_v1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment