TensorFlow Serving C++ API Documentation
evhttp_connection.cc
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 <memory>
21 #include <string>
22 #include <utility>
23 
24 #include "absl/strings/str_cat.h"
25 #include "tensorflow_serving/util/net_http/internal/net_logging.h"
26 #include "tensorflow_serving/util/net_http/public/response_code_enum.h"
27 
28 namespace tensorflow {
29 namespace serving {
30 namespace net_http {
31 
32 TestEvHTTPConnection::~TestEvHTTPConnection() {
33  if (evcon_ != nullptr) {
34  evhttp_connection_free(evcon_);
35  }
36  if (http_uri_ != nullptr) {
37  evhttp_uri_free(http_uri_);
38  }
39 
40  event_base_free(ev_base_);
41 }
42 
43 // This needs be called with any async SendRequest()
44 void TestEvHTTPConnection::Terminate() {
45  event_base_loopexit(ev_base_, nullptr);
46  if (loop_exit_ != nullptr) {
47  loop_exit_->WaitForNotification();
48  }
49 }
50 
51 std::unique_ptr<TestEvHTTPConnection> TestEvHTTPConnection::Connect(
52  absl::string_view url) {
53  std::string url_str(url.data(), url.size());
54  struct evhttp_uri* http_uri = evhttp_uri_parse(url_str.c_str());
55  if (http_uri == nullptr) {
56  NET_LOG(ERROR, "Failed to connect : event_base_new()");
57  return nullptr;
58  }
59 
60  const char* host = evhttp_uri_get_host(http_uri);
61  if (host == nullptr) {
62  NET_LOG(ERROR, "url must have a host %.*s", static_cast<int>(url.size()),
63  url.data());
64  return nullptr;
65  }
66 
67  int port = evhttp_uri_get_port(http_uri);
68  if (port == -1) {
69  port = 80;
70  }
71 
72  auto result = Connect(host, port);
73  evhttp_uri_free(http_uri);
74 
75  return result;
76 }
77 
78 std::unique_ptr<TestEvHTTPConnection> TestEvHTTPConnection::Connect(
79  absl::string_view host, int port) {
80  std::unique_ptr<TestEvHTTPConnection> result(new TestEvHTTPConnection());
81 
82  result->ev_base_ = event_base_new();
83  if (result->ev_base_ == nullptr) {
84  NET_LOG(ERROR, "Failed to connect : event_base_new()");
85  return nullptr;
86  }
87 
88  // blocking call (DNS resolution)
89  std::string host_str(host.data(), host.size());
90  result->evcon_ = evhttp_connection_base_bufferevent_new(
91  result->ev_base_, nullptr, nullptr, host_str.c_str(),
92  static_cast<uint16_t>(port));
93  if (result->evcon_ == nullptr) {
94  NET_LOG(ERROR,
95  "Failed to connect : evhttp_connection_base_bufferevent_new()");
96  return nullptr;
97  }
98 
99  evhttp_connection_set_retries(result->evcon_, 0);
100 
101  // TODO(wenboz): make this an option (default to 5s)
102  evhttp_connection_set_timeout(result->evcon_, 5);
103 
104  return result;
105 }
106 
107 namespace {
108 
109 // Copy ev response data to ClientResponse.
110 void PopulateResponse(evhttp_request* req, TestClientResponse* response) {
111  response->status =
112  static_cast<HTTPStatusCode>(evhttp_request_get_response_code(req));
113 
114  struct evkeyvalq* headers = evhttp_request_get_input_headers(req);
115  struct evkeyval* header;
116  for (header = headers->tqh_first; header; header = header->next.tqe_next) {
117  response->headers.emplace_back(header->key, header->value);
118  }
119 
120  char buffer[1024];
121  int nread;
122 
123  while ((nread = evbuffer_remove(evhttp_request_get_input_buffer(req), buffer,
124  sizeof(buffer))) > 0) {
125  absl::StrAppend(&response->body,
126  absl::string_view(buffer, static_cast<size_t>(nread)));
127  }
128 }
129 
130 evhttp_cmd_type GetMethodEnum(absl::string_view method, bool with_body) {
131  if (method.compare("GET") == 0) {
132  return EVHTTP_REQ_GET;
133  } else if (method.compare("POST") == 0) {
134  return EVHTTP_REQ_POST;
135  } else if (method.compare("HEAD") == 0) {
136  return EVHTTP_REQ_HEAD;
137  } else if (method.compare("PUT") == 0) {
138  return EVHTTP_REQ_PUT;
139  } else if (method.compare("DELETE") == 0) {
140  return EVHTTP_REQ_DELETE;
141  } else if (method.compare("OPTIONS") == 0) {
142  return EVHTTP_REQ_OPTIONS;
143  } else if (method.compare("TRACE") == 0) {
144  return EVHTTP_REQ_TRACE;
145  } else if (method.compare("CONNECT") == 0) {
146  return EVHTTP_REQ_CONNECT;
147  } else if (method.compare("PATCH") == 0) {
148  return EVHTTP_REQ_PATCH;
149  } else {
150  if (with_body) {
151  return EVHTTP_REQ_POST;
152  } else {
153  return EVHTTP_REQ_GET;
154  }
155  }
156 }
157 
158 void ResponseDone(evhttp_request* req, void* ctx) {
159  TestClientResponse* response = reinterpret_cast<TestClientResponse*>(ctx);
160 
161  if (req == nullptr) {
162  // TODO(wenboz): make this a util and check safety
163  int errcode = EVUTIL_SOCKET_ERROR();
164  NET_LOG(ERROR, "socket error = %s (%d)",
165  evutil_socket_error_to_string(errcode), errcode);
166  return;
167  }
168 
169  PopulateResponse(req, response);
170 
171  if (response->done != nullptr) {
172  response->done();
173  }
174 }
175 
176 // Returns false if there is any error.
177 bool GenerateEvRequest(evhttp_connection* evcon,
178  const TestClientRequest& request,
179  TestClientResponse* response) {
180  evhttp_request* evreq = evhttp_request_new(ResponseDone, response);
181  if (evreq == nullptr) {
182  NET_LOG(ERROR, "Failed to send request : evhttp_request_new()");
183  return false;
184  }
185 
186  evkeyvalq* output_headers = evhttp_request_get_output_headers(evreq);
187  for (auto header : request.headers) {
188  std::string key(header.first.data(), header.first.size());
189  std::string value(header.second.data(), header.second.size());
190  evhttp_add_header(output_headers, key.c_str(), value.c_str());
191  }
192 
193  evhttp_add_header(output_headers, "Connection", "close");
194 
195  if (!request.body.empty()) {
196  evbuffer* output_buffer = evhttp_request_get_output_buffer(evreq);
197 
198  std::string body(request.body.data(), request.body.size());
199  evbuffer_add(output_buffer, body.c_str(), request.body.size());
200 
201  char length_header[16];
202  evutil_snprintf(length_header, sizeof(length_header) - 1, "%lu",
203  request.body.size());
204  evhttp_add_header(output_headers, "Content-Length", length_header);
205  }
206 
207  std::string uri(request.uri_path.data(), request.uri_path.size());
208  int r = evhttp_make_request(
209  evcon, evreq, GetMethodEnum(request.method, !request.body.empty()),
210  uri.c_str());
211  if (r != 0) {
212  NET_LOG(ERROR, "evhttp_make_request() failed");
213  return false;
214  }
215 
216  return true;
217 }
218 
219 } // namespace
220 
221 // Sends the request and has the connection closed
222 bool TestEvHTTPConnection::BlockingSendRequest(const TestClientRequest& request,
223  TestClientResponse* response) {
224  if (!GenerateEvRequest(evcon_, request, response)) {
225  NET_LOG(ERROR, "Failed to generate the ev_request");
226  return false;
227  }
228 
229  // inline loop blocking
230  event_base_dispatch(ev_base_);
231  return true;
232 }
233 
234 bool TestEvHTTPConnection::SendRequest(const TestClientRequest& request,
235  TestClientResponse* response) {
236  if (this->executor_ == nullptr) {
237  NET_LOG(ERROR, "EventExecutor is not configured.");
238  return false;
239  }
240 
241  if (!GenerateEvRequest(evcon_, request, response)) {
242  NET_LOG(ERROR, "Failed to generate the ev_request");
243  return false;
244  }
245 
246  executor_->Schedule([this]() {
247  loop_exit_.reset(new absl::Notification());
248  event_base_dispatch(ev_base_);
249  loop_exit_->Notify();
250  });
251 
252  return true;
253 }
254 
255 void TestEvHTTPConnection::SetExecutor(
256  std::unique_ptr<EventExecutor> executor) {
257  this->executor_ = std::move(executor);
258 }
259 
260 } // namespace net_http
261 } // namespace serving
262 } // namespace tensorflow