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