mirror of
https://github.com/drogonframework/drogon.git
synced 2025-09-22 00:00:33 -04:00
Enhancements on files part. (#803)
Co-authored-by: an-tao <antao2002@gmail.com>
This commit is contained in:
parent
0ec2f51fbf
commit
df51674792
@ -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");
|
||||
@ -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 ¶m : 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);
|
||||
|
@ -10,6 +10,7 @@ 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:
|
||||
@ -17,6 +18,8 @@ class Attachment : public drogon::HttpController<Attachment>
|
||||
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);
|
||||
};
|
||||
|
@ -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");
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
@ -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.
|
||||
@ -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.
|
||||
|
@ -19,14 +19,24 @@
|
||||
#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] == '.' &&
|
||||
@ -34,51 +44,37 @@ int HttpFileImpl::save(const std::string &path) const
|
||||
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);
|
||||
ensureSlashPostfix(fileName);
|
||||
fileName += fileName_;
|
||||
return saveTo(fileName);
|
||||
}
|
||||
int HttpFileImpl::save() const
|
||||
int HttpFileImpl::saveAs(const std::string &fileName) 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] == '/'))
|
||||
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)
|
||||
@ -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());
|
||||
@ -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);
|
||||
implPtr_->setFileName(fileName);
|
||||
}
|
||||
|
||||
string_view HttpFile::getFileExtension() const
|
||||
{
|
||||
return implPtr_->getFileExtension();
|
||||
}
|
||||
|
||||
FileType HttpFile::getFileType() const
|
||||
{
|
||||
return implPtr_->getFileType();
|
||||
}
|
||||
|
||||
void HttpFile::setFile(const char *data, size_t length)
|
||||
@ -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
|
||||
|
@ -13,6 +13,7 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpUtils.h"
|
||||
#include <drogon/utils/string_view.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
|
||||
@ -29,20 +30,26 @@ class HttpFileImpl
|
||||
const std::string &getFileName() const
|
||||
{
|
||||
return fileName_;
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the file name, usually called by the MultiPartParser parser.
|
||||
void setFileName(const std::string &filename)
|
||||
void setFileName(const std::string &fileName)
|
||||
{
|
||||
fileName_ = filename;
|
||||
};
|
||||
fileName_ = fileName;
|
||||
}
|
||||
|
||||
/// Return the file extension;
|
||||
string_view getFileExtension() const
|
||||
{
|
||||
return drogon::getFileExtension(fileName_);
|
||||
}
|
||||
|
||||
/// Set the contents of the file, usually called by the MultiPartParser
|
||||
/// parser.
|
||||
void setFile(const char *data, size_t length)
|
||||
{
|
||||
fileContent_ = string_view{data, length};
|
||||
};
|
||||
}
|
||||
|
||||
/// Save the file to the file system.
|
||||
/**
|
||||
@ -62,17 +69,17 @@ class HttpFileImpl
|
||||
|
||||
/// 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;
|
||||
|
||||
/// Return the file length.
|
||||
size_t fileLength() const noexcept
|
||||
{
|
||||
return fileContent_.length();
|
||||
};
|
||||
}
|
||||
|
||||
const char *fileData() const noexcept
|
||||
{
|
||||
@ -95,9 +102,15 @@ class HttpFileImpl
|
||||
itemName_ = itemName;
|
||||
}
|
||||
|
||||
/// Return the type of file.
|
||||
FileType getFileType() const
|
||||
{
|
||||
return parseFileType(getFileExtension());
|
||||
}
|
||||
|
||||
/// Return the md5 string of the file
|
||||
std::string getMd5() const;
|
||||
int saveTo(const std::string &pathAndFilename) const;
|
||||
int saveTo(const std::string &pathAndFileName) const;
|
||||
void setRequest(const HttpRequestPtr &req)
|
||||
{
|
||||
requestPtr_ = req;
|
||||
|
@ -603,4 +603,20 @@ ContentType parseContentType(const string_view &contentType)
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
FileType parseFileType(const string_view &fileExtension)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/List_of_file_formats
|
||||
static const std::unordered_map<string_view, FileType> map_{
|
||||
{"", FT_UNKNOWN}, {"html", FT_DOCUMENT}, {"docx", FT_DOCUMENT},
|
||||
{"zip", FT_ARCHIVE}, {"rar", FT_ARCHIVE}, {"xz", FT_ARCHIVE},
|
||||
{"7z", FT_ARCHIVE}, {"tgz", FT_ARCHIVE}, {"gz", FT_ARCHIVE},
|
||||
{"bz2", FT_ARCHIVE}, {"mp3", FT_AUDIO}, {"wav", FT_AUDIO},
|
||||
{"avi", FT_MEDIA}, {"gif", FT_MEDIA}, {"mp4", FT_MEDIA},
|
||||
{"mov", FT_MEDIA}, {"png", FT_IMAGE}, {"jpeg", FT_IMAGE},
|
||||
{"jpg", FT_IMAGE}, {"ico", FT_IMAGE}};
|
||||
auto iter = map_.find(fileExtension);
|
||||
if (iter == map_.end())
|
||||
return FT_CUSTOM;
|
||||
return iter->second;
|
||||
}
|
||||
} // namespace drogon
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
*
|
||||
* HttpUtils.h
|
||||
* An Tao
|
||||
* @file HttpUtils.h
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
@ -21,10 +21,19 @@
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
ContentType parseContentType(const string_view &contentType);
|
||||
const string_view &webContentTypeToString(ContentType contenttype);
|
||||
const string_view &statusCodeToString(int code);
|
||||
ContentType getContentType(const std::string &fileName);
|
||||
ContentType parseContentType(const string_view &contentType);
|
||||
FileType parseFileType(const string_view &fileExtension);
|
||||
inline string_view getFileExtension(const std::string &fileName)
|
||||
{
|
||||
auto pos = fileName.rfind('.');
|
||||
if (pos == std::string::npos)
|
||||
return "";
|
||||
return string_view(&fileName[pos + 1], fileName.length() - pos - 1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline constexpr const char *contentLengthFormatString()
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ add_executable(base64_unittest Base64Unittest.cpp)
|
||||
add_executable(pubsubservice_unittest PubSubServiceUnittest.cpp)
|
||||
add_executable(httpdate_unittest HttpDateUnittest.cpp)
|
||||
add_executable(httpheader_unittest HttpHeaderUnittest.cpp)
|
||||
add_executable(filetype_unittest FileTypeUnittest.cpp)
|
||||
if(Brotli_FOUND)
|
||||
add_executable(brotli_unittest BrotliUnittest.cpp)
|
||||
endif()
|
||||
@ -22,7 +23,8 @@ set(UNITTEST_TARGETS
|
||||
base64_unittest
|
||||
pubsubservice_unittest
|
||||
httpdate_unittest
|
||||
httpheader_unittest)
|
||||
httpheader_unittest
|
||||
filetype_unittest)
|
||||
if(Brotli_FOUND)
|
||||
set(UNITTEST_TARGETS ${UNITTEST_TARGETS} brotli_unittest)
|
||||
endif()
|
||||
|
42
unittest/FileTypeUnittest.cpp
Normal file
42
unittest/FileTypeUnittest.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "../lib/src/HttpUtils.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
using namespace drogon;
|
||||
|
||||
TEST(ExtensionTest, normal)
|
||||
{
|
||||
std::string str{"drogon.jpg"};
|
||||
EXPECT_TRUE(getFileExtension(str) == "jpg");
|
||||
}
|
||||
|
||||
TEST(ExtensionTest, negative)
|
||||
{
|
||||
std::string str{"drogon."};
|
||||
EXPECT_TRUE(getFileExtension(str) == "");
|
||||
str = "drogon";
|
||||
EXPECT_TRUE(getFileExtension(str) == "");
|
||||
str = "";
|
||||
EXPECT_TRUE(getFileExtension(str) == "");
|
||||
str = "....";
|
||||
EXPECT_TRUE(getFileExtension(str) == "");
|
||||
}
|
||||
|
||||
TEST(FileTypeTest, normal)
|
||||
{
|
||||
EXPECT_EQ(parseFileType("jpg"), FT_IMAGE);
|
||||
EXPECT_EQ(parseFileType("mp4"), FT_MEDIA);
|
||||
EXPECT_EQ(parseFileType("csp"), FT_CUSTOM);
|
||||
EXPECT_EQ(parseFileType("html"), FT_DOCUMENT);
|
||||
}
|
||||
|
||||
TEST(FileTypeTest, negative)
|
||||
{
|
||||
EXPECT_EQ(parseFileType(""), FT_UNKNOWN);
|
||||
EXPECT_EQ(parseFileType("don'tknow"), FT_CUSTOM);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user