Repository: http4s/http4s (scala)

Issue:
Query params are not sent with HTTP/2
When sending request using an ember client with HTTP/2 enabled, query params are omitted, the server doesn't receive them. Without HTTP/2, query params are sent as expected. Same when using the JDK http client.

Reproducer (scala-cli friendly): https://gist.github.com/ybasket/ab3b4eff4fa6bd58209054a94ddb1772

```scala
  def run: IO[Unit] = EmberClientBuilder
    .default[IO]
    .withHttp2 // comment me to fix behaviour
    .build
    .use { client =>
      client.expect[String](Request(uri = uri"https://httpbin.org/anything?foo=bar")).flatMap(IO.println)
    }
```

Pull request description:
RFC 7540 specifies query params must be included in the :path pseudo header, without this change, ember would just silently discard them.

Closes #7178

Patch:
diff --git a/ember-core/shared/src/main/scala/org/http4s/ember/core/h2/PseudoHeaders.scala b/ember-core/shared/src/main/scala/org/http4s/ember/core/h2/PseudoHeaders.scala
index 7c564e5a965..1428089ba64 100644
--- a/ember-core/shared/src/main/scala/org/http4s/ember/core/h2/PseudoHeaders.scala
+++ b/ember-core/shared/src/main/scala/org/http4s/ember/core/h2/PseudoHeaders.scala
@@ -38,10 +38,14 @@ private[h2] object PseudoHeaders {
 
   import org.http4s.Request
   def requestToHeaders[F[_]](req: Request[F]): NonEmptyList[(String, String, Boolean)] = {
+    // RFC 7540 §8.1.2.3 specifies :path includes path and query
     val path = {
-      val s = req.uri.path.renderString
-      if (s.isEmpty) "/"
-      else s
+      val p = req.uri.path.renderString
+      val q = req.queryString
+      val query = if (q.isEmpty) q else s"?$q"
+      if (p.isEmpty) {
+        if (req.method === Method.OPTIONS && req.uri.query.isEmpty) "*" else s"/$query"
+      } else s"$p$query"
     }
     val l = NonEmptyList.of(
       (METHOD, req.method.toString, false),


Test patch:
diff --git a/ember-core/shared/src/test/scala/org/http4s/ember/core/h2/PsuedoHeadersSuite.scala b/ember-core/shared/src/test/scala/org/http4s/ember/core/h2/PsuedoHeadersSuite.scala
index d190be79f1f..e17e61e900d 100644
--- a/ember-core/shared/src/test/scala/org/http4s/ember/core/h2/PsuedoHeadersSuite.scala
+++ b/ember-core/shared/src/test/scala/org/http4s/ember/core/h2/PsuedoHeadersSuite.scala
@@ -56,4 +56,49 @@ class PsuedoHeadersSuite extends Http4sSuite {
     )
     assertEquals(test, expected)
   }
+
+  test("requestToHeaders should include query in :path") {
+    val request = Request[fs2.Pure](uri = Uri().withQueryParam("q", "v") / "foo")
+
+    val test = PseudoHeaders.requestToHeaders(request)
+    val expected = NonEmptyList.of(
+      (":method", "GET", false),
+      (":scheme", "https", false),
+      (":path", "/foo?q=v", false),
+      (":authority", "", false),
+    )
+
+    assertEquals(test, expected)
+
+  }
+
+  test("requestToHeaders should include query in :path and treat missing path as /") {
+    val request = Request[fs2.Pure](uri = Uri().withQueryParam("q", "v"))
+
+    val test = PseudoHeaders.requestToHeaders(request)
+    val expected = NonEmptyList.of(
+      (":method", "GET", false),
+      (":scheme", "https", false),
+      (":path", "/?q=v", false),
+      (":authority", "", false),
+    )
+
+    assertEquals(test, expected)
+
+  }
+
+  test("requestToHeaders should map OPTIONS requests without path to *") {
+    val request = Request[fs2.Pure](method = Method.OPTIONS, uri = Uri())
+
+    val test = PseudoHeaders.requestToHeaders(request)
+    val expected = NonEmptyList.of(
+      (":method", "OPTIONS", false),
+      (":scheme", "https", false),
+      (":path", "*", false),
+      (":authority", "", false),
+    )
+
+    assertEquals(test, expected)
+
+  }
 }