Optimize file upload

This commit is contained in:
antao 2019-01-27 15:59:56 +08:00
parent b37aa3da6c
commit cef7dc177e
5 changed files with 142 additions and 125 deletions

View File

@ -2,74 +2,65 @@
<html>
<head>
<meta charset="UTF-8">
<title>XMLHttpRequest上传文件</title>
<title>File upload</title>
<script type="text/javascript">
//图片上传
var xhr;
//上传文件方法
//File uploading method
function UpladFile() {
var fileObj = document.getElementById("file").files[0]; // js 获取文件对象
var url = "http://localhost:8080" + "/api/attachment/upload"; // 接收上传文件的后台地址
var fileObj = document.getElementById("file").files[0]; // js get file object
var url = "http://localhost:8848" + "/api/attachment/upload";
var form = new FormData(); // FormData 对象
form.append("file", fileObj); // 文件对象
var form = new FormData(); // FormData object
form.append("file", fileObj); // File object
xhr = new XMLHttpRequest(); // XMLHttpRequest 对象
xhr.open("post", url, true); //post方式url为服务器请求地址true 该参数规定请求是否异步处理。
xhr.onload = uploadComplete; //请求完成
xhr.onerror = uploadFailed; //请求失败
xhr = new XMLHttpRequest(); // XMLHttpRequest object
xhr.open("post", url, true); //post
xhr.onload = uploadComplete;
xhr.onerror = uploadFailed;
xhr.upload.onprogress = progressFunction;//【上传进度调用方法实现】
xhr.upload.onloadstart = function(){//上传开始执行方法
ot = new Date().getTime(); //设置上传开始时间
oloaded = 0;//设置上传开始时以上传的文件大小为0
xhr.upload.onprogress = progressFunction;
xhr.upload.onloadstart = function(){
ot = new Date().getTime();
oloaded = 0;
};
xhr.send(form); //开始上传发送form数据
xhr.send(form);
}
//上传成功响应
function uploadComplete(evt) {
//服务断接收完文件返回的结果
var data = JSON.parse(evt.target.responseText);
if(data.success) {
alert("上传成功!");
if(data.result == "ok") {
alert("Uploaded successfully!");
}else{
alert("上传失败!");
alert("Upload failed!");
}
}
//上传失败
function uploadFailed(evt) {
alert("上传失败!");
alert("Upload failed!");
}
//取消上传
function cancleUploadFile(){
xhr.abort();
}
//上传进度实现方法,上传过程中会频繁调用该方法
function progressFunction(evt) {
var progressBar = document.getElementById("progressBar");
var percentageDiv = document.getElementById("percentage");
// event.total是需要传输的总字节event.loaded是已经传输的字节。如果event.lengthComputable不为真则event.total等于0
if (evt.lengthComputable) {//
progressBar.max = evt.total;
progressBar.value = evt.loaded;
percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";
}
var time = document.getElementById("time");
var nt = new Date().getTime();//获取当前时间
var pertime = (nt-ot)/1000; //计算出上次调用该方法时到现在的时间差单位为s
ot = new Date().getTime(); //重新赋值时间,用于下次计算
var perload = evt.loaded - oloaded; //计算该分段上传的文件大小单位b
oloaded = evt.loaded;//重新赋值已上传文件大小,用以下次计算
//上传速度计算
var speed = perload/pertime;//单位b/s
var nt = new Date().getTime();
var pertime = (nt-ot)/1000;
ot = new Date().getTime();
var perload = evt.loaded - oloaded;
oloaded = evt.loaded;
var speed = perload/pertime;
var bspeed = speed;
var units = 'b/s';//单位名称
var units = 'b/s';
if(speed/1024>1){
speed = speed/1024;
units = 'k/s';
@ -79,10 +70,9 @@
units = 'M/s';
}
speed = speed.toFixed(1);
//剩余时间
var resttime = ((evt.total-evt.loaded)/bspeed).toFixed(1);
time.innerHTML = ',速度:'+speed+units+',剩余时间:'+resttime+'s';
if(bspeed==0) time.innerHTML = '上传已取消';
time.innerHTML = ',Speed: '+speed+units+', the remaining time: '+resttime+'s';
if(bspeed==0) time.innerHTML = 'Upload cancelled';
}
</script>
</head>
@ -91,7 +81,7 @@
<span id="percentage"></span><span id="time"></span>
<br /><br />
<input type="file" id="file" name="myfile" />
<input type="button" onclick="UpladFile()" value="上传" />
<input type="button" onclick="cancleUploadFile()" value="取消" />
<input type="button" onclick="UpladFile()" value="Upload" />
<input type="button" onclick="cancleUploadFile()" value="Cancel" />
</body>
</html>

View File

@ -11,27 +11,35 @@ void Attachment::get(const HttpRequestPtr &req,
void Attachment::upload(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback)
{
FileUpload fileUpload;
fileUpload.parse(req);
auto files = fileUpload.getFiles();
for (auto const &file : files)
MultiPartParser fileUpload;
if(fileUpload.parse(req)==0)
{
LOG_DEBUG << "file:"
<< file.getFileName()
<< "(len="
<< file.fileLength()
<< ",md5="
<< file.getMd5()
<< ")";
file.save();
file.save("123");
file.saveAs("456/hehe");
file.saveAs("456/7/8/9/"+file.getMd5());
file.save("..");
file.save(".xx");
file.saveAs("../xxx");
auto files = fileUpload.getFiles();
for (auto const &file : files)
{
LOG_DEBUG << "file:"
<< file.getFileName()
<< "(len="
<< file.fileLength()
<< ",md5="
<< file.getMd5()
<< ")";
file.save();
file.save("123");
file.saveAs("456/hehe");
file.saveAs("456/7/8/9/" + file.getMd5());
file.save("..");
file.save(".xx");
file.saveAs("../xxx");
}
Json::Value json;
json["result"] = "ok";
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
return;
}
auto resp = HttpResponse::newHttpResponse();
resp->setStatusCode(k200OK);
Json::Value json;
json["result"] = "failed";
auto resp = HttpResponse::newHttpJsonResponse(json);
callback(resp);
}

View File

@ -30,7 +30,7 @@
#include <drogon/version.h>
#include <drogon/NotFound.h>
#include <drogon/HttpClient.h>
#include <drogon/FileUpload.h>
#include <drogon/MultiPart.h>
#include <trantor/net/EventLoop.h>
#include <drogon/CacheMap.h>
#include <memory>

View File

@ -1,6 +1,6 @@
/**
*
* FileUpload.h
* MultiPart.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
@ -14,12 +14,15 @@
#pragma once
#include "HttpRequest.h"
#include <drogon/HttpRequest.h>
#include <drogon/HttpResponse.h>
#include <string>
#include <map>
#include <vector>
namespace drogon
{
class HttpFile
{
public:
@ -31,6 +34,7 @@ class HttpFile
/// Set the contents of the file, usually called by the FileUpload parser.
void setFile(const std::string &file) { _fileContent = file; };
void setFile(std::string &&file) { _fileContent = std::move(file); }
/// Save the file to the file system.
/**
@ -66,11 +70,12 @@ class HttpFile
std::string _fileName;
std::string _fileContent;
};
class FileUpload
class MultiPartParser
{
public:
FileUpload(){};
~FileUpload(){};
MultiPartParser(){};
~MultiPartParser(){};
/// Get files, This method should be called after calling the parse() method.
const std::vector<HttpFile> &getFiles();
@ -80,9 +85,16 @@ class FileUpload
/// Parse the http request stream to get files and parameters.
int parse(const HttpRequestPtr &req);
/// Parse the http response stream to get files and parameters.
int parse(const HttpResponsePtr &req);
protected:
std::vector<HttpFile> _files;
std::map<std::string, std::string> _parameters;
int parseEntity(const std::string &str);
int parse(const std::string &content, const std::string &boundary);
int parseEntity(const char *begin, const char *end);
};
typedef MultiPartParser FileUpload; /// In order to be compatible with old interfaces
} // namespace drogon

View File

@ -1,6 +1,6 @@
/**
*
* FileUpload.cc
* MultiPart.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
@ -15,7 +15,7 @@
#include "HttpRequestImpl.h"
#include "HttpUtils.h"
#include <drogon/utils/Utilities.h>
#include <drogon/FileUpload.h>
#include <drogon/MultiPart.h>
#include <drogon/HttpAppFramework.h>
#ifdef USE_OPENSSL
#include <openssl/md5.h>
@ -31,15 +31,17 @@
using namespace drogon;
const std::vector<HttpFile> &FileUpload::getFiles()
const std::vector<HttpFile> &MultiPartParser::getFiles()
{
return _files;
}
const std::map<std::string, std::string> &FileUpload::getParameters() const
const std::map<std::string, std::string> &MultiPartParser::getParameters() const
{
return _parameters;
};
int FileUpload::parse(const HttpRequestPtr &req)
int MultiPartParser::parse(const HttpRequestPtr &req)
{
if (req->method() != Post)
return -1;
@ -61,7 +63,59 @@ int FileUpload::parse(const HttpRequestPtr &req)
return -1;
std::string boundary = contentType.substr(pos + 9);
//std::cout << "boundary[" << boundary << "]" << std::endl;
std::string content = req->query();
auto &content = req->query();
return parse(content, boundary);
}
int MultiPartParser::parse(const HttpResponsePtr &resp)
{
/// TODO:
return 0;
}
int MultiPartParser::parseEntity(const char *begin, const char *end)
{
static const char entityName[] = "name=\"";
static const char quotationMark[] = "\"";
static const char fileName[] = "filename=\"";
static const char CRLF[] = "\r\n\r\n";
auto pos = std::search(begin, end, entityName, entityName + 6);
if (pos == end)
return -1;
pos += 6;
auto pos1 = std::search(pos, end, quotationMark, quotationMark + 1);
if (pos1 == end)
return -1;
std::string name(pos, pos1);
pos = std::search(pos1, end, fileName, fileName + 10);
if (pos == end)
{
pos1 = std::search(pos1, end, CRLF, CRLF + 4);
if (pos1 == end)
return -1;
_parameters[name] = std::string(pos1 + 4, end);
return 0;
}
else
{
pos += 10;
auto pos1 = std::search(pos, end, quotationMark, quotationMark + 1);
if (pos1 == end)
return -1;
HttpFile file;
file.setFileName(std::string(pos, pos1));
pos1 = std::search(pos1, end, CRLF, CRLF + 4);
if (pos1 == end)
return -1;
file.setFile(std::string(pos1 + 4, end));
_files.push_back(std::move(file));
return 0;
}
}
int MultiPartParser::parse(const std::string &content, const std::string &boundary)
{
std::string::size_type pos1, pos2;
pos1 = 0;
pos2 = content.find(boundary);
@ -79,59 +133,12 @@ int FileUpload::parse(const HttpRequestPtr &req)
// std::cout<<"pos1="<<pos1<<" pos2="<<pos2<<std::endl;
if (content[pos2 - 4] == '\r' && content[pos2 - 3] == '\n' && content[pos2 - 2] == '-' && content[pos2 - 1] == '-')
pos2 -= 4;
if (parseEntity(content.substr(pos1, pos2 - pos1)) != 0)
if (parseEntity(content.c_str() + pos1, content.c_str() + pos2) != 0)
return -1;
//pos2+=boundary.length();
}
return 0;
}
int FileUpload::parseEntity(const std::string &str)
{
// parseEntity:[Content-Disposition: form-data; name="userfile1"; filename="appID.txt"
// Content-Type: text/plain
//
// AA004MV7QI]
// pos1=190 pos2=251
// parseEntity:[YvBu
// Content-Disposition: form-data; name="text"
//
// text]
// std::cout<<"parseEntity:["<<str<<"]"<<std::endl;
std::string::size_type pos = str.find("name=\"");
if (pos == std::string::npos)
return -1;
pos += 6;
std::string::size_type pos1 = str.find("\"", pos);
if (pos1 == std::string::npos)
return -1;
std::string name = str.substr(pos, pos1 - pos);
// std::cout<<"name=["<<name<<"]\n";
pos = str.find("filename=\"");
if (pos == std::string::npos)
{
pos1 = str.find("\r\n\r\n");
if (pos1 == std::string::npos)
return -1;
_parameters[name] = str.substr(pos1 + 4);
return 0;
}
else
{
pos += 10;
std::string::size_type pos1 = str.find("\"", pos);
if (pos1 == std::string::npos)
return -1;
HttpFile file;
file.setFileName(str.substr(pos, pos1 - pos));
pos1 = str.find("\r\n\r\n");
if (pos1 == std::string::npos)
return -1;
file.setFile(str.substr(pos1 + 4));
_files.push_back(file);
return 0;
}
}
int HttpFile::save(const std::string &path) const
{
@ -145,7 +152,7 @@ int HttpFile::save(const std::string &path) const
(path.length() >= 3 && path[0] == '.' && path[1] == '.' && path[2] == '/') ||
path == "." || path == "..")
{
//Absolute or relative path
//Absolute or relative path
}
else
{
@ -180,7 +187,7 @@ int HttpFile::saveAs(const std::string &filename) const
(filename.length() >= 2 && filename[0] == '.' && filename[1] == '/') ||
(filename.length() >= 3 && filename[0] == '.' && filename[1] == '.' && filename[2] == '/'))
{
//Absolute or relative path
//Absolute or relative path
}
else
{