Skip to content

Commit

Permalink
Move Http.fromFileZIO to Handler. (#2239)
Browse files Browse the repository at this point in the history
move Http.fromFileZIO to Handler.
  • Loading branch information
landlockedsurfer authored Jun 13, 2023
1 parent 9b29756 commit 8ceaf03
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
42 changes: 41 additions & 1 deletion zio-http/src/main/scala/zio/http/Handler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import zio.http.Header.HeaderType
import zio.http.internal.HeaderModifier
import zio.stream.ZStream

import java.io.File
import java.io.{File, FileNotFoundException, IOException}
import java.nio.charset.Charset
import java.nio.file.{AccessDeniedException, NotDirectoryException}
import scala.reflect.ClassTag
import scala.util.control.NonFatal // scalafix:ok;

Expand Down Expand Up @@ -725,6 +726,45 @@ object Handler {
): Handler[R, Err, In, Out] =
http.toHandler(default)

private def determineMediaType(filePath: String): Option[MediaType] = {
filePath.lastIndexOf(".") match {
case -1 => None
case i =>
// Extract file extension
val ext = filePath.substring(i + 1)
MediaType.forFileExtension(ext)
}
}

def fromFileZIO[R](getFile: ZIO[R, Throwable, File])(implicit trace: Trace): Handler[R, Throwable, Any, Response] = {
Handler.fromZIO[R, Throwable, Response](
getFile.flatMap { file =>
if (!file.exists()) {
ZIO.fail(new FileNotFoundException())
} else if (file.isFile && !file.canRead) {
ZIO.fail(new AccessDeniedException(file.getAbsolutePath))
} else {
if (file.isFile) {
val length = Headers(Header.ContentLength(file.length()))
val response = http.Response(headers = length, body = Body.fromFile(file))
val pathName = file.toPath.toString

// Set MIME type in the response headers. This is only relevant in
// case of RandomAccessFile transfers as browsers use the MIME type,
// not the file extension, to determine how to process a URL.
// {{{<a href="MSDN Doc">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type</a>}}}
determineMediaType(pathName) match {
case Some(mediaType) => ZIO.succeed(response.withHeader(Header.ContentType(mediaType)))
case None => ZIO.succeed(response)
}
} else {
ZIO.fail(new NotDirectoryException(s"Found directory instead of a file."))
}
}
},
)
}

/**
* Creates a Handler that always succeeds with a 200 status code and the
* provided ZStream as the body
Expand Down
33 changes: 33 additions & 0 deletions zio-http/src/test/scala/zio/http/HandlerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package zio.http

import java.io.FileNotFoundException
import java.nio.file.{Files, NotDirectoryException}

import zio._
import zio.test.Assertion._
import zio.test.TestAspect.timeout
Expand Down Expand Up @@ -351,5 +354,35 @@ object HandlerSpec extends ZIOSpecDefault with ExitAssertion {
assert(http.apply {})(isSuccess(equalTo(1)))
},
),
suite("fromFileZIO")(
test("must load response from file") {
ZIO.acquireRelease(ZIO.attempt(Files.createTempFile("", ".jpg"))) { tempPath =>
ZIO.attempt(Files.deleteIfExists(tempPath)).ignore
} flatMap { tempPath =>
val tempFile = tempPath.toFile
val http = Handler.fromFileZIO(ZIO.succeed(tempFile))
for {
r <- http.apply {}
} yield {
assert(r.status)(equalTo(Status.Ok)) &&
assert(r.headers)(contains(Header.ContentType(MediaType.image.`jpeg`))) &&
assert(r.body)(equalTo(Body.fromFile(tempFile)))
}
}
},
test("must fail if file does not exist") {
val http = Handler.fromFileZIO(ZIO.succeed(new java.io.File("does-not-exist")))
assertZIO(http.apply {}.exit)(failsWithA[FileNotFoundException])
},
test("must fail if given file is a directory") {
ZIO.acquireRelease(ZIO.attempt(Files.createTempDirectory(""))) { tempPath =>
ZIO.attempt(Files.deleteIfExists(tempPath)).ignore
} flatMap { tempPath =>
val tempFile = tempPath.toFile
val http = Handler.fromFileZIO(ZIO.succeed(tempFile))
assertZIO(http.apply {}.exit)(failsWithA[NotDirectoryException])
}
},
),
) @@ timeout(10 seconds)
}

0 comments on commit 8ceaf03

Please sign in to comment.