Support h2c upgrade
h2c upgrade is a mechanism for supporting http/2 on connections that
might not support alpn [1]. This is done by the client specifying
Connection: upgrade
Upgrade: h2c
This looks very similar to a websocket upgrade, which h2c replacing
websocket. Because of this, the existing upgrade code needs some
upgrades to avoid parsing twice.
Tested:
```
curl -u root:0penBmc --http2 -k http://192.168.7.2:443/redfish/v1/SessionService/Sessions
```
Succeeds and verbose logging shows that http upgrade succeeded
websocket_test.py in the scripts directory connects and reports events
[1] https://datatracker.ietf.org/doc/html/rfc7540#section-11.8
Change-Id: I8f76e355f99f21337d310ef2f345e6aaa253b48b
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index 7069331..a6773bc 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -16,6 +16,7 @@
#include "mutual_tls.hpp"
#include "sessions.hpp"
#include "str_utility.hpp"
+#include "utility.hpp"
#include <boost/asio/error.hpp>
#include <boost/asio/ip/tcp.hpp>
@@ -31,6 +32,7 @@
#include <boost/beast/http/message_generator.hpp>
#include <boost/beast/http/parser.hpp>
#include <boost/beast/http/read.hpp>
+#include <boost/beast/http/rfc7230.hpp>
#include <boost/beast/http/status.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/none.hpp>
@@ -254,6 +256,69 @@
instance.body_limit(boost::none);
}
+ // returns whether connection was upgraded
+ bool doUpgrade(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+ {
+ using boost::beast::http::field;
+ using boost::beast::http::token_list;
+
+ bool isSse =
+ isContentTypeAllowed(req->getHeaderValue("Accept"),
+ http_helpers::ContentType::EventStream, false);
+
+ bool isWebsocket = false;
+ bool isH2c = false;
+ // Check connection header is upgrade
+ if (token_list{req->req[field::connection]}.exists("upgrade"))
+ {
+ BMCWEB_LOG_DEBUG("{} Connection: Upgrade header was present",
+ logPtr(this));
+ // Parse if upgrade is h2c or websocket
+ token_list upgrade{req->req[field::upgrade]};
+ isWebsocket = upgrade.exists("websocket");
+ isH2c = upgrade.exists("h2c");
+ BMCWEB_LOG_DEBUG("{} Upgrade isWebsocket: {} isH2c: {}",
+ logPtr(this), isWebsocket, isH2c);
+ }
+
+ if (BMCWEB_EXPERIMENTAL_HTTP2 && isH2c)
+ {
+ std::string_view base64settings = req->req[field::http2_settings];
+ if (utility::base64Decode<true>(base64settings, http2settings))
+ {
+ res.result(boost::beast::http::status::switching_protocols);
+ res.addHeader(boost::beast::http::field::connection, "Upgrade");
+ res.addHeader(boost::beast::http::field::upgrade, "h2c");
+ }
+ }
+
+ // websocket and SSE are only allowed on GET
+ if (req->req.method() == boost::beast::http::verb::get)
+ {
+ if (isWebsocket || isSse)
+ {
+ asyncResp->res.setCompleteRequestHandler(
+ [self(shared_from_this())](crow::Response& thisRes) {
+ if (thisRes.result() != boost::beast::http::status::ok)
+ {
+ // When any error occurs before handle upgradation,
+ // the result in response will be set to respective
+ // error. By default the Result will be OK (200),
+ // which implies successful handle upgrade. Response
+ // needs to be sent over this connection only on
+ // failure.
+ self->completeRequest(thisRes);
+ return;
+ }
+ });
+ BMCWEB_LOG_INFO("{} Upgrading socket", logPtr(this));
+ handler->handleUpgrade(req, asyncResp, std::move(adaptor));
+ return true;
+ }
+ }
+ return false;
+ }
+
void handle()
{
std::error_code reqEc;
@@ -323,30 +388,8 @@
[self(shared_from_this())](crow::Response& thisRes) {
self->completeRequest(thisRes);
});
- bool isSse =
- isContentTypeAllowed(req->getHeaderValue("Accept"),
- http_helpers::ContentType::EventStream, false);
- std::string_view upgradeType(
- req->getHeaderValue(boost::beast::http::field::upgrade));
- if ((req->isUpgrade() &&
- bmcweb::asciiIEquals(upgradeType, "websocket")) ||
- isSse)
+ if (doUpgrade(asyncResp))
{
- asyncResp->res.setCompleteRequestHandler(
- [self(shared_from_this())](crow::Response& thisRes) {
- if (thisRes.result() != boost::beast::http::status::ok)
- {
- // When any error occurs before handle upgradation,
- // the result in response will be set to respective
- // error. By default the Result will be OK (200),
- // which implies successful handle upgrade. Response
- // needs to be sent over this connection only on
- // failure.
- self->completeRequest(thisRes);
- return;
- }
- });
- handler->handleUpgrade(req, asyncResp, std::move(adaptor));
return;
}
std::string_view expected =
@@ -680,6 +723,14 @@
return;
}
+ if (res.result() == boost::beast::http::status::switching_protocols)
+ {
+ auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
+ std::move(adaptor), handler, getCachedDateStr);
+ http2->startFromSettings(http2settings);
+ return;
+ }
+
if (res.result() == boost::beast::http::status::continue_)
{
// Reset the result to ok
@@ -797,7 +848,7 @@
std::shared_ptr<crow::Request> req;
std::string accept;
-
+ std::string http2settings;
crow::Response res;
std::shared_ptr<persistent_data::UserSession> userSession;