1 mike 1.1 /*
2 **==============================================================================
3 **
4 ** Open Management Infrastructure (OMI)
5 **
6 ** Copyright (c) Microsoft Corporation
7 **
|
8 krisbash 1.4 ** Licensed under the Apache License, Version 2.0 (the "License"); you may not
9 ** use this file except in compliance with the License. You may obtain a copy
10 ** of the License at
11 **
12 ** http://www.apache.org/licenses/LICENSE-2.0
|
13 mike 1.1 **
14 ** THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
15 krisbash 1.4 ** KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
16 ** WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
17 ** MERCHANTABLITY OR NON-INFRINGEMENT.
|
18 mike 1.1 **
|
19 krisbash 1.4 ** See the Apache 2 License for the specific language governing permissions
|
20 mike 1.1 ** and limitations under the License.
21 **
22 **==============================================================================
23 */
24
|
25 krisbash 1.4 #include <iostream>
26 #include <memory>
27 #include <semaphore.h>
28 #include <openssl/ssl.h>
29 #include <openssl/err.h>
30
|
31 mike 1.1 #include "httpclientcxx.h"
32 #include <common.h>
|
33 krisbash 1.4 #include <pal/atomic.h>
34 #include <pal/sleep.h>
|
35 mike 1.2 #include <http/httpclient.h>
|
36 krisbash 1.4 #include <pal/thread.h>
37
38 //#define ENABLE_TRACING 1
39 #if defined ENABLE_TRACING
40 # define TRACING_LEVEL 3
41 # include <deprecated/logging/logging.h>
42 #else
43 # define LOGD2(s)
44 # define LOGE2(s)
45 # define LOGW2(s)
46 #endif
47
48 static sem_t s_connectionSemaphore;
49 static bool s_connectionSemaphoreInitialized = false;
50 static pthread_mutex_t s_connectionSemaphoreLock;
51 static sem_t s_sendSemaphore;
52 static pthread_mutex_t s_sendLock;
53 static bool s_notifySet = false;
|
54 mike 1.1
55 using namespace std;
56
57 // callback for thread (run function)
58 BEGIN_EXTERNC
59
|
60 krisbash 1.4 static MI_Uint32 MI_CALL _proc(void*);
61
62 // callback for user's functions executed in background thread
|
63 mike 1.1 static void threadDelegation(void* self, Message* message);
64
|
65 krisbash 1.4 // HTTP callbacks
|
66 mike 1.1 static void httpClientCallbackOnStatus(
67 ::HttpClient* http,
68 void* callbackData,
69 MI_Result result);
70
71 static MI_Boolean httpClientCallbackOnResponse(
72 ::HttpClient* http,
73 void* callbackData,
74 const HttpClientResponseHeader* headers,
75 MI_Sint64 contentSize,
|
76 krisbash 1.4 MI_Boolean lastChunk,
|
77 mike 1.1 Page** data);
78
79 END_EXTERNC
80
|
81 krisbash 1.4 /*
82 Convert a 4-bit binary value to its hexadecimal character representation, using uppercase
83 A - F and leading zero, as needed, as per RFC 3986.
84
85 @param[in] n - the number to be converted
86
87 @returns the hexadecimal representation of n
88 */
89 static unsigned int ToHexit(
90 unsigned int n)
91 {
92 if (n <= 9)
93 {
94 return n + '0';
95 }
96 if (n <= 15)
97 {
98 return n + 'A' - 10;
99 }
100 return '?';
101 }
102 krisbash 1.4
103 /*
104 Replace a hostname or URI string encoded using the UTF-8 character set with
105 another string that specifies the same hostname or URI encoded using the
106 escapes specified in RFC 3986. For a URI, as specified in the RFC, escaping
107 stops at the first '?' character after the last slash.
108
109 The scheme, for example, "http:", and host name are not normalized to lower case
110 as specified in RFC 3986; neither are path segments mormalized.
111
112 This function does not check for correctly-formatted UTF-8 multibyte characters,
113 it simply encodes the characters in the input string character-by-character.
114
115 @param[in] UriString - the unescaped URI string
116
117 @returns the escaped URI string
118 */
119 static std::string EscapeUriString(const char* UriString, bool UriSyntax)
120 {
121 std::string EscapedUriString;
122
123 krisbash 1.4 EscapedUriString.reserve(strlen(UriString) * 3); // reserve space so the "+=" operator is more efficient
124 for (const char* Uri = UriString; *Uri != '\0'; Uri++)
125 {
126 unsigned char c = (unsigned char)*Uri;
127 if ((c >= '0' && c <= '9') ||
128 (c >= 'A' && c <= 'Z') ||
129 (c >= 'a' && c <= 'z') ||
130 strchr("-._~/,!'$&%:;*\\", (int)c) != NULL)
131 {
132 // an unreserved character or delimiter
133 EscapedUriString += (char)c;
134 }
135 else if (c == '?')
136 {
137 // a '?': if a URI, check for '?' before or after the final '/'
138 EscapedUriString += '?';
139 if (UriSyntax && strchr(Uri, '/') == NULL)
140 {
141 // if a URI and there is no '/' after this, this finishes translation
142 if (*(Uri + 1) != '\0')
143 {
144 krisbash 1.4 EscapedUriString += std::string(Uri + 1);
145 }
146 break;
147 }
148 }
149 else
150 {
151 // a character that must be escaped
152 EscapedUriString += '%';
153 EscapedUriString += ToHexit((unsigned int)c >> 4);
154 EscapedUriString += ToHexit((unsigned int)c & 0x0000000F);
155 }
156 }
157 return EscapedUriString;
158 }
159
|
160 mike 1.1 HTTPCLIENT_BEGIN
161
|
162 krisbash 1.4 /* helper structure used to post messages from calling thread
|
163 mike 1.1 to background processing thread
164 */
165 class NotifyItem
166 {
167 public:
168 // data generic for all calls
|
169 krisbash 1.4 enum Type
170 {
|
171 mike 1.1 CONNECT, START_REQUEST, DELETE_HTTP, SET_TIMEOUT
172 } _type;
173 class HttpClientRep* _rep;
174 Message* _msg;
175
|
176 krisbash 1.4 // Connect-specific
|
177 mike 1.1 std::string _host;
178 unsigned short _port;
179 bool _secure;
|
180 krisbash 1.4 std::string _trustedCertsDir;
181 std::string _certFile;
182 std::string _privateKeyFile;
183 sem_t *_connectionSemaphore;
184 bool *_connectWorked;
185 bool *_connectComplete;
|
186 mike 1.1
|
187 krisbash 1.4 // start-request- specific
|
188 mike 1.1 std::string _verb;
189 std::string _uri;
190 std::map< std::string, std::string > _extraHeaders;
191 std::vector< unsigned char > _data;
192
|
193 krisbash 1.4 // timeout-specific
194 MI_Uint64 _timeout;
195
|
196 mike 1.1 // connect request
197 NotifyItem(
198 class HttpClientRep* rep,
|
199 krisbash 1.4 const char* host,
200 unsigned short port,
201 bool secure,
202 const char* trustedCertsDir,
203 const char* certFile,
204 const char* privateKeyFile,
205 sem_t * connectionSemaphore,
206 bool * connectWorked,
207 bool * connectComplete) :
|
208 mike 1.1 _type(CONNECT),
209 _rep(rep),
210 _host(host),
211 _port(port),
|
212 krisbash 1.4 _secure(secure),
213 _trustedCertsDir(trustedCertsDir == NULL ? "" : trustedCertsDir),
214 _certFile(certFile == NULL ? "" : certFile),
215 _privateKeyFile(privateKeyFile == NULL ? "" : privateKeyFile),
216 _connectionSemaphore(connectionSemaphore),
217 _connectWorked(connectWorked),
218 _connectComplete(connectComplete)
|
219 mike 1.1 {
220 _InitMsg();
221 }
222
223 // start-request
224 NotifyItem(
225 class HttpClientRep* rep,
226 const char* verb,
227 const char* uri,
228 const std::map< std::string, std::string >& extraHeaders,
229 const std::vector< unsigned char >& data):
230 _type(START_REQUEST),
231 _rep(rep),
232 _port(0),
233 _secure(false),
234 _verb(verb),
235 _uri(uri),
236 _extraHeaders(extraHeaders),
237 _data(data)
238 {
239 _InitMsg();
240 mike 1.1 }
241
242 // delete-http item
243 NotifyItem(
244 class HttpClientRep* rep):
245 _type(DELETE_HTTP),
246 _rep(rep),
247 _port(0),
248 _secure(false)
249 {
250 _InitMsg();
251 }
252
253 // set Timeout item
254 NotifyItem(
255 class HttpClientRep* rep,
|
256 krisbash 1.4 MI_Uint64 timeout):
|
257 mike 1.1 _type(SET_TIMEOUT),
258 _rep(rep),
259 _port(0),
|
260 krisbash 1.4 _secure(false),
261 _timeout(timeout)
|
262 mike 1.1 {
263 _InitMsg();
264 }
265
266 ~NotifyItem()
267 {
268 Message_Release(_msg);
269 }
270
271 private:
272 NotifyItem(const NotifyItem&);
273 void operator=(const NotifyItem&);
274
275 void _InitMsg()
276 {
|
277 krisbash 1.4 _msg = __Message_New(NoOpReqTag, sizeof (NoOpReq), 0, 0, CALLSITE);
278 _msg->argPtr = PtrToUint64(this);
279 *((void**)&_msg->clientId) = this;
280 LOGD2((ZT("NotifyItem::InitMsg - argument is this: %p --> 0x%lX"), this, (unsigned long)_msg->argPtr));
|
281 mike 1.1 }
282 };
283
284 /* Helper class - background thread operations */
285 class IOThread
286 {
287 public:
288 IOThread();
289 ~IOThread();
290
291 bool Start();
292
293 /* delegate work to background thread */
|
294 krisbash 1.4 bool PostItem(NotifyItem* item);
|
295 mike 1.1
296 //private:
297 // not supported
298 IOThread(const IOThread&);
299 void operator = (const IOThread&);
300
301 // impl
302
|
303 krisbash 1.4 // mirror functions for public API.
304 // note: these functions are always called from the context of the background thread
|
305 mike 1.1
306 void ConnectTh(NotifyItem* item);
307 void StartRequestTh(NotifyItem* item);
308 void DeleteHttpTh(NotifyItem* item);
309 void SetTimeoutTh(NotifyItem* item);
310
311 // data
|
312 krisbash 1.4 Thread* _th;
313 Selector _selector;
|
314 mike 1.1 };
315
316 /* thread handle - used to store ref-counted pointer to IOThread */
317 class IOThreadHandle
318 {
319 struct Item {
|
320 krisbash 1.4 ptrdiff_t ref;
|
321 mike 1.1 IOThread t;
322
323 Item() :ref(0){}
324 };
325
326 Item* _p;
327
328 void _AddRef()
329 {
|
330 krisbash 1.4 if (_p != NULL)
331 Atomic_Inc(&_p->ref);
|
332 mike 1.1 }
333
334 void _Release()
335 {
|
336 krisbash 1.4 if (_p != NULL && Atomic_Dec(&_p->ref) == 0)
|
337 mike 1.1 {
338 delete _p;
339 }
340 }
341 public:
342
343 // full set of ctors/dtors/assign operators
344 ~IOThreadHandle() {_Release();}
345 IOThreadHandle() : _p(0) {}
346 IOThreadHandle(const IOThreadHandle& x) : _p(x._p) {_AddRef();}
347 IOThreadHandle& operator =(const IOThreadHandle& x)
348 {
|
349 krisbash 1.4 if (_p != x._p)
|
350 mike 1.1 {
351 _Release();
352 _p = x._p;
353 _AddRef();
354 }
355 return *this;
356 }
357
358 // accessor
|
359 krisbash 1.4 IOThread* operator ->()
|
360 mike 1.1 {
361 return &_p->t;
362 }
363
364 // allocator
365 void Alloc()
366 {
367 _Release();
368 _p = new Item;
369 _AddRef();
370 }
371 };
372
373 // forward declaration
374 static IOThreadHandle _GetThreadObj();
375
|
376 krisbash 1.4 // impl class
|
377 mike 1.1 class HttpClientRep
378 {
379 public:
|
380 krisbash 1.4 HttpClientRep(HttpClientCallback* callback) :
|
381 mike 1.1 _callback(callback),
|
382 krisbash 1.4 _timeoutMS(1000),
383 _httpClient(NULL),
|
384 mike 1.1 _notify(false),
|
385 krisbash 1.4 _destroyed(false),
386 _lastStatus(httpclient::OKAY)
|
387 mike 1.1 {
388 _th = _GetThreadObj();
389 }
390
391 ~HttpClientRep()
392 {
393 if (_httpClient)
394 HttpClient_Delete(_httpClient);
395 }
396
397 // data
398 HttpClientCallback* _callback;
399 IOThreadHandle _th;
400 int _timeoutMS;
401 ::HttpClient* _httpClient;
402 bool _notify;
403 bool _destroyed;
|
404 krisbash 1.4 httpclient::Result _lastStatus;
|
405 mike 1.1 };
406
407 /* ****************************************************** */
408
|
409 krisbash 1.4 IOThread::IOThread()
|
410 mike 1.1 {
|
411 krisbash 1.4 _th = new Thread;
|
412 mike 1.1 }
413
414 IOThread::~IOThread()
415 {
416 // notify about stopping!
417 Selector_StopRunning(&_selector);
418
|
419 krisbash 1.4 Thread_Destroy(_th);
420 delete _th;
|
421 mike 1.1
|
422 krisbash 1.4 // clean up
|
423 mike 1.1 Selector_RemoveAllHandlers(&_selector);
424 Selector_Destroy(&_selector);
425 }
426
427 bool IOThread::Start()
428 {
|
429 krisbash 1.4 LOGD2((ZT("IOThread::Start - Begin. Initializing selector")));
|
430 mike 1.1 if (MI_RESULT_OK != Selector_Init(&_selector))
431 return false;
432
433 Selector_SetAllowEmptyFlag(&_selector, MI_TRUE);
434
|
435 krisbash 1.4 LOGD2((ZT("IOThread::Start - Creating Thread")));
436 if (Thread_CreateJoinable(_th, _proc, NULL, this) != MI_RESULT_OK)
437 {
438 LOGE2((ZT("IOThread::Start - Creation of thread failed")));
|
439 mike 1.1 return false;
|
440 krisbash 1.4 }
|
441 mike 1.1
|
442 krisbash 1.4 LOGD2((ZT("IOThread::Start - OK exit")));
|
443 mike 1.1 return true;
444 }
445
|
446 krisbash 1.4 bool IOThread::PostItem(NotifyItem* item)
447 {
448 #ifdef ENABLE_TRACING
449 LOGD2((ZT("IoThread::PostItem - Posting item: %d (%s)"), item->_type, notifyitemtypestr(item->_type)));
450 if (item->_type == NotifyItem::CONNECT)
451 {
452 LOGD2((ZT("PostItem - CONNECT. host: %s, port %d"), item->_host.c_str(), item->_port));
453 }
454 if (item->_type == NotifyItem::START_REQUEST)
455 {
456 LOGD2((ZT("PostItem - START_REQUEST. verb: %s, URI: %s"), item->_verb.c_str(), item->_uri.c_str()));
457 }
458 if (item->_type == NotifyItem::SET_TIMEOUT)
459 {
460 LOGD2((ZT("PostItem - SET_TIMEOUT. timeout: %lu us"), (unsigned long)item->_timeout));
461 }
462 #endif
|
463 mike 1.1
464 MI_Result res = Selector_CallInIOThread(&_selector, threadDelegation, this, item->_msg);
465
466 return res == MI_RESULT_OK;
467 }
468
469 void IOThread::ConnectTh(NotifyItem* item)
470 {
471 MI_Result res = HttpClient_New_Connector(
472 &item->_rep->_httpClient,
473 &_selector,
474 item->_host.c_str(),
475 item->_port,
476 item->_secure,
477 httpClientCallbackOnStatus,
478 httpClientCallbackOnResponse,
|
479 krisbash 1.4 item->_rep,
480 item->_trustedCertsDir.c_str(),
481 item->_certFile.c_str(),
482 item->_privateKeyFile.c_str());
|
483 mike 1.1
|
484 krisbash 1.4 if (res != MI_RESULT_OK)
|
485 mike 1.1 {
|
486 krisbash 1.4 *item->_connectWorked = false;
487 LOGE2((ZT("IOThread::ConnectTh - HTTP client connect failed with result: %d (%s)"), (int)res, mistrerror(res)));
|
488 mike 1.1 item->_rep->_callback->OnStatus(httpclient::FAILED);
|
489 krisbash 1.4 item->_rep->_notify = true;
490 }
491 else
492 {
493 // item->_rep->_callback->OnStatus(httpclient::OKAY);
494 LOGD2((ZT("IOThread::ConnectTh - HTTP client connect succeeded")));
495 *item->_connectWorked = true;
|
496 mike 1.1 }
497
|
498 krisbash 1.4 *item->_connectComplete = true;
499 HttpClient_SetTimeout(item->_rep->_httpClient, (MI_Uint64)item->_rep->_timeoutMS * 1000);
500 sem_post(&s_connectionSemaphore);
501 LOGD2((ZT("IOThread::ConnectTh - HttpClient_New_Connector returned result: %d (%s), timeout: %d ms"), (int)res, mistrerror(res), item->_rep->_timeoutMS));
502 *item->_connectComplete = true;
|
503 mike 1.1 }
504
505 void IOThread::StartRequestTh(NotifyItem* item)
506 {
|
507 krisbash 1.4 Page* c_data = NULL;
|
508 mike 1.1 const char* c_verb = item->_verb.c_str();
509 HttpClientRequestHeaders c_headers;
510 std::vector< std::string > headers_strings;
511 std::vector< const char* > headers_pointers;
512
513 memset(&c_headers, 0, sizeof(c_headers));
514
515 if (!item->_data.empty() > 0)
516 {
|
517 krisbash 1.4 c_data = (Page*)PAL_Malloc(item->_data.size() + sizeof (Page));
|
518 mike 1.1 /* clear header */
519 memset(c_data, 0, sizeof(Page));
520
521 c_data->u.s.size = (unsigned int)item->_data.size();
522
523 memcpy(c_data+1, &item->_data[0], item->_data.size());
524 }
525
526 if (!item->_extraHeaders.empty())
527 {
528 // create array of strings
529 for (std::map< std::string, std::string >::const_iterator it = item->_extraHeaders.begin(); it != item->_extraHeaders.end(); it++)
530 {
531 std::string s = it->first;
532 s += ": ";
533 s += it->second;
534 headers_strings.push_back(s);
535 }
536
537 // create array of pointers
538 for (size_t i = 0; i < headers_strings.size(); i++)
539 mike 1.1 {
540 headers_pointers.push_back(headers_strings[i].c_str());
541 }
542
543 // initialize c-struct:
544 c_headers.size = headers_pointers.size();
545 c_headers.data = &headers_pointers[0];
546 }
547
548 MI_Result res = HttpClient_StartRequest(
549 item->_rep->_httpClient,
550 c_verb,
551 item->_uri.c_str(),
552 &c_headers,
553 &c_data);
554
|
555 krisbash 1.4 if (c_data != NULL)
556 {
557 PAL_Free(c_data);
558 c_data = NULL;
559 }
|
560 mike 1.1
|
561 krisbash 1.4 if (res != MI_RESULT_OK)
|
562 mike 1.1 {
|
563 krisbash 1.4 LOGE2((ZT("IOThread::_StartRequestTh - HTTP client request failed with error: %d (%s)"), (int)res, mistrerror(res)));
564 item->_rep->_callback->OnStatus(res == MI_RESULT_TIME_OUT ? httpclient::TIMEOUT : httpclient::FAILED);
565 }
566 else
567 {
568 LOGD2((ZT("IOThread::_StartRequestTh - HTTP client request succeeded")));
569 item->_rep->_callback->OnStatus(httpclient::OKAY);
|
570 mike 1.1 }
571 }
572
573 void IOThread::DeleteHttpTh(NotifyItem* item)
574 {
|
575 krisbash 1.4 LOGD2((ZT("_DeleteHttpTh - Deleting HTTP thread")));
576
577 // Clean up here as the first thread is not waiting for any signal for this to be completed.
|
578 mike 1.1 HttpClient_Delete(item->_rep->_httpClient);
|
579 krisbash 1.4 item->_rep->_httpClient = NULL;
580 item->_rep->_destroyed = true;
|
581 mike 1.1
|
582 krisbash 1.4 LOGD2((ZT("_DeleteHttpTh - Done")));
|
583 mike 1.1 }
584
585 void IOThread::SetTimeoutTh(NotifyItem* item)
586 {
|
587 krisbash 1.4 LOGD2((ZT("IOThread::SetTimeoutTh - Item: %p, rep: %p, timeout: %lu us"), item, item->_rep, (unsigned long)item->_rep->_timeoutMS * 1000));
588 HttpClient_SetTimeout(item->_rep->_httpClient, (MI_Uint64)item->_rep->_timeoutMS * 1000);
|
589 mike 1.1 }
590
591 static IOThreadHandle _GetThreadObj()
592 {
593 static IOThreadHandle s_obj;
594 static int s_init = 0;
595 static pthread_mutex_t s_mutex = PTHREAD_MUTEX_INITIALIZER;
596
597 /* check if we may need to init */
598 if (!s_init)
599 {
600 pthread_mutex_lock(&s_mutex);
601
602 /* check if we really need to init or get here by race-condition */
603 if (!s_init)
604 {
605 s_obj.Alloc();
606 s_obj->Start();
607 s_init = 1;
608 }
|
609 krisbash 1.4
|
610 mike 1.1 pthread_mutex_unlock(&s_mutex);
611
612 }
613
614 return s_obj;
615 }
616
617 /* ******************************************** */
618
619 HttpClient::~HttpClient()
620 {
|
621 krisbash 1.4 LOGD2((ZT("HttpClient::~HttpClient - Begin")));
622
623 pthread_mutex_lock(&m_httpClientLock);
624
|
625 mike 1.1 NotifyItem* item = new NotifyItem(_rep);
626
|
627 krisbash 1.4 if (_rep != NULL)
|
628 mike 1.1 {
|
629 krisbash 1.4 if (_rep->_th->PostItem(item))
630 {
631 /* wait for thread to complete operation */
632 while (!_rep->_destroyed)
633 usleep(50);
634 }
635 delete _rep;
|
636 mike 1.1 }
|
637 krisbash 1.4 pthread_mutex_unlock(&m_httpClientLock);
638 LOGD2((ZT("HttpClient::~HttpClient finished")));
|
639 mike 1.1 }
640
|
641 krisbash 1.4 HttpClient::HttpClient(
642 HttpClientCallback* callback)
|
643 mike 1.1 {
|
644 krisbash 1.4 pthread_mutex_init(&m_httpClientLock, NULL);
645 pthread_mutex_lock(&m_httpClientLock);
646
|
647 mike 1.1 _rep = new HttpClientRep(callback);
|
648 krisbash 1.4 pthread_mutex_unlock(&m_httpClientLock);
649 LOGD2((ZT("HttpClient::HttpClient finished")));
|
650 mike 1.1 }
651
|
652 krisbash 1.4 Result HttpClient::Connect(
653 const char* host,
654 unsigned short port,
655 bool secure,
656 const char* trustedCertsDir,
657 const char* certFile,
658 const char* privateKeyFile)
|
659 mike 1.1 {
|
660 krisbash 1.4 if (!s_connectionSemaphoreInitialized)
661 {
662 s_connectionSemaphoreInitialized = true;
663 sem_init(&s_connectionSemaphore, 0, 0);
664 sem_init(&s_sendSemaphore, 0, 0);
665 pthread_mutex_init(&s_connectionSemaphoreLock, NULL);
666 pthread_mutex_init(&s_sendLock, NULL);
667 }
668
669 pthread_mutex_lock(&s_connectionSemaphoreLock);
670
671 bool connectWorked = false;
672 bool connectComplete = false;
673
674 LOGD2((ZT("HttpClient::Connect - host: %s, port: %u"), host, (unsigned int)port));
675 if (secure)
676 {
677 LOGD2((ZT("HttpClient::Connect - trustedCertsDir: %s, certFile: %s, privateKeyFile: %s"), trustedCertsDir, certFile, privateKeyFile));
678 }
679
680 std::string EncodedHost(EscapeUriString(host, false));
681 krisbash 1.4
682 LOGD2((ZT("HttpClient::Connect - Beginning connection")));
683
684 NotifyItem* item = new NotifyItem(_rep,
685 EncodedHost.empty() ? "" : EncodedHost.c_str(),
686 port,
687 secure,
688 trustedCertsDir,
689 certFile,
690 privateKeyFile,
691 &s_connectionSemaphore,
692 &connectWorked,
693 &connectComplete);
|
694 mike 1.1
695 if (!_rep->_th->PostItem(item))
696 {
697 delete item;
|
698 krisbash 1.4 LOGE2((ZT("HttpClient::Connect - PostItem failed")));
|
699 mike 1.1 return httpclient::FAILED;
700 }
701
|
702 krisbash 1.4 #if defined(__hpux) || defined(macos) || defined(__SunOS_5_9) // these OSs don't have sem_timedwait
703 // Yeah, sure, this is async. You betcha. That's why we're going to wait
704 // here until the connect is complete.
705 LOGD2((ZT("HttpClient::Connect - Beginning wait for connection complete semaphore")));
706 while (!connectComplete)
707 {
708 LOGD2((ZT("HttpClient::Connect - Waiting for connection complete semaphore")));
709 sem_wait(&s_connectionSemaphore);
710 LOGD2((ZT("HttpClient::Connect - Connection complete semaphore received. connectComplete was %s"), connectComplete ? "True" : "False"));
711 }
712 #else
713 // Wait 30 seconds until the connect is complete or there was an error.
714 struct timespec timeout_time;
715 time_t current_time = time(NULL);
716
717 LOGD2((ZT("HttpClient::Connect - Waiting for connection complete semaphore")));
718 timeout_time.tv_sec = current_time + 31; // 30 - 31 seconds, since we don't compute tv_nsec.
719 timeout_time.tv_nsec = 0;
720 if (sem_timedwait(&s_connectionSemaphore, &timeout_time) < 0)
721 { // in most cases, errno here will be ETIMEDOUT
722 LOGD2((ZT("HttpClient::Connect - Connect process error: %d (%s)"), errno, strerror(errno)));
723 krisbash 1.4 }
724 #endif
725 LOGD2((ZT("HttpClient::Connect - Connection request done. connectComplete was %d, connectWorked was: %d"), connectComplete, connectComplete));
726 // sem_destroy(&connectionSemaphore);
727
728 pthread_mutex_unlock(&s_connectionSemaphoreLock);
729 if (!connectWorked)
730 {
731 // failure
732 LOGE2((ZT("HttpClient::Connect - connection failed")));
733 return httpclient::FAILED;
734 }
735
736 LOGD2((ZT("HttpClient::Connect - OK")));
|
737 mike 1.1 return httpclient::OKAY;
738 }
739
740 Result HttpClient::StartRequest(
741 const char* verb,
742 const char* uri,
743 const std::map< std::string, std::string >& extraHeaders,
744 const std::vector< unsigned char >& data,
745 bool blockUntilCompleted)
746 {
|
747 krisbash 1.4 pthread_mutex_lock(&s_sendLock);
748
749 std::string EncodedUri(EscapeUriString(uri, true));
750
751 LOGD2((ZT("HttpClient::StartRequest - verb: %s, URI: %s"), verb, uri));
752 NotifyItem* item = new NotifyItem(_rep, verb, EncodedUri.empty() ? "" : EncodedUri.c_str(), extraHeaders, data);
753 _rep->_notify = false;
754 s_notifySet = false;
|
755 mike 1.1
756 if (!_rep->_th->PostItem(item))
757 {
|
758 krisbash 1.4 LOGE2((ZT("HttpClient::StartRequest - PostItem failed")));
759 pthread_mutex_unlock(&s_sendLock);
|
760 mike 1.1 delete item;
761 return httpclient::FAILED;
762 }
763
764 /* wait for thread to complete operation */
|
765 krisbash 1.4 if (blockUntilCompleted)
766 {
767 httpclient::Result res;
768
769 #if defined(__hpux) || defined(macos) || defined(__SunOS_5_9) // these OSs don't have sem_timedwait
770 // Yeah, sure, this is async. You betcha. That's why we're going to wait
771 // here until the send is complete.
772 while (!s_notifySet)
773 {
774 sem_wait(&s_sendSemaphore);
775 }
776 #else
777 // Wait 30 seconds until the send is complete or there was an error
778 struct timespec timeout_time;
779 time_t current_time = time(NULL);
780 int r;
781
782 LOGD2((ZT("HttpClient::StartRequest - Waiting for send semaphore")));
783 timeout_time.tv_sec = current_time + 31; // 30 - 31 seconds, since we don't compute tv_nsec.
784 timeout_time.tv_nsec = 0;
785 if ((r = sem_timedwait(&s_sendSemaphore, &timeout_time)) < 0)
786 krisbash 1.4 { // in most cases, errno here will be ETIMEDOUT
787 LOGE2((ZT("HttpClient::StartRequest - status: %d, errno: %d (%s)"), r, errno, strerror(errno)));
788 }
789 else
790 {
791 LOGD2((ZT("HttpClient::StartRequest - sem_timedwait returned status: %d"), r));
792 }
793 #endif
794
795 if (!_rep->_notify)
796 {
797 LOGE2((ZT("HttpClient::StartRequest - Timed out. rep: %p"), _rep));
798 res = httpclient::TIMEOUT;
799 }
800 else
801 res = _rep->_lastStatus;
|
802 mike 1.1
803 _rep->_notify = false;
|
804 krisbash 1.4 pthread_mutex_unlock(&s_sendLock);
805 return res;
806 }
|
807 mike 1.1
|
808 krisbash 1.4 pthread_mutex_unlock(&s_sendLock);
809 return httpclient::OKAY;
|
810 mike 1.1 }
811
812 void HttpClient::SetOperationTimeout(
813 int timeoutMS)
814 {
815 _rep->_timeoutMS = timeoutMS;
816
|
817 krisbash 1.4 NotifyItem* item = new NotifyItem(_rep, timeoutMS * 1000);
|
818 mike 1.1
819 if (!_rep->_th->PostItem(item))
820 {
|
821 krisbash 1.4 LOGE2((ZT("HttpClient::SetOperationTimeout - PostItem failed")));
|
822 mike 1.1 delete item;
823 }
824 }
825
826 HTTPCLIENT_END
827
|
828 krisbash 1.4 static MI_Uint32 _proc(
829 void* self)
|
830 mike 1.1 {
831 httpclient::IOThread* pThis = (httpclient::IOThread*)self;
832 // keep runnning until terminated
|
833 krisbash 1.4 LOGD2((ZT("_proc - Begin. Running selector thread")));
834 Selector_Run(&pThis->_selector, TIME_NEVER, MI_FALSE);
|
835 mike 1.1
|
836 krisbash 1.4 LOGD2((ZT("_proc - OK exit")));
|
837 mike 1.1 return 0;
838 }
839
|
840 krisbash 1.4 static void threadDelegation(
841 void* self,
842 Message* message)
|
843 mike 1.1 {
844 httpclient::IOThread* pThis = (httpclient::IOThread*)self;
|
845 krisbash 1.4 httpclient::NotifyItem* item = (httpclient::NotifyItem*)Uint64ToPtr(message->argPtr);
846 LOGD2((ZT("threadDelegation - item: %p, Argument: %lX"), item, (unsigned long)item->_msg->argPtr));
|
847 mike 1.1
|
848 krisbash 1.4 if (item != NULL)
|
849 mike 1.1 {
|
850 krisbash 1.4 switch (item->_type)
851 {
852 case httpclient::NotifyItem::CONNECT:
853 pThis->ConnectTh(item);
854 break;
855
856 case httpclient::NotifyItem::START_REQUEST:
857 pThis->StartRequestTh(item);
858 break;
859
860 case httpclient::NotifyItem::DELETE_HTTP:
861 pThis->DeleteHttpTh(item);
862 // delete item->_rep;
863 break;
864
865 case httpclient::NotifyItem::SET_TIMEOUT:
866 pThis->SetTimeoutTh(item);
867 break;
868
869 default:
870 assert(!"unexpected item type");
871 krisbash 1.4 break;
872 }
|
873 mike 1.1
|
874 krisbash 1.4 delete item;
|
875 mike 1.1 }
876 }
877
878 static void httpClientCallbackOnStatus(
879 ::HttpClient* http,
880 void* callbackData,
881 MI_Result result)
882 {
883 httpclient::HttpClientRep* rep = (httpclient::HttpClientRep*)callbackData;
|
884 krisbash 1.4 httpclient::Result user_res = httpclient::FAILED;
|
885 mike 1.1
|
886 krisbash 1.4 LOGD2((ZT("httpClientCallbackOnStatus - Begin. MI result: %d (%s)"), (int)result, mistrerror(result)));
887 if (result == MI_RESULT_OK)
888 user_res = httpclient::OKAY;
889 else if (result == MI_RESULT_TIME_OUT)
890 user_res = httpclient::TIMEOUT;
891 else if (result == MI_RESULT_NOT_FOUND)
892 user_res = httpclient::NOTFOUND;
893 else if (result == MI_RESULT_FAILED)
894 user_res = httpclient::TIMEOUT;
895
896 if (rep != NULL && rep->_callback != NULL)
897 {
898 LOGD2((ZT("httpClientCallbackOnStatus - Calling caller's callback. HTTP client status: %d (%s)"), user_res, clientstatusstr(user_res)));
899 rep->_callback->OnStatus(user_res);
900 }
901
902 rep->_lastStatus = user_res;
903 LOGD2((ZT("httpClientCallbackOnstatus - Notify was set. rep: %p"), rep));
904 s_notifySet = true;
|
905 mike 1.1 rep->_notify = true;
|
906 krisbash 1.4 sem_post(&s_sendSemaphore);
|
907 mike 1.1 }
908
909 static MI_Boolean httpClientCallbackOnResponse(
910 ::HttpClient* http,
911 void* callbackData,
912 const HttpClientResponseHeader* headers,
913 MI_Sint64 contentSize,
|
914 krisbash 1.4 MI_Boolean lastChunk,
|
915 mike 1.1 Page** data)
916 {
917 httpclient::HttpClientRep* rep = (httpclient::HttpClientRep*)callbackData;
918
|
919 krisbash 1.4 LOGD2((ZT("httpClientCallbackOnResponse - content size (-1 means use chunk size): %ld, lastChunk: %d"), (long)contentSize, (int)lastChunk));
920
921 if (headers != NULL)
|
922 mike 1.1 {
923 std::map< std::string, std::string > user_headers;
924
925 for (MI_Uint32 i = 0; i < headers->sizeHeaders; i++)
926 {
|
927 krisbash 1.4 LOGD2((ZT("httpClientCallbackOnResponse - H: %s --> %s"), headers->headers[i].name, headers->headers[i].value));
|
928 mike 1.1 user_headers[headers->headers[i].name] = headers->headers[i].value;
929 }
930
|
931 krisbash 1.4 if (headers->httpError < 100 || headers->httpError > 999)
932 {
933 LOGE2((ZT("httpClientCallbackOnResponse - HTTP status < 100 or > 999 in callback: %d"), headers->httpError));
934 }
935 LOGD2((ZT("httpClientCallbackOnResponse - Returning %ld bytes data. HTTP status: %d"), (long)contentSize, headers->httpError));
936 if (!rep->_callback->OnResponseHeader(headers->httpError, user_headers, (int)contentSize))
937 {
938 LOGE2((ZT("httpClientCallbackOnResponse - Error returning header")));
939
|
940 mike 1.1 return MI_FALSE;
|
941 krisbash 1.4 }
|
942 mike 1.1 }
943
|
944 krisbash 1.4 if (data != NULL && *data != NULL)
|
945 mike 1.1 {
946 std::vector< unsigned char > user_data(
|
947 krisbash 1.4 (unsigned char*)(*data + 1), (unsigned char*)(*data + 1) + (*data)->u.s.size);
|
948 mike 1.1
|
949 krisbash 1.4 if (!rep->_callback->OnResponseData(user_data, lastChunk))
950 {
951 LOGE2((ZT("httpClientCallbackOnResponse - Error from OnResponseData")));
|
952 mike 1.1 return MI_FALSE;
|
953 krisbash 1.4 }
954 LOGD2((ZT("httpClientCallbackOnResponse - Returning %u bytes data"), (unsigned int)(*data)->u.s.size));
|
955 mike 1.1 }
956
|
957 krisbash 1.4 LOGD2((ZT("httpClientCallbackOnStatusResponse - Returning OK (MI_TRUE)")));
|
958 mike 1.1 return MI_TRUE;
959 }
|