acl/app/wizard_demo/httpd_download/http_servlet.cpp

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