Support file upload requests

This commit is contained in:
antao 2019-01-29 17:38:55 +08:00
parent 4a412e905e
commit 55ba4c5196
14 changed files with 245 additions and 25 deletions

View File

@ -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 &param : 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);

View File

@ -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[])

View File

@ -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() {}
};

View File

@ -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;

View File

@ -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

View 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

View File

@ -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";

View 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;
}

View 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

View File

@ -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 &param : 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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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";

View File

@ -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