(file) Return to httpclientcxx.cpp CVS log (file) (dir) Up to [OMI] / omi / deprecated / httpclientcxx

  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              }

ViewCVS 0.9.2