Search code examples
f#suave

Sending (zip) file with Suave with the correct name


I have been researching creating a web server using Suave. Currently, I am trying to get it to send a zip file in a GET request. I have successfully made my application send a file, but the name of the file I receive when doing the request in Postman is "response" or "response.html", depending on which function from Suave's Files module I use. That said, when I manually rename the file to .zip, I can open and unzip it like a normal .zip file, which means that it is really the downloaded file's name that is the problem. The code below is what I've got now.

open JSON
open System
open System.Threading
open Suave
open Suave.Filters
open Suave.Operators
open Suave.Successful
open Suave.Web
open System.IO
open Suave.Writers

let sampleJsonPath = @"C:\VS Projects\Research\Sample.json"
let sampleZipPath = @"C:\VS Projects\Research\Sample.zip"
let getJson() =
    File.ReadAllText(sampleJsonPath)

[<EntryPoint>]
let main argv = 
    let cts = new CancellationTokenSource()
    let mimeTypes =
        defaultMimeTypesMap
            @@ (function | ".zip" -> createMimeType "compression/zip" false | _ -> None)

    let config =
        { defaultConfig with 
            mimeTypesMap = mimeTypes
            cancellationToken = cts.Token
        }

    let app = 
        choose 
            [ GET >=> choose
                [ path "/hello" >=> OK "Hello GET"
                path "/jsonString" >=> OK (getJson())
                path "/jsonFile" >=> warbler (fun _ -> getJson() |> JSON)// for only on startup: ... >=> (getJson() |> JSON)
                path "/zip" >=> Files.sendFile sampleZipPath false
                path "/goodbye" >=> OK "Goodbye GET" ]
            POST >=> choose
                [ path "/hello" >=> OK "Hello POST"
                pathScan "/content/%d" (fun param -> OK (sprintf "Found integer:\n%d" param))
                pathScan "/content/%s" (fun param -> OK (sprintf "Found content:\n%s" param))
                path "/goodbye">=> OK "Goodbye POST" ]
            RequestErrors.BAD_REQUEST "Unknown request encountered"
            ]
    let listening, server = startWebServerAsync config app

    Async.Start(server, cts.Token)
    printfn "Ready to receive requests"
    Console.ReadKey true |> ignore

    cts.Cancel()

    0 // return an integer exit code

Googling has so far turned up nothing I could use. I have also tried many of the other functions in Suave's Files module instead, including Files.browseFile sampleZipPath "Sample.zip" (which also gives a file named "response.html") and Files.file sampleZipPath (which gives a file name "response"), but so far without success.

How do I supply the name of file to be sent?


Solution

  • Filename is set with HTTP response header "Content-Disposition" and is not handled by Suave automatically:

       let setFileName name =
         setHeader  "Content-Disposition" (sprintf "inline; filename=\"%s\"" name)
    

    So, the code will be as

    path "/zip" >=> setFileName "Sample.zip"
                >=> Files.sendFile sampleZipPath false
    

    Depending on desired behaviur you can replace inline; part with attachment; to show "Save file" dialog in browser