├── .gitmodules ├── 1.jpg ├── LICENSE ├── Makefile ├── README.md ├── multipart_parser.cpp ├── multipart_parser.h ├── parser_test.cpp └── run.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cpprestsdk"] 2 | path = cpprestsdk 3 | url = https://github.com/Microsoft/cpprestsdk.git 4 | -------------------------------------------------------------------------------- /1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndsonYe/MultipartEncoder/ab4182accd6e69128dd3cbc20c1c08f921b86be0/1.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 YangangYe 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 | PROJECT := multipart_parser_test 2 | CXX := g++ 3 | 4 | SRC_DIR := . 5 | SRC := $(wildcard $(SRC_DIR)/*.cpp) 6 | 7 | HEADERS := $(wildcard $(SRC_DIR)/*.h) 8 | 9 | #CXXFLAGS := -Wall -std=c++11 10 | CXXFLAGS := -Wall -Werror -std=c++11 11 | CXXFLAGS += -I./cpprestsdk/Release/include 12 | ifdef DEBUG 13 | CXXFLAGS += -g -DDEBUG 14 | FLAVOR := debug 15 | else 16 | CXXFLAGS += -O3 17 | FLAVOR := release 18 | endif 19 | 20 | BUILD_DIR := build/$(FLAVOR) 21 | OBJ_DIR := obj/$(FLAVOR)/$(PROJECT) 22 | BIN := $(BUILD_DIR)/$(PROJECT) 23 | 24 | 25 | LDFLAGS := -L/usr/local/lib \ 26 | -L./cpprestsdk/Release/build/Binaries \ 27 | -lcpprest -lboost_system -lssl -lcrypto -pthread 28 | 29 | SRC_OBJS := $(SRC:%.cpp=$(OBJ_DIR)/%.o) 30 | 31 | all: $(BIN) 32 | 33 | $(BIN): $(SRC_OBJS) 34 | mkdir -p $(dir $@) 35 | $(CXX) $^ $(CPPFLAGS) $(LDFLAGS) -o $@ 36 | 37 | $(SRC_OBJS): $(OBJ_DIR)/%.o : %.cpp 38 | mkdir -p $(dir $@) 39 | $(CXX) $(CXXFLAGS) -c $(CPPFLAGS) $< -o $@ 40 | 41 | 42 | .PHONY: clean 43 | clean: 44 | -rm -r $(OBJ_DIR) 45 | -rm $(BIN) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultipartEncoder: A C++ implementation of encoding multipart/form-data 2 | 3 | You may find the asynchronous http-client, i.e. [cpprestsdk](https://github.com/Microsoft/cpprestsdk), does not support posting a multipart/form-data request. This MultipartEncoder is a work around to generate the body content of multipart/form-data format. So that then you can use a cpp HTTP-client, which is not limited to cpprestsdk, to post a multipart/form-data request by setting the encoded body content. 4 | 5 | ## Build & Run 6 | 7 | 1. Clone the MultipartEncoder repository 8 | ```Shell 9 | # Make sure to clone with --recursive 10 | git clone --recursive https://github.com/AndsonYe/MultipartEncoder.git 11 | ``` 12 | 13 | 2. If not cloned with --recursive, you need to manually get the cpprestsdk submodle 14 | 15 | *Ignore this step if you followed step 1 above.* 16 | 17 | ```Shell 18 | git submodule update --init --recursive 19 | ``` 20 | 21 | 3. Install dependencies required by cpprestsdk 22 | ```Shell 23 | sudo apt-get install g++ git make zlib1g-dev libboost-all-dev libssl-dev cmake 24 | ``` 25 | 26 | 4. Build cpprestsdk. Suppose the directory you cloned MultipartEncoder into is `MultipartEncoder_ROOT` 27 | ```Shell 28 | cd $MultipartEncoder_ROOT/cpprestsdk/Release 29 | mkdir build 30 | cd build 31 | cmake .. 32 | make -j$(nproc) 33 | ``` 34 | 35 | 5. Build MultipartEncoder sample 36 | ```Shell 37 | cd $MultipartEncoder_ROOT 38 | make 39 | ``` 40 | 6. Run the sample 41 | ```Shell 42 | ./run.sh 43 | ``` 44 | 45 | The response is writtern in file $MultipartEncoder/results 46 | 47 | ## Usage 48 | 49 | ```cpp 50 | MultipartParser parser; //Create parser instance; 51 | parser.AddParameter(key, value); //Add text parameters using AddParameter 52 | parser.AddFile(key, file_path); //Add file content using AddFile 53 | std::string boundary = parser.boundary(); //Get the boundary generated by parser, each parser has its unique boundary, this boundary should be set in the HTTP request's header 54 | std::string body = parser.GenBodyContent(); //Get the encoded multipart/form-data body content 55 | ``` 56 | Then you can use `boundary` and `body` in any HTTP clients. 57 | 58 | Check the [parser_test.cpp](https://github.com/AndsonYe/MultipartEncoder/blob/master/parser_test.cpp) for complete example. 59 | -------------------------------------------------------------------------------- /multipart_parser.cpp: -------------------------------------------------------------------------------- 1 | #include "multipart_parser.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace web{ 9 | namespace http{ 10 | 11 | const std::string MultipartParser::boundary_prefix_("----CppRestSdkClient"); 12 | const std::string MultipartParser::rand_chars_("0123456789" 13 | "abcdefghijklmnopqrstuvwxyz" 14 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 15 | MultipartParser::MultipartParser() 16 | { 17 | int i = 0; 18 | int len = rand_chars_.size(); 19 | boundary_ = boundary_prefix_; 20 | while(i < 16) 21 | { 22 | int idx = rand() % len; 23 | boundary_.push_back(rand_chars_[idx]); 24 | ++i; 25 | } 26 | } 27 | 28 | const std::string &MultipartParser::GenBodyContent() 29 | { 30 | std::vector > futures; 31 | body_content_.clear(); 32 | for(auto &file:files_) 33 | { 34 | std::future content_futures = std::async(std::launch::async, [&file]() 35 | { 36 | std::ifstream ifile(file.second, std::ios::binary | std::ios::ate); 37 | std::streamsize size = ifile.tellg(); 38 | ifile.seekg(0, std::ios::beg); 39 | char *buff = new char[size]; 40 | ifile.read(buff, size); 41 | ifile.close(); 42 | std::string ret(buff, size); 43 | delete[] buff; 44 | return ret; 45 | }); 46 | futures.push_back(std::move(content_futures)); 47 | } 48 | 49 | for(auto ¶m:params_) 50 | { 51 | body_content_ += "\r\n--"; 52 | body_content_ += boundary_; 53 | body_content_ += "\r\nContent-Disposition: form-data; name=\""; 54 | body_content_ += param.first; 55 | body_content_ += "\"\r\n\r\n"; 56 | body_content_ += param.second; 57 | } 58 | 59 | for(size_t i = 0; i < files_.size(); ++i) 60 | { 61 | std::string filename; 62 | std::string content_type; 63 | std::string file_content = futures[i].get(); 64 | _get_file_name_type(files_[i].second, &filename, &content_type); 65 | body_content_ += "\r\n--"; 66 | body_content_ += boundary_; 67 | body_content_ += "\r\nContent-Disposition: form-data; name=\""; 68 | body_content_ += files_[i].first; 69 | body_content_ += "\"; filename=\""; 70 | body_content_ += filename; 71 | body_content_ += "\"\r\nContent-Type: "; 72 | body_content_ += content_type; 73 | body_content_ += "\r\n\r\n"; 74 | body_content_ += file_content; 75 | } 76 | body_content_ += "\r\n--"; 77 | body_content_ += boundary_; 78 | body_content_ += "--\r\n"; 79 | return body_content_; 80 | } 81 | 82 | void MultipartParser::_get_file_name_type(const std::string &file_path, std::string *filename, std::string *content_type) 83 | { 84 | if (filename == NULL || content_type == NULL) return; 85 | 86 | size_t last_spliter = file_path.find_last_of("/\\"); 87 | *filename = file_path.substr(last_spliter + 1); 88 | size_t dot_pos = filename->find_last_of("."); 89 | if (dot_pos == std::string::npos) 90 | { 91 | *content_type = "application/octet-stream"; 92 | return; 93 | } 94 | std::string ext = filename->substr(dot_pos + 1); 95 | std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); 96 | if (ext == "jpg" || ext == "jpeg") 97 | { 98 | *content_type = "image/jpeg"; 99 | return; 100 | } 101 | if (ext == "txt" || ext == "log") 102 | { 103 | *content_type = "text/plain"; 104 | return; 105 | } 106 | *content_type = "application/octet-stream"; 107 | return; 108 | } 109 | 110 | } //namespace web::http 111 | } //namespace web 112 | 113 | -------------------------------------------------------------------------------- /multipart_parser.h: -------------------------------------------------------------------------------- 1 | /********************************************************************************* 2 | * File Name : gen_multipart.h 3 | * Created By : Ye Yangang 4 | * Creation Date : [2017-02-20 16:50] 5 | * Last Modified : [AUTO_UPDATE_BEFORE_SAVE] 6 | * Description : Generate multipart/form-data POST body 7 | **********************************************************************************/ 8 | 9 | #ifndef GEN_MULTIPART_H_ 10 | #define GEN_MULTIPART_H_ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | namespace web{ 17 | namespace http{ 18 | 19 | class MultipartParser 20 | { 21 | public: 22 | MultipartParser(); 23 | inline const std::string &body_content() 24 | { 25 | return body_content_; 26 | } 27 | inline const std::string &boundary() 28 | { 29 | return boundary_; 30 | } 31 | inline void AddParameter(const std::string &name, const std::string &value) 32 | { 33 | params_.push_back(std::move(std::pair(name, value))); 34 | } 35 | inline void AddFile(const std::string &name, const std::string &value) 36 | { 37 | files_.push_back(std::move(std::pair(name, value))); 38 | } 39 | const std::string &GenBodyContent(); 40 | 41 | private: 42 | void _get_file_name_type(const std::string &file_path, std::string *filenae, std::string *content_type); 43 | private: 44 | static const std::string boundary_prefix_; 45 | static const std::string rand_chars_; 46 | std::string boundary_; 47 | std::string body_content_; 48 | std::vector > params_; 49 | std::vector > files_; 50 | }; 51 | 52 | } //namespace web::http 53 | } //namespace web 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /parser_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "multipart_parser.h" 8 | 9 | using namespace utility; // Common utilities like string conversions 10 | using namespace web; // Common features like URIs. 11 | using namespace web::http; // Common HTTP functionality 12 | using namespace web::http::client; // HTTP client features 13 | using namespace concurrency::streams; // Asynchronous streams 14 | 15 | int main(int argc, char* argv[]) 16 | { 17 | auto fileStream = std::make_shared(); 18 | 19 | // Open stream to output file. 20 | pplx::task requestTask = fstream::open_ostream(U("results")).then([=](ostream outFile) 21 | { 22 | *fileStream = outFile; 23 | 24 | //Use MultipartParser to get the encoded body content and boundary 25 | MultipartParser parser; 26 | parser.AddParameter("Filename", "1.jpg"); 27 | parser.AddFile("file", "1.jpg"); 28 | std::string boundary = parser.boundary(); 29 | std::string body = parser.GenBodyContent(); 30 | std::cout << body << std::endl; 31 | 32 | //Set up http client and request 33 | http_request req; 34 | http_client client(U("http://www.filedropper.com/index.php?xml=true")); 35 | req.set_method(web::http::methods::POST); 36 | req.set_body(body, "multipart/form-data; boundary=" + boundary); 37 | return client.request(req); 38 | }) 39 | .then([=](pplx::task response_task) 40 | { 41 | http_response response = response_task.get(); 42 | return response.body().read_to_end(fileStream->streambuf()); 43 | }) 44 | .then([=](size_t) 45 | { 46 | return fileStream->close(); 47 | }); 48 | 49 | // Wait for all the outstanding I/O to complete and handle any exceptions 50 | try 51 | { 52 | requestTask.wait(); 53 | } 54 | catch (const std::exception &e) 55 | { 56 | printf("Error exception:%s\n", e.what()); 57 | } 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export LD_LIBRARY_PATH=./cpprestsdk/Release/build/Binaries:$LD_LIBRARY_PATH 4 | 5 | ./build/release/multipart_parser_test 6 | --------------------------------------------------------------------------------