Skip to content

Commit a207ecc

Browse files
committed
burl: add --upload-file option
1 parent ddf3533 commit a207ecc

File tree

9 files changed

+464
-177
lines changed

9 files changed

+464
-177
lines changed

example/client/burl/file.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// Copyright (c) 2024 Mohammad Nejati
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_io
8+
//
9+
10+
#include "file.hpp"
11+
12+
#include <boost/http_proto/file.hpp>
13+
#include <boost/system/system_error.hpp>
14+
15+
namespace http_proto = boost::http_proto;
16+
using system_error = boost::system::system_error;
17+
18+
std::uint64_t
19+
filesize(const std::string& path)
20+
{
21+
http_proto::file file;
22+
boost::system::error_code ec;
23+
24+
file.open(path.c_str(), http_proto::file_mode::scan, ec);
25+
if(ec)
26+
throw system_error{ ec };
27+
28+
const auto size = file.size(ec);
29+
if(ec)
30+
throw system_error{ ec };
31+
32+
return size;
33+
}
34+
35+
core::string_view
36+
filename(core::string_view path) noexcept
37+
{
38+
const auto pos = path.find_last_of("/\\");
39+
if((pos != core::string_view::npos))
40+
return path.substr(pos + 1);
41+
return path;
42+
}

example/client/burl/file.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// Copyright (c) 2024 Mohammad Nejati
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/cppalliance/http_io
8+
//
9+
10+
#ifndef BURL_FILE_HPP
11+
#define BURL_FILE_HPP
12+
13+
#include <boost/core/detail/string_view.hpp>
14+
15+
#include <cstdint>
16+
17+
namespace core = boost::core;
18+
19+
std::uint64_t
20+
filesize(const std::string& path);
21+
22+
core::string_view
23+
filename(core::string_view path) noexcept;
24+
25+
#endif

example/client/burl/main.cpp

Lines changed: 35 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include "base64.hpp"
1313
#include "connect.hpp"
1414
#include "cookie.hpp"
15+
#include "file.hpp"
16+
#include "message.hpp"
1517
#include "mime_type.hpp"
1618
#include "multipart_form.hpp"
1719
#include "urlencoded_form.hpp"
@@ -96,124 +98,6 @@ can_reuse_connection(
9698
return true;
9799
}
98100

99-
class json_body
100-
{
101-
std::string body_;
102-
103-
public:
104-
void
105-
append(core::string_view value) noexcept
106-
{
107-
body_.append(value);
108-
}
109-
110-
void
111-
append(std::istream& is)
112-
{
113-
body_.append(std::istreambuf_iterator<char>{ is }, {});
114-
}
115-
116-
core::string_view
117-
content_type() const noexcept
118-
{
119-
return "application/json";
120-
}
121-
122-
std::size_t
123-
content_length() const noexcept
124-
{
125-
return body_.size();
126-
}
127-
128-
buffers::const_buffer
129-
body() const noexcept
130-
{
131-
return { body_.data(), body_.size() };
132-
}
133-
};
134-
135-
class message
136-
{
137-
std::variant<
138-
std::monostate,
139-
json_body,
140-
urlencoded_form,
141-
multipart_form> body_;
142-
public:
143-
message() = default;
144-
145-
message(json_body&& json_body)
146-
: body_{ std::move(json_body) }
147-
{
148-
}
149-
150-
message(urlencoded_form&& form)
151-
: body_{ std::move(form) }
152-
{
153-
}
154-
155-
message(multipart_form&& form)
156-
: body_{ std::move(form) }
157-
{
158-
}
159-
160-
void
161-
set_headers(http_proto::request& req) const
162-
{
163-
std::visit(
164-
[&](auto& f)
165-
{
166-
if constexpr(!std::is_same_v<
167-
decltype(f), const std::monostate&>)
168-
{
169-
req.set_method(http_proto::method::post);
170-
171-
auto content_length = f.content_length();
172-
req.set_content_length(content_length);
173-
if(content_length >= 1024 * 1024)
174-
{
175-
req.set(
176-
http_proto::field::expect,
177-
"100-continue");
178-
}
179-
180-
req.set(
181-
http_proto::field::content_type,
182-
f.content_type());
183-
}
184-
},
185-
body_);
186-
}
187-
188-
void
189-
start_serializer(
190-
http_proto::serializer& ser,
191-
http_proto::request& req) const
192-
{
193-
std::visit(
194-
[&](auto& f)
195-
{
196-
if constexpr(std::is_same_v<
197-
decltype(f), const multipart_form&>)
198-
{
199-
ser.start<
200-
multipart_form::source>(req, &f);
201-
}
202-
else if constexpr(
203-
std::is_same_v<decltype(f), const json_body&> ||
204-
std::is_same_v<decltype(f), const urlencoded_form&>)
205-
{
206-
ser.start(req, f.body());
207-
}
208-
else
209-
{
210-
ser.start(req);
211-
}
212-
},
213-
body_);
214-
}
215-
};
216-
217101
http_proto::request
218102
create_request(
219103
const po::variables_map& vm,
@@ -629,6 +513,9 @@ main(int argc, char* argv[])
629513
("unix-socket",
630514
po::value<std::string>()->value_name("<path>"),
631515
"Connect through this Unix domain socket")
516+
("upload-file,T",
517+
po::value<std::string>()->value_name("<file>"),
518+
"Transfer local FILE to destination")
632519
("url",
633520
po::value<std::string>()->value_name("<url>"),
634521
"URL to work with")
@@ -774,9 +661,14 @@ main(int argc, char* argv[])
774661

775662
auto msg = message{};
776663

777-
if((!!vm.count("form") + !!vm.count("data") + !!vm.count("json")) == 2)
664+
if((!!vm.count("form") +
665+
!!vm.count("data") +
666+
!!vm.count("json") +
667+
!!vm.count("upload-file")) == 2)
668+
{
778669
throw std::runtime_error{
779670
"You can only select one HTTP request method"};
671+
}
780672

781673
if(vm.count("form"))
782674
{
@@ -872,6 +764,30 @@ main(int argc, char* argv[])
872764
msg = std::move(body);
873765
}
874766

767+
if(vm.count("upload-file"))
768+
{
769+
msg = [&]()-> message
770+
{
771+
auto path = vm.at("upload-file").as<std::string>();
772+
if(path == "-")
773+
return stdin_body{};
774+
775+
// Append the filename to the URL if it
776+
// doesn't already end with one
777+
auto segs = url.encoded_segments();
778+
if(segs.empty())
779+
{
780+
segs.push_back(::filename(path));
781+
}
782+
else if(auto back = --segs.end(); back->empty())
783+
{
784+
segs.replace(back, ::filename(path));
785+
}
786+
787+
return file_body{ std::move(path) };
788+
}();
789+
}
790+
875791
auto cookie_jar = std::optional<::cookie_jar>{};
876792
auto explicit_cookies = std::string{};
877793

0 commit comments

Comments
 (0)