Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

如何搭建server端文件下载服务? #644

Open
Coastchb opened this issue Nov 12, 2024 · 15 comments
Open

如何搭建server端文件下载服务? #644

Coastchb opened this issue Nov 12, 2024 · 15 comments

Comments

@Coastchb
Copy link

Coastchb commented Nov 12, 2024

我想基于cinatra搭建一个(.wav)文件下载的服务,然后客户端通过curl来发送请求、接收文件。

参考样例 example/main.cpp中的byte_ranges_download()做了精简(删掉了客户端请求的部分)。
能正常运行,通过curl http://127.0.0.1:8090/test_multiple_range.txt 能下载test_multiple_range.txt 文件。
请教一下,该如何下载其他文件,比如通过其他程序生成的wav文件?

另外:server.set_static_res_dir("", ""); 这一句是把当前目录作为返回目录?是不是在这个目录下的所有文件应该都能被下载呢?(但尝试手动创建了a.txt, 使用curl http://127.0.0.1:8090/a.txt提示404错误)

@qicosmos
Copy link
Owner

  coro_http_server server(10, 8090);
  server.set_static_res_dir("", "");
  server.sync_start();

这样就可以下载执行文件所在目录的文件了。不知道你的代码怎么写的,另外是用的最新代码吗?

@Coastchb
Copy link
Author

Coastchb commented Nov 13, 2024

@qicosmos 写法跟你的一样,能下载my_test_file.txt, a.txt, b.txt, c.txt。手动创建了d.txt,但是不能下载。麻烦看下:

`
void create_file(std::string filename, size_t file_size = 64) {
std::ofstream file(filename, std::ios::binary);
if (file) {
std::string str(file_size, 'C');
file.write(str.data(), str.size());
}
}

async_simple::coro::Lazy byte_ranges_download() {
create_file("my_test_file.txt", 64);
create_file("a.txt", 64);
create_file("b.txt", 64);
create_file("c.txt", 64);
coro_http_server server(1, 8090);
server.set_static_res_dir("", "");
server.async_start();
std::this_thread::sleep_for(200000s);

}

int main() {
async_simple::coro::syncAwait(byte_ranges_download());
return 0;
}
`

@qicosmos
Copy link
Owner

这个是静态文件下载服务器,目录里面的文件不能是新创建的,即要求文件不可变。
如果你需要动态增加的文件能下载,那就自己参考例子写代码实现。

@Coastchb
Copy link
Author

这个是静态文件下载服务器,目录里面的文件不能是新创建的,即要求文件不可变。 如果你需要动态增加的文件能下载,那就自己参考例子写代码实现。

@qicosmos 请问是指参考set_static_res_dir这个函数实现动态增加文件可下载吗?

@qicosmos
Copy link
Owner

不是,需要自己写代码实现,类似于这个函数:https://github.com/qicosmos/cinatra/blob/master/include/cinatra/coro_http_server.hpp#L398
需要自己去根据请求的文件名去查找文件,读文件内容并发送文件内容。

@qicosmos
Copy link
Owner

具体可以在用户群里讨论一下。

@Coastchb
Copy link
Author

具体可以在用户群里讨论一下。

好的 感谢

@qicosmos
Copy link
Owner

hi 把你的问题如何解决的在这里发一下吧。

@Coastchb
Copy link
Author

hi 把你的问题如何解决的在这里发一下吧。

@qicosmos 如下处理:

std::ifstream file(wav_filename, std::ios::binary);
if (!file) {
std::cerr << "Failed to open file:" << wav_filename << std::endl;
res.set_status_and_content(status_type::open_file_failed, "Failed to open wav file!\n");
co_return;
}

// 读取文件全部内容到字符串流
std::stringstream buffer;
buffer << file.rdbuf();
file.close();

std::string content = buffer.str();

// 使用读取到的数据进行操作
res.set_status_and_content(status_type::ok, content);

@qicosmos
Copy link
Owner

为啥不用静态文件下载的那个方法去做呢,不需要自己写代码去做这个吧。

@Coastchb
Copy link
Author

为啥不用静态文件下载的那个方法去做呢,不需要自己写代码去做这个吧。

@qicosmos 静态文件下载是只能下载文件夹中已有的文件吧?(接收到请求前已有的文件)
我这是在接收到请求之后动态生成的wav文件,所以只能根据生成的文件名来加载数据然后传输给客户端。

@qicosmos
Copy link
Owner

是的,那个只能下载已有的文件。

@qicosmos
Copy link
Owner

对于静态文件服务器来说设置一下format type就行了。

  coro_http_server server(1, 9001);
  server.set_static_res_dir("", "");
  server.set_file_resp_format_type(file_resp_format_type::chunked); // 设置为chunked格式下载
  server.sync_start();

自己写代码读文件按chunked格式发送数据参考这个例子:
https://github.com/qicosmos/cinatra/blob/master/tests/test_coro_http_server.cpp#L770

调用begin_chunked() 开始chunked流传输;
不断的co_await 调用write_chunked(data) 写chunked 数据;
当数据写完之后调用end_chunked() 结束chunked 数据传输;

@qicosmos
Copy link
Owner

如果产生数据很慢的话,需要将其异步化,如果生成数据有异步回调接口的话,则使用async_simple Promise 将异步回调转换成协程:

async_simple::Promise<std::string> p;
gen_data([&]{
  // after generate data
  std::string data = generate();
  p.setValue(std::move(data));
});

co_await p.getFuture();

如果生成数据没有异步接口,则可以通过cinatra 提供的线程池将生成数据放到线程池中然后co_await 数据生成:

auto data = co_await coro_io::post([]{
  return generate();
});

co_await resp.get_conn()->write_chunked(data);

@osabc
Copy link

osabc commented Dec 4, 2024

仿set_static_res_dir写个静态目录

server.set_static_dir("/static/(.*)", "./static");

添加源码

  template <typename... Aspects>
  void set_static_dir(std::string_view uri_suffix = "",
                      std::string_view file_path = "./static",
                      Aspects &&...aspects) {
    bool has_double_dot = (file_path.find("..") != std::string::npos) ||
                          (uri_suffix.find("..") != std::string::npos);
    if (has_double_dot) {
      CINATRA_LOG_ERROR << "invalid file path: " << file_path;
      std::exit(1);
    }

    bool has_regex = uri_suffix.find("(.*)") != std::string::npos;
    if (!has_regex) {
      CINATRA_LOG_ERROR << "invalid uri_suffix: " << uri_suffix;
      std::exit(1);
    }

    set_http_handler<cinatra::GET>(
        std::string{uri_suffix},
        [this, &uri_suffix, &file_path](
            coro_http_request &req,
            coro_http_response &resp) -> async_simple::coro::Lazy<void> {
          std::string file_name;
          std::string url = std::string{req.get_url()};

          std::smatch match;
          if (std::regex_match(url, match,
                               std::regex(std::string{uri_suffix}))) {
            file_name = match[1].str();
          }

          std::string static_dir_;
          if (!file_path.empty()) {
            static_dir_ =
                fs::absolute(std::filesystem::path(file_path)).string();
          }
          else {
            static_dir_ = fs::absolute(fs::current_path()).string();
          }
          file_name = fs::absolute(static_dir_ + "/" + file_name).string();

          std::string_view extension = get_extension(file_name);
          std::string_view mime = get_mime_type(extension);
          auto range_str = req.get_header_value("Range");

          std::string content;
          detail::resize(content, chunked_size_);

          coro_io::coro_file in_file{};
          in_file.open(file_name, std::ios::in);
          if (!in_file.is_open()) {
            resp.set_status_and_content(status_type::not_found,
                                        file_name + "not found");
            co_return;
          }

          size_t file_size = fs::file_size(file_name);

          if (format_type_ == file_resp_format_type::chunked &&
              range_str.empty()) {
            resp.set_format_type(format_type::chunked);
            bool ok;
            if (ok = co_await resp.get_conn()->begin_chunked(); !ok) {
              co_return;
            }

            while (true) {
              auto [ec, size] =
                  co_await in_file.async_read(content.data(), content.size());
              if (ec) {
                resp.set_status(status_type::no_content);
                co_await resp.get_conn()->reply();
                co_return;
              }

              bool r = co_await resp.get_conn()->write_chunked(
                  std::string_view(content.data(), size));
              if (!r) {
                co_return;
              }

              if (in_file.eof()) {
                co_await resp.get_conn()->end_chunked();
                break;
              }
            }
          }
          else {
            auto pos = range_str.find('=');
            if (pos != std::string_view::npos) {
              range_str = range_str.substr(pos + 1);
              bool is_valid = true;
              auto ranges =
                  parse_ranges(range_str, fs::file_size(file_name), is_valid);
              if (!is_valid) {
                resp.set_status(status_type::range_not_satisfiable);
                co_return;
              }

              assert(!ranges.empty());

              if (ranges.size() == 1) {
                // single part
                auto [start, end] = ranges[0];
                in_file.seek(start, std::ios::beg);
                size_t part_size = end + 1 - start;
                int status = (part_size == file_size) ? 200 : 206;
                std::string content_range = "Content-Range: bytes ";
                content_range.append(std::to_string(start))
                    .append("-")
                    .append(std::to_string(end))
                    .append("/")
                    .append(std::to_string(file_size))
                    .append(CRCF);
                auto range_header = build_range_header(
                    mime, file_name, std::to_string(part_size), status,
                    content_range);
                resp.set_delay(true);
                bool r = co_await req.get_conn()->write_data(range_header);
                if (!r) {
                  co_return;
                }

                co_await send_single_part(in_file, content, req, resp,
                                          part_size);
              }
              else {
                // multiple ranges
                resp.set_delay(true);
                std::string file_size_str = std::to_string(file_size);
                size_t content_len = 0;
                std::vector<std::string> multi_heads =
                    build_part_heads(ranges, mime, file_size_str, content_len);
                auto range_header = build_multiple_range_header(content_len);
                bool r = co_await req.get_conn()->write_data(range_header);
                if (!r) {
                  co_return;
                }

                for (int i = 0; i < ranges.size(); i++) {
                  std::string &part_header = multi_heads[i];
                  r = co_await req.get_conn()->write_data(part_header);
                  if (!r) {
                    co_return;
                  }

                  auto [start, end] = ranges[i];
                  bool ok = in_file.seek(start, std::ios::beg);
                  if (!ok) {
                    resp.set_status_and_content(status_type::bad_request,
                                                "invalid range");
                    co_await resp.get_conn()->reply();
                    co_return;
                  }
                  size_t part_size = end + 1 - start;

                  std::string_view more = CRCF;
                  if (i == ranges.size() - 1) {
                    more = MULTIPART_END;
                  }
                  r = co_await send_single_part(in_file, content, req, resp,
                                                part_size, more);
                  if (!r) {
                    co_return;
                  }
                }
              }
              co_return;
            }

            auto range_header =
                build_range_header(mime, file_name, std::to_string(file_size));
            resp.set_delay(true);
            bool r = co_await req.get_conn()->write_data(range_header);
            if (!r) {
              co_return;
            }

            while (true) {
              auto [ec, size] =
                  co_await in_file.async_read(content.data(), content.size());
              if (ec) {
                resp.set_status(status_type::no_content);
                co_await resp.get_conn()->reply();
                co_return;
              }

              r = co_await req.get_conn()->write_data(
                  std::string_view(content.data(), size));
              if (!r) {
                co_return;
              }

              if (in_file.eof()) {
                break;
              }
            }
          }
        },
        std::forward<Aspects>(aspects)...);
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants