diff --git a/core/src/main/scala/org/http4s/StaticFile.scala b/core/src/main/scala/org/http4s/StaticFile.scala index 5265c5a004c..3db01ffbc05 100644 --- a/core/src/main/scala/org/http4s/StaticFile.scala +++ b/core/src/main/scala/org/http4s/StaticFile.scala @@ -66,31 +66,39 @@ object StaticFile { def fromURL[F[_]](url: URL, blocker: Blocker, req: Option[Request[F]] = None)(implicit F: Sync[F], - cs: ContextShift[F]): OptionT[F, Response[F]] = - OptionT.liftF(F.delay { - val urlConn = url.openConnection - val lastmod = HttpDate.fromEpochSecond(urlConn.getLastModified / 1000).toOption - val ifModifiedSince = req.flatMap(_.headers.get(`If-Modified-Since`)) - val expired = (ifModifiedSince, lastmod).mapN(_.date < _).getOrElse(true) - - if (expired) { - val lastModHeader: List[Header] = lastmod.map(`Last-Modified`(_)).toList - val contentType = nameToContentType(url.getPath).toList - val len = urlConn.getContentLengthLong - val lenHeader = - if (len >= 0) `Content-Length`.unsafeFromLong(len) - else `Transfer-Encoding`(TransferCoding.chunked) - val headers = Headers(lenHeader :: lastModHeader ::: contentType) - - Response( - headers = headers, - body = readInputStream[F](F.delay(url.openStream), DefaultBufferSize, blocker) - ) - } else { - urlConn.getInputStream.close() - Response(NotModified) + cs: ContextShift[F]): OptionT[F, Response[F]] = { + val fileUrl = url.getFile() + val file = new File(fileUrl) + OptionT.apply(F.delay { + if (file.isDirectory()) + None + else { + val urlConn = url.openConnection + val lastmod = HttpDate.fromEpochSecond(urlConn.getLastModified / 1000).toOption + val ifModifiedSince = req.flatMap(_.headers.get(`If-Modified-Since`)) + val expired = (ifModifiedSince, lastmod).mapN(_.date < _).getOrElse(true) + + if (expired) { + val lastModHeader: List[Header] = lastmod.map(`Last-Modified`(_)).toList + val contentType = nameToContentType(url.getPath).toList + val len = urlConn.getContentLengthLong + val lenHeader = + if (len >= 0) `Content-Length`.unsafeFromLong(len) + else `Transfer-Encoding`(TransferCoding.chunked) + val headers = Headers(lenHeader :: lastModHeader ::: contentType) + + Some( + Response( + headers = headers, + body = readInputStream[F](F.delay(url.openStream), DefaultBufferSize, blocker) + )) + } else { + urlConn.getInputStream.close() + Some(Response(NotModified)) + } } }) + } def calcETag[F[_]: Sync]: File => F[String] = f => diff --git a/tests/src/test/resources/foo/fooz.txt b/tests/src/test/resources/foo/fooz.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/src/test/scala/org/http4s/StaticFileSpec.scala b/tests/src/test/scala/org/http4s/StaticFileSpec.scala index 6dfff7bf780..ea794dfa8f9 100644 --- a/tests/src/test/scala/org/http4s/StaticFileSpec.scala +++ b/tests/src/test/scala/org/http4s/StaticFileSpec.scala @@ -276,5 +276,14 @@ class StaticFileSpec extends Http4sSpec with Http4sLegacyMatchersIO { .map(_.flatMap(_.contentLength)) len must returnValue(Some(24005L)) } + + "return none from a URL that is a directory" in { + // val url = getClass.getResource("/foo") + val s = StaticFile + .fromURL[IO](getClass.getResource("/foo"), testBlocker) + .value + .unsafeRunSync + s must_== None + } } }