ConnectionProvider.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. /***************************************************************************
  2. *
  3. * Project _____ __ ____ _ _
  4. * ( _ ) /__\ (_ _)_| |_ _| |_
  5. * )(_)( /(__)\ )( (_ _)(_ _)
  6. * (_____)(__)(__)(__) |_| |_|
  7. *
  8. *
  9. * Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
  10. *
  11. * Licensed under the Apache License, Version 2.0 (the "License");
  12. * you may not use this file except in compliance with the License.
  13. * You may obtain a copy of the License at
  14. *
  15. * http://www.apache.org/licenses/LICENSE-2.0
  16. *
  17. * Unless required by applicable law or agreed to in writing, software
  18. * distributed under the License is distributed on an "AS IS" BASIS,
  19. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  20. * See the License for the specific language governing permissions and
  21. * limitations under the License.
  22. *
  23. ***************************************************************************/
  24. #include "./ConnectionProvider.hpp"
  25. #include "oatpp/network/tcp/Connection.hpp"
  26. #include "oatpp/core/utils/ConversionUtils.hpp"
  27. #include <fcntl.h>
  28. #if defined(WIN32) || defined(_WIN32)
  29. #include <io.h>
  30. #include <winsock2.h>
  31. #include <ws2tcpip.h>
  32. #else
  33. #include <netdb.h>
  34. #include <arpa/inet.h>
  35. #include <sys/socket.h>
  36. #include <unistd.h>
  37. #endif
  38. namespace oatpp { namespace network { namespace tcp { namespace client {
  39. void ConnectionProvider::ConnectionInvalidator::invalidate(const std::shared_ptr<data::stream::IOStream>& connection) {
  40. /************************************************
  41. * WARNING!!!
  42. *
  43. * shutdown(handle, SHUT_RDWR) <--- DO!
  44. * close(handle); <--- DO NOT!
  45. *
  46. * DO NOT CLOSE file handle here -
  47. * USE shutdown instead.
  48. * Using close prevent FDs popping out of epoll,
  49. * and they'll be stuck there forever.
  50. ************************************************/
  51. auto c = std::static_pointer_cast<network::tcp::Connection>(connection);
  52. v_io_handle handle = c->getHandle();
  53. #if defined(WIN32) || defined(_WIN32)
  54. shutdown(handle, SD_BOTH);
  55. #else
  56. shutdown(handle, SHUT_RDWR);
  57. #endif
  58. }
  59. ConnectionProvider::ConnectionProvider(const network::Address& address)
  60. : m_invalidator(std::make_shared<ConnectionInvalidator>())
  61. , m_address(address)
  62. {
  63. setProperty(PROPERTY_HOST, address.host);
  64. setProperty(PROPERTY_PORT, oatpp::utils::conversion::int32ToStr(address.port));
  65. }
  66. provider::ResourceHandle<data::stream::IOStream> ConnectionProvider::get() {
  67. auto portStr = oatpp::utils::conversion::int32ToStr(m_address.port);
  68. struct addrinfo hints;
  69. memset(&hints, 0, sizeof(struct addrinfo));
  70. hints.ai_socktype = SOCK_STREAM;
  71. hints.ai_flags = 0;
  72. hints.ai_protocol = 0;
  73. switch(m_address.family) {
  74. case Address::IP_4: hints.ai_family = AF_INET; break;
  75. case Address::IP_6: hints.ai_family = AF_INET6; break;
  76. default:
  77. hints.ai_family = AF_UNSPEC;
  78. }
  79. struct addrinfo* result;
  80. auto res = getaddrinfo(m_address.host->c_str(), portStr->c_str(), &hints, &result);
  81. if (res != 0) {
  82. #if defined(WIN32) || defined(_WIN32)
  83. throw std::runtime_error("[oatpp::network::tcp::client::ConnectionProvider::getConnection()]. "
  84. "Error. Call to getaddrinfo() failed with code " + std::to_string(res));
  85. #else
  86. std::string errorString = "[oatpp::network::tcp::client::ConnectionProvider::getConnection()]. Error. Call to getaddrinfo() failed: ";
  87. throw std::runtime_error(errorString.append(gai_strerror(res)));
  88. #endif
  89. }
  90. if (result == nullptr) {
  91. throw std::runtime_error("[oatpp::network::tcp::client::ConnectionProvider::getConnection()]. Error. Call to getaddrinfo() returned no results.");
  92. }
  93. struct addrinfo* currResult = result;
  94. oatpp::v_io_handle clientHandle = INVALID_IO_HANDLE;
  95. while(currResult != nullptr) {
  96. clientHandle = socket(currResult->ai_family, currResult->ai_socktype, currResult->ai_protocol);
  97. if(clientHandle >= 0) {
  98. if(connect(clientHandle, currResult->ai_addr, (int)currResult->ai_addrlen) == 0) {
  99. break;
  100. } else {
  101. #if defined(WIN32) || defined(_WIN32)
  102. ::closesocket(clientHandle);
  103. #else
  104. ::close(clientHandle);
  105. #endif
  106. }
  107. }
  108. currResult = currResult->ai_next;
  109. }
  110. freeaddrinfo(result);
  111. if(currResult == nullptr) {
  112. throw std::runtime_error("[oatpp::network::tcp::client::ConnectionProvider::getConnection()]: Error. Can't connect.");
  113. }
  114. #ifdef SO_NOSIGPIPE
  115. int yes = 1;
  116. v_int32 ret = setsockopt(clientHandle, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(int));
  117. if(ret < 0) {
  118. OATPP_LOGD("[oatpp::network::tcp::client::ConnectionProvider::getConnection()]", "Warning. Failed to set %s for socket", "SO_NOSIGPIPE");
  119. }
  120. #endif
  121. return provider::ResourceHandle<data::stream::IOStream>(
  122. std::make_shared<oatpp::network::tcp::Connection>(clientHandle),
  123. m_invalidator
  124. );
  125. }
  126. oatpp::async::CoroutineStarterForResult<const provider::ResourceHandle<data::stream::IOStream>&> ConnectionProvider::getAsync() {
  127. class ConnectCoroutine : public oatpp::async::CoroutineWithResult<ConnectCoroutine, const provider::ResourceHandle<oatpp::data::stream::IOStream>&> {
  128. private:
  129. std::shared_ptr<ConnectionInvalidator> m_connectionInvalidator;
  130. network::Address m_address;
  131. oatpp::v_io_handle m_clientHandle;
  132. private:
  133. struct addrinfo* m_result;
  134. struct addrinfo* m_currentResult;
  135. bool m_isHandleOpened;
  136. public:
  137. ConnectCoroutine(const std::shared_ptr<ConnectionInvalidator>& connectionInvalidator,
  138. const network::Address& address)
  139. : m_connectionInvalidator(connectionInvalidator)
  140. , m_address(address)
  141. , m_result(nullptr)
  142. , m_currentResult(nullptr)
  143. , m_isHandleOpened(false)
  144. {}
  145. ~ConnectCoroutine() {
  146. if(m_result != nullptr) {
  147. freeaddrinfo(m_result);
  148. }
  149. }
  150. Action act() override {
  151. auto portStr = oatpp::utils::conversion::int32ToStr(m_address.port);
  152. struct addrinfo hints;
  153. memset(&hints, 0, sizeof(struct addrinfo));
  154. hints.ai_socktype = SOCK_STREAM;
  155. hints.ai_flags = 0;
  156. hints.ai_protocol = 0;
  157. switch(m_address.family) {
  158. case Address::IP_4: hints.ai_family = AF_INET; break;
  159. case Address::IP_6: hints.ai_family = AF_INET6; break;
  160. default:
  161. hints.ai_family = AF_UNSPEC;
  162. }
  163. // TODO make call to get addrinfo non-blocking !!!
  164. auto res = getaddrinfo(m_address.host->c_str(), portStr->c_str(), &hints, &m_result);
  165. if (res != 0) {
  166. return error<async::Error>(
  167. "[oatpp::network::tcp::client::ConnectionProvider::getConnectionAsync()]. Error. Call to getaddrinfo() failed.");
  168. }
  169. m_currentResult = m_result;
  170. if (m_result == nullptr) {
  171. return error<async::Error>(
  172. "[oatpp::network::tcp::client::ConnectionProvider::getConnectionAsync()]. Error. Call to getaddrinfo() returned no results.");
  173. }
  174. return yieldTo(&ConnectCoroutine::iterateAddrInfoResults);
  175. }
  176. Action iterateAddrInfoResults() {
  177. /*
  178. * Close previously opened socket here.
  179. * Don't ever close socket in the method which returns action ioWait or ioRepeat
  180. */
  181. if(m_isHandleOpened) {
  182. m_isHandleOpened = false;
  183. #if defined(WIN32) || defined(_WIN32)
  184. ::closesocket(m_clientHandle);
  185. #else
  186. ::close(m_clientHandle);
  187. #endif
  188. }
  189. if(m_currentResult != nullptr) {
  190. m_clientHandle = socket(m_currentResult->ai_family, m_currentResult->ai_socktype, m_currentResult->ai_protocol);
  191. #if defined(WIN32) || defined(_WIN32)
  192. if (m_clientHandle == INVALID_SOCKET) {
  193. m_currentResult = m_currentResult->ai_next;
  194. return repeat();
  195. }
  196. u_long flags = 1;
  197. ioctlsocket(m_clientHandle, FIONBIO, &flags);
  198. #else
  199. if (m_clientHandle < 0) {
  200. m_currentResult = m_currentResult->ai_next;
  201. return repeat();
  202. }
  203. fcntl(m_clientHandle, F_SETFL, O_NONBLOCK);
  204. #endif
  205. #ifdef SO_NOSIGPIPE
  206. int yes = 1;
  207. v_int32 ret = setsockopt(m_clientHandle, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(int));
  208. if(ret < 0) {
  209. OATPP_LOGD("[oatpp::network::tcp::client::ConnectionProvider::getConnectionAsync()]", "Warning. Failed to set %s for socket", "SO_NOSIGPIPE");
  210. }
  211. #endif
  212. m_isHandleOpened = true;
  213. return yieldTo(&ConnectCoroutine::doConnect);
  214. }
  215. return error<Error>("[oatpp::network::tcp::client::ConnectionProvider::getConnectionAsync()]: Error. Can't connect.");
  216. }
  217. Action doConnect() {
  218. errno = 0;
  219. auto res = connect(m_clientHandle, m_currentResult->ai_addr, (int)m_currentResult->ai_addrlen);
  220. #if defined(WIN32) || defined(_WIN32)
  221. auto error = WSAGetLastError();
  222. if(res == 0 || error == WSAEISCONN) {
  223. return _return(provider::ResourceHandle<data::stream::IOStream>(
  224. std::make_shared<oatpp::network::tcp::Connection>(m_clientHandle),
  225. m_connectionInvalidator
  226. ));
  227. }
  228. if(error == WSAEWOULDBLOCK || error == WSAEINPROGRESS) {
  229. return ioWait(m_clientHandle, oatpp::async::Action::IOEventType::IO_EVENT_WRITE);
  230. } else if(error == WSAEINTR || error == WSAEALREADY) {
  231. return ioRepeat(m_clientHandle, oatpp::async::Action::IOEventType::IO_EVENT_WRITE);
  232. } else if(error == WSAEINVAL) {
  233. return AbstractCoroutine::error(new async::Error(
  234. "[oatpp::network::tcp::client::ConnectionProvider::doConnect()]: Error. The parameter m_clientHandle is a listening socket."));
  235. }
  236. #else
  237. if(res == 0 || errno == EISCONN) {
  238. return _return(provider::ResourceHandle<data::stream::IOStream>(
  239. std::make_shared<oatpp::network::tcp::Connection>(m_clientHandle),
  240. m_connectionInvalidator
  241. ));
  242. }
  243. if(errno == EALREADY || errno == EINPROGRESS) {
  244. return ioWait(m_clientHandle, oatpp::async::Action::IOEventType::IO_EVENT_WRITE);
  245. } else if(errno == EINTR) {
  246. return ioRepeat(m_clientHandle, oatpp::async::Action::IOEventType::IO_EVENT_WRITE);
  247. }
  248. #endif
  249. m_currentResult = m_currentResult->ai_next;
  250. return yieldTo(&ConnectCoroutine::iterateAddrInfoResults);
  251. }
  252. };
  253. return ConnectCoroutine::startForResult(m_invalidator, m_address);
  254. }
  255. }}}}