Skip to content

Commit

Permalink
Enhancements on files part. (drogonframework#803)
Browse files Browse the repository at this point in the history
Co-authored-by: an-tao <[email protected]>
  • Loading branch information
0rangeFox and an-tao authored Apr 17, 2021
1 parent 0ec2f51 commit df51674
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 63 deletions.
56 changes: 54 additions & 2 deletions examples/simple_example/api_Attachment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ void Attachment::upload(const HttpRequestPtr &req,
for (auto const &file : files)
{
LOG_DEBUG << "file:" << file.getFileName()
<< "(len=" << file.fileLength()
<< "(extension=" << file.getFileExtension()
<< ",type=" << file.getFileType()
<< ",len=" << file.fileLength()
<< ",md5=" << file.getMd5() << ")";
file.save();
file.save("123");
Expand All @@ -43,7 +45,57 @@ void Attachment::upload(const HttpRequestPtr &req,
return;
}
LOG_DEBUG << "upload error!";
// LOG_DEBUG<<req->con
// LOG_DEBUG << req->con
Json::Value json;
json["result"] = "failed";
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
}

void Attachment::uploadImage(
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
MultiPartParser fileUpload;

// At this endpoint, we only accept one file
if (fileUpload.parse(req) == 0 && fileUpload.getFiles().size() == 1)
{
// LOG_DEBUG << "upload image good!";
Json::Value json;

// Get the first file received
auto &file = fileUpload.getFiles()[0];

// There are 2 ways to check if the file extension is an image.
// First way
if (file.getFileType() == FT_IMAGE)
{
json["isImage"] = true;
}
// Second way
auto fileExtension = file.getFileExtension();
if (fileExtension == "png" || fileExtension == "jpeg" ||
fileExtension == "jpg" || fileExtension == "ico" /* || etc... */)
{
json["isImage"] = true;
}
else
{
json["isImage"] = false;
}

json["result"] = "ok";
for (auto &param : fileUpload.getParameters())
{
json[param.first] = param.second;
}
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
return;
}
LOG_DEBUG << "upload image error!";
// LOG_DEBUG << req->con
Json::Value json;
json["result"] = "failed";
auto resp = HttpResponse::newHttpJsonResponse(json);
Expand Down
3 changes: 3 additions & 0 deletions examples/simple_example/api_Attachment.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ class Attachment : public drogon::HttpController<Attachment>
// use METHOD_ADD to add your custom processing function here;
METHOD_ADD(Attachment::get, "", Get); // Path is '/api/attachment'
METHOD_ADD(Attachment::upload, "/upload", Post);
METHOD_ADD(Attachment::uploadImage, "/uploadImage", Post);
METHOD_ADD(Attachment::download, "/download", Get);
METHOD_LIST_END
// your declaration of processing function maybe like this:
void get(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void upload(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void uploadImage(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
void download(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback);
};
Expand Down
34 changes: 34 additions & 0 deletions examples/simple_example_test/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,40 @@ void doTest(const HttpClientPtr &client,
exit(1);
}
});

// return;
// Test file upload, file type and extension interface.
UploadFile image("./drogon.jpg");
req = HttpRequest::newFileUploadRequest({image});
req->setPath("/api/attachment/uploadImage");
req->setParameter("P1", "upload");
req->setParameter("P2", "test");
client->sendRequest(
req, [req, isHttps](ReqResult result, const HttpResponsePtr &resp) {
if (result == ReqResult::Ok)
{
auto json = resp->getJsonObject();
if (json && (*json)["isImage"].asBool() &&
(*json)["P1"] == "upload" && (*json)["P2"] == "test")
{
outputGood(req, isHttps);
// std::cout << (*json) << std::endl;
}
else
{
LOG_DEBUG << resp->getBody().length();
LOG_DEBUG << resp->getBody();
LOG_ERROR << "Error!";
exit(1);
}
}
else
{
LOG_ERROR << "Error!";
exit(1);
}
});

req = HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->setPath("/api/v1/this_will_fail");
Expand Down
11 changes: 11 additions & 0 deletions lib/inc/drogon/HttpTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ enum ContentType
CT_CUSTOM
};

enum FileType
{
FT_UNKNOWN = 0,
FT_CUSTOM,
FT_DOCUMENT,
FT_ARCHIVE,
FT_AUDIO,
FT_MEDIA,
FT_IMAGE
};

enum HttpMethod
{
Get = 0,
Expand Down
15 changes: 12 additions & 3 deletions lib/inc/drogon/MultiPart.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <drogon/exports.h>
#include <drogon/HttpRequest.h>
#include <drogon/utils/string_view.h>
#include <map>
#include <unordered_map>
#include <string>
Expand All @@ -36,11 +37,19 @@ class DROGON_EXPORT HttpFile
/// Return the file name;
const std::string &getFileName() const;

/// Return the file extension;
/// Note: After the HttpFile object is destroyed, do not use this
/// string_view object.
string_view getFileExtension() const;

/// Return the name of the item in multiple parts.
const std::string &getItemName() const;

/// Return the type of file.
FileType getFileType() const;

/// Set the file name, usually called by the MultiPartParser parser.
void setFileName(const std::string &filename);
void setFileName(const std::string &fileName);

/// Set the contents of the file, usually called by the MultiPartParser
/// parser.
Expand All @@ -64,11 +73,11 @@ class DROGON_EXPORT HttpFile

/// Save the file to file system with a new name
/**
* @param filename if the parameter isn't prefixed with "/", "./" or "../",
* @param fileName if the parameter isn't prefixed with "/", "./" or "../",
* the full path is app().getUploadPath()+"/"+filename, otherwise the file
* is saved as the filename
*/
int saveAs(const std::string &filename) const;
int saveAs(const std::string &fileName) const;

/**
* @brief return the content of the file.
Expand Down
96 changes: 51 additions & 45 deletions lib/src/HttpFileImpl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,66 +19,62 @@
#include <iostream>

using namespace drogon;
// Verify if last char of path is a slash, otherwise, add the slash
static inline void ensureSlashPostfix(std::string &path)
{
if (path[path.length() - 1] != '/')
path += '/';
}

int HttpFileImpl::save() const
{
return save(HttpAppFrameworkImpl::instance().getUploadPath());
}

int HttpFileImpl::save(const std::string &path) const
{
assert(!path.empty());
if (fileName_ == "")
if (fileName_.empty())
return -1;
std::string filename;
auto tmpPath = path;
std::string fileName;
if (path[0] == '/' ||
(path.length() >= 2 && path[0] == '.' && path[1] == '/') ||
(path.length() >= 3 && path[0] == '.' && path[1] == '.' &&
path[2] == '/') ||
path == "." || path == "..")
{
// Absolute or relative path
fileName = path;
}
else
{
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
if (uploadPath[uploadPath.length() - 1] == '/')
tmpPath = uploadPath + path;
else
tmpPath = uploadPath + "/" + path;
fileName = HttpAppFrameworkImpl::instance().getUploadPath();
ensureSlashPostfix(fileName);
fileName += path;
}

if (utils::createPath(tmpPath) < 0)
if (utils::createPath(fileName) < 0)
return -1;

if (tmpPath[tmpPath.length() - 1] != '/')
{
filename = tmpPath + "/";
filename.append(fileName_.data(), fileName_.length());
}
else
filename = tmpPath.append(fileName_.data(), fileName_.length());

return saveTo(filename);
}
int HttpFileImpl::save() const
{
return save(HttpAppFrameworkImpl::instance().getUploadPath());
}
int HttpFileImpl::saveAs(const std::string &filename) const
{
assert(!filename.empty());
auto pathAndFileName = filename;
if (filename[0] == '/' ||
(filename.length() >= 2 && filename[0] == '.' && filename[1] == '/') ||
(filename.length() >= 3 && filename[0] == '.' && filename[1] == '.' &&
filename[2] == '/'))
ensureSlashPostfix(fileName);
fileName += fileName_;
return saveTo(fileName);
}
int HttpFileImpl::saveAs(const std::string &fileName) const
{
assert(!fileName.empty());
std::string pathAndFileName;
if (fileName[0] == '/' ||
(fileName.length() >= 2 && fileName[0] == '.' && fileName[1] == '/') ||
(fileName.length() >= 3 && fileName[0] == '.' && fileName[1] == '.' &&
fileName[2] == '/'))
{
// Absolute or relative path
pathAndFileName = fileName;
}
else
{
auto &uploadPath = HttpAppFrameworkImpl::instance().getUploadPath();
if (uploadPath[uploadPath.length() - 1] == '/')
pathAndFileName = uploadPath + filename;
else
pathAndFileName = uploadPath + "/" + filename;
pathAndFileName = HttpAppFrameworkImpl::instance().getUploadPath();
ensureSlashPostfix(pathAndFileName);
pathAndFileName += fileName;
}
auto pathPos = pathAndFileName.rfind('/');
if (pathPos != std::string::npos)
Expand All @@ -89,10 +85,10 @@ int HttpFileImpl::saveAs(const std::string &filename) const
}
return saveTo(pathAndFileName);
}
int HttpFileImpl::saveTo(const std::string &pathAndFilename) const
int HttpFileImpl::saveTo(const std::string &pathAndFileName) const
{
LOG_TRACE << "save uploaded file:" << pathAndFilename;
std::ofstream file(pathAndFilename, std::ios::binary);
LOG_TRACE << "save uploaded file:" << pathAndFileName;
std::ofstream file(pathAndFileName, std::ios::binary);
if (file.is_open())
{
file.write(fileContent_.data(), fileContent_.size());
Expand All @@ -115,9 +111,19 @@ const std::string &HttpFile::getFileName() const
return implPtr_->getFileName();
}

void HttpFile::setFileName(const std::string &filename)
void HttpFile::setFileName(const std::string &fileName)
{
implPtr_->setFileName(fileName);
}

string_view HttpFile::getFileExtension() const
{
return implPtr_->getFileExtension();
}

FileType HttpFile::getFileType() const
{
implPtr_->setFileName(filename);
return implPtr_->getFileType();
}

void HttpFile::setFile(const char *data, size_t length)
Expand All @@ -135,9 +141,9 @@ int HttpFile::save(const std::string &path) const
return implPtr_->save(path);
}

int HttpFile::saveAs(const std::string &filename) const
int HttpFile::saveAs(const std::string &fileName) const
{
return implPtr_->saveAs(filename);
return implPtr_->saveAs(fileName);
}

size_t HttpFile::fileLength() const noexcept
Expand Down
Loading

0 comments on commit df51674

Please sign in to comment.