File Handling & Static Files
Serve static files, handle uploads, and stream large files with Suave.
Serving Static Files
Serve files from a directory:
open Suave
open Suave.Files
open Suave.Operators
open Suave.Filters
let app =
choose [
path "/" >=> Files.browseFileHome "index.html"
Files.browseHome // Serve all files from current directory
]
Serving from Specific Directory
Serve files from a specific folder:
open Suave
open Suave.Files
let app =
choose [
Files.browse "./public" // Serve from ./public directory
RequestErrors.NOT_FOUND "Not found"
]
Directory Listing
Allow directory browsing:
open Suave
open Suave.Files
open Suave.Operators
let app =
choose [
browseFileHome "index.html"
browseHome // Allows directory listing
]
File Downloads
Serve files for download with appropriate headers:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Writers
let app =
choose [
path "/download/file.pdf" >=>
setHeader "Content-Disposition" "attachment; filename=file.pdf" >=>
Files.file "./files/file.pdf"
]
File Upload Handling
Handle file uploads from forms:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
open Suave.ServerErrors
open System.IO
let app =
choose [
POST >=> path "/upload" >=> fun ctx ->
async {
try
let files = ctx.request.files
if files.Count = 0 then
return! RequestErrors.BAD_REQUEST "No file provided" ctx
else
let file = files.[0]
let uploadDir = "./uploads"
Directory.CreateDirectory(uploadDir) |> ignore
let savePath = Path.Combine(uploadDir, file.fileName)
// Uploads land on disk at tempFilePath; copy/move from there
File.Copy(file.tempFilePath, savePath, true)
return! OK (sprintf "File uploaded: %s" file.fileName) ctx
with ex ->
return! INTERNAL_ERROR (sprintf "Upload failed: %s" ex.Message) ctx
}
]
Streaming Large Files
Stream large files without loading entire content to memory:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Files
// Files.file streams from disk without buffering the whole payload
let app =
choose [
path "/large-file" >=> file "./data/large-file.bin"
]
Serving Specific File Types
Serve specific file types with correct MIME types:
open Suave
open Suave.Files
open Suave.Operators
open Suave.Filters
open Suave.Writers
let app =
choose [
pathRegex @".*\.pdf$" >=>
setHeader "Content-Type" "application/pdf" >=>
browseHome
pathRegex @".*\.txt$" >=>
setHeader "Content-Type" "text/plain" >=>
browseHome
pathRegex @".*\.json$" >=>
setHeader "Content-Type" "application/json" >=>
browseHome
browseHome
]
Conditional File Serving
Serve files conditionally based on path:
open Suave
open Suave.Files
open Suave.Operators
open Suave.Filters
let app =
choose [
// API routes
path "/api" >=> RequestErrors.NOT_FOUND "API not found"
// Static files
GET >=> Files.browseHome
// Everything else
RequestErrors.NOT_FOUND "Not found"
]
File Caching
Enable HTTP caching for static files:
open Suave
open Suave.Files
open Suave.Operators
open Suave.Filters
open Suave.Writers
let cacheFor (seconds: int) =
setHeader "Cache-Control" (sprintf "public, max-age=%d" seconds)
let app =
choose [
pathRegex @".*\.(js|css)$" >=>
cacheFor 86400 >=> // 1 day
browseHome
pathRegex @".*\.(jpg|png|gif|svg)$" >=>
cacheFor 604800 >=> // 7 days
browseHome
pathRegex @".*\.html$" >=>
cacheFor 3600 >=> // 1 hour
browseHome
browseHome
]
Serving SPA (Single Page Application)
Serve SPA with fallback to index.html:
open Suave
open Suave.Files
open Suave.Operators
open Suave.Filters
let app =
choose [
// API routes first
path "/api" >=> RequestErrors.NOT_FOUND "API not implemented"
// Static assets with long cache
GET >=> pathRegex @"^/(css|js|images)/" >=> Files.browseHome
// Everything else falls back to index.html for SPA routing
GET >=> Files.browseFileHome "index.html"
// Default
Files.browseHome
]