mirror of
https://gitee.com/acl-dev/acl.git
synced 2024-12-15 17:30:53 +08:00
9697f95b8f
This reverts commit 15d999759e
.
238 lines
6.3 KiB
C++
238 lines
6.3 KiB
C++
#include "stdafx.h"
|
|
#include "http_servlet.h"
|
|
|
|
http_servlet::http_servlet(const char* filepath, acl::socket_stream* conn,
|
|
acl::session* session)
|
|
: acl::HttpServlet(conn, session)
|
|
{
|
|
if (filepath && *filepath)
|
|
filepath_ = filepath;
|
|
}
|
|
|
|
http_servlet::~http_servlet(void)
|
|
{
|
|
|
|
}
|
|
|
|
bool http_servlet::reply(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res, int status, const char* fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
acl::string buf;
|
|
buf.vformat(fmt, ap);
|
|
va_end(ap);
|
|
res.setStatus(status)
|
|
.setKeepAlive(req.isKeepAlive())
|
|
.setContentType("text/html; charset=utf-8")
|
|
.setContentLength(buf.length());
|
|
return res.write(buf);
|
|
}
|
|
|
|
bool http_servlet::doError(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res)
|
|
{
|
|
acl::string hdr;
|
|
req.getClient()->sprint_header(hdr);
|
|
logger("error request head:\r\n%s\r\n", hdr.c_str());
|
|
|
|
(void) reply(req, res, 400, "unknown request method\r\n");
|
|
return false;
|
|
}
|
|
|
|
bool http_servlet::doUnknown(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res)
|
|
{
|
|
acl::string hdr;
|
|
req.getClient()->sprint_header(hdr);
|
|
logger("request head:\r\n%s\r\n", hdr.c_str());
|
|
|
|
(void) reply(req, res, 400, "unknown request method\r\n");
|
|
return false;
|
|
}
|
|
|
|
bool http_servlet::doGet(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res)
|
|
{
|
|
return doPost(req, res);
|
|
}
|
|
|
|
bool http_servlet::doPost(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res)
|
|
{
|
|
// 如果需要 http session 控制,请打开下面注释,且需要保证
|
|
// 在 master_service.cpp 的函数 thread_on_read 中设置的
|
|
// memcached 服务正常工作
|
|
/*
|
|
const char* sid = req.getSession().getAttribute("sid");
|
|
if (*sid == 0)
|
|
req.getSession().setAttribute("sid", "xxxxxx");
|
|
sid = req.getSession().getAttribute("sid");
|
|
*/
|
|
|
|
// 调试:将客户端请求头记录在日志中
|
|
acl::string hdr;
|
|
req.getClient()->sprint_header(hdr);
|
|
logger("request head:\r\n%s\r\n", hdr.c_str());
|
|
|
|
long long int range_from, range_to;
|
|
if (req.getRange(range_from, range_to) == false)
|
|
return transfer_file(req, res);
|
|
else
|
|
return transfer_file(req, res, range_from, range_to);
|
|
}
|
|
|
|
// 普通下载过程
|
|
bool http_servlet::transfer_file(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res)
|
|
{
|
|
acl::ifstream in;
|
|
|
|
// 只读方式打开文件流
|
|
if (in.open_read(filepath_) == false)
|
|
{
|
|
logger_error("open file %s error %s", filepath_.c_str(),
|
|
acl::last_serror());
|
|
return reply(req, res, 500, "open %s error %s",
|
|
filepath_.c_str(), acl::last_serror());
|
|
}
|
|
|
|
long long int fsize = in.fsize();
|
|
if (fsize <= 0)
|
|
return reply(req, res, 500, "invalid file size: %lld", fsize);
|
|
|
|
acl::string hdr_entry;
|
|
acl::string filename;
|
|
filename.basename(in.file_path()); // 从文件全路径中提取文件名
|
|
hdr_entry.format("attachment;filename=\"%s\"", filename.c_str());
|
|
|
|
// 设置 HTTP 响应头中的字段
|
|
res.setStatus(200)
|
|
.setKeepAlive(req.isKeepAlive())
|
|
.setContentLength(fsize)
|
|
.setContentType("application/octet-stream")
|
|
// 设置 HTTP 头中的文件名
|
|
.setHeader("Content-Disposition", hdr_entry.c_str());
|
|
|
|
acl::string hdr;
|
|
res.getHttpHeader().build_response(hdr);
|
|
logger("response head:\r\n%s\r\n", hdr.c_str());
|
|
|
|
long long n = 0;
|
|
char buf[8192];
|
|
|
|
// 从文件流中读取数据并将数据发给客户端
|
|
while (!in.eof())
|
|
{
|
|
int ret = in.read(buf, sizeof(buf), false);
|
|
if (ret == -1)
|
|
break;
|
|
if (res.write(buf, ret) == false)
|
|
return false;
|
|
n += ret;
|
|
//acl_doze(100); // 休息 100 ms 便于测试
|
|
}
|
|
|
|
return n == fsize ? true : false;
|
|
}
|
|
|
|
// 支持断点续传的数据传输过程
|
|
bool http_servlet::transfer_file(acl::HttpServletRequest& req,
|
|
acl::HttpServletResponse& res,
|
|
long long range_from, long long range_to)
|
|
{
|
|
if (range_from < 0)
|
|
return reply(req, res, 400, "invalid range_from: %lld",
|
|
range_from);
|
|
if (range_to > 0 && range_to < range_from)
|
|
return reply(req, res, 400, "range_from(%lld) > range_to(%lld)",
|
|
range_from, range_to);
|
|
|
|
long long length;
|
|
if (range_to >= range_from)
|
|
length = range_to - range_from + 1;
|
|
else
|
|
length = -1;
|
|
|
|
acl::ifstream in;
|
|
// 只读方式打开文件流
|
|
if (in.open_read(filepath_) == false)
|
|
{
|
|
logger_error("open file %s error %s", filepath_.c_str(),
|
|
acl::last_serror());
|
|
return reply(req, res, 500, "open %s error %s",
|
|
filepath_.c_str(), acl::last_serror());
|
|
}
|
|
|
|
long long int fsize = in.fsize();
|
|
if (fsize <= 0)
|
|
return reply(req, res, 500, "invalid file size: %lld", fsize);
|
|
|
|
// 如果偏移位置超过了文件总长度,则返回错误
|
|
if (range_to >= fsize)
|
|
return reply(req, res, 400, "range_to(%lld) >= fsize(%lld)",
|
|
range_to, fsize);
|
|
|
|
// 如果客户端要求从指定偏移位置至文件尾,则重新计算需要读取的数据长度,
|
|
// 此值作为实际要传输给客户端的长度
|
|
if (length == -1)
|
|
length = fsize - range_from;
|
|
|
|
// 定位文件偏移位置
|
|
if (in.fseek(range_from, SEEK_SET) < 0)
|
|
return reply(req, res, 500, "fseek(%lld) error %s",
|
|
range_from, acl::last_serror());
|
|
|
|
acl::string hdr_entry;
|
|
acl::string filename;
|
|
filename.basename(in.file_path()); // 从文件全路径中提取文件名
|
|
hdr_entry.format("attachment;filename=\"%s\"", filename.c_str());
|
|
|
|
// 设置 HTTP 响应头中的字段
|
|
res.setStatus(206) // 响应状态 206 表示部分数据
|
|
.setKeepAlive(req.isKeepAlive())// 是否保持长连接
|
|
.setContentLength(length) // 实际要传输的数据长度
|
|
.setContentType("application/octet-stream") // 数据类型
|
|
// 设置 HTTP 头中的文件名
|
|
.setHeader("Content-Disposition", hdr_entry.c_str())
|
|
// 设置本次传输区间的起始偏移位置及数据的总长度
|
|
.setRange(range_from, range_to > 0
|
|
? range_to : fsize - 1, fsize);
|
|
|
|
// 调试:将 HTTP 响应头记在本地日志中
|
|
acl::string hdr;
|
|
res.getHttpHeader().build_response(hdr);
|
|
logger("response head:\r\n%s\r\n", hdr.c_str());
|
|
|
|
char buf[8192];
|
|
int ret;
|
|
size_t size;
|
|
|
|
// 从文件指定位置读取数据,并将数据传输给客户端
|
|
while (!in.eof() && length > 0)
|
|
{
|
|
size = sizeof(buf) > (size_t) length ?
|
|
(size_t) length : sizeof(buf);
|
|
|
|
// 第三个参数为 false 表示仅进行一次读操作,不必待缓冲区满后再返回
|
|
ret = in.read(buf, size, false);
|
|
if (ret == -1)
|
|
{
|
|
printf("read over: %s\r\n", acl::last_serror());
|
|
break;
|
|
}
|
|
if (res.write(buf, ret) == false)
|
|
return false;
|
|
length -= ret;
|
|
//acl_doze(100); // 休息 100 ms 便于测试
|
|
}
|
|
|
|
if (length != 0)
|
|
{
|
|
logger_error("read file failed");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|