Skip to content

Commit e5fd625

Browse files
Merge pull request #1912 from jeremiahmao:master
PiperOrigin-RevId: 425377932
2 parents 335ee2d + 78e94e9 commit e5fd625

File tree

13 files changed

+710
-62
lines changed

13 files changed

+710
-62
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Description: a lightweight http client
2+
3+
package(
4+
default_visibility = [
5+
"//tensorflow_serving/util/net_http:__subpackages__",
6+
],
7+
)
8+
9+
licenses(["notice"])
10+
11+
cc_library(
12+
name = "evhttp_client",
13+
srcs = [
14+
"evhttp_connection.cc",
15+
],
16+
hdrs = [
17+
"evhttp_connection.h",
18+
],
19+
deps = [
20+
"//tensorflow_serving/util/net_http/client/test_client/public:http_client_api",
21+
"//tensorflow_serving/util/net_http/internal:net_logging",
22+
"//tensorflow_serving/util/net_http/server/public:http_server_api",
23+
"@com_github_libevent_libevent//:libevent",
24+
"@com_google_absl//absl/strings",
25+
"@com_google_absl//absl/synchronization",
26+
],
27+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
The client library is still under development, and has yet to be finalized.
2+
3+
It should be primarily used for writing tests for users of the
4+
ServerRequestInterface and HTTPServerInterface APIs to verify basic
5+
functionality, and the current state should be considered experimental.
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/* Copyright 2018 Google Inc. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
16+
// libevent based client implementation
17+
18+
#include "tensorflow_serving/util/net_http/client/test_client/internal/evhttp_connection.h"
19+
20+
#include "absl/strings/str_cat.h"
21+
#include "tensorflow_serving/util/net_http/internal/net_logging.h"
22+
#include "tensorflow_serving/util/net_http/server/public/response_code_enum.h"
23+
24+
namespace tensorflow {
25+
namespace serving {
26+
namespace net_http {
27+
28+
TestEvHTTPConnection::~TestEvHTTPConnection() {
29+
if (evcon_ != nullptr) {
30+
evhttp_connection_free(evcon_);
31+
}
32+
if (http_uri_ != nullptr) {
33+
evhttp_uri_free(http_uri_);
34+
}
35+
36+
event_base_free(ev_base_);
37+
}
38+
39+
// This needs be called with any async SendRequest()
40+
void TestEvHTTPConnection::Terminate() {
41+
event_base_loopexit(ev_base_, nullptr);
42+
if (loop_exit_ != nullptr) {
43+
loop_exit_->WaitForNotification();
44+
}
45+
}
46+
47+
std::unique_ptr<TestEvHTTPConnection> TestEvHTTPConnection::Connect(
48+
absl::string_view url) {
49+
std::string url_str(url.data(), url.size());
50+
struct evhttp_uri* http_uri = evhttp_uri_parse(url_str.c_str());
51+
if (http_uri == nullptr) {
52+
NET_LOG(ERROR, "Failed to connect : event_base_new()");
53+
return nullptr;
54+
}
55+
56+
const char* host = evhttp_uri_get_host(http_uri);
57+
if (host == nullptr) {
58+
NET_LOG(ERROR, "url must have a host %.*s", static_cast<int>(url.size()),
59+
url.data());
60+
return nullptr;
61+
}
62+
63+
int port = evhttp_uri_get_port(http_uri);
64+
if (port == -1) {
65+
port = 80;
66+
}
67+
68+
auto result = Connect(host, port);
69+
evhttp_uri_free(http_uri);
70+
71+
return result;
72+
}
73+
74+
std::unique_ptr<TestEvHTTPConnection> TestEvHTTPConnection::Connect(
75+
absl::string_view host, int port) {
76+
std::unique_ptr<TestEvHTTPConnection> result(new TestEvHTTPConnection());
77+
78+
result->ev_base_ = event_base_new();
79+
if (result->ev_base_ == nullptr) {
80+
NET_LOG(ERROR, "Failed to connect : event_base_new()");
81+
return nullptr;
82+
}
83+
84+
// blocking call (DNS resolution)
85+
std::string host_str(host.data(), host.size());
86+
result->evcon_ = evhttp_connection_base_bufferevent_new(
87+
result->ev_base_, nullptr, nullptr, host_str.c_str(),
88+
static_cast<uint16_t>(port));
89+
if (result->evcon_ == nullptr) {
90+
NET_LOG(ERROR,
91+
"Failed to connect : evhttp_connection_base_bufferevent_new()");
92+
return nullptr;
93+
}
94+
95+
evhttp_connection_set_retries(result->evcon_, 0);
96+
97+
// TODO(wenboz): make this an option (default to 5s)
98+
evhttp_connection_set_timeout(result->evcon_, 5);
99+
100+
return result;
101+
}
102+
103+
namespace {
104+
105+
// Copy ev response data to ClientResponse.
106+
void PopulateResponse(evhttp_request* req, TestClientResponse* response) {
107+
response->status =
108+
static_cast<HTTPStatusCode>(evhttp_request_get_response_code(req));
109+
110+
struct evkeyvalq* headers = evhttp_request_get_input_headers(req);
111+
struct evkeyval* header;
112+
for (header = headers->tqh_first; header; header = header->next.tqe_next) {
113+
response->headers.emplace_back(header->key, header->value);
114+
}
115+
116+
char buffer[1024];
117+
int nread;
118+
119+
while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req), buffer,
120+
sizeof(buffer))) > 0) {
121+
absl::StrAppend(&response->body,
122+
absl::string_view(buffer, static_cast<size_t>(nread)));
123+
}
124+
}
125+
126+
evhttp_cmd_type GetMethodEnum(absl::string_view method, bool with_body) {
127+
if (method.compare("GET") == 0) {
128+
return EVHTTP_REQ_GET;
129+
} else if (method.compare("POST") == 0) {
130+
return EVHTTP_REQ_POST;
131+
} else if (method.compare("HEAD") == 0) {
132+
return EVHTTP_REQ_HEAD;
133+
} else if (method.compare("PUT") == 0) {
134+
return EVHTTP_REQ_PUT;
135+
} else if (method.compare("DELETE") == 0) {
136+
return EVHTTP_REQ_DELETE;
137+
} else if (method.compare("OPTIONS") == 0) {
138+
return EVHTTP_REQ_OPTIONS;
139+
} else if (method.compare("TRACE") == 0) {
140+
return EVHTTP_REQ_TRACE;
141+
} else if (method.compare("CONNECT") == 0) {
142+
return EVHTTP_REQ_CONNECT;
143+
} else if (method.compare("PATCH") == 0) {
144+
return EVHTTP_REQ_PATCH;
145+
} else {
146+
if (with_body) {
147+
return EVHTTP_REQ_POST;
148+
} else {
149+
return EVHTTP_REQ_GET;
150+
}
151+
}
152+
}
153+
154+
void ResponseDone(evhttp_request* req, void* ctx) {
155+
TestClientResponse* response = reinterpret_cast<TestClientResponse*>(ctx);
156+
157+
if (req == nullptr) {
158+
// TODO(wenboz): make this a util and check safety
159+
int errcode = EVUTIL_SOCKET_ERROR();
160+
NET_LOG(ERROR, "socket error = %s (%d)",
161+
evutil_socket_error_to_string(errcode), errcode);
162+
return;
163+
}
164+
165+
PopulateResponse(req, response);
166+
167+
if (response->done != nullptr) {
168+
response->done();
169+
}
170+
}
171+
172+
// Returns false if there is any error.
173+
bool GenerateEvRequest(evhttp_connection* evcon,
174+
const TestClientRequest& request,
175+
TestClientResponse* response) {
176+
evhttp_request* evreq = evhttp_request_new(ResponseDone, response);
177+
if (evreq == nullptr) {
178+
NET_LOG(ERROR, "Failed to send request : evhttp_request_new()");
179+
return false;
180+
}
181+
182+
evkeyvalq* output_headers = evhttp_request_get_output_headers(evreq);
183+
for (auto header : request.headers) {
184+
std::string key(header.first.data(), header.first.size());
185+
std::string value(header.second.data(), header.second.size());
186+
evhttp_add_header(output_headers, key.c_str(), value.c_str());
187+
}
188+
189+
evhttp_add_header(output_headers, "Connection", "close");
190+
191+
if (!request.body.empty()) {
192+
evbuffer* output_buffer = evhttp_request_get_output_buffer(evreq);
193+
194+
std::string body(request.body.data(), request.body.size());
195+
evbuffer_add(output_buffer, body.c_str(), request.body.size());
196+
197+
char length_header[16];
198+
evutil_snprintf(length_header, sizeof(length_header) - 1, "%lu",
199+
request.body.size());
200+
evhttp_add_header(output_headers, "Content-Length", length_header);
201+
}
202+
203+
std::string uri(request.uri_path.data(), request.uri_path.size());
204+
int r = evhttp_make_request(
205+
evcon, evreq, GetMethodEnum(request.method, !request.body.empty()),
206+
uri.c_str());
207+
if (r != 0) {
208+
NET_LOG(ERROR, "evhttp_make_request() failed");
209+
return false;
210+
}
211+
212+
return true;
213+
}
214+
215+
} // namespace
216+
217+
// Sends the request and has the connection closed
218+
bool TestEvHTTPConnection::BlockingSendRequest(const TestClientRequest& request,
219+
TestClientResponse* response) {
220+
if (!GenerateEvRequest(evcon_, request, response)) {
221+
NET_LOG(ERROR, "Failed to generate the ev_request");
222+
return false;
223+
}
224+
225+
// inline loop blocking
226+
event_base_dispatch(ev_base_);
227+
return true;
228+
}
229+
230+
bool TestEvHTTPConnection::SendRequest(const TestClientRequest& request,
231+
TestClientResponse* response) {
232+
if (this->executor_ == nullptr) {
233+
NET_LOG(ERROR, "EventExecutor is not configured.");
234+
return false;
235+
}
236+
237+
if (!GenerateEvRequest(evcon_, request, response)) {
238+
NET_LOG(ERROR, "Failed to generate the ev_request");
239+
return false;
240+
}
241+
242+
executor_->Schedule([this]() {
243+
loop_exit_.reset(new absl::Notification());
244+
event_base_dispatch(ev_base_);
245+
loop_exit_->Notify();
246+
});
247+
248+
return true;
249+
}
250+
251+
void TestEvHTTPConnection::SetExecutor(
252+
std::unique_ptr<EventExecutor> executor) {
253+
this->executor_ = std::move(executor);
254+
}
255+
256+
} // namespace net_http
257+
} // namespace serving
258+
} // namespace tensorflow
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* Copyright 2018 Google Inc. All Rights Reserved.
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
==============================================================================*/
15+
16+
#ifndef TENSORFLOW_SERVING_UTIL_NET_HTTP_CLIENT_TEST_CLIENT_INTERNAL_EVHTTP_CONNECTION_H_
17+
#define TENSORFLOW_SERVING_UTIL_NET_HTTP_CLIENT_TEST_CLIENT_INTERNAL_EVHTTP_CONNECTION_H_
18+
19+
#include <functional>
20+
#include <memory>
21+
#include <string>
22+
#include <utility>
23+
#include <vector>
24+
25+
#include "absl/strings/string_view.h"
26+
#include "absl/synchronization/notification.h"
27+
#include "libevent/include/event2/buffer.h"
28+
#include "libevent/include/event2/bufferevent.h"
29+
#include "libevent/include/event2/event.h"
30+
#include "libevent/include/event2/http.h"
31+
#include "libevent/include/event2/keyvalq_struct.h"
32+
#include "libevent/include/event2/util.h"
33+
34+
// TODO(wenboz): move EventExecutor to net_http/common
35+
#include "tensorflow_serving/util/net_http/client/test_client/public/httpclient_interface.h"
36+
#include "tensorflow_serving/util/net_http/server/public/httpserver_interface.h"
37+
38+
namespace tensorflow {
39+
namespace serving {
40+
namespace net_http {
41+
42+
// The following types may be moved to an API interface in future.
43+
44+
class TestEvHTTPConnection final : public TestHTTPClientInterface {
45+
public:
46+
TestEvHTTPConnection() = default;
47+
48+
~TestEvHTTPConnection() override;
49+
50+
TestEvHTTPConnection(const TestEvHTTPConnection& other) = delete;
51+
TestEvHTTPConnection& operator=(const TestEvHTTPConnection& other) = delete;
52+
53+
// Terminates the connection.
54+
void Terminate() override;
55+
56+
// Returns a new connection given an absolute URL.
57+
// Always treat the URL scheme as "http" for now.
58+
// Returns nullptr if any error
59+
static std::unique_ptr<TestEvHTTPConnection> Connect(absl::string_view url);
60+
61+
// Returns a new connection to the specified host:port.
62+
// Returns nullptr if any error
63+
static std::unique_ptr<TestEvHTTPConnection> Connect(absl::string_view host,
64+
int port);
65+
66+
// Returns a new connection to the specified port of localhost.
67+
// Returns nullptr if any error
68+
static std::unique_ptr<TestEvHTTPConnection> ConnectLocal(int port) {
69+
return Connect("localhost", port);
70+
}
71+
72+
// Sends a request and blocks the caller till a response is received
73+
// or any error has happened.
74+
// Returns false if any error.
75+
bool BlockingSendRequest(const TestClientRequest& request,
76+
TestClientResponse* response) override;
77+
78+
// Sends a request and returns immediately. The response will be handled
79+
// asynchronously via the response->done callback.
80+
// Returns false if any error in sending the request, or if the executor
81+
// has not been configured.
82+
bool SendRequest(const TestClientRequest& request,
83+
TestClientResponse* response) override;
84+
85+
// Sets the executor for processing requests asynchronously.
86+
void SetExecutor(std::unique_ptr<EventExecutor> executor) override;
87+
88+
private:
89+
struct event_base* ev_base_;
90+
struct evhttp_uri* http_uri_;
91+
struct evhttp_connection* evcon_;
92+
93+
std::unique_ptr<EventExecutor> executor_;
94+
95+
std::unique_ptr<absl::Notification> loop_exit_;
96+
};
97+
98+
} // namespace net_http
99+
} // namespace serving
100+
} // namespace tensorflow
101+
102+
#endif // TENSORFLOW_SERVING_UTIL_NET_HTTP_CLIENT_TEST_CLIENT_INTERNAL_EVHTTP_CONNECTION_H_

0 commit comments

Comments
 (0)