Line data Source code
1 : /*
2 : * Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
3 : * Copyright Contributors to the OpenSDN project
4 : */
5 :
6 : #include "http_client.h"
7 : #include <boost/bind/bind.hpp>
8 : #include <boost/algorithm/string.hpp>
9 : #include "base/address_util.h"
10 : #include "base/task_annotations.h"
11 : #include "io/event_manager.h"
12 : #include "base/logging.h"
13 : #include "http_curl.h"
14 :
15 : using namespace std;
16 : using namespace boost::placeholders;
17 :
18 0 : HttpClientSession::HttpClientSession(HttpClient *client, Socket *socket)
19 0 : : TcpSession(client, socket) , delete_called_(0) {
20 0 : set_observer(boost::bind(&HttpClientSession::OnEvent, this, _1, _2));
21 0 : }
22 :
23 0 : void HttpClientSession::OnRead(Buffer buffer) {
24 0 : return;
25 : }
26 :
27 0 : void HttpClientSession::OnEvent(TcpSession *session, Event event) {
28 0 : if (connection_) {
29 0 : connection_->client()->
30 0 : ProcessEvent(boost::bind(&HttpClientSession::OnEventInternal,
31 0 : this, TcpSessionPtr(session), event));
32 : }
33 0 : }
34 :
35 0 : void HttpClientSession::OnEventInternal(TcpSessionPtr session, Event event) {
36 0 : if (event_cb_ && !event_cb_.empty()) {
37 0 : event_cb_(static_cast<HttpClientSession *>(session.get()), event);
38 : }
39 :
40 0 : if (event == CLOSE) {
41 0 : goto error;
42 : }
43 0 : if (event == ACCEPT) {
44 0 : goto error;
45 : }
46 0 : if (event == CONNECT_COMPLETE) {
47 0 : goto error;
48 : }
49 0 : if (event == CONNECT_FAILED) {
50 0 : goto error;
51 : }
52 0 : if (event == EVENT_NONE) {
53 0 : goto error;
54 : }
55 0 : return;
56 :
57 0 : error:
58 : // Call callback function with error;
59 0 : return;
60 : }
61 :
62 0 : void HttpClientSession::RegisterEventCb(SessionEventCb cb) {
63 0 : event_cb_ = cb;
64 0 : }
65 :
66 : namespace {
67 0 : boost::asio::ip::address String2Addr(const std::string& host) {
68 0 : boost::system::error_code ec;
69 0 : return AddressFromString(host, &ec);
70 : }
71 : }
72 :
73 0 : HttpConnection::HttpConnection(boost::asio::ip::tcp::endpoint ep,
74 0 : size_t id, HttpClient *client) :
75 0 : host_(ep.address().to_string()), endpoint_(ep),
76 0 : id_(id), cb_(NULL), offset_(0), curl_handle_(NULL),
77 0 : session_(NULL), client_(client), use_ssl_(false), client_cert_(""),
78 0 : client_cert_type_("PEM"), client_key_(""), ca_cert_(""), state_(STATUS) {
79 0 : }
80 :
81 0 : HttpConnection::HttpConnection(const std::string& host, int port,
82 0 : size_t id, HttpClient *client) :
83 0 : host_(host), endpoint_(String2Addr(host), port),
84 0 : id_(id), cb_(NULL), offset_(0), curl_handle_(NULL),
85 0 : session_(NULL), client_(client), use_ssl_(false), client_cert_(""),
86 0 : client_cert_type_("PEM"), client_key_(""), ca_cert_(""), state_(STATUS) {
87 0 : }
88 :
89 0 : HttpConnection::~HttpConnection() {
90 0 : delete_session();
91 0 : }
92 :
93 0 : std::string HttpConnection::make_url(std::string &path) {
94 0 : std::ostringstream ret;
95 :
96 0 : if (use_ssl_) {
97 0 : ret << "https://" << host_;
98 : } else {
99 0 : ret << "http://" << host_;
100 : }
101 0 : if (endpoint_.port() != 0) {
102 0 : ret << ":" << endpoint_.port();
103 : }
104 0 : ret << "/" << path;
105 :
106 0 : return ret.str();
107 0 : }
108 :
109 0 : HttpClientSession *HttpConnection::CreateSession() {
110 : HttpClientSession *session =
111 0 : static_cast<HttpClientSession *>(client_->CreateSession());
112 0 : if (session) {
113 0 : session->SetConnection(this);
114 : }
115 0 : return session;
116 : }
117 :
118 0 : void HttpConnection::delete_session() {
119 0 : HttpClientSession *session = session_;
120 0 : if (session_) {
121 : {
122 0 : std::scoped_lock lock(session_->mutex());
123 0 : session_->SetConnection(NULL);
124 0 : session_ = NULL;
125 0 : }
126 0 : client_->DeleteSession(session);
127 : }
128 0 : }
129 :
130 0 : void HttpConnection::set_session(HttpClientSession *session) {
131 0 : session_ = session;
132 0 : if (session && event_cb_ && !event_cb_.empty())
133 0 : session->RegisterEventCb(event_cb_);
134 0 : }
135 :
136 0 : int HttpConnection::HttpGet(const std::string &path, HttpCb cb) {
137 0 : std::vector<std::string> hdr_options;
138 0 : return HttpGet(path, false, true, false, hdr_options, cb);
139 0 : }
140 :
141 0 : int HttpConnection::HttpGet(const std::string &path, bool header,
142 : bool short_timeout, bool reuse,
143 : std::vector<std::string> &hdr_options,
144 : HttpCb cb) {
145 0 : const std::string body;
146 :
147 0 : client()->ProcessEvent(boost::bind(&HttpConnection::HttpProcessInternal,
148 0 : this, body, path, bool2bf(header, short_timeout,
149 : reuse), hdr_options, cb, HTTP_GET));
150 0 : return 0;
151 0 : }
152 :
153 0 : int HttpConnection::HttpHead(const std::string &path, bool header, bool short_timeout,
154 : bool reuse, std::vector<std::string> &hdr_options,
155 : HttpCb cb) {
156 0 : const std::string body;
157 0 : client()->ProcessEvent(boost::bind(&HttpConnection::HttpProcessInternal,
158 0 : this, body, path, bool2bf(header, short_timeout,
159 : reuse), hdr_options, cb, HTTP_HEAD));
160 0 : return 0;
161 0 : }
162 :
163 0 : int HttpConnection::HttpPut(const std::string &put_string,
164 : const std::string &path, HttpCb cb) {
165 0 : std::vector<std::string> hdr_options;
166 0 : return HttpPut(put_string, path, false, true, false, hdr_options, cb);
167 0 : }
168 :
169 0 : int HttpConnection::HttpPut(const std::string &put_string,
170 : const std::string &path, bool header,
171 : bool short_timeout,
172 : bool reuse, std::vector<std::string> &hdr_options,
173 : HttpCb cb) {
174 0 : client()->ProcessEvent(boost::bind(&HttpConnection::HttpProcessInternal,
175 : this, put_string, path,
176 0 : bool2bf(header, short_timeout, reuse),
177 : hdr_options, cb, HTTP_PUT));
178 0 : return 0;
179 : }
180 :
181 0 : int HttpConnection::HttpPost(const std::string &post_string,
182 : const std::string &path, HttpCb cb) {
183 0 : std::vector<std::string> hdr_options;
184 0 : return HttpPost(post_string, path, false, true, false, hdr_options, cb);
185 0 : }
186 :
187 0 : int HttpConnection::HttpPost(const std::string &post_string,
188 : const std::string &path, bool header,
189 : bool short_timeout, bool reuse,
190 : std::vector<std::string> &hdr_options, HttpCb cb) {
191 0 : client()->ProcessEvent(boost::bind(&HttpConnection::HttpProcessInternal,
192 0 : this, post_string, path, bool2bf(header,
193 : short_timeout, reuse), hdr_options, cb, HTTP_POST));
194 0 : return 0;
195 : }
196 :
197 0 : int HttpConnection::HttpDelete(const std::string &path, HttpCb cb) {
198 0 : std::vector<std::string> hdr_options;
199 0 : return HttpDelete(path, false, true, false, hdr_options, cb);
200 0 : }
201 :
202 0 : int HttpConnection::HttpDelete(const std::string &path, bool header, bool short_timeout,
203 : bool reuse, std::vector<std::string> &hdr_options,
204 : HttpCb cb) {
205 0 : const std::string body;
206 0 : client()->ProcessEvent(boost::bind(&HttpConnection::HttpProcessInternal,
207 0 : this, body, path, bool2bf(header, short_timeout,
208 : reuse), hdr_options, cb, HTTP_DELETE));
209 0 : return 0;
210 0 : }
211 :
212 0 : void HttpConnection::ClearCallback() {
213 0 : cb_ = NULL;
214 0 : }
215 :
216 0 : void HttpConnection::HttpProcessInternal(const std::string body,
217 : std::string path,
218 : unsigned short hdr_shortTimeout_reuse,
219 : std::vector<std::string> hdr_options,
220 : HttpCb cb, http_method method) {
221 : bool short_timeout, reuse;
222 0 : bf2bool(hdr_shortTimeout_reuse, sent_hdr_, short_timeout, reuse);
223 0 : state_ = STATUS;
224 0 : status_ = 0;
225 0 : version_.clear();
226 0 : reason_.clear();
227 0 : if (client()->AddConnection(this) == false) {
228 : // connection already exists
229 0 : if (!reuse)
230 0 : return;
231 : }
232 :
233 0 : struct _GlobalInfo *gi = client()->GlobalInfo();
234 0 : struct _ConnInfo *curl_handle = new_conn(this, gi, sent_hdr_, short_timeout,
235 : reuse);
236 0 : if (!curl_handle) {
237 0 : LOG(DEBUG, "Http : unable to create new connection");
238 0 : return;
239 : }
240 :
241 0 : if (curl_handle_) {
242 : // delete existing curl_handle
243 0 : del_curl_handle(curl_handle_, gi);
244 : }
245 0 : curl_handle->connection = this;
246 0 : set_curl_handle(curl_handle);
247 :
248 0 : cb_ = cb;
249 :
250 0 : std::string url = make_url(path);
251 0 : set_url(curl_handle_, url.c_str());
252 :
253 : // set special curl options requested in http connection
254 0 : std::map<CURLoption, int> *curl_options = this->curl_options();
255 0 : std::map<CURLoption, int>::iterator iter = curl_options->begin();
256 0 : while (iter != curl_options->end()) {
257 0 : set_curl_option(curl_handle_->easy, iter->first, iter->second);
258 0 : iter++;
259 : }
260 :
261 : // set SSL curl options
262 0 : if (use_ssl_) {
263 0 : set_ssl_options(curl_handle_, client_cert_.c_str(),
264 : client_cert_type_.c_str(), client_key_.c_str(), ca_cert_.c_str());
265 : }
266 :
267 : // Add header options to the get request
268 0 : for (uint32_t i = 0; i < hdr_options.size(); ++i)
269 0 : set_header_options(curl_handle_, hdr_options[i].c_str());
270 :
271 0 : switch (method) {
272 0 : case HTTP_GET:
273 0 : http_get(curl_handle_, gi);
274 0 : break;
275 :
276 0 : case HTTP_HEAD:
277 0 : http_head(curl_handle_, gi);
278 0 : break;
279 :
280 0 : case HTTP_POST:
281 0 : if (!hdr_options.size()) {
282 : // if no header options are set, set the content type
283 0 : set_header_options(curl_handle_, "Content-Type: application/xml");
284 : }
285 0 : set_post_string(curl_handle_, body.c_str(), body.size());
286 0 : http_post(curl_handle_, gi);
287 0 : break;
288 :
289 0 : case HTTP_PUT:
290 0 : if (!hdr_options.size()) {
291 : // if no header options are set, set the content type
292 0 : set_header_options(curl_handle_, "Content-Type: application/xml");
293 : }
294 0 : set_put_string(curl_handle_, body.c_str(), body.size());
295 0 : http_put(curl_handle_, gi);
296 0 : break;
297 :
298 0 : case HTTP_DELETE:
299 0 : http_delete(curl_handle_, gi);
300 0 : break;
301 :
302 0 : default:
303 0 : assert(0);
304 : }
305 0 : }
306 :
307 0 : void HttpConnection::AssignData(const char *ptr, size_t size) {
308 :
309 0 : buf_.assign(ptr, size);
310 :
311 : // callback to client
312 0 : boost::system::error_code error;
313 0 : if (cb_ != NULL)
314 0 : cb_(buf_, error);
315 0 : }
316 :
317 0 : void HttpConnection::AssignHeader(const char *ptr, size_t size) {
318 :
319 0 : buf_.assign(ptr, size);
320 :
321 0 : switch (state_) {
322 0 : case STATUS: {
323 0 : status_ = 0;
324 0 : int i = buf_.find(' ', 0);
325 0 : version_ = boost::algorithm::trim_copy(buf_.substr(0, i));
326 0 : int j = buf_.find(' ', i+1);
327 0 : status_ = atoi(buf_.substr(i+1, j).c_str());
328 0 : reason_ = boost::algorithm::trim_copy(buf_.substr(j+1));
329 : #ifdef __DEBUG__
330 : //for (std::string::iterator ii=buf_.begin()+i+1;
331 : // ii != buf_.begin()+j; ii++) {
332 : // status_ = (status_ << 3) + (status_ << 1) + (*ii - '0');
333 : //}
334 : std::cout << "Status Line: " << std::dec << status_ << ":"
335 : << reason_ << "(" << version_ << ")" << std::endl;
336 : #endif
337 0 : state_ = HEADER;
338 0 : break;
339 : }
340 0 : case HEADER:
341 0 : if (buf_ != "\r\n") {
342 0 : std::istringstream iss(buf_);
343 0 : std::string tok;
344 0 : while (std::getline(iss, tok, '\r') && tok != "\n") {
345 0 : unsigned int i = tok.find(':', 0);
346 : if (i != std::string::npos) {
347 0 : headers_.insert(std::make_pair(
348 0 : boost::algorithm::trim_copy(tok.substr(0, i)),
349 0 : boost::algorithm::trim_copy(tok.substr(i + 1))));
350 : }
351 : }
352 0 : }
353 0 : break;
354 : }
355 0 : boost::system::error_code error;
356 : // callback to client *backward compatibility*
357 0 : if (sent_hdr_ && cb_ != NULL) {
358 0 : cb_(buf_, error);
359 : }
360 0 : }
361 :
362 0 : const std::string &HttpConnection::GetData() {
363 0 : return buf_;
364 : }
365 :
366 0 : void HttpConnection::UpdateOffset(size_t bytes) {
367 0 : offset_ += bytes;
368 0 : }
369 :
370 0 : size_t HttpConnection::GetOffset() {
371 0 : return offset_;
372 : }
373 :
374 2 : HttpClient::HttpClient(EventManager *evm, std::string task_name) :
375 : TcpServer(evm),
376 2 : curl_timer_(TimerManager::CreateTimer(*evm->io_service(), task_name,
377 : TaskScheduler::GetInstance()->GetTaskId(task_name), 0)),
378 2 : id_(0), work_queue_(TaskScheduler::GetInstance()->GetTaskId(task_name), 0,
379 4 : boost::bind(&HttpClient::DequeueEvent, this, _1)) {
380 2 : gi_ = (struct _GlobalInfo *)malloc(sizeof(struct _GlobalInfo));
381 2 : memset(gi_, 0, sizeof(struct _GlobalInfo));
382 2 : }
383 :
384 2 : void HttpClient::ShutdownInternal() {
385 :
386 2 : for (HttpConnectionMap::iterator iter = map_.begin(), next = iter;
387 2 : iter != map_.end(); iter = next) {
388 0 : next++;
389 0 : RemoveConnectionInternal(iter->second);
390 : }
391 :
392 2 : curl_multi_cleanup(gi_->multi);
393 2 : TimerManager::DeleteTimer(curl_timer_);
394 2 : SessionShutdown();
395 :
396 : /* Schedule a shutdown of WorkQueue */
397 2 : work_queue_.ScheduleShutdown();
398 2 : assert(!map_.size());
399 2 : }
400 :
401 : /*
402 : * Ensure task that calls this function is not mutually exclusive
403 : * to the task that runs HttpClient::ShutdownInternal()
404 : *
405 : * Tight loop to check the Callback has been scheduled
406 : */
407 2 : void HttpClient::Shutdown() {
408 2 : work_queue_.Enqueue(boost::bind(&HttpClient::ShutdownInternal,
409 : this));
410 :
411 2 : uint32_t count = 0;
412 4 : while (!(work_queue_.deleted() == true) && count++ < 10000) {
413 2 : usleep(1000);
414 : }
415 :
416 2 : assert(work_queue_.deleted() == true);
417 2 : }
418 :
419 2 : HttpClient::~HttpClient() {
420 2 : free(gi_);
421 2 : }
422 :
423 2 : void HttpClient::Init() {
424 2 : curl_init(this);
425 2 : }
426 :
427 2 : void HttpClient::SessionShutdown() {
428 2 : TcpServer::Shutdown();
429 2 : }
430 :
431 0 : boost::asio::io_context *HttpClient::io_service() {
432 0 : return this->event_manager()->io_service();
433 : };
434 :
435 0 : TcpSession *HttpClient::AllocSession(Socket *socket) {
436 0 : HttpClientSession *session = new HttpClientSession(this, socket);
437 0 : return session;
438 : }
439 :
440 0 : TcpSession *HttpClient::CreateSession() {
441 0 : TcpSession *session = TcpServer::CreateSession();
442 0 : Socket *socket = session->socket();
443 0 : boost::system::error_code err;
444 0 : socket->open(boost::asio::ip::tcp::v4(), err);
445 :
446 0 : if (err) {
447 0 : LOG(ERROR, "http socket open failed: " << err);
448 0 : return NULL;
449 : }
450 :
451 0 : err = session->SetSocketOptions();
452 0 : return session;
453 : }
454 :
455 0 : HttpConnection *HttpClient::CreateConnection(boost::asio::ip::tcp::endpoint ep) {
456 0 : HttpConnection *conn = new HttpConnection(ep, ++id_, this);
457 0 : return conn;
458 : }
459 :
460 0 : HttpConnection *HttpClient::CreateConnection(const std::string& host, int port) {
461 0 : HttpConnection *conn = new HttpConnection(host, port, ++id_, this);
462 0 : return conn;
463 : }
464 :
465 0 : bool HttpClient::AddConnection(HttpConnection *conn) {
466 0 : Key key = std::make_pair(conn->endpoint(), conn->id());
467 0 : if (map_.find(key) == map_.end()) {
468 0 : map_.insert(key, conn);
469 0 : return true;
470 : }
471 0 : return false;
472 : }
473 :
474 0 : void HttpClient::RemoveConnection(HttpConnection *connection) {
475 0 : connection->ClearCallback();
476 0 : work_queue_.Enqueue(boost::bind(&HttpClient::RemoveConnectionInternal,
477 : this, connection));
478 0 : }
479 :
480 0 : void HttpClient::ProcessEvent(EnqueuedCb cb) {
481 0 : if(!cb.empty()) {
482 0 : work_queue_.Enqueue(cb);
483 : }
484 0 : }
485 :
486 0 : void HttpClient::TimerErrorHandler(std::string name, std::string error) {
487 0 : }
488 :
489 0 : bool HttpClient::TimerCb() {
490 0 : return timer_cb(gi_);
491 : }
492 :
493 0 : void HttpClient::StartTimer(long timeout_ms) {
494 0 : CancelTimer();
495 0 : curl_timer_->Start(timeout_ms, boost::bind(&HttpClient::TimerCb, this));
496 0 : }
497 :
498 0 : void HttpClient::CancelTimer() {
499 0 : curl_timer_->Cancel();
500 0 : }
501 :
502 0 : bool HttpClient::IsErrorHard(const boost::system::error_code &ec) {
503 0 : return TcpSession::IsSocketErrorHard(ec);
504 : }
505 :
506 0 : void HttpClient::RemoveConnectionInternal(HttpConnection *connection) {
507 0 : boost::asio::ip::tcp::endpoint endpoint = connection->endpoint();
508 0 : size_t id = connection->id();
509 0 : del_conn(connection, gi_);
510 0 : map_.erase(std::make_pair(endpoint, id));
511 0 : return;
512 : }
513 :
514 2 : bool HttpClient::DequeueEvent(EnqueuedCb cb) {
515 2 : cb();
516 2 : return true;
517 : }
|