Skip to main content

File and Upload Directives

File and upload directives serve static files from the filesystem or classpath, and handle multipart file uploads. These directives wrap http4s StaticFile utilities and multipart parsing into the stir DSL.

Note: directory listing is not supported. Requests to directory paths are rejected.

File Serving

getFromFile

Completes GET requests with the content of the given file. If the file cannot be found or read, the request is rejected.

val route: Route = path("download") {
getFromFile("/var/data/report.pdf")
}

The directive also accepts a java.io.File instance:

import java.io.File

val route: Route = path("download") {
getFromFile(new File("/var/data/report.pdf"))
}

getFromResource

Completes GET requests with the content of the given classpath resource. If the resource cannot be found, a 404 response is returned. Requests for resource "directories" (paths ending with /) are rejected.

val route: Route = path("static" / "logo") {
getFromResource("assets/logo.png")
}

getFromDirectory

Completes GET requests with the content of a file underneath the given directory. The unmatched path from the request URI is appended to the directory path. Path traversal attacks are prevented by validating that the resolved path is a descendant of the base directory.

val route: Route = pathPrefix("files") {
getFromDirectory("/var/www/static/")
}
// GET /files/css/style.css -> serves /var/www/static/css/style.css

getFromResourceDirectory

Same as getFromDirectory except that files are served from the classpath rather than the filesystem.

val route: Route = pathPrefix("assets") {
getFromResourceDirectory("public")
}
// GET /assets/js/app.js -> serves classpath resource public/js/app.js

File Uploads

File upload directives process multipart form data. They use FileInfo to carry metadata about uploaded parts:

// FileInfo(fieldName: String, fileName: String, contentType: `Content-Type`)

fileUpload

Extracts a single multipart file part as a tuple of FileInfo and Stream[IO, Byte]. The request is rejected with MissingFormFieldRejection if no part with the given field name exists, or if the matched part lacks a filename or content-type. If multiple parts share the same name, only the first is used.

val route: Route = (post & path("upload")) {
fileUpload("document") { case (metadata, byteStream) =>
complete(s"Received file: ${metadata.fileName}")
}
}

fileUploadAll

Extracts all multipart file parts with the given field name as a sequence of (FileInfo, Stream[IO, Byte]) tuples. Files are buffered to temporary files on disk and cleaned up after the stream is consumed.

val route: Route = (post & path("upload-many")) {
fileUploadAll("documents") { files =>
complete(s"Received ${files.size} files")
}
}

storeUploadedFile

Streams the bytes of a single multipart file part to disk. The destination file is determined by the provided function. Returns a tuple of FileInfo and the destination File. If multiple parts share the same name, only the first is used. On write failure, the destination file is deleted.

import java.io.File

val route: Route = (post & path("upload")) {
storeUploadedFile("file", info => new File(s"/tmp/${info.fileName}")) {
case (metadata, file) =>
complete(s"Stored ${metadata.fileName} at ${file.getAbsolutePath}")
}
}

storeUploadedFiles

Streams all multipart file parts with the given field name to disk. Returns a sequence of (FileInfo, File) tuples.

import java.io.File

val route: Route = (post & path("upload-batch")) {
storeUploadedFiles("files", info => new File(s"/tmp/${info.fileName}")) { files =>
complete(s"Stored ${files.size} files")
}
}