jaulib v1.3.0
Jau Support Library (C++, Java, ..)
test_iostream01.cpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 * Copyright (c) 2021 ZAFENA AB
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining
7 * a copy of this software and associated documentation files (the
8 * "Software"), to deal in the Software without restriction, including
9 * without limitation the rights to use, copy, modify, merge, publish,
10 * distribute, sublicense, and/or sell copies of the Software, and to
11 * permit persons to whom the Software is furnished to do so, subject to
12 * the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 */
25
26#include <cassert>
27#include <cinttypes>
28#include <cstring>
29
30#include <thread>
31#include <pthread.h>
32
34
35#include <jau/file_util.hpp>
36#include <jau/io_util.hpp>
37#include <jau/byte_stream.hpp>
38
39#include <jau/debug.hpp>
40
41#include "test_httpd.hpp"
42
43extern "C" {
44 #include <unistd.h>
45}
46
47using namespace jau::fractions_i64_literals;
48using namespace jau::int_literals;
49
51 public:
52 const std::string url_input_root = "http://localhost:8080/";
53 const std::string basename_10kiB = "testfile_data_10kiB.bin";
54
56 // produce fresh demo data
57
59 {
60 std::string one_line = "Hello World, this is a test and I like it. Exactly 100 characters long. 0123456780 abcdefghjklmnop..";
62
63 REQUIRE( ofs.good() == true );
64 REQUIRE( ofs.is_open() == true );
65
66 for(size_t i=0; i < 1024_uz * 10_uz; i+=one_line.size()) { // 10kiB
67 REQUIRE( one_line.size() == ofs.write(one_line.data(), one_line.size()) );
68 }
69 }
71 int res = std::system("killall mini_httpd");
72 (void)res;
73 const std::string cwd = jau::fs::get_cwd();
74 const std::string cmd = std::string(mini_httpd_exe)+" -p 8080 -l "+cwd+"/mini_httpd.log";
75 jau::PLAIN_PRINT(true, "%s", cmd.c_str());
76 res = std::system(cmd.c_str());
77 (void)res;
78 }
79 }
80
83 int res = std::system("killall mini_httpd");
84 (void)res;
85 }
86 }
87
89 {
90 std::vector<std::string_view> protos = jau::io::uri_tk::supported_protocols();
91 jau::PLAIN_PRINT(true, "test00_protocols: Supported protocols: %zu: %s", protos.size(), jau::to_string(protos, ",").c_str());
92#ifdef USE_LIBCURL
93 REQUIRE( 0 < protos.size() );
94#else
95 REQUIRE( 0 == protos.size() );
96#endif
97 }
98 const bool http_support_expected = jau::io::uri_tk::protocol_supported("http:");
99 const bool file_support_expected = jau::io::uri_tk::protocol_supported("file:");
100 {
101 const std::string url = url_input_root + basename_10kiB;
102 REQUIRE( false == jau::io::uri_tk::is_local_file_protocol(url) );
103 REQUIRE( http_support_expected == jau::io::uri_tk::protocol_supported(url) );
104 }
105 {
106 const std::string url = "https://localhost:8080/" + basename_10kiB;
107 REQUIRE( false == jau::io::uri_tk::is_local_file_protocol(url) );
108 REQUIRE( http_support_expected == jau::io::uri_tk::protocol_supported(url) );
109 }
110 {
111 const std::string url = "file://" + basename_10kiB;
112 REQUIRE( true == jau::io::uri_tk::is_local_file_protocol(url) );
113 REQUIRE( file_support_expected == jau::io::uri_tk::protocol_supported(url) );
114 }
115 {
116 const std::string url = "lala://localhost:8080/" + basename_10kiB;
117 REQUIRE( false == jau::io::uri_tk::is_local_file_protocol(url) );
118 REQUIRE( false == jau::io::uri_tk::protocol_supported(url) );
119 }
120 {
121 // sync read_url_stream w/ unknown protocol
122 const std::string url = "lala://localhost:8080/" + basename_10kiB;
124 size_t consumed_calls = 0;
125 uint64_t consumed_total_bytes = 0;
126 jau::io::StreamConsumerFunc consume = [&](jau::io::secure_vector<uint8_t>& data, bool is_final) noexcept -> bool {
127 (void)is_final;
128 consumed_calls++;
129 consumed_total_bytes += data.size();
130 return true;
131 };
132 uint64_t http_total_bytes = jau::io::read_url_stream(url, buffer, consume);
133 REQUIRE( 0 == http_total_bytes );
134 REQUIRE( consumed_total_bytes == http_total_bytes );
135 REQUIRE( 0 == consumed_calls );
136 }
137 {
138 // async read_url_stream w/ unknown protocol
139 const std::string url = "lala://localhost:8080/" + basename_10kiB;
140
142 jau::io::url_header_sync url_header_sync;
143 jau::relaxed_atomic_bool url_has_content_length;
144 jau::relaxed_atomic_uint64 url_content_length;
145 jau::relaxed_atomic_uint64 url_total_read;
147
148 std::unique_ptr<std::thread> http_thread = jau::io::read_url_stream(url, rb, url_header_sync, url_has_content_length, url_content_length, url_total_read, result);
149 REQUIRE( nullptr == http_thread );
150 REQUIRE( url_header_sync.completed() == true );
151 REQUIRE( url_has_content_length == false );
152 REQUIRE( url_content_length == 0 );
153 REQUIRE( url_content_length == url_total_read );
154 REQUIRE( jau::io::async_io_result_t::FAILED == result );
155 }
156 }
157
160 jau::PLAIN_PRINT(true, "http not supported, abort\n");
161 return;
162 }
163 const jau::fs::file_stats in_stats(basename_10kiB);
164 const size_t file_size = in_stats.size();
165 const std::string url_input = url_input_root + basename_10kiB;
166
167 jau::io::ByteOutStream_File outfile("testfile01_01_out.bin");
168 REQUIRE( outfile.good() );
169 REQUIRE( outfile.is_open() );
170
172 size_t consumed_calls = 0;
173 uint64_t consumed_total_bytes = 0;
174 jau::io::StreamConsumerFunc consume = [&](jau::io::secure_vector<uint8_t>& data, bool is_final) noexcept -> bool {
175 consumed_calls++;
176 if( data.size() != outfile.write(data.data(), data.size()) ) {
177 return false;
178 }
179 consumed_total_bytes += data.size();
180 jau::PLAIN_PRINT(true, "test01_sync_ok #%zu: consumed size %zu, total %" PRIu64 ", capacity %zu, final %d",
181 consumed_calls, data.size(), consumed_total_bytes, data.capacity(), is_final );
182 return true;
183 };
184 uint64_t http_total_bytes = jau::io::read_url_stream(url_input, buffer, consume);
185 const uint64_t out_bytes_total = outfile.tellp();
186 jau::PLAIN_PRINT(true, "test01_sync_ok Done: total %" PRIu64 ", capacity %zu", consumed_total_bytes, buffer.capacity());
187
188 REQUIRE( file_size == http_total_bytes );
189 REQUIRE( consumed_total_bytes == http_total_bytes );
190 REQUIRE( consumed_total_bytes == out_bytes_total );
191 }
192
195 jau::PLAIN_PRINT(true, "http not supported, abort\n");
196 return;
197 }
198 const std::string url_input = url_input_root + "doesnt_exists.txt";
199
200 jau::io::ByteOutStream_File outfile("testfile02_01_out.bin");
201 REQUIRE( outfile.good() );
202 REQUIRE( outfile.is_open() );
203
205 size_t consumed_calls = 0;
206 uint64_t consumed_total_bytes = 0;
207 jau::io::StreamConsumerFunc consume = [&](jau::io::secure_vector<uint8_t>& data, bool is_final) noexcept -> bool {
208 consumed_calls++;
209 if( data.size() != outfile.write(data.data(), data.size()) ) {
210 return false;
211 }
212 consumed_total_bytes += data.size();
213 jau::PLAIN_PRINT(true, "test02_sync_404 #%zu: consumed size %zu, total %" PRIu64 ", capacity %zu, final %d",
214 consumed_calls, data.size(), consumed_total_bytes, data.capacity(), is_final );
215 return true;
216 };
217 uint64_t http_total_bytes = jau::io::read_url_stream(url_input, buffer, consume);
218 const uint64_t out_bytes_total = outfile.tellp();
219 jau::PLAIN_PRINT(true, "test02_sync_404 Done: total %" PRIu64 ", capacity %zu", consumed_total_bytes, buffer.capacity());
220
221 REQUIRE( 0 == http_total_bytes );
222 REQUIRE( consumed_total_bytes == http_total_bytes );
223 REQUIRE( consumed_total_bytes == out_bytes_total );
224 }
225
228 jau::PLAIN_PRINT(true, "http not supported, abort\n");
229 return;
230 }
231 const jau::fs::file_stats in_stats(basename_10kiB);
232 const size_t file_size = in_stats.size();
233 const std::string url_input = url_input_root + basename_10kiB;
234
235 jau::io::ByteOutStream_File outfile("testfile11_01_out.bin");
236 REQUIRE( outfile.good() );
237 REQUIRE( outfile.is_open() );
238
239 constexpr const size_t buffer_size = 4096;
241 jau::io::url_header_sync url_header_sync;
242 jau::relaxed_atomic_bool url_has_content_length;
243 jau::relaxed_atomic_uint64 url_content_length;
244 jau::relaxed_atomic_uint64 url_total_read;
246
247 std::unique_ptr<std::thread> http_thread = jau::io::read_url_stream(url_input, rb, url_header_sync, url_has_content_length, url_content_length, url_total_read, result);
248 REQUIRE( nullptr != http_thread );
249
250 jau::io::secure_vector<uint8_t> buffer(buffer_size);
251 size_t consumed_loops = 0;
252 uint64_t consumed_total_bytes = 0;
253
254 while( jau::io::async_io_result_t::NONE == result || !rb.isEmpty() ) {
255 consumed_loops++;
256 // const size_t consumed_bytes = content_length >= 0 ? std::min(buffer_size, content_length - consumed_total_bytes) : rb.getSize();
257 const size_t consumed_bytes = rb.getBlocking(buffer.data(), buffer_size, 1, 500_ms);
258 consumed_total_bytes += consumed_bytes;
259 jau::PLAIN_PRINT(true, "test11_async_ok.0 #%zu: consumed[this %zu, total %" PRIu64 ", result %d, rb %s",
260 consumed_loops, consumed_bytes, consumed_total_bytes, result.load(), rb.toString().c_str() );
261 if( consumed_bytes != outfile.write(buffer.data(), consumed_bytes) ) {
262 break;
263 }
264 }
265 const uint64_t out_bytes_total = outfile.tellp();
266 jau::PLAIN_PRINT(true, "test11_async_ok.X Done: total %" PRIu64 ", result %d, rb %s",
267 consumed_total_bytes, (int)result.load(), rb.toString().c_str() );
268
269 http_thread->join();
270
271 REQUIRE( url_header_sync.completed() == true );
272 REQUIRE( url_has_content_length == true );
273 REQUIRE( url_content_length == file_size );
274 REQUIRE( url_content_length == consumed_total_bytes );
275 REQUIRE( url_content_length == url_total_read );
276 REQUIRE( url_content_length == out_bytes_total );
277 REQUIRE( jau::io::async_io_result_t::SUCCESS == result );
278 }
279
282 jau::PLAIN_PRINT(true, "http not supported, abort\n");
283 return;
284 }
285 const std::string url_input = url_input_root + "doesnt_exists.txt";
286
287 jau::io::ByteOutStream_File outfile("testfile12_01_out.bin");
288 REQUIRE( outfile.good() );
289 REQUIRE( outfile.is_open() );
290
291 constexpr const size_t buffer_size = 4096;
293 jau::io::url_header_sync url_header_sync;
294 jau::relaxed_atomic_bool url_has_content_length;
295 jau::relaxed_atomic_uint64 url_content_length;
296 jau::relaxed_atomic_uint64 url_total_read;
298
299 std::unique_ptr<std::thread> http_thread = jau::io::read_url_stream(url_input, rb, url_header_sync, url_has_content_length, url_content_length, url_total_read, result);
300 REQUIRE( nullptr != http_thread );
301
302 jau::io::secure_vector<uint8_t> buffer(buffer_size);
303 size_t consumed_loops = 0;
304 uint64_t consumed_total_bytes = 0;
305
306 while( jau::io::async_io_result_t::NONE == result || !rb.isEmpty() ) {
307 consumed_loops++;
308 // const size_t consumed_bytes = content_length >= 0 ? std::min(buffer_size, content_length - consumed_total_bytes) : rb.getSize();
309 const size_t consumed_bytes = rb.getBlocking(buffer.data(), buffer_size, 1, 500_ms);
310 consumed_total_bytes += consumed_bytes;
311 jau::PLAIN_PRINT(true, "test12_async_404.0 #%zu: consumed[this %zu, total %" PRIu64 ", result %d, rb %s",
312 consumed_loops, consumed_bytes, consumed_total_bytes, result.load(), rb.toString().c_str() );
313 if( consumed_bytes != outfile.write(reinterpret_cast<char*>(buffer.data()), consumed_bytes) ) {
314 break;
315 }
316 }
317 const uint64_t out_bytes_total = outfile.tellp();
318 jau::PLAIN_PRINT(true, "test12_async_404.X Done: total %" PRIu64 ", result %d, rb %s",
319 consumed_total_bytes, (int)result.load(), rb.toString().c_str() );
320
321 http_thread->join();
322
323 REQUIRE( url_header_sync.completed() == true );
324 REQUIRE( url_has_content_length == false );
325 REQUIRE( url_content_length == 0 );
326 REQUIRE( url_content_length == consumed_total_bytes );
327 REQUIRE( url_content_length == url_total_read );
328 REQUIRE( url_content_length == out_bytes_total );
329 REQUIRE( jau::io::async_io_result_t::FAILED == result );
330 }
331
332};
333
334METHOD_AS_TEST_CASE( TestIOStream01::test00_protocols, "TestIOStream01 - test00_protocols");
335METHOD_AS_TEST_CASE( TestIOStream01::test01_sync_ok, "TestIOStream01 - test01_sync_ok");
336METHOD_AS_TEST_CASE( TestIOStream01::test02_sync_404, "TestIOStream01 - test02_sync_404");
337METHOD_AS_TEST_CASE( TestIOStream01::test11_async_ok, "TestIOStream01 - test11_async_ok");
338METHOD_AS_TEST_CASE( TestIOStream01::test12_async_404, "TestIOStream01 - test12_async_404");
339
const std::string basename_10kiB
const std::string url_input_root
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
Definition: file_util.hpp:406
uint64_t size() const noexcept
Returns the size in bytes of this element if is_file(), otherwise zero.
Definition: file_util.hpp:597
Class template jau::function is a general-purpose static-polymorphic function wrapper.
File based byte output stream, including named file descriptor.
size_t write(const void *, size_t) noexcept override
Write to the data sink.
uint64_t tellp() const noexcept override
Returns the output position indicator.
bool is_open() const noexcept override
Checks if the stream has an associated file.
bool good() const noexcept
Checks if no error nor eof() has occurred i.e.
Synchronization for URL header completion as used by asynchronous read_url_stream().
Definition: io_util.hpp:177
bool completed() const noexcept
Returns whether URL header is completed.
Definition: io_util.hpp:197
bool getBlocking(Value_type &result, const fraction_i64 &timeout, bool &timeout_occurred) noexcept
Dequeues the oldest enqueued element.
bool isEmpty() const noexcept
Returns true if this ring buffer is empty, otherwise false.
std::string toString() const noexcept
Returns a short string representation incl.
std::string to_string(const endian_t v) noexcept
Return std::string representation of the given endian.
std::string get_cwd() noexcept
Return the current working directory or empty on failure.
Definition: file_util.cpp:82
bool remove(const std::string &path, const traverse_options topts=traverse_options::none) noexcept
Remove the given path.
Definition: file_util.cpp:1173
std::vector< T, jau::callocator_sec< T > > secure_vector
Definition: io_util.hpp:46
bool is_local_file_protocol(const std::string_view &uri) noexcept
Returns true if the uri-scheme of given uri matches the local file protocol, i.e.
Definition: io_util.cpp:204
uint64_t read_url_stream(const std::string &url, secure_vector< uint8_t > &buffer, const StreamConsumerFunc &consumer_fn) noexcept
Synchronous URL stream reader using the given StreamConsumerFunc consumer_fn.
Definition: io_util.cpp:309
std::vector< std::string_view > supported_protocols() noexcept
Returns a list of supported protocol supported by libcurl network protocols, queried at runtime.
Definition: io_util.cpp:159
const size_t BEST_URLSTREAM_RINGBUFFER_SIZE
Definition: byte_stream.cpp:73
bool protocol_supported(const std::string_view &uri) noexcept
Returns true if the uri-scheme of given uri matches a supported by libcurl network protocols otherwis...
Definition: io_util.cpp:194
@ NONE
Operation still in progress.
@ FAILED
Operation failed.
@ SUCCESS
Operation succeeded.
void PLAIN_PRINT(const bool printPrefix, const char *format,...) noexcept
Use for unconditional plain messages, prefix '[elapsed_time] ' if printPrefix == true.
Definition: debug.cpp:258
CXX_ALWAYS_INLINE _Tp load() const noexcept
constexpr std::string_view mini_httpd_exe
Definition: test_httpd.hpp:33
METHOD_AS_TEST_CASE(TestIOStream01::test00_protocols, "TestIOStream01 - test00_protocols")