Hyper 1.0-rc.3 and http2: getting "����[…]"

I tried to convert the "Getting Started with a Server" guide for hyper 1.0 from HTTP1 to HTTP2.

I first tried this:

@@ -3,7 +3,7 @@
 
 use http_body_util::Full;
 use hyper::body::Bytes;
-use hyper::server::conn::http1;
+use hyper::server::conn::http2;
 use hyper::service::service_fn;
 use hyper::{Request, Response};
 use tokio::net::TcpListener;
@@ -26,7 +26,7 @@
         // Spawn a tokio task to serve multiple connections concurrently
         tokio::task::spawn(async move {
             // Finally, we bind the incoming connection to our `hello` service
-            if let Err(err) = http1::Builder::new()
+            if let Err(err) = http2::Builder::new()
                 // `service_fn` converts our function in a `Service`
                 .serve_connection(stream, service_fn(hello))
                 .await

But apparently hyper::server::conn::http2::Builder has a different function signature than http1::Builder.

Looking into the http2::Builder docs, I have to implement an…

[…] executor which is a type that implements Http2ConnExec trait.

Checking out the docs for that Http2ConnExec trait, I learned that:

[Http2ConnExec] is sealed and cannot be implemented for types outside this crate.

Too bad the docs don't show any implementors. :thinking: But to the rescue:

[Http2ConnExec] is implemented for any type that implements Executor trait for any future.

Okay, lets head to the documentation of Executor, and I find that example:

struct TokioExecutor;

impl<F> Executor<F> for TokioExecutor
where
    F: Future + Send + 'static,
    F::Output: Send + 'static,
{
    fn execute(&self, future: F) {
        tokio::spawn(future);
    }
}

Adding that to my code, and changing…

-            if let Err(err) = http2::Builder::new()
+            if let Err(err) = http2::Builder::new(TokioExecutor)

… it complains about TokioExecutor not being Clone. Okay, I'm adding that too:

+#[derive(Clone)]
 struct TokioExecutor;

Now the program runs! But when I dare to use my webbrowser to connect to the running server with http://localhost:3000/, I get…

��������������@�����

…shown on a blank page. Network inspector doesn't show a status code. Weird.

That is in Mozilla Firefox. I tried Chrome/Chromium, and get:

This page isn’t working

localhost sent an invalid response.

ERR_INVALID_HTTP_RESPONSE

Oh, and using curl, I get:

% curl -v http://localhost:3000/
*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.87.0
> Accept: */*
> 
* Received HTTP/0.9 when not allowed
* Closing connection 0
curl: (1) Received HTTP/0.9 when not allowed

So what's going on here? Does it have to do with HTTP2 only being supported with TLS?

The output in the console where I run cargo run gives me:

Error serving connection: hyper::Error(Http2, Error { kind: GoAway(b"", PROTOCOL_ERROR, Library) })

I just tested to use https://localhost:3000/ instead of http://localhost:3000. But it doesn't work either.

On Firefox:

Secure Connection Failed

An error occurred during a connection to localhost:3000. PR_END_OF_FILE_ERROR

Error code: PR_END_OF_FILE_ERROR

The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
Please contact the website owners to inform them of this problem.

Learn more…

On Chrome:

This site can’t provide a secure connection

localhost sent an invalid response.

ERR_SSL_PROTOCOL_ERROR

And with curl:

% curl -v https://localhost:3000/
*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
*  CAfile: none
*  CApath: /etc/ssl/certs/
* [CONN-0-0][CF-SSL] TLSv1.3 (OUT), TLS handshake, Client hello (1):
* OpenSSL/1.1.1o: error:1408F10B:SSL routines:ssl3_get_record:wrong version number
* Closing connection 0
curl: (35) OpenSSL/1.1.1o: error:1408F10B:SSL routines:ssl3_get_record:wrong version number

And on the server side the same error:

Error serving connection: hyper::Error(Http2, Error { kind: GoAway(b"", PROTOCOL_ERROR, Library) })

I suspect you've set up the server to serve unencrypted HTTP/2 (H/2 without TLS).

This protocol is impossible to access from browsers, because they chose to never support it. And it's impossible to use via regular insecure HTTP clients, because unencrypted HTTP has no way to negotiate protocol version before the first request, so clients always default to requesting HTTP/1.

Curl has --http2-prior-knowledge to override it, but for normal use you will have to also set up TLS for your server.

4 Likes

Ah, that's it. So I can't really test it without a TLS certificate (except with curl, thanks for the trick).

You can pretty easily set up a TLS certificate, though – I reckon self-signed ones can be made acceptable by browsers (as well as curl --insecure), I've done it before, at least in a pet project of mine. So you don't need to actually pay $$$ for a CA and set up a domain.

2 Likes

Since 2015 you have been able to get certificates for free via Let's Encrypt if you have a domain, which is $$ rather than $$$ and useful for other things. Even if you want to test various servers, you can get separate certs for foo.yourdomain.example and bar.yourdomain.example, so only one domain you control is necessary.

(Perhaps you already know this but many people haven't heard, so I thought it is worth clarifying.)

3 Likes

I have been using both self-signed certificates, as well as self-signed CAs (to sign several certificates with the same self-signed CA), as well as Let's Encrypt. I think for testing, I'll likely go for a self-signed certificate, but I still find it a bit tedious to setup (and still need to figure out how to use that in hyper).

Side rant: Personally, I find it annoying that TLS is enforced. Same annoying that OpenSSH doesn't support a NULL cypher anymore out of the box (unless I'm mistaken). There are scenarios where using crypto is just unnecessary overhead (e.g. in a local network or otherwise secured IP network, or when connecting to a backend/service on the same host using a secure port or authenticated local socket) or where it's even forbidden to use for a reason (e.g. amateur radio, where interception/sniffing of foreign connections is a wanted feature/possibility). Anyway, that's out of scope of any Rust crate.[/rant]

Yep, I know about Let's Encrypt, but I specifically didn't want to mention it, as I think it's not useful for local testing. But for deployment, it's surely a pretty good alternative.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.