question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Inconsistent "Host header required for HTTP/1.1 request" error

See original GitHub issue

Hello 😃

Using version 0.20.0-M7, but also happens on 0.19.0.

import cats.effect.{ContextShift, IO}
import org.http4s.Uri
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.ExecutionContext.Implicits.global


object IssueBad extends App {
  private implicit val cs: ContextShift[IO] = IO.contextShift(global)

  val uri: Uri = Uri().withPath(s"http://localhost:8080")

  BlazeClientBuilder[IO](global).resource.use { client =>
    client.expect[String](uri)
  }.unsafeRunSync()
}

This fails for me with:

Exception in thread "main" java.lang.IllegalArgumentException: Host header required for HTTP/1.1 request
	at org.http4s.client.blaze.Http1Connection.validateRequest(Http1Connection.scala:329)
	at org.http4s.client.blaze.Http1Connection.executeRequest(Http1Connection.scala:124)
	at org.http4s.client.blaze.Http1Connection.$anonfun$runRequest$1(Http1Connection.scala:103)
	at cats.effect.internals.IORunLoop$.liftedTree1$1(IORunLoop.scala:95)
	at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:95)
	at cats.effect.internals.IORunLoop$RestartCallback.signal(IORunLoop.scala:351)
	at cats.effect.internals.IORunLoop$RestartCallback.apply(IORunLoop.scala:372)
	at cats.effect.internals.IORunLoop$RestartCallback.apply(IORunLoop.scala:312)
	at cats.effect.internals.Callback$AsyncIdempotentCallback.run(Callback.scala:127)
	at cats.effect.internals.Trampoline.cats$effect$internals$Trampoline$$immediateLoop(Trampoline.scala:70)
	at cats.effect.internals.Trampoline.startLoop(Trampoline.scala:36)
	at cats.effect.internals.TrampolineEC$JVMTrampoline.super$startLoop(TrampolineEC.scala:93)
	at cats.effect.internals.TrampolineEC$JVMTrampoline.$anonfun$startLoop$1(TrampolineEC.scala:93)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
	at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:81)
	at cats.effect.internals.TrampolineEC$JVMTrampoline.startLoop(TrampolineEC.scala:93)
	at cats.effect.internals.Trampoline.execute(Trampoline.scala:43)
	at cats.effect.internals.TrampolineEC.execute(TrampolineEC.scala:44)
	at cats.effect.internals.Callback$AsyncIdempotentCallback.apply(Callback.scala:133)
	at cats.effect.internals.Callback$AsyncIdempotentCallback.apply(Callback.scala:120)
	at cats.effect.concurrent.Deferred$ConcurrentDeferred.$anonfun$unsafeRegister$1(Deferred.scala:205)
	at cats.effect.concurrent.Deferred$ConcurrentDeferred.$anonfun$unsafeRegister$1$adapted(Deferred.scala:205)
	at cats.effect.concurrent.Deferred$ConcurrentDeferred.$anonfun$notifyReadersLoop$1(Deferred.scala:241)
	at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
	at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:87)
	at cats.effect.internals.IORunLoop$RestartCallback.signal(IORunLoop.scala:351)
	at cats.effect.internals.IORunLoop$RestartCallback.apply(IORunLoop.scala:372)
	at cats.effect.internals.IORunLoop$RestartCallback.apply(IORunLoop.scala:312)
	at cats.effect.internals.IOShift$Tick.run(IOShift.scala:36)
	at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

I can fix it just by turning the Uri into a String:

import cats.effect.{ContextShift, IO}
import org.http4s.Uri
import org.http4s.client.blaze.BlazeClientBuilder

import scala.concurrent.ExecutionContext.Implicits.global


object IssueOK extends App {
  private implicit val cs: ContextShift[IO] = IO.contextShift(global)

  val uriString: String = Uri().withPath("http://localhost:8080").toString()

  BlazeClientBuilder[IO](global).resource.use { client =>
    client.expect[String](uriString)
  }.unsafeRunSync()
}

It also fails, since I don’t have anything running at localhost:8080, but this error is expected.

Exception in thread "main" java.net.ConnectException: Connection refused
	at sun.nio.ch.UnixAsynchronousSocketChannelImpl.checkConnect(Native Method)
	at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishConnect(UnixAsynchronousSocketChannelImpl.java:252)
	at sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:198)
	at sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:213)
	at sun.nio.ch.EPollPort$EventHandlerTask.run(EPollPort.java:293)
	at java.lang.Thread.run(Thread.java:748)

I would expect that both pieces of code to have the same behavior.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:8 (7 by maintainers)

github_iconTop GitHub Comments

1reaction
rossabakercommented, May 29, 2019

The problem is that Uri.withPath assumes the entire thing is a path, rather than noting that part of it should be the scheme and authority. URI as a case class is tempting, but it allows this trap.

For some prior art, java.net.URI does not have setters on the individual components. You have to reparse the entire String. That’s not efficient when constructing a String, but does neatly work around this issue.

0reactions
kevinmeredithcommented, May 29, 2019

Thanks, Ross, for the explanation. So the following seem to me to be possible courses of action:

  1. Take java.net.URI’s approach, which means re-parsing the entire String
  2. Validate the input to Uri#withPath
  3. Leave it as-is

1 would be a breaking change since withPath, along with maybe other methods, would be deleted. 2 would be a breaking change, I think, since it’d, I assume, change the return type of Uri#withPath to be an Option[Uri] or Either[?, Uri]?

What are your thoughts, Ross?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Is the Host: header required over SSL? - Server Fault
A HTTP/1.0 request does not need a Host according to the standard, but this header is still usually needed in practice to decide...
Read more >
HTTP/1.1: Header Field Definitions
The Accept request-header field can be used to specify certain media types which are acceptable for the response. Accept headers can be used...
Read more >
How to identify and exploit HTTP Host header vulnerabilities
In this section, we'll look more closely at how you can identify whether a website is vulnerable to HTTP Host header attacks. We'll...
Read more >
#1724 (Nginx doesn't sanitize and is inconsistent with multiple ...
A server MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message that lacks a Host header field and...
Read more >
Inconsistent Interpretation of HTTP Requests ('HTTP Request ...
The interpretation of HTTP responses can be manipulated if response headers include a space between the header name and colon, or if HTTP...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found