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