mirror of
https://github.com/drogonframework/drogon.git
synced 2025-09-22 00:00:33 -04:00
Support file upload requests
This commit is contained in:
parent
4a412e905e
commit
55ba4c5196
@ -16,7 +16,9 @@ void Attachment::upload(const HttpRequestPtr &req,
|
||||
MultiPartParser fileUpload;
|
||||
if (fileUpload.parse(req) == 0)
|
||||
{
|
||||
//LOG_DEBUG << "upload good!";
|
||||
auto files = fileUpload.getFiles();
|
||||
//LOG_DEBUG << "file num=" << files.size();
|
||||
for (auto const &file : files)
|
||||
{
|
||||
LOG_DEBUG << "file:"
|
||||
@ -36,10 +38,16 @@ void Attachment::upload(const HttpRequestPtr &req,
|
||||
}
|
||||
Json::Value json;
|
||||
json["result"] = "ok";
|
||||
for (auto ¶m : fileUpload.getParameters())
|
||||
{
|
||||
json[param.first] = param.second;
|
||||
}
|
||||
auto resp = HttpResponse::newHttpJsonResponse(json);
|
||||
callback(resp);
|
||||
return;
|
||||
}
|
||||
LOG_DEBUG << "upload error!";
|
||||
//LOG_DEBUG<<req->con
|
||||
Json::Value json;
|
||||
json["result"] = "failed";
|
||||
auto resp = HttpResponse::newHttpJsonResponse(json);
|
||||
|
@ -31,6 +31,12 @@ using namespace drogon;
|
||||
void outputGood(const HttpRequestPtr &req)
|
||||
{
|
||||
static int i = 0;
|
||||
// auto start = req->creationDate();
|
||||
// auto end = trantor::Date::now();
|
||||
// int ms = end.microSecondsSinceEpoch() - start.microSecondsSinceEpoch();
|
||||
// ms = ms / 1000;
|
||||
// char str[32];
|
||||
// sprintf(str, "%6dms", ms);
|
||||
static std::mutex mtx;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
@ -574,6 +580,36 @@ void doTest(const HttpClientPtr &client, std::promise<int> &pro)
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
//return;
|
||||
// Test file upload
|
||||
UploadFile file1("./drogon.jpg");
|
||||
UploadFile file2("./config.example.json", "config.json", "file2");
|
||||
req = HttpRequest::newFileUploadRequest({file1, file2});
|
||||
req->setPath("/api/attachment/upload");
|
||||
req->setParameter("P1", "upload");
|
||||
req->setParameter("P2", "test");
|
||||
client->sendRequest(req, [=](ReqResult result, const HttpResponsePtr &resp) {
|
||||
if (result == ReqResult::Ok)
|
||||
{
|
||||
auto json = resp->getJsonObject();
|
||||
if (json && (*json)["result"].asString() == "ok" && (*json)["P1"] == "upload" & (*json)["P2"] == "test")
|
||||
{
|
||||
outputGood(req);
|
||||
//std::cout << (*json) << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << resp->getBody().length();
|
||||
LOG_ERROR << "Error!";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Error!";
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
#include <drogon/HttpTypes.h>
|
||||
#include <drogon/Session.h>
|
||||
#include <drogon/UploadFile.h>
|
||||
#include <trantor/net/InetAddress.h>
|
||||
#include <trantor/utils/Date.h>
|
||||
#include <json/json.h>
|
||||
@ -90,8 +91,8 @@ class HttpRequest
|
||||
/// Return the local IP address and port
|
||||
virtual const trantor::InetAddress &localAddr() const = 0;
|
||||
|
||||
/// Returns the receive timestamp set by the framework.
|
||||
virtual const trantor::Date &receiveDate() const = 0;
|
||||
/// Returns the creation timestamp set by the framework.
|
||||
virtual const trantor::Date &creationDate() const = 0;
|
||||
|
||||
/// Get the Json object of the request
|
||||
virtual const std::shared_ptr<Json::Value> getJsonObject() const = 0;
|
||||
@ -114,6 +115,7 @@ class HttpRequest
|
||||
static HttpRequestPtr newHttpRequest();
|
||||
static HttpRequestPtr newHttpJsonRequest(const Json::Value &data);
|
||||
static HttpRequestPtr newHttpFormPostRequest();
|
||||
static HttpRequestPtr newFileUploadRequest(const std::vector<UploadFile> &files);
|
||||
virtual ~HttpRequest() {}
|
||||
};
|
||||
|
||||
|
@ -34,7 +34,7 @@ class HttpResponse
|
||||
|
||||
virtual HttpStatusCode statusCode() = 0;
|
||||
|
||||
virtual const trantor::Date &createDate() const = 0;
|
||||
virtual const trantor::Date &creationDate() const = 0;
|
||||
|
||||
virtual void setStatusCode(HttpStatusCode code) = 0;
|
||||
|
||||
|
@ -92,7 +92,8 @@ enum ContentType
|
||||
CT_IMAGE_GIF,
|
||||
CT_IMAGE_XICON,
|
||||
CT_IMAGE_ICNS,
|
||||
CT_IMAGE_BMP
|
||||
CT_IMAGE_BMP,
|
||||
CT_MULTIPART_FORM_DATA
|
||||
};
|
||||
|
||||
enum HttpMethod
|
||||
|
60
lib/inc/drogon/UploadFile.h
Normal file
60
lib/inc/drogon/UploadFile.h
Normal file
@ -0,0 +1,60 @@
|
||||
/**
|
||||
*
|
||||
* UploadFile.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
class UploadFile
|
||||
{
|
||||
public:
|
||||
/// This class represents an upload file which will be transferred to the server via the multipart/form-data format
|
||||
/**
|
||||
* @param filePath: The file location on local host, including file name.
|
||||
* @param fileName: The file name provided to the server. If it is empty by default, the file name in the @param filePath
|
||||
* will be provided to the server.
|
||||
* @param itemName: The item name on the browser form.
|
||||
*/
|
||||
explicit UploadFile(const std::string &filePath, const std::string &fileName = "", const std::string &itemName = "file")
|
||||
: _path(filePath),
|
||||
_itemName(itemName)
|
||||
{
|
||||
if (!fileName.empty())
|
||||
{
|
||||
_fileName = fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto pos = filePath.rfind("/");
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
_fileName = filePath.substr(pos + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_fileName = filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::string &path() const { return _path; }
|
||||
const std::string &fileName() const { return _fileName; }
|
||||
const std::string &itemName() const { return _itemName; }
|
||||
|
||||
private:
|
||||
std::string _path;
|
||||
std::string _fileName;
|
||||
std::string _itemName;
|
||||
};
|
||||
} // namespace drogon
|
@ -223,7 +223,7 @@ void HttpControllersRouter::doControllerHandler(const CtrlBinderPtr &ctrlBinderP
|
||||
responsePtr = ctrlBinderPtr->_responsePtr;
|
||||
}
|
||||
|
||||
if (responsePtr && (responsePtr->expiredTime() == 0 || (trantor::Date::now() < responsePtr->createDate().after(responsePtr->expiredTime()))))
|
||||
if (responsePtr && (responsePtr->expiredTime() == 0 || (trantor::Date::now() < responsePtr->creationDate().after(responsePtr->expiredTime()))))
|
||||
{
|
||||
//use cached response!
|
||||
LOG_TRACE << "Use cached response";
|
||||
|
28
lib/src/HttpFileUploadRequest.cc
Normal file
28
lib/src/HttpFileUploadRequest.cc
Normal file
@ -0,0 +1,28 @@
|
||||
/**
|
||||
*
|
||||
* HttpFileUploadRequest.cc
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HttpFileUploadRequest.h"
|
||||
#include <drogon/utils/Utilities.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
HttpFileUploadRequest::HttpFileUploadRequest(const std::vector<UploadFile> &files)
|
||||
: _files(files)
|
||||
{
|
||||
_boundary = genRandomString(32);
|
||||
setMethod(drogon::Post);
|
||||
setVersion(drogon::HttpRequest::kHttp11);
|
||||
setContentType("multipart/form-data; boundary=" + _boundary);
|
||||
_contentType = CT_MULTIPART_FORM_DATA;
|
||||
}
|
35
lib/src/HttpFileUploadRequest.h
Normal file
35
lib/src/HttpFileUploadRequest.h
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
*
|
||||
* HttpFileUploadRequest.h
|
||||
* An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "HttpRequestImpl.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
|
||||
class HttpFileUploadRequest : public HttpRequestImpl
|
||||
{
|
||||
public:
|
||||
const std::string &boundary() const { return _boundary; }
|
||||
const std::vector<UploadFile> &files() const { return _files; }
|
||||
HttpFileUploadRequest(const std::vector<UploadFile> &files);
|
||||
|
||||
private:
|
||||
std::string _boundary;
|
||||
std::vector<UploadFile> _files;
|
||||
};
|
||||
|
||||
} // namespace drogon
|
@ -13,8 +13,11 @@
|
||||
*/
|
||||
|
||||
#include "HttpRequestImpl.h"
|
||||
#include "HttpFileUploadRequest.h"
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
@ -124,7 +127,7 @@ void HttpRequestImpl::appendToBuffer(MsgBuffer *output) const
|
||||
}
|
||||
|
||||
std::string content;
|
||||
if (!_parameters.empty())
|
||||
if (!_parameters.empty() && _contentType != CT_MULTIPART_FORM_DATA)
|
||||
{
|
||||
for (auto const &p : _parameters)
|
||||
{
|
||||
@ -145,7 +148,7 @@ void HttpRequestImpl::appendToBuffer(MsgBuffer *output) const
|
||||
{
|
||||
///Can't set parameters in content in this case
|
||||
LOG_ERROR << "You can't set parameters in the query string when the request content type is JSON and http method is POST or PUT";
|
||||
LOG_ERROR << "Please put these parameters in the path or the json string";
|
||||
LOG_ERROR << "Please put these parameters into the path or into the json string";
|
||||
content.clear();
|
||||
}
|
||||
}
|
||||
@ -164,7 +167,52 @@ void HttpRequestImpl::appendToBuffer(MsgBuffer *output) const
|
||||
return;
|
||||
}
|
||||
output->append("\r\n");
|
||||
|
||||
if (_contentType == CT_MULTIPART_FORM_DATA)
|
||||
{
|
||||
auto mReq = dynamic_cast<const HttpFileUploadRequest *>(this);
|
||||
assert(mReq);
|
||||
for (auto ¶m : mReq->getParameters())
|
||||
{
|
||||
content.append("--");
|
||||
content.append(mReq->boundary());
|
||||
content.append("\r\n");
|
||||
content.append("Content-Disposition: form-data; name=\"");
|
||||
content.append(param.first);
|
||||
content.append("\"\r\n\r\n");
|
||||
content.append(param.second);
|
||||
content.append("\r\n");
|
||||
}
|
||||
for (auto &file : mReq->files())
|
||||
{
|
||||
content.append("--");
|
||||
content.append(mReq->boundary());
|
||||
content.append("\r\n");
|
||||
content.append("Content-Disposition: form-data; name=\"");
|
||||
content.append(file.itemName());
|
||||
content.append("\"; filename=\"");
|
||||
content.append(file.fileName());
|
||||
content.append("\"\r\n\r\n");
|
||||
std::ifstream infile(file.path(), std::ifstream::binary);
|
||||
if (!infile)
|
||||
{
|
||||
LOG_ERROR << file.path() << " not found";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::streambuf *pbuf = infile.rdbuf();
|
||||
std::streamsize filesize = pbuf->pubseekoff(0, infile.end);
|
||||
pbuf->pubseekoff(0, infile.beg); // rewind
|
||||
std::string str;
|
||||
str.resize(filesize);
|
||||
pbuf->sgetn(&str[0], filesize);
|
||||
content.append(std::move(str));
|
||||
}
|
||||
content.append("\r\n");
|
||||
}
|
||||
content.append("--");
|
||||
content.append(mReq->boundary());
|
||||
content.append("--");
|
||||
}
|
||||
assert(!(!content.empty() && !_content.empty()));
|
||||
if (!content.empty() || !_content.empty())
|
||||
{
|
||||
@ -201,12 +249,10 @@ void HttpRequestImpl::appendToBuffer(MsgBuffer *output) const
|
||||
}
|
||||
|
||||
output->append("\r\n");
|
||||
//LOG_INFO<<"request(no body):"<<output->peek();
|
||||
if (!content.empty())
|
||||
output->append(content);
|
||||
if (!_content.empty())
|
||||
output->append(_content);
|
||||
//LOG_INFO << output->peek();
|
||||
}
|
||||
|
||||
void HttpRequestImpl::addHeader(const char *start, const char *colon, const char *end)
|
||||
@ -296,3 +342,8 @@ HttpRequestPtr HttpRequest::newHttpJsonRequest(const Json::Value &data)
|
||||
req->setContent(writeString(builder, data));
|
||||
return req;
|
||||
}
|
||||
|
||||
HttpRequestPtr HttpRequest::newFileUploadRequest(const std::vector<UploadFile> &files)
|
||||
{
|
||||
return std::make_shared<HttpFileUploadRequest>(files);
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ class HttpRequestImpl : public HttpRequest
|
||||
HttpRequestImpl()
|
||||
: _method(Invalid),
|
||||
_version(kUnknown),
|
||||
_date(trantor::Date::now()),
|
||||
_contentLen(0)
|
||||
{
|
||||
}
|
||||
@ -199,7 +200,7 @@ class HttpRequestImpl : public HttpRequest
|
||||
return _local;
|
||||
}
|
||||
|
||||
virtual const trantor::Date &receiveDate() const override
|
||||
virtual const trantor::Date &creationDate() const override
|
||||
{
|
||||
return _date;
|
||||
}
|
||||
@ -343,8 +344,7 @@ class HttpRequestImpl : public HttpRequest
|
||||
return _contentType;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
protected:
|
||||
friend class HttpRequest;
|
||||
void setContentType(const std::string &contentType)
|
||||
{
|
||||
@ -354,13 +354,12 @@ class HttpRequestImpl : public HttpRequest
|
||||
{
|
||||
addHeader("Content-Type", std::move(contentType));
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
HttpMethod _method;
|
||||
Version _version;
|
||||
std::string _path;
|
||||
std::string _query;
|
||||
|
||||
//trantor::Date receiveTime_;
|
||||
std::unordered_map<std::string, std::string> _headers;
|
||||
std::unordered_map<std::string, std::string> _cookies;
|
||||
std::unordered_map<std::string, std::string> _parameters;
|
||||
@ -369,11 +368,11 @@ class HttpRequestImpl : public HttpRequest
|
||||
trantor::InetAddress _peer;
|
||||
trantor::InetAddress _local;
|
||||
trantor::Date _date;
|
||||
ContentType _contentType = CT_TEXT_PLAIN;
|
||||
|
||||
|
||||
protected:
|
||||
std::string _content;
|
||||
size_t _contentLen;
|
||||
ContentType _contentType = CT_TEXT_PLAIN;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<HttpRequestImpl> HttpRequestImplPtr;
|
||||
|
@ -35,7 +35,7 @@ class HttpResponseImpl : public HttpResponse
|
||||
public:
|
||||
explicit HttpResponseImpl()
|
||||
: _statusCode(kUnknown),
|
||||
_createDate(trantor::Date::now()),
|
||||
_creationDate(trantor::Date::now()),
|
||||
_closeConnection(false),
|
||||
_leftBodyLength(0),
|
||||
_currentChunkLength(0),
|
||||
@ -48,9 +48,9 @@ class HttpResponseImpl : public HttpResponse
|
||||
return _statusCode;
|
||||
}
|
||||
|
||||
virtual const trantor::Date &createDate() const override
|
||||
virtual const trantor::Date &creationDate() const override
|
||||
{
|
||||
return _createDate;
|
||||
return _creationDate;
|
||||
}
|
||||
|
||||
virtual void setStatusCode(HttpStatusCode code) override
|
||||
@ -369,7 +369,7 @@ class HttpResponseImpl : public HttpResponse
|
||||
std::unordered_map<std::string, std::string> _headers;
|
||||
std::unordered_map<std::string, Cookie> _cookies;
|
||||
HttpStatusCode _statusCode;
|
||||
trantor::Date _createDate;
|
||||
trantor::Date _creationDate;
|
||||
Version _v;
|
||||
std::string _statusMessage;
|
||||
bool _closeConnection;
|
||||
|
@ -129,7 +129,7 @@ void HttpSimpleControllersRouter::doControllerHandler(SimpleControllerRouterItem
|
||||
std::lock_guard<std::mutex> guard(item._mutex);
|
||||
responsePtr = item._responsePtr;
|
||||
}
|
||||
if (responsePtr && (responsePtr->expiredTime() == 0 || (trantor::Date::now() < responsePtr->createDate().after(responsePtr->expiredTime()))))
|
||||
if (responsePtr && (responsePtr->expiredTime() == 0 || (trantor::Date::now() < responsePtr->creationDate().after(responsePtr->expiredTime()))))
|
||||
{
|
||||
//use cached response!
|
||||
LOG_TRACE << "Use cached response";
|
||||
|
@ -99,7 +99,7 @@ std::string webContentTypeToString(ContentType contenttype)
|
||||
|
||||
case CT_APPLICATION_X_FORM:
|
||||
return "application/x-www-form-urlencoded";
|
||||
|
||||
|
||||
case CT_APPLICATION_XML:
|
||||
return "application/xml; charset=utf-8";
|
||||
|
||||
@ -163,4 +163,4 @@ std::string webContentTypeToString(ContentType contenttype)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace drogon
|
Loading…
x
Reference in New Issue
Block a user