http2channel.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. /*
  2. * MIT License
  3. *
  4. * Copyright (c) 2019 Alexey Edelev <semlanik@gmail.com>, Viktor Kopp <vifactor@gmail.com>
  5. *
  6. * This file is part of qtprotobuf project https://git.semlanik.org/semlanik/qtprotobuf
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy of this
  9. * software and associated documentation files (the "Software"), to deal in the Software
  10. * without restriction, including without limitation the rights to use, copy, modify,
  11. * merge, publish, distribute, sublicense, and/or sell copies of the Software, and
  12. * to permit persons to whom the Software is furnished to do so, subject to the following
  13. * conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in all copies
  16. * or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  19. * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  20. * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  21. * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  22. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. * DEALINGS IN THE SOFTWARE.
  24. */
  25. #include "http2channel.h"
  26. //FIXME: workaround for build issue
  27. #undef QT_LINKED_OPENSSL
  28. #include <QUrl>
  29. #include <QNetworkAccessManager>
  30. #include <QNetworkReply>
  31. #include <QNetworkRequest>
  32. #include <QEventLoop>
  33. #include <QTimer>
  34. #include <QtEndian>
  35. #include "asyncreply.h"
  36. #include "abstractclient.h"
  37. #include "abstractcredentials.h"
  38. #include <unordered_map>
  39. #include "qtprotobuflogging.h"
  40. using namespace qtprotobuf;
  41. namespace {
  42. /*!
  43. * This QNetworkReply::NetworkError -> AbstractChannel::StatusCode mapping should be kept in sync with original
  44. * <a href="https://github.com/grpc/grpc/blob/master/doc/statuscodes.md">gRPC status codes</a>
  45. */
  46. const static std::unordered_map<QNetworkReply::NetworkError, AbstractChannel::StatusCode> StatusCodeMap = {
  47. { QNetworkReply::ConnectionRefusedError, AbstractChannel::Unavailable },
  48. { QNetworkReply::RemoteHostClosedError, AbstractChannel::Unavailable },
  49. { QNetworkReply::HostNotFoundError, AbstractChannel::Unavailable },
  50. { QNetworkReply::TimeoutError, AbstractChannel::DeadlineExceeded },
  51. { QNetworkReply::OperationCanceledError, AbstractChannel::Unavailable },
  52. { QNetworkReply::SslHandshakeFailedError, AbstractChannel::PermissionDenied },
  53. { QNetworkReply::TemporaryNetworkFailureError, AbstractChannel::Unknown },
  54. { QNetworkReply::NetworkSessionFailedError, AbstractChannel::Unavailable },
  55. { QNetworkReply::BackgroundRequestNotAllowedError, AbstractChannel::Unknown },
  56. { QNetworkReply::TooManyRedirectsError, AbstractChannel::Unavailable },
  57. { QNetworkReply::InsecureRedirectError, AbstractChannel::PermissionDenied },
  58. { QNetworkReply::UnknownNetworkError, AbstractChannel::Unknown },
  59. { QNetworkReply::ProxyConnectionRefusedError, AbstractChannel::Unavailable },
  60. { QNetworkReply::ProxyConnectionClosedError, AbstractChannel::Unavailable },
  61. { QNetworkReply::ProxyNotFoundError, AbstractChannel::Unavailable },
  62. { QNetworkReply::ProxyTimeoutError, AbstractChannel::DeadlineExceeded },
  63. { QNetworkReply::ProxyAuthenticationRequiredError, AbstractChannel::Unauthenticated },
  64. { QNetworkReply::UnknownProxyError, AbstractChannel::Unknown },
  65. { QNetworkReply::ContentAccessDenied, AbstractChannel::PermissionDenied },
  66. { QNetworkReply::ContentOperationNotPermittedError, AbstractChannel::PermissionDenied },
  67. { QNetworkReply::ContentNotFoundError, AbstractChannel::NotFound },
  68. { QNetworkReply::AuthenticationRequiredError, AbstractChannel::PermissionDenied },
  69. { QNetworkReply::ContentReSendError, AbstractChannel::DataLoss },
  70. { QNetworkReply::ContentConflictError, AbstractChannel::InvalidArgument },
  71. { QNetworkReply::ContentGoneError, AbstractChannel::DataLoss },
  72. { QNetworkReply::UnknownContentError, AbstractChannel::Unknown },
  73. { QNetworkReply::ProtocolUnknownError, AbstractChannel::Unknown },
  74. { QNetworkReply::ProtocolInvalidOperationError, AbstractChannel::Unimplemented },
  75. { QNetworkReply::ProtocolFailure, AbstractChannel::Unknown },
  76. { QNetworkReply::InternalServerError, AbstractChannel::Internal },
  77. { QNetworkReply::OperationNotImplementedError, AbstractChannel::Unimplemented },
  78. { QNetworkReply::ServiceUnavailableError, AbstractChannel::Unavailable },
  79. { QNetworkReply::UnknownServerError, AbstractChannel::Unknown }};
  80. const char *GrpcAcceptEncodingHeader = "grpc-accept-encoding";
  81. const char *AcceptEncodingHeader = "accept-encoding";
  82. const char *TEHeader = "te";
  83. const char *GrpcStatusHeader = "grpc-status";
  84. }
  85. namespace qtprotobuf {
  86. struct Http2ChannelPrivate {
  87. struct ExpectedData {
  88. int expectedSize;
  89. QByteArray container;
  90. };
  91. QUrl url;
  92. QNetworkAccessManager nm;
  93. AbstractCredentials credentials;
  94. QSslConfiguration sslConfig;
  95. std::unordered_map<QNetworkReply *, ExpectedData> activeStreamReplies;
  96. QNetworkReply *post(const QString &method, const QString &service, const QByteArray &args, bool stream = false) {
  97. QUrl callUrl = url;
  98. callUrl.setPath("/" + service + "/" + method);
  99. qProtoDebug() << "Service call url: " << callUrl;
  100. QNetworkRequest request(callUrl);
  101. request.setHeader(QNetworkRequest::ContentTypeHeader, "application/grpc");
  102. request.setRawHeader(GrpcAcceptEncodingHeader, "identity,deflate,gzip");
  103. request.setRawHeader(AcceptEncodingHeader, "identity,gzip");
  104. request.setRawHeader(TEHeader, "trailers");
  105. request.setSslConfiguration(sslConfig);
  106. AbstractCredentials::CredentialMap callCredentials = credentials.callCredentials();
  107. for (auto i = callCredentials.begin(); i != callCredentials.end(); ++i) {
  108. request.setRawHeader(i.key().data(), i.value().toString().toUtf8());
  109. }
  110. request.setAttribute(QNetworkRequest::Http2DirectAttribute, true);
  111. QByteArray msg(5, '\0');
  112. *(int *)(msg.data() + 1) = qToBigEndian(args.size());
  113. msg += args;
  114. qProtoDebug() << "SEND: " << msg.size();
  115. QNetworkReply *networkReply = nm.post(request, msg);
  116. QObject::connect(networkReply, &QNetworkReply::sslErrors, [networkReply](const QList<QSslError> &errors) {
  117. qProtoCritical() << errors;
  118. // TODO: filter out noncritical SSL handshake errors
  119. // FIXME: error due to ssl failure is not transferred to the client: last error will be Operation canceled
  120. Http2ChannelPrivate::abortNetworkReply(networkReply);
  121. });
  122. if (!stream) {
  123. //TODO: Add configurable timeout logic
  124. QTimer::singleShot(6000, networkReply, [networkReply]() {
  125. Http2ChannelPrivate::abortNetworkReply(networkReply);
  126. });
  127. }
  128. return networkReply;
  129. }
  130. static void abortNetworkReply(QNetworkReply *networkReply) {
  131. if (networkReply->isRunning()) {
  132. networkReply->abort();
  133. }
  134. }
  135. static QByteArray processReply(QNetworkReply *networkReply, AbstractChannel::StatusCode &statusCode) {
  136. //Check if no network error occured
  137. if (networkReply->error() != QNetworkReply::NoError) {
  138. statusCode = StatusCodeMap.at(networkReply->error());
  139. return {};
  140. }
  141. //Check if server answer with error
  142. statusCode = static_cast<AbstractChannel::StatusCode>(networkReply->rawHeader(GrpcStatusHeader).toInt());
  143. if (statusCode != AbstractChannel::StatusCode::Ok) {
  144. return {};
  145. }
  146. //Message size doesn't matter for now
  147. return networkReply->readAll().mid(5);
  148. }
  149. Http2ChannelPrivate(const AbstractCredentials &credentials_) : credentials(credentials_) {
  150. if (credentials.channelCredentials().contains(QLatin1String("sslConfig"))) {
  151. sslConfig = credentials.channelCredentials().value(QLatin1String("sslConfig")).value<QSslConfiguration>();
  152. }
  153. if (sslConfig.isNull()) {
  154. url.setScheme("http");
  155. } else {
  156. url.setScheme("https");
  157. }
  158. }
  159. Http2ChannelPrivate(const QUrl &_url, const AbstractCredentials &_credentials)
  160. : url(_url)
  161. , credentials(_credentials)
  162. {
  163. if (url.scheme() == "https") {
  164. if (!credentials.channelCredentials().contains(QLatin1String("sslConfig"))) {
  165. throw std::invalid_argument("Https connection requested but not ssl configuration provided.");
  166. }
  167. sslConfig = credentials.channelCredentials().value(QLatin1String("sslConfig")).value<QSslConfiguration>();
  168. } else if (url.scheme().isEmpty()) {
  169. url.setScheme("http");
  170. }
  171. }
  172. };
  173. }
  174. Http2Channel::Http2Channel(const QString &addr, quint16 port, const AbstractCredentials &credentials) : AbstractChannel()
  175. , d(new Http2ChannelPrivate(credentials))
  176. {
  177. d->url.setHost(addr, QUrl::StrictMode);
  178. d->url.setPort(port);
  179. }
  180. Http2Channel::Http2Channel(const QUrl &url, const AbstractCredentials &credentials) : AbstractChannel()
  181. , d(new Http2ChannelPrivate(url, credentials))
  182. {
  183. }
  184. Http2Channel::~Http2Channel()
  185. {
  186. delete d;
  187. }
  188. AbstractChannel::StatusCode Http2Channel::call(const QString &method, const QString &service, const QByteArray &args, QByteArray &ret)
  189. {
  190. QEventLoop loop;
  191. QNetworkReply *networkReply = d->post(method, service, args);
  192. QObject::connect(networkReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
  193. //If reply was finished in same stack it doesn't make sense to start event loop
  194. if (!networkReply->isFinished()) {
  195. loop.exec();
  196. }
  197. StatusCode grpcStatus = StatusCode::Unknown;
  198. ret = d->processReply(networkReply, grpcStatus);
  199. qProtoDebug() << __func__ << "RECV: " << ret.toHex() << "grpcStatus" << grpcStatus;
  200. return grpcStatus;
  201. }
  202. void Http2Channel::call(const QString &method, const QString &service, const QByteArray &args, qtprotobuf::AsyncReply *reply)
  203. {
  204. QNetworkReply *networkReply = d->post(method, service, args);
  205. auto connection = QObject::connect(networkReply, &QNetworkReply::finished, reply, [reply, networkReply]() {
  206. StatusCode grpcStatus = StatusCode::Unknown;
  207. QByteArray data = Http2ChannelPrivate::processReply(networkReply, grpcStatus);
  208. qProtoDebug() << "RECV: " << data;
  209. if (StatusCode::Ok == grpcStatus ) {
  210. reply->setData(data);
  211. reply->finished();
  212. } else {
  213. reply->setData({});
  214. reply->error(grpcStatus);
  215. }
  216. });
  217. QObject::connect(reply, &AsyncReply::error, networkReply, [networkReply, connection](AbstractChannel::StatusCode code) {
  218. QObject::disconnect(connection);
  219. Http2ChannelPrivate::abortNetworkReply(networkReply);
  220. });
  221. }
  222. void Http2Channel::subscribe(const QString &method, const QString &service, const QByteArray &args, AbstractClient *client, const std::function<void (const QByteArray &)> &handler)
  223. {
  224. QNetworkReply *networkReply = d->post(method, service, args, true);
  225. auto connection = QObject::connect(networkReply, &QNetworkReply::readyRead, client, [networkReply, handler, this]() {
  226. auto replyIt = d->activeStreamReplies.find(networkReply);
  227. QByteArray data = networkReply->readAll();
  228. qProtoDebug() << "RECV" << data.size();
  229. if (replyIt == d->activeStreamReplies.end()) {
  230. qProtoDebug() << data.toHex();
  231. int expectedDataSize = qFromBigEndian(*(int *)(data.data() + 1)) + 5;
  232. qProtoDebug() << "First chunk received: " << data.size() << " expectedDataSize: " << expectedDataSize;
  233. if (expectedDataSize == 0) {
  234. handler(QByteArray());
  235. return;
  236. }
  237. Http2ChannelPrivate::ExpectedData dataContainer{expectedDataSize, QByteArray{}};
  238. d->activeStreamReplies.insert({networkReply, dataContainer});
  239. replyIt = d->activeStreamReplies.find(networkReply);
  240. }
  241. Http2ChannelPrivate::ExpectedData &dataContainer = replyIt->second;
  242. dataContainer.container.append(data);
  243. qProtoDebug() << "Proceed chunk: " << data.size() << " dataContainer: " << dataContainer.container.size() << " capacity: " << dataContainer.expectedSize;
  244. if (dataContainer.container.size() == dataContainer.expectedSize) {
  245. qProtoDebug() << "Full data received: " << data.size() << " dataContainer: " << dataContainer.container.size() << " capacity: " << dataContainer.expectedSize;
  246. handler(dataContainer.container.mid(5));
  247. d->activeStreamReplies.erase(replyIt);
  248. }
  249. });
  250. QObject::connect(client, &AbstractClient::destroyed, networkReply, [networkReply, connection, this](){
  251. d->activeStreamReplies.erase(networkReply);
  252. QObject::disconnect(connection);
  253. Http2ChannelPrivate::abortNetworkReply(networkReply);
  254. });
  255. //TODO: seems this connection might be invalid in case if this destroyed.
  256. //Think about correct handling of this situation
  257. QObject::connect(networkReply, &QNetworkReply::finished, [networkReply, this]() {
  258. d->activeStreamReplies.erase(networkReply);
  259. //TODO: implement error handling and subscription recovery here
  260. Http2ChannelPrivate::abortNetworkReply(networkReply);
  261. });
  262. }
  263. void Http2Channel::abort(AsyncReply *reply)
  264. {
  265. assert(reply != nullptr);
  266. reply->setData({});
  267. reply->error(StatusCode::Aborted);
  268. }