前几篇文章已经介绍了文件的下载与上传,操作的都是文件,而如果是操作目录呢,应该怎么做呢?这里介绍的还是 mongoose 源码接口。先看实际操作的效果:
这里有两个目录,点击后可以进到目录里:
可以点击查看目录里文件的内容:
要实现目录的展示,就加了一段代码:
整个 handleRequest() 函数的代码为:
void handleRequest(struct mg_connection *connection, int32_t event, void *data)
{
struct http_message* msg = (struct http_message*)data;
std::string uri(msg->uri.p, msg->uri.len);
tracef("connect from ip: %s, uri = %s\n", inet_ntoa(connection->sa.sin.sin_addr), uri.c_str());
//设置资源根目录
if(uri == "/")
{
struct mg_serve_http_opts opts;
memset(&opts, 0, sizeof(opts));
opts.enable_directory_listing = "yes";
opts.document_root = "./";
mg_serve_http(connection, msg, opts);
//第一次请求根目录时,可以不往下处理了
return;
}
CHttpResponse response;
//保存请求头,用于应答
for (int32_t index = 0; index < MG_MAX_HTTP_HEADERS; ++index)
{
if (msg->header_names[index].p == nullptr || msg->header_names[index].len == 0
|| std::string(msg->header_names[index].p,msg->header_names[index].len).find("Accept") != std::string::npos)
{
continue;
}
std::string header(msg->header_names[index].p, msg->header_names[index].len);
std::string value(msg->header_values[index].p, msg->header_values[index].len);
response.setHead(header.c_str(), value.c_str());
}
//请求的方法,GET、POST、PUT等
std::string method(msg->method.p, msg->method.len);
tracef("request method: %s\n", method.c_str());
std::string fileName = uri.substr(1);
if(fileName.at(fileName.length() - 1) == '/') //目录最后多一个"/"
{
fileName = fileName.substr(0, fileName.length() - 1);
}
struct stat st;
//文件或目录存在
if(lstat(fileName.c_str(), &st) == 0)
{
if(S_ISDIR(st.st_mode)) //目录
{
tracef("%s is a dirctory\n", fileName.c_str());
struct mg_serve_http_opts opts;
memset(&opts, 0, sizeof(opts));
opts.enable_directory_listing = "yes";
opts.document_root = "./";
mg_serve_http(connection, msg, opts);
}
else if(S_ISREG(st.st_mode)) //普通文件
{
tracef("%s is a regular file\n", fileName.c_str());
if(method == "GET")
{
if(!isGiantFile(fileName.c_str()))
{
if(response.setBody(fileName.c_str()) == 0) //文件大小为0直接返回错误信息
{
mg_http_send_error(connection, 500, "file length is 0");
return;
}
std::string content;
response.getContent(content);
mg_send(connection, content.c_str(), content.length());
}
else
{
//处理大文件,采用分块传输的方式
std::string headers;
headers.append("HTTP/1.1 200 ok").append("\r\n");
response.getHead(headers);
headers.append("Content-Type: application/octet-stream").append("\r\n");
headers.append("Transfer-Encoding: chunked").append("\r\n").append("\r\n");
mg_send(connection, headers.c_str(), headers.length());
sendGiantFile(connection, fileName.c_str());
}
}
}
}
else
{
mg_http_send_error(connection, 404, NULL);
}
}
这里只是简单地处理了目录和普通文件的区别,还有其他如字符设备文件、块设备文件、链接文件、socket 文件这里不作处理。
函数原型:
void mg_serve_http(struct mg_connection *nc, struct http_message *hm, struct mg_serve_http_opts opts)
就是用来设置目录的,我们可以简单看下这个函数做了什么。
函数的第三个参数其实可以不给成员赋值,这里都赋了默认值 。其实这个参数最主要关心两个成员:opts.document_root 和 opts.enable_directory_listing。opts.document_root 主要是标记起点,可以理解为从哪里去查找文件或目录来进行展示;opts.enable_directory_listing 可以理解为是否要展示。
接下来的函数 mg_uri_to_local_path(hm, &opts, &path, &path_info) 就是要把请求过来的目录(uri)进行本地化处理,因为我们请求目录的时候的 uri 是这样的:
http://10.91.90.99:8190/compile/
那服务器必须要把目标目录 compile 进行本地化,最后得出它是在哪个目录下,如用 GDB 跟踪调试时,当请求目录是 compile 时,实际得出的是:
这样就得出了 compile 的相对路径了,那程序本身就可以访问这个目录。
MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
const struct mg_str *path_info,
struct http_message *hm,
struct mg_serve_http_opts *opts)
而这个函数最主要是做什么呢?它其实就是显示指定目录下文件列表了:
static void mg_send_directory_listing(struct mg_connection *nc, const char *dir,
struct http_message *hm,
struct mg_serve_http_opts *opts)
这个函数里就是收集指定目录下的文件信息了,然后它会将它们组成 html 的形式进行发送,浏览器收到后就以 html 页面的形式展示,以 chunked 方式发送,这个 html 形式如下:
源码片段如下:
这样目录操作就已经正常了。