问题现象描述
在使用libwebsockets实现HTTPS文件服务时,发现响应头中缺少Content-Disposition: attachment
字段,导致浏览器无法自动触发下载行为。以下是问题复现的关键代码片段:
struct lws_http_mount mount = {
.mount_next = NULL,
.mountpoint = "/",
.origin = "var/www/",
.extra_mimetypes = "application/zip",
.origin_protocol = LWSMPRO_FILE
};
if (lws_serve_http_file(wsi, "/tmp/log.zip", "application/zip", NULL, 0) < 0) {
lwsl_err("Failed to serve file: %s\n", "/tmp/log.zp");
lws_return_http_status(wsi, HTTP_STATUS_INTERNAL_SERVER_ERROR, NULL);
return;
}
核心问题分析
libwebsockets的默认文件服务实现不会自动添加Content-Disposition
头,这是设计上的选择。我们需要通过回调函数手动添加这个关键响应头。
完整解决方案
以下是实现文件下载功能的完整示例代码,包含自定义响应头的处理:
static int callback_http(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
switch (reason) {
case LWS_CALLBACK_HTTP: {
// 检查请求路径是否为下载文件
if (strcmp((const char *)in, "/download") == 0) {
// 设置响应头
unsigned char buffer[1024];
unsigned char *start = buffer, *end = &buffer[sizeof(buffer)-1];
// 添加Content-Disposition头
if (lws_add_http_header_by_name(wsi,
(unsigned char *)"content-disposition",
(unsigned char *)"attachment; filename=\"logs.zip\"",
32, &start, end)) {
return -1;
}
// 设置MIME类型
if (lws_add_http_header_content_type(wsi,
"application/zip", &start, end)) {
return -1;
}
// 结束头处理
if (lws_finalize_http_header(wsi, &start, end)) {
return -1;
}
// 开始写入响应体
lws_callback_on_writable(wsi);
return 0;
}
break;
}
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
// 文件传输完成后的处理
return -1;
case LWS_CALLBACK_HTTP_WRITEABLE: {
// 实际发送文件内容
char buf[4096];
int fd = open("/tmp/log.zip", O_RDONLY);
if (fd < 0) return -1;
int n = read(fd, buf, sizeof(buf));
if (n < 0) {
close(fd);
return -1;
}
if (lws_write(wsi, (unsigned char *)buf, n, LWS_WRITE_HTTP) != n) {
close(fd);
return -1;
}
close(fd);
break;
}
}
return 0;
}
替代方案:使用lws_serve_http_file的扩展
如果你更倾向于使用lws_serve_http_file
的简化接口,可以通过以下方式扩展功能:
int serve_file_with_disposition(struct lws *wsi, const char *filepath,
const char *mimetype, const char *disposition) {
// 先设置响应头
unsigned char buffer[1024];
unsigned char *start = buffer, *end = &buffer[sizeof(buffer)-1];
if (lws_add_http_header_by_name(wsi,
(unsigned char *)"content-disposition",
(unsigned char *)disposition,
strlen(disposition), &start, end)) {
return -1;
}
// 然后调用标准文件服务
return lws_serve_http_file(wsi, filepath, mimetype, NULL, 0);
}
// 使用示例
serve_file_with_disposition(wsi, "/tmp/log.zip",
"application/zip",
"attachment; filename=\"logs.zip\"");
测试验证
使用Postman或curl测试时,现在应该能看到完整的响应头:
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="logs.zip"
Content-Length: 123456
Connection: keep-alive
常见问题排查
- 确保回调函数已正确注册到协议中
- 检查文件路径是否有读取权限
- 验证MIME类型设置是否正确
- 如果使用HTTPS,确保证书配置正确