├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── Makefile ├── README.md ├── src ├── http_client.cpp ├── http_client.hpp ├── ilogger.cpp ├── ilogger.hpp ├── main.cpp ├── minio_client.cpp └── minio_client.hpp └── workspace ├── echo.txt └── upload.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | /objs 35 | /workspace/pro 36 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/src/**", 7 | "/data/wuhan/lean/openssl1.1.1j/include/**", 8 | "/data/wuhan/lean/curl7.77.0-DEV/include/**" 9 | ], 10 | "defines": [], 11 | "compilerPath": "/usr/bin/gcc", 12 | "cStandard": "gnu11", 13 | "cppStandard": "gnu++11", 14 | "intelliSenseMode": "linux-gcc-x64", 15 | "configurationProvider": "ms-vscode.makefile-tools", 16 | "browse": { 17 | "path": [ 18 | "${workspaceFolder}/src/**", 19 | "/data/wuhan/lean/openssl1.1.1j/include/**", 20 | "/data/wuhan/lean/curl7.77.0-DEV/include/**" 21 | ], 22 | "limitSymbolsToIncludedHeaders": false, 23 | "databaseFilename": "" 24 | } 25 | } 26 | ], 27 | "version": 4 28 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "g++ - 生成和调试活动文件", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/workspace/pro", 12 | "args": [], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}/workspace", 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "为 gdb 启用整齐打印", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ], 25 | "preLaunchTask": "build", 26 | "miDebuggerPath": "/usr/bin/gdb" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.cpp": "cpp", 4 | "sstream": "cpp", 5 | "random": "cpp", 6 | "unordered_map": "cpp", 7 | "__tree": "cpp", 8 | "map": "cpp", 9 | "__nullptr": "cpp", 10 | "cstddef": "cpp", 11 | "exception": "cpp", 12 | "initializer_list": "cpp", 13 | "new": "cpp", 14 | "optional": "cpp", 15 | "type_traits": "cpp", 16 | "typeinfo": "cpp", 17 | "array": "cpp", 18 | "atomic": "cpp", 19 | "*.tcc": "cpp", 20 | "bitset": "cpp", 21 | "cctype": "cpp", 22 | "chrono": "cpp", 23 | "clocale": "cpp", 24 | "cmath": "cpp", 25 | "codecvt": "cpp", 26 | "cstdarg": "cpp", 27 | "cstdint": "cpp", 28 | "cstdio": "cpp", 29 | "cstdlib": "cpp", 30 | "cstring": "cpp", 31 | "ctime": "cpp", 32 | "cwchar": "cpp", 33 | "cwctype": "cpp", 34 | "deque": "cpp", 35 | "vector": "cpp", 36 | "algorithm": "cpp", 37 | "filesystem": "cpp", 38 | "functional": "cpp", 39 | "ratio": "cpp", 40 | "string_view": "cpp", 41 | "system_error": "cpp", 42 | "tuple": "cpp", 43 | "fstream": "cpp", 44 | "iomanip": "cpp", 45 | "iosfwd": "cpp", 46 | "iostream": "cpp", 47 | "istream": "cpp", 48 | "limits": "cpp", 49 | "memory": "cpp", 50 | "mutex": "cpp", 51 | "ostream": "cpp", 52 | "numeric": "cpp", 53 | "stdexcept": "cpp", 54 | "streambuf": "cpp", 55 | "thread": "cpp", 56 | "cinttypes": "cpp", 57 | "utility": "cpp", 58 | "string": "cpp", 59 | "locale": "cpp" 60 | } 61 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "type": "shell", 5 | "label": "build", 6 | "command": "make pro -j64" 7 | } 8 | ], 9 | "version": "2.0.0" 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 手写AI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | cpp_srcs := $(shell find src -name "*.cpp") 3 | cpp_objs := $(patsubst %.cpp,%.o,$(cpp_srcs)) 4 | cpp_objs := $(subst src/,objs/,$(cpp_objs)) 5 | 6 | openssl_path := /data/wuhan/lean/openssl1.1.1j 7 | curl_path := /data/wuhan/lean/curl7.77.0-DEV 8 | 9 | include_paths := src \ 10 | $(openssl_path)/include \ 11 | $(curl_path)/include 12 | 13 | library_paths := \ 14 | $(openssl_path)/lib \ 15 | $(curl_path)/lib 16 | 17 | link_librarys := ssl crypto curl stdc++ dl 18 | 19 | run_paths := $(foreach item,$(library_paths),-Wl,-rpath=$(item)) 20 | include_paths := $(foreach item,$(include_paths),-I$(item)) 21 | library_paths := $(foreach item,$(library_paths),-L$(item)) 22 | link_librarys := $(foreach item,$(link_librarys),-l$(item)) 23 | 24 | cpp_compile_flags := -std=c++11 -fPIC -m64 -g -fopenmp -w -O0 25 | link_flags := -pthread -fopenmp 26 | 27 | cpp_compile_flags += $(include_paths) 28 | link_flags += $(library_paths) $(link_librarys) $(run_paths) 29 | 30 | pro : workspace/pro 31 | 32 | workspace/pro : $(cpp_objs) 33 | @echo Link $@ 34 | @mkdir -p $(dir $@) 35 | @g++ $^ -o $@ $(link_flags) 36 | 37 | objs/%.o : src/%.cpp 38 | @echo Compile CXX $< 39 | @mkdir -p $(dir $@) 40 | @g++ -c $< -o $@ $(cpp_compile_flags) 41 | 42 | run : workspace/pro 43 | @cd workspace && ./pro 44 | 45 | clean : 46 | @rm -rf objs workspace/pro 47 | 48 | .PHONY : clean pro run -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minio-cpp-sdk,minio上传的C++API 2 | - 基于调用libcurl和openssl,实现对minio操作 3 | - 提交AWS的访问协议,实现对minio(创建bucket、查询bucket、上传、下载) 4 | - 被AWS的cpp-sdk的65w个文件吓怕了,就想传个文件咋就这么难 5 | 6 | # 示例代码 7 | ```C++ 8 | const char server = "https://play.min.io:9000"; 9 | const char access_key = "Q3AM3UQ867SPQQA43P2F"; 10 | const char secret_key = "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"; 11 | MinioClient minio(server, access_key, secret_key); 12 | 13 | const char local_file = "echo.txt"; 14 | minio.upload_file("/test-bucket/echo.txt", local_file); 15 | auto data = minio.get_file("/test-bucket/echo.txt"); 16 | ``` 17 | 18 | # 使用 19 | 1. 配置openssl,下载并编译,例如:https://www.openssl.org/source/old/1.1.1/ 20 | 2. 配置curl,配置具有ssl支持的curl,例如:https://curl.se/download.html 21 | 3. 修改Makefile中的openssl和curl路径,运行`make run` 22 | ```bash 23 | cd minio-cpp-sdk 24 | make run -j6 25 | ``` 26 | 27 | # 关于我们-手写AI 28 | - 我们的B站:https://space.bilibili.com/1413433465/ 29 | - 我们的博客:http://zifuture.com:8090 30 | 31 | # 参考: 32 | - 注意,主要分析手段,是使用minio的js-sdk看他提交http时给的参数是什么,进而估计出C++应该怎么写 33 | - minio的js-sdk用的签名方式不同(是AWS4-HMAC-SHA256),如果能完全模拟他,就更好了 34 | 1. https://github.com/minio/minio/issues/8136 35 | 2. https://github.com/kneufeld/minio-put 36 | 3. https://docs.min.io/docs/javascript-client-quickstart-guide.html 37 | 4. https://github.com/minio/minio-js/blob/master/src/main/minio.js -------------------------------------------------------------------------------- /src/http_client.cpp: -------------------------------------------------------------------------------- 1 | #include "http_client.hpp" 2 | #include "ilogger.hpp" 3 | #include 4 | #include 5 | 6 | extern "C"{ 7 | #include 8 | } 9 | 10 | using namespace std; 11 | 12 | 13 | HttpBodyData::HttpBodyData(const std::string& data){ 14 | this->pdata = data.data(); 15 | this->size = data.size(); 16 | } 17 | 18 | HttpBodyData::HttpBodyData(const void* pdata, size_t size){ 19 | this->pdata = pdata; 20 | this->size = size; 21 | } 22 | 23 | enum QueryType : int{ 24 | QueryType_PostFrom, 25 | QueryType_PostBody, 26 | QueryType_PutBody, 27 | QueryType_PutFile, 28 | QueryType_Get, 29 | QueryType_Put 30 | }; 31 | 32 | struct HttpClientReadStream{ 33 | const unsigned char* pdata = nullptr; 34 | size_t data_size = 0; 35 | int curosr = 0; 36 | }; 37 | 38 | class HttpClientImpl : public HttpClient{ 39 | public: 40 | HttpClientImpl(const string& url){ 41 | 42 | curl_global_init(CURL_GLOBAL_ALL); 43 | 44 | url_ = url; 45 | add_header("Accept: */*"); 46 | add_header("Charset: utf-8"); 47 | //add_header("Expect:"); 48 | } 49 | 50 | virtual const string& response_body() const override{ 51 | return data_; 52 | } 53 | 54 | virtual int state_code() const override{ 55 | return state_code_; 56 | } 57 | 58 | virtual HttpClient* verbose() override{ 59 | verbose_ = true; 60 | return this; 61 | } 62 | 63 | virtual HttpClient* add_header(const string& value) override{ 64 | headers_.emplace_back(value); 65 | return this; 66 | } 67 | 68 | virtual HttpClient* add_param(const string& name, const string& value) override{ 69 | params_[name] = value; 70 | return this; 71 | } 72 | 73 | virtual HttpClient* timeout(int timeout_second) override { 74 | timeout_second_ = timeout_second; 75 | return this; 76 | } 77 | 78 | virtual bool post_body(const HttpBodyData& body) override{ 79 | type_ = QueryType_PostBody; 80 | body_ = body; 81 | return query(); 82 | } 83 | 84 | virtual bool put_body(const HttpBodyData& body) override{ 85 | type_ = QueryType_PutBody; 86 | body_ = body; 87 | return query(); 88 | } 89 | 90 | virtual bool put_file(const std::string& file) override{ 91 | type_ = QueryType_PutFile; 92 | put_file_ = file; 93 | return query(); 94 | } 95 | 96 | virtual bool put(){ 97 | type_ = QueryType_Put; 98 | return query(); 99 | } 100 | 101 | virtual bool post(){ 102 | type_ = QueryType_PostFrom; 103 | return query(); 104 | } 105 | 106 | virtual bool get(){ 107 | type_ = QueryType_Get; 108 | return query(); 109 | } 110 | 111 | static size_t write_bytes(void *ptr, size_t size, size_t count, void *stream){ 112 | string* buffer = ((string*)stream); 113 | buffer->insert(buffer->end(), (char*)ptr, (char*)ptr + size* count); 114 | return size* count; 115 | } 116 | 117 | static size_t read_bytes(void *ptr, size_t size, size_t count, void *userdata){ 118 | HttpClientReadStream* stream = ((HttpClientReadStream*)userdata); 119 | size_t remain = stream->data_size - stream->curosr; 120 | size_t copyed_size = min(size * count, remain); 121 | 122 | memcpy(ptr, stream->pdata + stream->curosr, copyed_size); 123 | stream->curosr += copyed_size; 124 | return copyed_size; 125 | } 126 | 127 | virtual const std::string response_header(const std::string& name) const override{ 128 | 129 | auto iter = response_header_.find(name); 130 | if(iter == response_header_.end()) 131 | return ""; 132 | 133 | return iter->second; 134 | } 135 | 136 | virtual bool has_response_header(const std::string& name) const override{ 137 | return response_header_.find(name) != response_header_.end(); 138 | } 139 | 140 | virtual const std::vector& response_headers() const override{ 141 | return response_header_lines_; 142 | } 143 | 144 | virtual const std::string& error_message() const override{ 145 | return error_; 146 | } 147 | 148 | bool query(){ 149 | 150 | state_code_ = 0; 151 | data_.clear(); 152 | response_header_lines_.clear(); 153 | response_header_.clear(); 154 | 155 | curl_slist* headers = nullptr; 156 | curl_httppost *formpost = nullptr; 157 | curl_httppost *lastptr = nullptr; 158 | CURL* curl = curl_easy_init(); 159 | FILE* fput_file_handle = nullptr; 160 | size_t put_file_size = 0; 161 | HttpClientReadStream stream_put_body; 162 | 163 | if(type_ == QueryType_PutFile){ 164 | 165 | put_file_size = iLogger::file_size(put_file_); 166 | fput_file_handle = fopen(put_file_.c_str(), "rb"); 167 | if(fput_file_handle == nullptr){ 168 | INFOE("Open file %s failed.", put_file_.c_str()); 169 | return false; 170 | } 171 | } 172 | 173 | for (auto& k : params_){ 174 | curl_formadd(&formpost, 175 | (curl_httppost**)&lastptr, 176 | CURLFORM_COPYNAME, k.first.c_str(), 177 | CURLFORM_COPYCONTENTS, k.second.c_str(), 178 | CURLFORM_END); 179 | } 180 | 181 | for(auto& value : headers_){ 182 | headers = curl_slist_append(headers, value.c_str()); 183 | } 184 | 185 | if(iLogger::begin_with(url_, "https://")){ 186 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 187 | curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1); 188 | } 189 | 190 | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 191 | curl_easy_setopt(curl, CURLOPT_URL, url_.c_str()); 192 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_bytes); 193 | curl_easy_setopt(curl, CURLOPT_HEADER, 1); 194 | curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); 195 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data_); 196 | curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_second_); 197 | curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose_ ? 1 : 0); 198 | 199 | if(type_ == QueryType_PostBody){ 200 | curl_easy_setopt(curl, CURLOPT_POST, 1); 201 | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body_.pdata); 202 | curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)body_.size); 203 | }else if(type_ == QueryType_PutBody){ 204 | stream_put_body.pdata = static_cast(body_.pdata); 205 | stream_put_body.data_size = body_.size; 206 | stream_put_body.curosr = 0; 207 | curl_easy_setopt(curl, CURLOPT_PUT, 1); 208 | curl_easy_setopt(curl, CURLOPT_INFILE, &stream_put_body); 209 | curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_bytes); 210 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, (long)body_.size); 211 | }else if(type_ == QueryType_PutFile){ 212 | curl_easy_setopt(curl, CURLOPT_PUT, 1); 213 | curl_easy_setopt(curl, CURLOPT_INFILE, fput_file_handle); 214 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, (long)put_file_size); 215 | }else if(type_ == QueryType_PostFrom){ 216 | curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); 217 | }else if(type_ == QueryType_Put){ 218 | curl_easy_setopt(curl, CURLOPT_PUT, 1); 219 | curl_easy_setopt(curl, CURLOPT_INFILE, nullptr); 220 | curl_easy_setopt(curl, CURLOPT_INFILESIZE, (long)0); 221 | } 222 | 223 | bool ok = false; 224 | CURLcode res = curl_easy_perform(curl); 225 | if (res != CURLE_OK) { 226 | error_ = iLogger::format("Curl error, code is %d, %s", res, curl_easy_strerror(res)); 227 | goto err; 228 | } 229 | 230 | // do curl query 231 | { 232 | curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &state_code_); 233 | if (!(state_code_ >= 200 && state_code_ < 300)){ 234 | error_ = iLogger::format("Response code: %d\n", state_code_); 235 | goto err; 236 | } 237 | 238 | int line_position = data_.find("\r\n\r\n"); 239 | if(line_position == -1){ 240 | error_ = iLogger::format("Worng http response, no line token, code: %d\n", state_code_); 241 | goto err; 242 | } 243 | 244 | response_header_string_ = data_.substr(0, line_position); 245 | data_ = data_.substr(line_position + 4); 246 | 247 | response_header_lines_ = iLogger::split_string(response_header_string_, "\r\n"); 248 | for(auto& line : response_header_lines_){ 249 | int token_position = line.find(':'); 250 | if(token_position == -1) 251 | continue; 252 | 253 | auto key = line.substr(0, token_position); 254 | auto value = line.substr(token_position + 2); 255 | response_header_[key] = value; 256 | } 257 | ok = true; 258 | } 259 | 260 | err: 261 | curl_easy_cleanup(curl); 262 | curl_formfree(formpost); 263 | curl_slist_free_all(headers); 264 | 265 | if(fput_file_handle != nullptr){ 266 | fclose(fput_file_handle); 267 | } 268 | return ok; 269 | } 270 | 271 | virtual const std::string& response_header_string() const { 272 | return response_header_string_; 273 | } 274 | 275 | private: 276 | string url_; 277 | unordered_map params_; 278 | vector headers_; 279 | string response_header_string_; 280 | unordered_map response_header_; 281 | vector response_header_lines_; 282 | string data_; 283 | string error_; 284 | QueryType type_; 285 | HttpBodyData body_; 286 | string put_file_; 287 | int state_code_ = 0; 288 | bool verbose_ = false; 289 | int timeout_second_ = 60; 290 | }; 291 | 292 | shared_ptr newHttp(const string& url){ 293 | return shared_ptr(new HttpClientImpl(url)); 294 | } -------------------------------------------------------------------------------- /src/http_client.hpp: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_CLIENT_HPP 2 | #define HTTP_CLIENT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct HttpBodyData{ 9 | HttpBodyData() = default; 10 | HttpBodyData(const std::string& data); 11 | HttpBodyData(const void* pdata, size_t size); 12 | 13 | const void* pdata = nullptr; 14 | size_t size = 0; 15 | }; 16 | 17 | class HttpClient{ 18 | public: 19 | virtual HttpClient* add_header(const std::string& value) = 0; 20 | virtual HttpClient* add_param(const std::string& name, const std::string& value) = 0; 21 | virtual HttpClient* verbose() = 0; 22 | virtual HttpClient* timeout(int timeout_second) = 0; 23 | virtual bool post() = 0; 24 | virtual bool post_body(const HttpBodyData& body) = 0; 25 | virtual bool put_body(const HttpBodyData& body) = 0; 26 | virtual bool put_file(const std::string& file) = 0; 27 | virtual bool get() = 0; 28 | virtual bool put() = 0; 29 | virtual const std::string& response_body() const = 0; 30 | virtual int state_code() const = 0; 31 | virtual const std::string& error_message() const = 0; 32 | virtual const std::string response_header(const std::string& name) const = 0; 33 | virtual const std::vector& response_headers() const = 0; 34 | virtual bool has_response_header(const std::string& name) const = 0; 35 | virtual const std::string& response_header_string() const = 0; 36 | }; 37 | 38 | std::shared_ptr newHttp(const std::string& url); 39 | 40 | #endif //HTTP_CLIENT_HPP -------------------------------------------------------------------------------- /src/ilogger.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if defined(_WIN32) 3 | # define U_OS_WINDOWS 4 | #else 5 | # define U_OS_LINUX 6 | #endif 7 | 8 | #include "ilogger.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #if defined(U_OS_WINDOWS) 23 | # define HAS_UUID 24 | # include 25 | # include 26 | # include 27 | # pragma comment(lib, "shlwapi.lib") 28 | # pragma comment(lib, "ole32.lib") 29 | # pragma comment(lib, "gdi32.lib") 30 | # undef min 31 | # undef max 32 | #endif 33 | 34 | #if defined(U_OS_LINUX) 35 | //# include 36 | # include 37 | # include 38 | # include 39 | # include 40 | # include 41 | # define strtok_s strtok_r 42 | #endif 43 | 44 | 45 | #if defined(U_OS_LINUX) 46 | #define __GetTimeBlock \ 47 | time_t timep; \ 48 | time(&timep); \ 49 | tm& t = *(tm*)localtime(&timep); 50 | #endif 51 | 52 | #if defined(U_OS_WINDOWS) 53 | #define __GetTimeBlock \ 54 | tm t; \ 55 | _getsystime(&t); 56 | #endif 57 | 58 | namespace iLogger{ 59 | 60 | using namespace std; 61 | 62 | const char* level_string(int level){ 63 | switch (level){ 64 | case ILOGGER_VERBOSE: return "verbo"; 65 | case ILOGGER_INFO: return "info"; 66 | case ILOGGER_WARNING: return "warn"; 67 | case ILOGGER_ERROR: return "error"; 68 | case ILOGGER_FATAL: return "fatal"; 69 | default: return "unknow"; 70 | } 71 | } 72 | 73 | std::tuple hsv2rgb(float h, float s, float v){ 74 | const int h_i = static_cast(h * 6); 75 | const float f = h * 6 - h_i; 76 | const float p = v * (1 - s); 77 | const float q = v * (1 - f*s); 78 | const float t = v * (1 - (1 - f) * s); 79 | float r, g, b; 80 | switch (h_i) { 81 | case 0:r = v; g = t; b = p;break; 82 | case 1:r = q; g = v; b = p;break; 83 | case 2:r = p; g = v; b = t;break; 84 | case 3:r = p; g = q; b = v;break; 85 | case 4:r = t; g = p; b = v;break; 86 | case 5:r = v; g = p; b = q;break; 87 | default:r = 1; g = 1; b = 1;break;} 88 | return make_tuple(static_cast(r * 255), static_cast(g * 255), static_cast(b * 255)); 89 | } 90 | 91 | std::tuple random_color(int id){ 92 | float h_plane = ((((unsigned int)id << 2) ^ 0x937151) % 100) / 100.0f;; 93 | float s_plane = ((((unsigned int)id << 3) ^ 0x315793) % 100) / 100.0f; 94 | return hsv2rgb(h_plane, s_plane, 1); 95 | } 96 | 97 | string date_now() { 98 | char time_string[20]; 99 | __GetTimeBlock; 100 | 101 | sprintf(time_string, "%04d-%02d-%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday); 102 | return time_string; 103 | } 104 | 105 | string time_now(){ 106 | char time_string[20]; 107 | __GetTimeBlock; 108 | 109 | sprintf(time_string, "%04d-%02d-%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); 110 | return time_string; 111 | } 112 | 113 | size_t file_size(const string& file){ 114 | #if defined(U_OS_LINUX) 115 | struct stat st; 116 | stat(file.c_str(), &st); 117 | return st.st_size; 118 | #elif defined(U_OS_WINDOWS) 119 | WIN32_FIND_DATAA find_data; 120 | HANDLE hFind = FindFirstFileA(file.c_str(), &find_data); 121 | if (hFind == INVALID_HANDLE_VALUE) 122 | return 0; 123 | 124 | FindClose(hFind); 125 | return (uint64_t)find_data.nFileSizeLow | ((uint64_t)find_data.nFileSizeHigh << 32); 126 | #endif 127 | } 128 | 129 | time_t last_modify(const string& file){ 130 | 131 | #if defined(U_OS_LINUX) 132 | struct stat st; 133 | stat(file.c_str(), &st); 134 | return st.st_mtim.tv_sec; 135 | #elif defined(U_OS_WINDOWS) 136 | INFOW("LastModify has not support on windows os"); 137 | return 0; 138 | #endif 139 | } 140 | 141 | void sleep(int ms) { 142 | this_thread::sleep_for(std::chrono::milliseconds(ms)); 143 | } 144 | 145 | int get_month_by_name(char* month){ 146 | if(strcmp(month,"Jan") == 0)return 0; 147 | if(strcmp(month,"Feb") == 0)return 1; 148 | if(strcmp(month,"Mar") == 0)return 2; 149 | if(strcmp(month,"Apr") == 0)return 3; 150 | if(strcmp(month,"May") == 0)return 4; 151 | if(strcmp(month,"Jun") == 0)return 5; 152 | if(strcmp(month,"Jul") == 0)return 6; 153 | if(strcmp(month,"Aug") == 0)return 7; 154 | if(strcmp(month,"Sep") == 0)return 8; 155 | if(strcmp(month,"Oct") == 0)return 9; 156 | if(strcmp(month,"Nov") == 0)return 10; 157 | if(strcmp(month,"Dec") == 0)return 11; 158 | return -1; 159 | } 160 | 161 | int get_week_day_by_name(char* wday){ 162 | if(strcmp(wday,"Sun") == 0)return 0; 163 | if(strcmp(wday,"Mon") == 0)return 1; 164 | if(strcmp(wday,"Tue") == 0)return 2; 165 | if(strcmp(wday,"Wed") == 0)return 3; 166 | if(strcmp(wday,"Thu") == 0)return 4; 167 | if(strcmp(wday,"Fri") == 0)return 5; 168 | if(strcmp(wday,"Sat") == 0)return 6; 169 | return -1; 170 | } 171 | 172 | time_t gmtime2ctime(const string& gmt){ 173 | 174 | char week[4] = {0}; 175 | char month[4] = {0}; 176 | tm date; 177 | sscanf(gmt.c_str(),"%3s, %2d %3s %4d %2d:%2d:%2d GMT",week,&date.tm_mday,month,&date.tm_year,&date.tm_hour,&date.tm_min,&date.tm_sec); 178 | date.tm_mon = get_month_by_name(month); 179 | date.tm_wday = get_week_day_by_name(week); 180 | date.tm_year = date.tm_year - 1900; 181 | return mktime(&date); 182 | } 183 | 184 | string gmtime(time_t t){ 185 | t += 28800; 186 | tm* gmt = ::gmtime(&t); 187 | 188 | // http://en.cppreference.com/w/c/chrono/strftime 189 | // e.g.: Sat, 22 Aug 2015 11:48:50 GMT 190 | // 5+ 3+4+ 5+ 9+ 3 = 29 191 | const char* fmt = "%a, %d %b %Y %H:%M:%S GMT"; 192 | char tstr[30]; 193 | strftime(tstr, sizeof(tstr), fmt, gmt); 194 | return tstr; 195 | } 196 | 197 | string gmtime_now() { 198 | return gmtime(time(nullptr)); 199 | } 200 | 201 | bool mkdir(const string& path){ 202 | #ifdef U_OS_WINDOWS 203 | return CreateDirectoryA(path.c_str(), nullptr); 204 | #else 205 | return ::mkdir(path.c_str(), 0755) == 0; 206 | #endif 207 | } 208 | 209 | bool mkdirs(const string& path){ 210 | 211 | if (path.empty()) return false; 212 | if (exists(path)) return true; 213 | 214 | string _path = path; 215 | char* dir_ptr = (char*)_path.c_str(); 216 | char* iter_ptr = dir_ptr; 217 | 218 | bool keep_going = *iter_ptr != 0; 219 | while (keep_going){ 220 | 221 | if (*iter_ptr == 0) 222 | keep_going = false; 223 | 224 | #ifdef U_OS_WINDOWS 225 | if (*iter_ptr == '/' || *iter_ptr == '\\' || *iter_ptr == 0){ 226 | #else 227 | if ((*iter_ptr == '/' && iter_ptr != dir_ptr) || *iter_ptr == 0){ 228 | #endif 229 | char old = *iter_ptr; 230 | *iter_ptr = 0; 231 | if (!exists(dir_ptr)){ 232 | if (!mkdir(dir_ptr)){ 233 | if(!exists(dir_ptr)){ 234 | INFOE("mkdirs %s return false.", dir_ptr); 235 | return false; 236 | } 237 | } 238 | } 239 | *iter_ptr = old; 240 | } 241 | iter_ptr++; 242 | } 243 | return true; 244 | } 245 | 246 | bool isfile(const string& file){ 247 | 248 | #if defined(U_OS_LINUX) 249 | struct stat st; 250 | stat(file.c_str(), &st); 251 | return S_ISREG(st.st_mode); 252 | #elif defined(U_OS_WINDOWS) 253 | INFOW("is_file has not support on windows os"); 254 | return 0; 255 | #endif 256 | } 257 | 258 | FILE* fopen_mkdirs(const string& path, const string& mode){ 259 | 260 | FILE* f = fopen(path.c_str(), mode.c_str()); 261 | if (f) return f; 262 | 263 | int p = path.rfind('/'); 264 | 265 | #if defined(U_OS_WINDOWS) 266 | int e = path.rfind('\\'); 267 | p = std::max(p, e); 268 | #endif 269 | if (p == -1) 270 | return nullptr; 271 | 272 | string directory = path.substr(0, p); 273 | if (!mkdirs(directory)) 274 | return nullptr; 275 | 276 | return fopen(path.c_str(), mode.c_str()); 277 | } 278 | 279 | bool exists(const string& path){ 280 | 281 | #ifdef U_OS_WINDOWS 282 | return ::PathFileExistsA(path.c_str()); 283 | #elif defined(U_OS_LINUX) 284 | return access(path.c_str(), R_OK) == 0; 285 | #endif 286 | } 287 | 288 | string format(const char* fmt, ...) { 289 | va_list vl; 290 | va_start(vl, fmt); 291 | char buffer[2048]; 292 | vsnprintf(buffer, sizeof(buffer), fmt, vl); 293 | return buffer; 294 | } 295 | 296 | string file_name(const string& path, bool include_suffix){ 297 | 298 | if (path.empty()) return ""; 299 | 300 | int p = path.rfind('/'); 301 | 302 | #ifdef U_OS_WINDOWS 303 | int e = path.rfind('\\'); 304 | p = max(p, e); 305 | #endif 306 | p += 1; 307 | 308 | //include suffix 309 | if (include_suffix) 310 | return path.substr(p); 311 | 312 | int u = path.rfind('.'); 313 | if (u == -1) 314 | return path.substr(p); 315 | 316 | if (u <= p) u = path.size(); 317 | return path.substr(p, u - p); 318 | } 319 | 320 | string directory(const string& path){ 321 | if (path.empty()) 322 | return "."; 323 | 324 | int p = path.rfind('/'); 325 | 326 | #ifdef U_OS_WINDOWS 327 | int e = path.rfind('\\'); 328 | p = max(p, e); 329 | #endif 330 | if(p == -1) 331 | return "."; 332 | 333 | return path.substr(0, p + 1); 334 | } 335 | 336 | bool begin_with(const string& str, const string& with){ 337 | 338 | if (str.length() < with.length()) 339 | return false; 340 | return strncmp(str.c_str(), with.c_str(), with.length()) == 0; 341 | } 342 | 343 | bool end_with(const string& str, const string& with){ 344 | 345 | if (str.length() < with.length()) 346 | return false; 347 | 348 | return strncmp(str.c_str() + str.length() - with.length(), with.c_str(), with.length()) == 0; 349 | } 350 | 351 | long long timestamp_now() { 352 | return chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); 353 | } 354 | 355 | static struct Logger{ 356 | mutex logger_lock_; 357 | string logger_directory; 358 | int logger_level{ILOGGER_INFO}; 359 | vector cache_, local_; 360 | shared_ptr flush_thread_; 361 | atomic keep_run_{false}; 362 | shared_ptr handler; 363 | bool logger_shutdown{false}; 364 | 365 | void write(const string& line) { 366 | 367 | lock_guard l(logger_lock_); 368 | if(logger_shutdown) 369 | return; 370 | 371 | if (!keep_run_) { 372 | 373 | if(flush_thread_) 374 | return; 375 | 376 | cache_.reserve(1000); 377 | keep_run_ = true; 378 | flush_thread_.reset(new thread(std::bind(&Logger::flush_job, this))); 379 | } 380 | cache_.emplace_back(line); 381 | } 382 | 383 | void flush() { 384 | 385 | if (cache_.empty()) 386 | return; 387 | 388 | { 389 | std::lock_guard l(logger_lock_); 390 | std::swap(local_, cache_); 391 | } 392 | 393 | if (!local_.empty() && !logger_directory.empty()) { 394 | 395 | auto now = date_now(); 396 | auto file = format("%s%s.txt", logger_directory.c_str(), now.c_str()); 397 | if (!exists(file)) { 398 | handler.reset(fopen_mkdirs(file, "wb"), fclose); 399 | } 400 | else if (!handler) { 401 | handler.reset(fopen_mkdirs(file, "a+"), fclose); 402 | } 403 | 404 | if (handler) { 405 | for (auto& line : local_) 406 | fprintf(handler.get(), "%s\n", line.c_str()); 407 | fflush(handler.get()); 408 | handler.reset(); 409 | } 410 | } 411 | local_.clear(); 412 | } 413 | 414 | void flush_job() { 415 | 416 | auto tick_begin = timestamp_now(); 417 | std::vector local; 418 | while (keep_run_) { 419 | 420 | if (timestamp_now() - tick_begin < 1000) { 421 | this_thread::sleep_for(std::chrono::milliseconds(100)); 422 | continue; 423 | } 424 | 425 | tick_begin = timestamp_now(); 426 | flush(); 427 | } 428 | flush(); 429 | } 430 | 431 | void set_save_directory(const string& loggerDirectory) { 432 | logger_directory = loggerDirectory; 433 | 434 | if (logger_directory.empty()) 435 | logger_directory = "."; 436 | 437 | #if defined(U_OS_LINUX) 438 | if (logger_directory.back() != '/') { 439 | logger_directory.push_back('/'); 440 | } 441 | #endif 442 | 443 | #if defined(U_OS_WINDOWS) 444 | if (logger_directory.back() != '/' && logger_directory.back() != '\\') { 445 | logger_directory.push_back('/'); 446 | } 447 | #endif 448 | } 449 | 450 | void set_logger_level(int level){ 451 | logger_level = level; 452 | } 453 | 454 | void close(){ 455 | { 456 | lock_guard l(logger_lock_); 457 | if (logger_shutdown) return; 458 | 459 | logger_shutdown = true; 460 | }; 461 | 462 | if (!keep_run_) return; 463 | keep_run_ = false; 464 | flush_thread_->join(); 465 | flush_thread_.reset(); 466 | handler.reset(); 467 | } 468 | 469 | virtual ~Logger(){ 470 | close(); 471 | } 472 | }__g_logger; 473 | 474 | void destroy_logger(){ 475 | __g_logger.close(); 476 | } 477 | 478 | static void remove_color_text(char* buffer){ 479 | 480 | //"\033[31m%s\033[0m" 481 | char* p = buffer; 482 | while(*p){ 483 | 484 | if(*p == 0x1B){ 485 | char np = *(p + 1); 486 | if(np == '['){ 487 | // has token 488 | char* t = p + 2; 489 | while(*t){ 490 | if(*t == 'm'){ 491 | t = t + 1; 492 | char* k = p; 493 | while(*t){ 494 | *k++ = *t++; 495 | } 496 | *k = 0; 497 | break; 498 | } 499 | t++; 500 | } 501 | } 502 | } 503 | p++; 504 | } 505 | } 506 | 507 | void set_logger_save_directory(const string& loggerDirectory){ 508 | __g_logger.set_save_directory(loggerDirectory); 509 | } 510 | 511 | void set_log_level(int level){ 512 | __g_logger.set_logger_level(level); 513 | } 514 | 515 | void __log_func(const char* file, int line, int level, const char* fmt, ...) { 516 | 517 | if(level > __g_logger.logger_level) 518 | return; 519 | 520 | string now = time_now(); 521 | va_list vl; 522 | va_start(vl, fmt); 523 | 524 | char buffer[2048]; 525 | string filename = file_name(file, true); 526 | int n = snprintf(buffer, sizeof(buffer), "[%s]", now.c_str()); 527 | 528 | #if defined(U_OS_WINDOWS) 529 | if (level == ILOGGER_FATAL || level == ILOGGER_ERROR) { 530 | n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level)); 531 | } 532 | else if (level == ILOGGER_WARNING) { 533 | n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level)); 534 | } 535 | else { 536 | n += snprintf(buffer + n, sizeof(buffer) - n, "[%s]", level_string(level)); 537 | } 538 | #elif defined(U_OS_LINUX) 539 | if (level == ILOGGER_FATAL || level == ILOGGER_ERROR) { 540 | n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[31m%s\033[0m]", level_string(level)); 541 | } 542 | else if (level == ILOGGER_WARNING) { 543 | n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[33m%s\033[0m]", level_string(level)); 544 | } 545 | else { 546 | n += snprintf(buffer + n, sizeof(buffer) - n, "[\033[32m%s\033[0m]", level_string(level)); 547 | } 548 | #endif 549 | 550 | n += snprintf(buffer + n, sizeof(buffer) - n, "[%s:%d]:", filename.c_str(), line); 551 | vsnprintf(buffer + n, sizeof(buffer) - n, fmt, vl); 552 | 553 | if (level == ILOGGER_FATAL || level == ILOGGER_ERROR) { 554 | fprintf(stderr, "%s\n", buffer); 555 | } 556 | else if (level == ILOGGER_WARNING) { 557 | fprintf(stdout, "%s\n", buffer); 558 | } 559 | else { 560 | fprintf(stdout, "%s\n", buffer); 561 | } 562 | 563 | if(!__g_logger.logger_directory.empty()){ 564 | #ifdef U_OS_LINUX 565 | // remove save color txt 566 | remove_color_text(buffer); 567 | #endif 568 | __g_logger.write(buffer); 569 | if (level == ILOGGER_FATAL) { 570 | __g_logger.flush(); 571 | fflush(stdout); 572 | abort(); 573 | } 574 | } 575 | } 576 | 577 | string load_text_file(const string& file){ 578 | ifstream in(file, ios::in | ios::binary); 579 | if (!in.is_open()) 580 | return {}; 581 | 582 | in.seekg(0, ios::end); 583 | size_t length = in.tellg(); 584 | 585 | string data; 586 | if (length > 0){ 587 | in.seekg(0, ios::beg); 588 | data.resize(length); 589 | 590 | in.read((char*)&data[0], length); 591 | } 592 | in.close(); 593 | return data; 594 | } 595 | 596 | std::vector load_file(const string& file){ 597 | 598 | ifstream in(file, ios::in | ios::binary); 599 | if (!in.is_open()) 600 | return {}; 601 | 602 | in.seekg(0, ios::end); 603 | size_t length = in.tellg(); 604 | 605 | std::vector data; 606 | if (length > 0){ 607 | in.seekg(0, ios::beg); 608 | data.resize(length); 609 | 610 | in.read((char*)&data[0], length); 611 | } 612 | in.close(); 613 | return data; 614 | } 615 | 616 | bool alphabet_equal(char a, char b, bool ignore_case){ 617 | if (ignore_case){ 618 | a = a > 'a' && a < 'z' ? a - 'a' + 'A' : a; 619 | b = b > 'a' && b < 'z' ? b - 'a' + 'A' : b; 620 | } 621 | return a == b; 622 | } 623 | 624 | static bool pattern_match_body(const char* str, const char* matcher, bool igrnoe_case){ 625 | // abcdefg.pnga *.png > false 626 | // abcdefg.png *.png > true 627 | // abcdefg.png a?cdefg.png > true 628 | 629 | if (!matcher || !*matcher || !str || !*str) return false; 630 | 631 | const char* ptr_matcher = matcher; 632 | while (*str){ 633 | if (*ptr_matcher == '?'){ 634 | ptr_matcher++; 635 | } 636 | else if (*ptr_matcher == '*'){ 637 | if (*(ptr_matcher + 1)){ 638 | if (pattern_match_body(str, ptr_matcher + 1, igrnoe_case)) 639 | return true; 640 | } 641 | else{ 642 | return true; 643 | } 644 | } 645 | else if (!alphabet_equal(*ptr_matcher, *str, igrnoe_case)){ 646 | return false; 647 | } 648 | else{ 649 | if (*ptr_matcher) 650 | ptr_matcher++; 651 | else 652 | return false; 653 | } 654 | str++; 655 | } 656 | 657 | while (*ptr_matcher){ 658 | if (*ptr_matcher != '*') 659 | return false; 660 | ptr_matcher++; 661 | } 662 | return true; 663 | } 664 | 665 | bool pattern_match(const char* str, const char* matcher, bool igrnoe_case){ 666 | // abcdefg.pnga *.png > false 667 | // abcdefg.png *.png > true 668 | // abcdefg.png a?cdefg.png > true 669 | 670 | if (!matcher || !*matcher || !str || !*str) return false; 671 | 672 | char filter[500]; 673 | strcpy(filter, matcher); 674 | 675 | vector arr; 676 | char* ptr_str = filter; 677 | char* ptr_prev_str = ptr_str; 678 | while (*ptr_str){ 679 | if (*ptr_str == ';'){ 680 | *ptr_str = 0; 681 | arr.push_back(ptr_prev_str); 682 | ptr_prev_str = ptr_str + 1; 683 | } 684 | ptr_str++; 685 | } 686 | 687 | if (*ptr_prev_str) 688 | arr.push_back(ptr_prev_str); 689 | 690 | for (int i = 0; i < arr.size(); ++i){ 691 | if (pattern_match_body(str, arr[i], igrnoe_case)) 692 | return true; 693 | } 694 | return false; 695 | } 696 | 697 | #ifdef U_OS_WINDOWS 698 | vector find_files(const string& directory, const string& filter, bool findDirectory, bool includeSubDirectory){ 699 | 700 | string realpath = directory; 701 | if (realpath.empty()) 702 | realpath = "./"; 703 | 704 | char backchar = realpath.back(); 705 | if (backchar != '\\' && backchar != '/') 706 | realpath += "/"; 707 | 708 | vector out; 709 | _WIN32_FIND_DATAA find_data; 710 | stack ps; 711 | ps.push(realpath); 712 | 713 | while (!ps.empty()) 714 | { 715 | string search_path = ps.top(); 716 | ps.pop(); 717 | 718 | HANDLE hFind = FindFirstFileA((search_path + "*").c_str(), &find_data); 719 | if (hFind != INVALID_HANDLE_VALUE){ 720 | do{ 721 | if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) 722 | continue; 723 | 724 | if (!findDirectory && (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY || 725 | findDirectory && (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY){ 726 | if (PathMatchSpecA(find_data.cFileName, filter.c_str())) 727 | out.push_back(search_path + find_data.cFileName); 728 | } 729 | 730 | if (includeSubDirectory && (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) 731 | ps.push(search_path + find_data.cFileName + "/"); 732 | 733 | } while (FindNextFileA(hFind, &find_data)); 734 | FindClose(hFind); 735 | } 736 | } 737 | return out; 738 | } 739 | #endif 740 | 741 | #ifdef U_OS_LINUX 742 | vector find_files(const string& directory, const string& filter, bool findDirectory, bool includeSubDirectory) 743 | { 744 | string realpath = directory; 745 | if (realpath.empty()) 746 | realpath = "./"; 747 | 748 | char backchar = realpath.back(); 749 | if (backchar != '\\' && backchar != '/') 750 | realpath += "/"; 751 | 752 | struct dirent* fileinfo; 753 | DIR* handle; 754 | stack ps; 755 | vector out; 756 | ps.push(realpath); 757 | 758 | while (!ps.empty()) 759 | { 760 | string search_path = ps.top(); 761 | ps.pop(); 762 | 763 | handle = opendir(search_path.c_str()); 764 | if (handle != 0) 765 | { 766 | while (fileinfo = readdir(handle)) 767 | { 768 | struct stat file_stat; 769 | if (strcmp(fileinfo->d_name, ".") == 0 || strcmp(fileinfo->d_name, "..") == 0) 770 | continue; 771 | 772 | if (lstat((search_path + fileinfo->d_name).c_str(), &file_stat) < 0) 773 | continue; 774 | 775 | if (!findDirectory && !S_ISDIR(file_stat.st_mode) || 776 | findDirectory && S_ISDIR(file_stat.st_mode)) 777 | { 778 | if (pattern_match(fileinfo->d_name, filter.c_str())) 779 | out.push_back(search_path + fileinfo->d_name); 780 | } 781 | 782 | if (includeSubDirectory && S_ISDIR(file_stat.st_mode)) 783 | ps.push(search_path + fileinfo->d_name + "/"); 784 | } 785 | closedir(handle); 786 | } 787 | } 788 | return out; 789 | } 790 | #endif 791 | 792 | string align_blank(const string& input, int align_size, char blank){ 793 | if(input.size() >= align_size) return input; 794 | string output = input; 795 | for(int i = 0; i < align_size - input.size(); ++i) 796 | output.push_back(blank); 797 | return output; 798 | } 799 | 800 | vector split_string(const string& str, const std::string& spstr){ 801 | 802 | vector res; 803 | if (str.empty()) return res; 804 | if (spstr.empty()) return{ str }; 805 | 806 | auto p = str.find(spstr); 807 | if (p == string::npos) return{ str }; 808 | 809 | res.reserve(5); 810 | string::size_type prev = 0; 811 | int lent = spstr.length(); 812 | const char* ptr = str.c_str(); 813 | 814 | while (p != string::npos){ 815 | int len = p - prev; 816 | if (len > 0){ 817 | res.emplace_back(str.substr(prev, len)); 818 | } 819 | prev = p + lent; 820 | p = str.find(spstr, prev); 821 | } 822 | 823 | int len = str.length() - prev; 824 | if (len > 0){ 825 | res.emplace_back(str.substr(prev, len)); 826 | } 827 | return res; 828 | } 829 | 830 | string replace_string(const string& str, const string& token, const string& value){ 831 | 832 | string opstr; 833 | 834 | if (value.length() > token.length()){ 835 | float numToken = str.size() / (float)token.size(); 836 | float newTokenLength = value.size() * numToken; 837 | opstr.resize(newTokenLength); 838 | } 839 | else{ 840 | opstr.resize(str.size()); 841 | } 842 | 843 | char* dest = &opstr[0]; 844 | const char* src = str.c_str(); 845 | string::size_type pos = 0; 846 | string::size_type prev = 0; 847 | size_t token_length = token.length(); 848 | size_t value_length = value.length(); 849 | const char* value_ptr = value.c_str(); 850 | bool keep = true; 851 | 852 | do{ 853 | pos = str.find(token, pos); 854 | if (pos == string::npos){ 855 | keep = false; 856 | pos = str.length(); 857 | } 858 | 859 | size_t copy_length = pos - prev; 860 | memcpy(dest, src + prev, copy_length); 861 | dest += copy_length; 862 | 863 | if (keep){ 864 | pos += token_length; 865 | prev = pos; 866 | memcpy(dest, value_ptr, value_length); 867 | dest += value_length; 868 | } 869 | } while (keep); 870 | 871 | size_t valid_size = dest - &opstr[0]; 872 | opstr.resize(valid_size); 873 | return opstr; 874 | } 875 | 876 | bool save_file(const string& file, const void* data, size_t length, bool mk_dirs){ 877 | 878 | if (mk_dirs){ 879 | int p = (int)file.rfind('/'); 880 | 881 | #ifdef U_OS_WINDOWS 882 | int e = (int)file.rfind('\\'); 883 | p = max(p, e); 884 | #endif 885 | if (p != -1){ 886 | if (!mkdirs(file.substr(0, p))) 887 | return false; 888 | } 889 | } 890 | 891 | FILE* f = fopen(file.c_str(), "wb"); 892 | if (!f) return false; 893 | 894 | if (data && length > 0){ 895 | if (fwrite(data, 1, length, f) != length){ 896 | fclose(f); 897 | return false; 898 | } 899 | } 900 | fclose(f); 901 | return true; 902 | } 903 | 904 | bool save_file(const string& file, const string& data, bool mk_dirs){ 905 | return save_file(file, data.data(), data.size(), mk_dirs); 906 | } 907 | 908 | bool save_file(const string& file, const vector& data, bool mk_dirs){ 909 | return save_file(file, data.data(), data.size(), mk_dirs); 910 | } 911 | 912 | static volatile bool g_has_exit_signal = false; 913 | static int g_signum = 0; 914 | static void signal_callback_handler(int signum){ 915 | INFO("Capture interrupt signal."); 916 | g_has_exit_signal = true; 917 | g_signum = signum; 918 | } 919 | 920 | int while_loop(){ 921 | signal(SIGINT, signal_callback_handler); 922 | signal(SIGQUIT, signal_callback_handler); 923 | while(!g_has_exit_signal){ 924 | this_thread::yield(); 925 | } 926 | INFO("Loop over."); 927 | return g_signum; 928 | } 929 | 930 | 931 | static unsigned char from_b64(unsigned char ch) { 932 | /* Inverse lookup map */ 933 | static const unsigned char tab[128] = { 934 | 255, 255, 255, 255, 935 | 255, 255, 255, 255, /* 0 */ 936 | 255, 255, 255, 255, 937 | 255, 255, 255, 255, /* 8 */ 938 | 255, 255, 255, 255, 939 | 255, 255, 255, 255, /* 16 */ 940 | 255, 255, 255, 255, 941 | 255, 255, 255, 255, /* 24 */ 942 | 255, 255, 255, 255, 943 | 255, 255, 255, 255, /* 32 */ 944 | 255, 255, 255, 62, 945 | 255, 255, 255, 63, /* 40 */ 946 | 52, 53, 54, 55, 947 | 56, 57, 58, 59, /* 48 */ 948 | 60, 61, 255, 255, 949 | 255, 200, 255, 255, /* 56 '=' is 200, on index 61 */ 950 | 255, 0, 1, 2, 951 | 3, 4, 5, 6, /* 64 */ 952 | 7, 8, 9, 10, 953 | 11, 12, 13, 14, /* 72 */ 954 | 15, 16, 17, 18, 955 | 19, 20, 21, 22, /* 80 */ 956 | 23, 24, 25, 255, 957 | 255, 255, 255, 255, /* 88 */ 958 | 255, 26, 27, 28, 959 | 29, 30, 31, 32, /* 96 */ 960 | 33, 34, 35, 36, 961 | 37, 38, 39, 40, /* 104 */ 962 | 41, 42, 43, 44, 963 | 45, 46, 47, 48, /* 112 */ 964 | 49, 50, 51, 255, 965 | 255, 255, 255, 255, /* 120 */ 966 | }; 967 | return tab[ch & 127]; 968 | } 969 | 970 | string base64_decode(const string& base64) { 971 | 972 | if(base64.empty()) 973 | return ""; 974 | 975 | int len = base64.size(); 976 | auto s = (const unsigned char*)base64.data(); 977 | unsigned char a, b, c, d; 978 | int orig_len = len; 979 | int dec_len = 0; 980 | string out_data; 981 | 982 | auto end_s = s + base64.size(); 983 | int count_eq = 0; 984 | while(*--end_s == '='){ 985 | count_eq ++; 986 | } 987 | out_data.resize(len / 4 * 3 - count_eq); 988 | 989 | char *dst = const_cast(out_data.data()); 990 | char *orig_dst = dst; 991 | while (len >= 4 && (a = from_b64(s[0])) != 255 && 992 | (b = from_b64(s[1])) != 255 && (c = from_b64(s[2])) != 255 && 993 | (d = from_b64(s[3])) != 255) { 994 | s += 4; 995 | len -= 4; 996 | if (a == 200 || b == 200) break; /* '=' can't be there */ 997 | *dst++ = a << 2 | b >> 4; 998 | if (c == 200) break; 999 | *dst++ = b << 4 | c >> 2; 1000 | if (d == 200) break; 1001 | *dst++ = c << 6 | d; 1002 | } 1003 | dec_len = (dst - orig_dst); 1004 | 1005 | // dec_len必定等于out_data.size() 1006 | return out_data; 1007 | } 1008 | 1009 | string base64_encode(const void* data, size_t size) { 1010 | 1011 | string encode_result; 1012 | encode_result.reserve(size / 3 * 4 + (size % 3 != 0 ? 4 : 0)); 1013 | 1014 | const unsigned char * current = static_cast(data); 1015 | static const char *base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 1016 | while(size > 2) { 1017 | encode_result += base64_table[current[0] >> 2]; 1018 | encode_result += base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; 1019 | encode_result += base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)]; 1020 | encode_result += base64_table[current[2] & 0x3f]; 1021 | 1022 | current += 3; 1023 | size -= 3; 1024 | } 1025 | 1026 | if(size > 0){ 1027 | encode_result += base64_table[current[0] >> 2]; 1028 | if(size%3 == 1) { 1029 | encode_result += base64_table[(current[0] & 0x03) << 4]; 1030 | encode_result += "=="; 1031 | } else if(size%3 == 2) { 1032 | encode_result += base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; 1033 | encode_result += base64_table[(current[1] & 0x0f) << 2]; 1034 | encode_result += "="; 1035 | } 1036 | } 1037 | return encode_result; 1038 | } 1039 | 1040 | 1041 | }; // namespace Logger -------------------------------------------------------------------------------- /src/ilogger.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef ILOGGER_HPP 3 | #define ILOGGER_HPP 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define ILOGGER_VERBOSE 4 12 | #define ILOGGER_INFO 3 13 | #define ILOGGER_WARNING 2 14 | #define ILOGGER_ERROR 1 15 | #define ILOGGER_FATAL 0 16 | #define INFOV(...) iLogger::__log_func(__FILE__, __LINE__, ILOGGER_VERBOSE, __VA_ARGS__) 17 | #define INFO(...) iLogger::__log_func(__FILE__, __LINE__, ILOGGER_INFO, __VA_ARGS__) 18 | #define INFOW(...) iLogger::__log_func(__FILE__, __LINE__, ILOGGER_WARNING, __VA_ARGS__) 19 | #define INFOE(...) iLogger::__log_func(__FILE__, __LINE__, ILOGGER_ERROR, __VA_ARGS__) 20 | #define INFOF(...) iLogger::__log_func(__FILE__, __LINE__, ILOGGER_FATAL, __VA_ARGS__) 21 | 22 | namespace iLogger{ 23 | 24 | using namespace std; 25 | 26 | string date_now(); 27 | string time_now(); 28 | string gmtime_now(); 29 | string gmtime(time_t t); 30 | time_t gmtime2ctime(const string& gmt); 31 | void sleep(int ms); 32 | 33 | bool isfile(const string& file); 34 | bool mkdir(const string& path); 35 | bool mkdirs(const string& path); 36 | bool exists(const string& path); 37 | string format(const char* fmt, ...); 38 | FILE* fopen_mkdirs(const string& path, const string& mode); 39 | string file_name(const string& path, bool include_suffix); 40 | string directory(const string& path); 41 | long long timestamp_now(); 42 | time_t last_modify(const string& file); 43 | vector load_file(const string& file); 44 | string load_text_file(const string& file); 45 | size_t file_size(const string& file); 46 | 47 | bool begin_with(const string& str, const string& with); 48 | bool end_with(const string& str, const string& with); 49 | vector split_string(const string& str, const std::string& spstr); 50 | string replace_string(const string& str, const string& token, const string& value); 51 | 52 | // h[0-1], s[0-1], v[0-1] 53 | // return, 0-255, 0-255, 0-255 54 | tuple hsv2rgb(float h, float s, float v); 55 | tuple random_color(int id); 56 | 57 | // abcdefg.pnga *.png > false 58 | // abcdefg.png *.png > true 59 | // abcdefg.png a?cdefg.png > true 60 | bool pattern_match(const char* str, const char* matcher, bool igrnoe_case = true); 61 | vector find_files( 62 | const string& directory, 63 | const string& filter = "*", bool findDirectory = false, bool includeSubDirectory = false); 64 | 65 | string align_blank(const string& input, int align_size, char blank=' '); 66 | bool save_file(const string& file, const vector& data, bool mk_dirs = true); 67 | bool save_file(const string& file, const string& data, bool mk_dirs = true); 68 | bool save_file(const string& file, const void* data, size_t length, bool mk_dirs = true); 69 | 70 | // 循环等待,并捕获例如ctrl+c等终止信号,收到信号后循环跳出并返回信号类型 71 | // 捕获:SIGINT(2)、SIGQUIT(3) 72 | int while_loop(); 73 | 74 | // 关于logger的api 75 | const char* level_string(int level); 76 | void set_logger_save_directory(const string& loggerDirectory); 77 | 78 | // 当日志的级别低于这个设置时,会打印出来,否则会直接跳过 79 | void set_log_level(int level); 80 | void __log_func(const char* file, int line, int level, const char* fmt, ...); 81 | void destroy_logger(); 82 | 83 | string base64_decode(const string& base64); 84 | string base64_encode(const void* data, size_t size); 85 | }; 86 | 87 | 88 | #endif // ILOGGER_HPP -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include "minio_client.hpp" 4 | #include "ilogger.hpp" 5 | 6 | int main() { 7 | 8 | // 对于自己搭建的服务器,http访问配置 9 | // const char* server = "http://192.168.16.109:9000"; 10 | // const char* access_key = "F2IHVVX44WVGYUIA1ESX"; 11 | // const char* secret_key = "UiJuXEG4V6ZLqCZ9ZbD9lqKEG8WwtaKeA3kh7Lui"; 12 | 13 | // 对于官方给的测试案例地址,https访问 14 | const char* server = "https://play.min.io:9000"; 15 | const char* access_key = "Q3AM3UQ867SPQQA43P2F"; 16 | const char* secret_key = "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"; 17 | MinioClient minio(server, access_key, secret_key); 18 | 19 | // int time_zone_to_gmt = -8 * 3600; 20 | // const char* server = "https://oss.ceph.com"; 21 | // const char* access_key = "xxx"; 22 | // const char* secret_key = "xxxxxx"; 23 | // MinioClient minio(server, access_key, secret_key, time_zone_to_gmt); 24 | 25 | 26 | ///////////////////////////////////////////////////////////////// 27 | INFO("===========================test get buckets========================="); 28 | auto buckets = minio.get_bucket_list(); 29 | INFO("total is %d", buckets.size()); 30 | 31 | for(int i = 0; i < buckets.size(); ++i) 32 | INFO("bucket[%d] = %s", i, buckets[i].c_str()); 33 | 34 | 35 | ///////////////////////////////////////////////////////////////// 36 | INFO("===========================test upload========================="); 37 | const char* local_file = "echo.txt"; 38 | const char* bucket_name = buckets[0].c_str(); 39 | if(minio.upload_file(iLogger::format("/%s/echo.txt", bucket_name), local_file)){ 40 | INFO("upload %s success, size: %d bytes", local_file, iLogger::file_size(local_file)); 41 | } 42 | 43 | 44 | ///////////////////////////////////////////////////////////////// 45 | INFO("===========================test download========================="); 46 | auto data = minio.get_file(iLogger::format("/%s/echo.txt", bucket_name)); 47 | INFO("download echo.txt, content is[%d bytes]: %s", data.size(), data.c_str()); 48 | 49 | 50 | ///////////////////////////////////////////////////////////////// 51 | INFO("===========================test upload data========================="); 52 | auto filedata = iLogger::load_text_file("echo.txt"); 53 | if(minio.upload_filedata(iLogger::format("/%s/echo-filedata.txt", bucket_name), filedata)){ 54 | INFO("upload filedata success, filedata.size = %d", filedata.size()); 55 | } 56 | 57 | 58 | ///////////////////////////////////////////////////////////////// 59 | INFO("===========================test download========================="); 60 | auto data2 = minio.get_file(iLogger::format("/%s/echo-filedata.txt", bucket_name)); 61 | INFO("download echo-filedata.txt, content is[%d bytes]: %s", data2.size(), data2.c_str()); 62 | return 0; 63 | } -------------------------------------------------------------------------------- /src/minio_client.cpp: -------------------------------------------------------------------------------- 1 | #include "minio_client.hpp" 2 | #include 3 | #include 4 | 5 | #include "http_client.hpp" 6 | #include "ilogger.hpp" 7 | 8 | using namespace std; 9 | 10 | static string base64_encode(const void* data, size_t size) { 11 | 12 | string encode_result; 13 | encode_result.reserve(size / 3 * 4 + (size % 3 != 0 ? 4 : 0)); 14 | 15 | const unsigned char * current = static_cast(data); 16 | static const char *base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 17 | while(size > 2) { 18 | encode_result += base64_table[current[0] >> 2]; 19 | encode_result += base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; 20 | encode_result += base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)]; 21 | encode_result += base64_table[current[2] & 0x3f]; 22 | 23 | current += 3; 24 | size -= 3; 25 | } 26 | 27 | if(size > 0){ 28 | encode_result += base64_table[current[0] >> 2]; 29 | if(size%3 == 1) { 30 | encode_result += base64_table[(current[0] & 0x03) << 4]; 31 | encode_result += "=="; 32 | } else if(size%3 == 2) { 33 | encode_result += base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)]; 34 | encode_result += base64_table[(current[1] & 0x0f) << 2]; 35 | encode_result += "="; 36 | } 37 | } 38 | return encode_result; 39 | } 40 | 41 | // 与date -R 结果一致 42 | static string gmtime_now(int correction_time){ 43 | time_t timet; 44 | time(&timet); 45 | timet += correction_time; 46 | 47 | tm& t = *(tm*)localtime(&timet); 48 | char timebuffer[100]; 49 | strftime(timebuffer, sizeof(timebuffer), "%a, %d %b %Y %X %z", &t); 50 | return timebuffer; 51 | } 52 | 53 | // openssl sha1 -hmac -binary 54 | static string hmac_encode_base64(const string& key, const void* data, size_t size){ 55 | 56 | // SHA1 needed 20 characters. 57 | unsigned int len = 20; 58 | unsigned char result[20]; 59 | 60 | HMAC_CTX* ctx = HMAC_CTX_new(); 61 | HMAC_Init_ex(ctx, key.data(), key.size(), EVP_sha1(), NULL); 62 | HMAC_Update(ctx, (unsigned char*)data, size); 63 | HMAC_Final(ctx, result, &len); 64 | HMAC_CTX_free(ctx); 65 | return base64_encode(result, len); 66 | } 67 | 68 | // echo -en ${_signature} | openssl sha1 -hmac ${s3_secret} -binary | base64 69 | static string minio_hmac_encode( 70 | const string& hash_key, 71 | const string& method, 72 | const string& content_type, 73 | const string& time, 74 | const string& path 75 | ){ 76 | char buffer[1000]; 77 | int result_length = snprintf( 78 | buffer, sizeof(buffer), 79 | "%s\n\n%s\n%s\n%s", 80 | method.c_str(), 81 | content_type.c_str(), 82 | time.c_str(), 83 | path.c_str() 84 | ); 85 | return hmac_encode_base64(hash_key, buffer, result_length); 86 | } 87 | 88 | static string extract_name(const string& response, int begin, int end){ 89 | 90 | int p = response.find("", begin); 91 | if(p == -1 || p >= end) return ""; 92 | p += 6; 93 | 94 | int e = response.find("", p); 95 | if(e == -1 || p >= e) return ""; 96 | return string(response.begin() + p, response.begin() + e); 97 | } 98 | 99 | static vector extract_buckets(const string& response){ 100 | 101 | string bucket_b_token = ""; 102 | string bucket_e_token = ""; 103 | vector names; 104 | int p = response.find(bucket_b_token); 105 | while(p != -1){ 106 | int e = response.find(bucket_e_token, p + bucket_b_token.size()); 107 | if(e == -1) 108 | break; 109 | 110 | names.emplace_back(move(extract_name(response, p, e))); 111 | p = response.find(bucket_b_token, e + bucket_e_token.size()); 112 | } 113 | return names; 114 | } 115 | 116 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 117 | 118 | MinioClient::MinioClient(const string& server, const string& access_key, const string& secret_key, int correction_time) 119 | :server(server), access_key(access_key), secret_key(secret_key), correction_time(correction_time) 120 | {} 121 | 122 | bool MinioClient::upload_file( 123 | const string& remote_path, 124 | const string& file 125 | ){ 126 | const char* content_type = "application/octet-stream"; 127 | auto time = gmtime_now(correction_time); 128 | auto signature = minio_hmac_encode( 129 | secret_key, "PUT", content_type, time, remote_path 130 | ); 131 | 132 | auto http = newHttp(iLogger::format("%s%s", server.c_str(), remote_path.c_str())); 133 | bool success = http 134 | ->add_header(iLogger::format("Date: %s", time.c_str())) 135 | ->add_header(iLogger::format("Content-Type: %s", content_type)) 136 | ->add_header(iLogger::format("Authorization: AWS %s:%s", access_key.c_str(), signature.c_str())) 137 | ->put_file(file); 138 | 139 | if(!success){ 140 | INFOE("post failed: %s\n%s", http->error_message().c_str(), http->response_body().c_str()); 141 | } 142 | return success; 143 | } 144 | 145 | bool MinioClient::upload_filedata( 146 | const string& remote_path, 147 | const string& filedata 148 | ){ 149 | return upload_filedata(remote_path, filedata.data(), filedata.size()); 150 | } 151 | 152 | bool MinioClient::upload_filedata( 153 | const string& remote_path, 154 | const void* file_data, size_t data_size 155 | ){ 156 | const char* content_type = "application/octet-stream"; 157 | auto time = gmtime_now(correction_time); 158 | auto signature = minio_hmac_encode( 159 | secret_key, "PUT", content_type, time, remote_path 160 | ); 161 | 162 | auto http = newHttp(iLogger::format("%s%s", server.c_str(), remote_path.c_str())); 163 | bool success = http 164 | ->add_header(iLogger::format("Date: %s", time.c_str())) 165 | ->add_header(iLogger::format("Content-Type: %s", content_type)) 166 | ->add_header(iLogger::format("Authorization: AWS %s:%s", access_key.c_str(), signature.c_str())) 167 | ->put_body(HttpBodyData(file_data, data_size)); 168 | 169 | if(!success){ 170 | INFOE("post failed: %s\n%s", http->error_message().c_str(), http->response_body().c_str()); 171 | } 172 | return success; 173 | } 174 | 175 | bool MinioClient::make_bucket(const std::string& name){ 176 | 177 | string path = "/" + name; 178 | const char* content_type = "text/plane"; 179 | 180 | auto time = gmtime_now(correction_time); 181 | auto signature = minio_hmac_encode( 182 | secret_key, "PUT", content_type, time, path 183 | ); 184 | 185 | auto http = newHttp(iLogger::format("%s%s", server.c_str(), path.c_str())); 186 | bool success = http 187 | ->add_header(iLogger::format("Date: %s", time.c_str())) 188 | ->add_header(iLogger::format("Content-Type: %s", content_type)) 189 | ->add_header(iLogger::format("Authorization: AWS %s:%s", access_key.c_str(), signature.c_str())) 190 | ->put(); 191 | 192 | if(!success){ 193 | INFOE("post failed: %s\n%s", http->error_message().c_str(), http->response_body().c_str()); 194 | } 195 | return success; 196 | } 197 | 198 | vector MinioClient::get_bucket_list(bool* pointer_success){ 199 | const char* path = "/"; 200 | const char* content_type = "text/plane"; 201 | 202 | auto time = gmtime_now(correction_time); 203 | auto signature = minio_hmac_encode( 204 | secret_key, "GET", content_type, time, path 205 | ); 206 | 207 | auto http = newHttp(iLogger::format("%s%s", server.c_str(), path)); 208 | bool success = http 209 | ->add_header(iLogger::format("Date: %s", time.c_str())) 210 | ->add_header(iLogger::format("Content-Type: %s", content_type)) 211 | ->add_header(iLogger::format("Authorization: AWS %s:%s", access_key.c_str(), signature.c_str())) 212 | ->get(); 213 | 214 | if(pointer_success) 215 | *pointer_success = success; 216 | 217 | if(!success){ 218 | INFOE("post failed: %s\n%s", http->error_message().c_str(), http->response_body().c_str()); 219 | return {}; 220 | } 221 | return extract_buckets(http->response_body()); 222 | } 223 | 224 | string MinioClient::get_file( 225 | const string& remote_path, bool* pointer_success 226 | ){ 227 | const char* content_type = "application/octet-stream"; 228 | auto time = gmtime_now(correction_time); 229 | auto signature = minio_hmac_encode( 230 | secret_key, "GET", content_type, time, remote_path 231 | ); 232 | 233 | auto http = newHttp(iLogger::format("%s%s", server.c_str(), remote_path.c_str())); 234 | bool success = http 235 | ->add_header(iLogger::format("Date: %s", time.c_str())) 236 | ->add_header(iLogger::format("Content-Type: %s", content_type)) 237 | ->add_header(iLogger::format("Authorization: AWS %s:%s", access_key.c_str(), signature.c_str())) 238 | ->get(); 239 | 240 | if(pointer_success) 241 | *pointer_success = success; 242 | 243 | if(!success){ 244 | INFOE("post failed: %s\n%s", http->error_message().c_str(), http->response_body().c_str()); 245 | return ""; 246 | } 247 | return http->response_body(); 248 | } -------------------------------------------------------------------------------- /src/minio_client.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file minio_client.cpp 3 | * @author 手写AI 4 | * 我们的B站:https://space.bilibili.com/1413433465/ 5 | * 我们的博客:http://zifuture.com:8090 6 | * 7 | * @brief 基于CURL提交AWS的访问协议,实现对minio进行操作(创建bucket、查询bucket、上传、下载) 8 | * 被AWS的cpp-sdk的65w个文件吓怕了,就想传个文件咋就这么难 9 | * 10 | * @date 2021年7月28日 17:56:02 11 | * 12 | * 注意,主要分析手段,是使用minio的js-sdk看他提交http时给的参数是什么,进而估计出C++应该怎么写 13 | * minio的js-sdk用的签名方式不同(是AWS4-HMAC-SHA256),如果能完全模拟他,就好了 14 | * 15 | * 参考: 16 | * 1. https://github.com/minio/minio/issues/8136 17 | * 2. https://github.com/kneufeld/minio-put 18 | * 3. https://docs.min.io/docs/javascript-client-quickstart-guide.html 19 | * 4. https://github.com/minio/minio-js/blob/master/src/main/minio.js 20 | */ 21 | 22 | #ifndef MINIO_CLIENT_HPP 23 | #define MINIO_CLIENT_HPP 24 | 25 | 26 | #include 27 | #include 28 | 29 | class MinioClient{ 30 | public: 31 | 32 | /** 33 | * @brief 创建一个minio的客户端 34 | * 这个类实际上没干嘛,仅仅是记录3个字符串避免重复传参数 35 | * 每个函数都是独立运行的,可以允许多线程 36 | * 37 | * @param server 指定服务器地址,例如:http://127.0.0.1:9000,注意不要多斜杠 38 | * @param access_key 指定访问的key,例如:F2IHVVX44WVGYUIA1ESX 39 | * @param secret_key 指定加密的key,例如:UiJuXEG4V6ZLqCZ9ZbD9lqKEG8WwtaKeA3kh7Lui 40 | */ 41 | MinioClient(const std::string& server, const std::string& access_key, const std::string& secret_key, int correction_time = 0); 42 | 43 | 44 | /** 45 | * @brief 上传文件到minio服务器-通过文件路径 46 | * 47 | * @param remote_path 指定远程路径,bucket也包含在内,例如:/test-bucket/wish/wish235.txt 48 | * @param file 指定本地的文件路径,例如:echo.txt 49 | * @return 如果成功返回true,否则返回false并打印消息 50 | */ 51 | bool upload_file(const std::string& remote_path, const std::string& file); 52 | 53 | 54 | /** 55 | * @brief 上传文件到minio服务器-通过文件数据 56 | * 57 | * @param remote_path 指定远程路径,bucket也包含在内,例如:/test-bucket/wish/wish235.txt 58 | * @param file 指定本地的文件路径,例如:echo.txt 59 | * @return 如果成功返回true,否则返回false并打印消息 60 | */ 61 | bool upload_filedata(const std::string& remote_path, const std::string& file_data); 62 | 63 | 64 | /** 65 | * @brief 上传文件到minio服务器-通过文件数据 66 | * 67 | * @param remote_path 指定远程路径,bucket也包含在内,例如:/test-bucket/wish/wish235.txt 68 | * @param file 指定本地的文件路径,例如:echo.txt 69 | * @return 如果成功返回true,否则返回false并打印消息 70 | */ 71 | bool upload_filedata(const std::string& remote_path, const void* file_data, size_t data_size); 72 | 73 | 74 | /** 75 | * @brief 获取服务器bucket列表 76 | * 77 | * @return std::vector 78 | */ 79 | std::vector get_bucket_list(bool* pointer_success = nullptr); 80 | 81 | 82 | /** 83 | * @brief 创建新的bucket,如果已经存在会报错 84 | * 85 | * @param name 指定bucket的名字,例如:test-bucket 86 | * @return true 87 | * @return false 88 | */ 89 | bool make_bucket(const std::string& name); 90 | 91 | 92 | /** 93 | * @brief 下载读取文件数据 94 | * 95 | * @param remote_path 指定远程路径,bucket也包含在内,例如:/test-bucket/wish/wish235.txt 96 | * @return std::string 返回文件数据,string类型打包的,string.data()是指针,string.size()是长度 97 | */ 98 | std::string get_file(const std::string& remote_path, bool* pointer_success=nullptr); 99 | 100 | private: 101 | std::string server; 102 | std::string access_key; 103 | std::string secret_key; 104 | int correction_time = 0; 105 | }; 106 | 107 | #endif // MINIO_CLIENT_HPP 108 | -------------------------------------------------------------------------------- /workspace/echo.txt: -------------------------------------------------------------------------------- 1 | 男儿何不带吴钩,收取关山五十州。 2 | 请君暂上凌烟阁,若个书生万户侯? -------------------------------------------------------------------------------- /workspace/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | host=192.168.16.109:9000 4 | s3_key='F2IHVVX44WVGYUIA1ESX' 5 | s3_secret='UiJuXEG4V6ZLqCZ9ZbD9lqKEG8WwtaKeA3kh7Lui' 6 | 7 | remote_file="/test-bucket/echo.txt" 8 | local_file="workspace/echo.txt" 9 | 10 | content_type="application/octet-stream" 11 | date=`date -R` 12 | _signature="PUT\n\n${content_type}\n${date}\n${remote_file}" 13 | signature=`echo -en ${_signature} | openssl sha1 -hmac ${s3_secret} -binary | base64` 14 | 15 | curl -v -X PUT -T "${local_file}" \ 16 | -H "Host: $host" \ 17 | -H "Date: ${date}" \ 18 | -H "Content-Type: ${content_type}" \ 19 | -H "Authorization: AWS ${s3_key}:${signature}" \ 20 | http://$host${remote_file} --------------------------------------------------------------------------------