pistacheio / pistache

A high-performance REST toolkit written in C++
https://pistacheio.github.io/pistache/
Apache License 2.0
3.12k stars 690 forks source link

How do I make an endpoint with a downloadable file in response? #1098

Open jaadeo opened 1 year ago

jaadeo commented 1 year ago

I'm trying to load a .csv file (Content-Disposition: attachment) when accessing the endpoint, but the tools I found for this (response.send() and serveFile() do not provide this capability.) How can I do it?

jaadeo commented 1 year ago

Sorry, I'm newbie in web things. The solution was to change the MIME Type to multipart/form-data. But how to set the name of the downloaded file? (Example: info.csv)

Fabio3rs commented 1 year ago

I think this should work:

std::string filename = "myfile.csv";

    using namespace Pistache::Http;
    response.headers().addRaw(Header::Raw{
        "Content-Disposition", "attachment; filename=" + filename + ";"});

    Pistache::Http::serveFile(response, filename,
                              Pistache::Http::Mime::MediaType("text/csv"));

If it doesn't work, maybe trying to make a header class

// This is incomplete but I think it can work
class ContentDisposition : public Pistache::Http::Header::Header {
  public:
    NAME("Content-Disposition")

    void parseRaw(const char * /*str*/, size_t /*len*/) override {}

    void write(std::ostream &os) const override {
        if (type.empty()) {
            return;
        }
        os << type;
        if (!htmlName.empty()) {
            os << "; name=" << htmlName;
        }
        if (!filename.empty()) {
            os << "; filename=" << filename;
        }

        os << ";";
    }

    ContentDisposition() = default;
    explicit ContentDisposition(std::string DispositionType,
                                std::string FileName)
        : type(std::move(DispositionType)), filename(std::move(FileName)) {}

    std::string type;
    std::string htmlName;
    std::string filename;
};

......

std::string filename = "myfile.csv";

    using namespace Pistache::Http;

    response.headers().add(
        std::make_shared<ContentDisposition>("attachment", filename));

    Pistache::Http::serveFile(response, filename,
                              Pistache::Http::Mime::MediaType("text/csv"));
jaadeo commented 1 year ago

@Fabio3rs The first method just does not work and offers to save the file by selecting a format and name. The second throws an error and my Nginx gives a 502 Bad Gateway. The logs give the following error: terminate called after throwing an instance of 'Pistache::Http::HttpError' what(): No such file or directory

Fabio3rs commented 1 year ago

@wjnao

terminate called after throwing an instance of 'Pistache::Http::HttpError' what(): No such file or directory

This custom header addition should not cause a problem with the file loading, It seems to be a problem with the current working directory and the path of the file, try to pass the full path of the file to the function Pistache::Http::serveFile(response, fullFilePath, Pistache::Http::Mime::MediaType("text/csv"));

or check the current running directory of your program and place the file there.

bkamba commented 1 year ago

Any updates to this issue? I can't seem to define it properly even after using your example, after instantiating the class it doesn't seem to recognize its inherited from the header class.

alexcocinda commented 1 year ago

Tried the same thing with a pdf and the raw headers seem to be ignored.

Fabio3rs commented 1 year ago

I think the raw headers are being ignored in the headers write code, but the tests I did with the custom classes worked here.

tyler92 commented 1 year ago

I faced with the same issue and it looks like

Pistache::Http::Mime::MediaType("text/csv")

and

Pistache::Http::Mime::MediaType::fromString("text/csv")

is not the same. The first option creates an object that has isValid() == false, but the second one leads to isValid() == true. It's due to the default second parameter of MediaType's constructor:

explicit MediaType(std::string raw, Parse parse = DontParse)

Not really obvious.