Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using nghttp2's C API Directly (not as a reverse proxy)? #1768

Closed
DragonOsman opened this issue Aug 2, 2022 · 5 comments
Closed

Using nghttp2's C API Directly (not as a reverse proxy)? #1768

DragonOsman opened this issue Aug 2, 2022 · 5 comments
Labels

Comments

@DragonOsman
Copy link

DragonOsman commented Aug 2, 2022

If I want to use nghttp2's C API directly (its functions for HTTP/2 support), without using it as a reverse proxy, can it be done? And is it possible to do this without compiling httpd as well? I'm using Windows so I won't be able to compile httpd without using Cygwin or WSL. I have WSL 2, but my first option is to host the app on a Windows VPS.

I don't know how to implement HTTP/2 support, and rather than learning I'd rather use code developed by someone else. I'd like to use nghttp2 for that if possible. And not as a proxy but just using the code directly.

@DragonOsman
Copy link
Author

I'd prefer to use the C API if possible since the C++ one is deprecated and will be removed after 2022, which makes it kind unreliable. But I still need to at least know what functions I need to use, so what example(s) should I look at? And would building just nghttp2.lib be enough for this use case?

@tatsuhiro-t
Copy link
Member

tatsuhiro-t commented Aug 4, 2022

Your question is too broad to answer.
If you are looking for an example, checkout examples directory.
We also provide tutorials to build client and server:

@DragonOsman
Copy link
Author

Sorry for making the question too broad. I'll try to narrow it down.

So, if given the code in this C++ file (my server app's main C++ code file), now that I've enabled ALPN and if it's correct, I probably just need to implement HTTP/2 features and get the server to "speak" the protocol. So my question is mainly just what functions of the nghttp2 C API I need to use and where. And also if I need to compile anything aside from just nghttp2 itself (like the httpd for example), if I just want to use the API and not try to drop the httpd in as a reverse proxy.

I'll also show how I'm trying to enable ALPN in my app to ask if it's correct, since I'm not sure if it is:

// Handles an HTTP server connection
void do_session(tcp::socket &socket, ssl::context &ctx, const std::string &doc_root, std::string_view googlekey,
	std::string_view currencykey)
{
	bool close{};
	boost::system::error_code ec;

	// Construct the stream around the socket
	ssl::stream<tcp::socket&> stream{ socket, ctx };

	// call client hello processing function and pass in callback
	auto ssl_ctx{ ctx.native_handle() };
	SSL* ssl{ SSL_new(ssl_ctx) };
	int alert_val{ 1 };
	int* alert{ &alert_val };
	void* arg{};
	SSL_CTX_set_client_hello_cb(ssl_ctx, set_client_hello_cb(ssl, alert, arg), nullptr);

	// Perform the SSL handshake
	stream.handshake(ssl::stream_base::server, ec);
	if (ec)
	{
		std::cerr << "Lines 692 and 693:\n";
		fail(ec, "handshake");
	}

	// This buffer is required to persist across reads 
	boost::beast::flat_buffer buffer;

	// This lambda is used to send messages 
	send_lambda<ssl::stream<tcp::socket&>> lambda{ stream, close, ec };

	for (;;)
	{
		// Read a request 
		http::request<http::string_body> req;
		http::read(stream, buffer, req, ec);
		if (ec == http::error::end_of_stream)
		{
			break;
		}
		if (ec)
		{
			std::cerr << "Lines 713 and 714:\n";
			return fail(ec, "read");
		}

		// Send the response 
		handle_request(doc_root, std::move(req), lambda, googlekey, currencykey);
		if (ec)
		{
			std::cerr << "Lines 721 and 722:\n";
			return fail(ec, "write");
		}
		if (close)
		{
			// This means we should close the connection, usually because 
			// the response indicated the "Connection: close" semantic. 
			break;
		}
	}

	// Perform the SSL shutdown 
	stream.shutdown(ec);
	if (ec)
	{
		std::cerr << "Lines 736 and 737:\n";
		return fail(ec, "shutdown");
	}

	// At this point the connection is closed gracefully
}

^that's the function that handles a server connection. As you can see, I'm calling SSL_CTX_set_client_hello_cb before the handshake method, while the SSL_CTX_set_alpn_select_cb function is called inside the callback for SSL_CTX_set_client_hello_cb, like this:

SSL_client_hello_cb_fn set_client_hello_cb(SSL* ssl, int* alert, void* arg)
{
	constexpr int ext_type{ TLSEXT_TYPE_application_layer_protocol_negotiation };
	const unsigned char** alpn_str{};
	std::size_t* alpn_str_len{};
	const int alpn_ext_present{ SSL_client_hello_get0_ext(ssl, ext_type, 
		                        alpn_str, alpn_str_len) };
	if (alpn_ext_present == 1)  // 1 means alpn extension is present
	{
		ssl::context ctx{ ssl::context::tlsv13 };
		load_server_certificate(ctx);
		auto* ssl_ctx{ ctx.native_handle() };
		const unsigned char** out{};
		unsigned char* outlen{};
		const unsigned char* in{};
		unsigned inlen{};
		void* arg{};
		SSL_CTX_set_alpn_select_cb(ssl_ctx, 
			set_alpn_cb(ssl, out, outlen, in, inlen, arg), 
			nullptr);

		return reinterpret_cast<SSL_client_hello_cb_fn>(SSL_CLIENT_HELLO_SUCCESS);
	}
	else if (alpn_ext_present == 0)  // 0 means alpn extension isn't present
	{
		return reinterpret_cast<SSL_client_hello_cb_fn>(SSL_CLIENT_HELLO_ERROR);
	}
}
SSL_CTX_alpn_select_cb_func set_alpn_cb(SSL* ssl, const unsigned char** out, unsigned char* outlen,
	const unsigned char* in, unsigned int inlen, void* arg)
{
	for (std::size_t i{}; i < inlen; i += 1 + in[i])
	{
		if (in[i + 0] == 2 && in[i + 1] == 'h' && in[i + 2] == '2')
		{
			*out = in + i + 1;
			*outlen = in[i];
		}
	}

	if (out[0][0] == 2 && out[0][1] == 'h' && out[0][2] == '2')
	{
		return reinterpret_cast<SSL_CTX_alpn_select_cb_func>(SSL_TLSEXT_ERR_OK);
	}
	return reinterpret_cast<SSL_CTX_alpn_select_cb_func>(SSL_TLSEXT_ERR_NOACK);
}

If ALPN is correctly enabled here, then my server probably already supports HTTP/2 and I just need to know what functions from the nghttp2 C API I need to use and where. [I also need to change the client code where I send requests for the currency conversion rates and such to enable ALPN there as well, and ask the server on the other end to negotiate HTTP/2 as well, but I'll do that after I've gotten this to work.]

@DragonOsman
Copy link
Author

Seems I can't build nghttp2-asio without cloning nghttp2 itself as well. I don't understand why the dependency has to stay when it's already in its own separate repo now. Please consider removing the dependency (and also assigning a maintainer since you don't want to do it yourself).

For now I'll clone the full C lib too, but I'll wait for you to remove the dependency. Could you also maybe add server and client examples that how how to use the C API with ASIO in a C++ project, though? I think that would help me out immensely. Thanks.

Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.

@github-actions github-actions bot added the Stale label Apr 14, 2024
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Apr 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants