From 464fc4da139c258877a3420aa90764271c52f0f0 Mon Sep 17 00:00:00 2001 From: lganzzzo Date: Thu, 25 Jul 2019 14:01:13 +0400 Subject: [PATCH] Tests. Simple And Async Multipart APIs. --- .../web/mime/multipart/InMemoryReader.cpp | 5 +- .../web/mime/multipart/InMemoryReader.hpp | 3 +- .../web/protocol/http/incoming/Response.cpp | 8 +++ .../web/protocol/http/incoming/Response.hpp | 16 ++++++ .../http/incoming/SimpleBodyDecoder.cpp | 2 +- .../protocol/http/outgoing/MultipartBody.cpp | 6 +-- .../protocol/http/outgoing/MultipartBody.hpp | 7 ++- test/oatpp/web/FullAsyncTest.cpp | 49 ++++++++++++++++++ test/oatpp/web/FullTest.cpp | 49 ++++++++++++++++++ test/oatpp/web/app/Client.hpp | 9 ++-- test/oatpp/web/app/Controller.hpp | 51 ++++--------------- test/oatpp/web/app/ControllerAsync.hpp | 37 ++++++-------- 12 files changed, 169 insertions(+), 73 deletions(-) diff --git a/src/oatpp/web/mime/multipart/InMemoryReader.cpp b/src/oatpp/web/mime/multipart/InMemoryReader.cpp index a3a5d8fe..061b33c1 100644 --- a/src/oatpp/web/mime/multipart/InMemoryReader.cpp +++ b/src/oatpp/web/mime/multipart/InMemoryReader.cpp @@ -67,8 +67,9 @@ data::v_io_size InMemoryReader::write(const void *data, data::v_io_size count) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // AsyncInMemoryReader -AsyncInMemoryReader::AsyncInMemoryReader(Multipart* multipart) - : m_parser(multipart->getBoundary(), std::make_shared(multipart)) +AsyncInMemoryReader::AsyncInMemoryReader(const std::shared_ptr& multipart) + : m_parser(multipart->getBoundary(), std::make_shared(multipart.get())) + , m_multipart(multipart) {} oatpp::async::Action AsyncInMemoryReader::writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine, diff --git a/src/oatpp/web/mime/multipart/InMemoryReader.hpp b/src/oatpp/web/mime/multipart/InMemoryReader.hpp index 9681fa45..6bcf9d74 100644 --- a/src/oatpp/web/mime/multipart/InMemoryReader.hpp +++ b/src/oatpp/web/mime/multipart/InMemoryReader.hpp @@ -79,13 +79,14 @@ public: class AsyncInMemoryReader : public oatpp::data::stream::AsyncWriteCallback { private: StatefulParser m_parser; + std::shared_ptr m_multipart; public: /** * Constructor. * @param multipart - Multipart object to save read data to. */ - AsyncInMemoryReader(Multipart* multipart); + AsyncInMemoryReader(const std::shared_ptr& multipart); oatpp::async::Action writeAsyncInline(oatpp::async::AbstractCoroutine* coroutine, oatpp::data::stream::AsyncInlineWriteData& inlineData, diff --git a/src/oatpp/web/protocol/http/incoming/Response.cpp b/src/oatpp/web/protocol/http/incoming/Response.cpp index e8182a2a..b8141b77 100644 --- a/src/oatpp/web/protocol/http/incoming/Response.cpp +++ b/src/oatpp/web/protocol/http/incoming/Response.cpp @@ -66,6 +66,10 @@ std::shared_ptr Response::getBodyDecoder() co return m_bodyDecoder; } +void Response::transferBody(data::stream::WriteCallback* writeCallback) const { + m_bodyDecoder->decode(m_headers, m_bodyStream.get(), writeCallback); +} + void Response::transferBodyToStream(oatpp::data::stream::OutputStream* toStream) const { m_bodyDecoder->decodeToStream(m_headers, m_bodyStream.get(), toStream); } @@ -74,6 +78,10 @@ oatpp::String Response::readBodyToString() const { return m_bodyDecoder->decodeToString(m_headers, m_bodyStream.get()); } +async::CoroutineStarter Response::transferBodyAsync(const std::shared_ptr& writeCallback) const { + return m_bodyDecoder->decodeAsync(m_headers, m_bodyStream, writeCallback); +} + oatpp::async::CoroutineStarter Response::transferBodyToStreamAsync(const std::shared_ptr& toStream) const { return m_bodyDecoder->decodeToStreamAsync(m_headers, m_bodyStream, toStream); } diff --git a/src/oatpp/web/protocol/http/incoming/Response.hpp b/src/oatpp/web/protocol/http/incoming/Response.hpp index 88af3750..84544fd3 100644 --- a/src/oatpp/web/protocol/http/incoming/Response.hpp +++ b/src/oatpp/web/protocol/http/incoming/Response.hpp @@ -113,6 +113,13 @@ public: */ std::shared_ptr getBodyDecoder() const; + /** + * Transfer body.
+ * Read body chunk by chunk and pass chunks to the `writeCallback`. + * @param writeCallback - &id:oatpp::data::stream::WriteCallback;. + */ + void transferBody(data::stream::WriteCallback* writeCallback) const; + /** * Decode and transfer body to toStream. * Use case example - stream huge body directly to file using relatively small buffer. @@ -139,6 +146,15 @@ public: // Async + /** + * Transfer body in Asynchronous manner.
+ * Read body chunk by chunk and pass chunks to the `writeCallback`. + * @param writeCallback - `std::shared_ptr` to &id:oatpp::data::stream::AsyncWriteCallback;. + * @return - &id:oatpp::async::CoroutineStarter;. + */ + async::CoroutineStarter transferBodyAsync(const std::shared_ptr& writeCallback) const; + + /** * Same as &l:Response::readBodyToDto (); but Async. * @param toStream - `std::shared_ptr` to &id:oatpp::data::stream::OutputStream;. diff --git a/src/oatpp/web/protocol/http/incoming/SimpleBodyDecoder.cpp b/src/oatpp/web/protocol/http/incoming/SimpleBodyDecoder.cpp index e179e7c8..7fa6f0f1 100644 --- a/src/oatpp/web/protocol/http/incoming/SimpleBodyDecoder.cpp +++ b/src/oatpp/web/protocol/http/incoming/SimpleBodyDecoder.cpp @@ -119,7 +119,7 @@ oatpp::async::CoroutineStarter SimpleBodyDecoder::doChunkedDecodingAsync(const s const v_int32 MAX_LINE_SIZE = 8; private: std::shared_ptr m_fromStream; - const std::shared_ptr& m_writeCallback; + std::shared_ptr m_writeCallback; std::shared_ptr m_buffer = oatpp::data::buffer::IOBuffer::createShared(); v_int32 m_currLineLength; v_char8 m_lineChar; diff --git a/src/oatpp/web/protocol/http/outgoing/MultipartBody.cpp b/src/oatpp/web/protocol/http/outgoing/MultipartBody.cpp index 720b113f..6995e59c 100644 --- a/src/oatpp/web/protocol/http/outgoing/MultipartBody.cpp +++ b/src/oatpp/web/protocol/http/outgoing/MultipartBody.cpp @@ -141,7 +141,7 @@ oatpp::async::Action MultipartBody::AsyncMultipartReadCallback::readAsyncInline( Action act() override { - if(m_this->m_state == STATE_FINISHED) { + if(m_inlineData->bytesLeft == 0 || m_this->m_state == STATE_FINISHED) { return finish(); } @@ -296,10 +296,10 @@ data::v_io_size MultipartBody::readHeaders(const std::shared_ptr& mul return res; } -MultipartBody::MultipartBody(const std::shared_ptr& multipart) +MultipartBody::MultipartBody(const std::shared_ptr& multipart, data::v_io_size chunkBufferSize) : ChunkedBody(std::make_shared(multipart), std::make_shared(multipart), - 4096) + chunkBufferSize) , m_multipart(multipart) {} diff --git a/src/oatpp/web/protocol/http/outgoing/MultipartBody.hpp b/src/oatpp/web/protocol/http/outgoing/MultipartBody.hpp index ce4fa2b8..30e5e2b4 100644 --- a/src/oatpp/web/protocol/http/outgoing/MultipartBody.hpp +++ b/src/oatpp/web/protocol/http/outgoing/MultipartBody.hpp @@ -122,8 +122,13 @@ public: * Constructor. * @param multipart - multipart object. */ - MultipartBody(const std::shared_ptr& multipart); + /** + * Constructor. + * @param multipart - multipart object. + * @param chunkBufferSize - buffer used for chunks in the `Transfer-Encoding: chunked` body. + */ + MultipartBody(const std::shared_ptr& multipart, data::v_io_size chunkBufferSize = 4096); /** * Declare `Transfer-Encoding: chunked`, `Content-Type: multipart/` header. diff --git a/test/oatpp/web/FullAsyncTest.cpp b/test/oatpp/web/FullAsyncTest.cpp index a248887d..27598b91 100644 --- a/test/oatpp/web/FullAsyncTest.cpp +++ b/test/oatpp/web/FullAsyncTest.cpp @@ -50,6 +50,9 @@ namespace oatpp { namespace test { namespace web { namespace { +typedef oatpp::web::mime::multipart::Multipart Multipart; +typedef oatpp::web::protocol::http::outgoing::MultipartBody MultipartBody; + class TestComponent { private: v_int32 m_port; @@ -113,6 +116,24 @@ public: }; +std::shared_ptr createMultipart(const std::unordered_map& map) { + + auto multipart = std::make_shared("0--qwerty1234--0"); + + for(auto& pair : map) { + + oatpp::web::mime::multipart::Headers partHeaders; + auto part = std::make_shared(partHeaders); + multipart->addPart(part); + part->putHeader("Content-Disposition", "form-data; name=\"" + pair.first + "\""); + part->setDataInfo(std::make_shared(pair.second)); + + } + + return multipart; + +} + } void FullAsyncTest::onRun() { @@ -203,6 +224,34 @@ void FullAsyncTest::onRun() { OATPP_ASSERT(returnedData == data); } + { // Multipart body + + std::unordered_map map; + map["value1"] = "Hello"; + map["value2"] = "World"; + auto multipart = createMultipart(map); + + auto body = std::make_shared(multipart, i + 1); + + auto response = client->multipartTest(i + 1, body); + OATPP_ASSERT(response->getStatusCode() == 200); + + multipart = std::make_shared(response->getHeaders()); + oatpp::web::mime::multipart::InMemoryReader multipartReader(multipart.get()); + response->transferBody(&multipartReader); + + OATPP_ASSERT(multipart->getAllParts().size() == 2); + auto part1 = multipart->getNamedPart("value1"); + auto part2 = multipart->getNamedPart("value2"); + + OATPP_ASSERT(part1); + OATPP_ASSERT(part2); + + OATPP_ASSERT(part1->getInMemoryData() == "Hello"); + OATPP_ASSERT(part2->getInMemoryData() == "World"); + + } + if((i + 1) % iterationsStep == 0) { auto ticks = oatpp::base::Environment::getMicroTickCount() - lastTick; lastTick = oatpp::base::Environment::getMicroTickCount(); diff --git a/test/oatpp/web/FullTest.cpp b/test/oatpp/web/FullTest.cpp index f827891f..536f65b3 100644 --- a/test/oatpp/web/FullTest.cpp +++ b/test/oatpp/web/FullTest.cpp @@ -50,6 +50,9 @@ namespace oatpp { namespace test { namespace web { namespace { +typedef oatpp::web::mime::multipart::Multipart Multipart; +typedef oatpp::web::protocol::http::outgoing::MultipartBody MultipartBody; + class TestComponent { private: v_int32 m_port; @@ -108,6 +111,24 @@ public: }; +std::shared_ptr createMultipart(const std::unordered_map& map) { + + auto multipart = std::make_shared("0--qwerty1234--0"); + + for(auto& pair : map) { + + oatpp::web::mime::multipart::Headers partHeaders; + auto part = std::make_shared(partHeaders); + multipart->addPart(part); + part->putHeader("Content-Disposition", "form-data; name=\"" + pair.first + "\""); + part->setDataInfo(std::make_shared(pair.second)); + + } + + return multipart; + +} + } void FullTest::onRun() { @@ -219,6 +240,34 @@ void FullTest::onRun() { OATPP_ASSERT(returnedData == data); } + { // Multipart body + + std::unordered_map map; + map["value1"] = "Hello"; + map["value2"] = "World"; + auto multipart = createMultipart(map); + + auto body = std::make_shared(multipart, i + 1); + + auto response = client->multipartTest(i + 1, body); + OATPP_ASSERT(response->getStatusCode() == 200); + + multipart = std::make_shared(response->getHeaders()); + oatpp::web::mime::multipart::InMemoryReader multipartReader(multipart.get()); + response->transferBody(&multipartReader); + + OATPP_ASSERT(multipart->getAllParts().size() == 2); + auto part1 = multipart->getNamedPart("value1"); + auto part2 = multipart->getNamedPart("value2"); + + OATPP_ASSERT(part1); + OATPP_ASSERT(part2); + + OATPP_ASSERT(part1->getInMemoryData() == "Hello"); + OATPP_ASSERT(part2->getInMemoryData() == "World"); + + } + if((i + 1) % iterationsStep == 0) { auto ticks = oatpp::base::Environment::getMicroTickCount() - lastTick; lastTick = oatpp::base::Environment::getMicroTickCount(); diff --git a/test/oatpp/web/app/Client.hpp b/test/oatpp/web/app/Client.hpp index fb0a074d..50cf5bf9 100644 --- a/test/oatpp/web/app/Client.hpp +++ b/test/oatpp/web/app/Client.hpp @@ -25,12 +25,17 @@ #ifndef oatpp_test_web_app_Client_hpp #define oatpp_test_web_app_Client_hpp +#include "oatpp/web/protocol/http/outgoing/MultipartBody.hpp" #include "oatpp/web/client/ApiClient.hpp" #include "oatpp/core/macro/codegen.hpp" namespace oatpp { namespace test { namespace web { namespace app { class Client : public oatpp::web::client::ApiClient { +public: + typedef oatpp::web::protocol::http::outgoing::MultipartBody MultipartBody; +public: + #include OATPP_CODEGEN_BEGIN(ApiClient) API_CLIENT_INIT(Client) @@ -42,11 +47,9 @@ class Client : public oatpp::web::client::ApiClient { API_CALL("GET", "headers", getWithHeaders, HEADER(String, param, "X-TEST-HEADER")) API_CALL("POST", "body", postBody, BODY_STRING(String, body)) API_CALL("POST", "echo", echoBody, BODY_STRING(String, body)) - API_CALL("GET", "header-value-set", headerValueSet, HEADER(String, valueSet, "X-VALUE-SET")) - API_CALL("GET", "chunked/{text-value}/{num-iterations}", getChunked, PATH(String, text, "text-value"), PATH(Int32, numIterations, "num-iterations")) - + API_CALL("POST", "test/multipart/{chunk-size}", multipartTest, PATH(Int32, chunkSize, "chunk-size"), BODY(std::shared_ptr, body)) API_CALL_ASYNC("GET", "/", getRootAsync) API_CALL_ASYNC("GET", "/", getRootAsyncWithCKA, HEADER(String, connection, "Connection")) diff --git a/test/oatpp/web/app/Controller.hpp b/test/oatpp/web/app/Controller.hpp index 0d045c29..f6ae39c3 100644 --- a/test/oatpp/web/app/Controller.hpp +++ b/test/oatpp/web/app/Controller.hpp @@ -156,56 +156,25 @@ public: ENDPOINT("GET", "chunked/{text-value}/{num-iterations}", chunked, PATH(String, text, "text-value"), PATH(Int32, numIterations, "num-iterations"), - REQUEST(std::shared_ptr, request)) { + REQUEST(std::shared_ptr, request)) + { auto body = std::make_shared (std::make_shared(text, numIterations->getValue()), nullptr, 1024); return OutgoingResponse::createShared(Status::CODE_200, body); } - ENDPOINT("POST", "test/multipart", multipartTest, REQUEST(std::shared_ptr, request)) { + ENDPOINT("POST", "test/multipart/{chunk-size}", multipartTest, + PATH(Int32, chunkSize, "chunk-size"), + REQUEST(std::shared_ptr, request)) + { - /* - oatpp::web::mime::multipart::Multipart multipart(request->getHeaders()); - oatpp::web::mime::multipart::InMemoryReader multipartReader(&multipart); + auto multipart = std::make_shared(request->getHeaders()); + oatpp::web::mime::multipart::InMemoryReader multipartReader(multipart.get()); request->transferBody(&multipartReader); - for(auto& part : multipart.getAllParts()) { - OATPP_LOGD("multipart", "name='%s', value='%s'", part->getName()->getData(), part->getInMemoryData()->getData()); - } - */ + auto responseBody = std::make_shared(multipart, chunkSize->getValue()); - oatpp::data::stream::ChunkedBuffer stream; - request->transferBodyToStream(&stream); - - return createResponse(Status::CODE_200, stream.toString()); - - } - - ENDPOINT("GET", "test/multipart", multipartGetTest) { - - auto multipart = std::make_shared("0--qwerty1234--0"); - - { - oatpp::web::mime::multipart::Headers partHeaders; - auto part = std::make_shared(partHeaders); - multipart->addPart(part); - part->putHeader("Content-Disposition", "form-data; name=\"part1\""); - oatpp::String data = "Hello"; - part->setDataInfo(std::make_shared(data)); - } - - { - oatpp::web::mime::multipart::Headers partHeaders; - auto part = std::make_shared(partHeaders); - multipart->addPart(part); - part->putHeader("Content-Disposition", "form-data; filename=\"file2.txt\""); - oatpp::String data = "World"; - part->setDataInfo(std::make_shared(data)); - } - - auto body = std::make_shared(multipart); - - return OutgoingResponse::createShared(Status::CODE_200, body); + return OutgoingResponse::createShared(Status::CODE_200, responseBody); } diff --git a/test/oatpp/web/app/ControllerAsync.hpp b/test/oatpp/web/app/ControllerAsync.hpp index 9a500030..6cb25b1e 100644 --- a/test/oatpp/web/app/ControllerAsync.hpp +++ b/test/oatpp/web/app/ControllerAsync.hpp @@ -27,6 +27,8 @@ #include "./DTOs.hpp" +#include "oatpp/web/mime/multipart/InMemoryReader.hpp" + #include "oatpp/web/protocol/http/outgoing/MultipartBody.hpp" #include "oatpp/web/protocol/http/outgoing/ChunkedBody.hpp" @@ -169,35 +171,28 @@ public: }; - ENDPOINT_ASYNC("GET", "test/multipart", MultipartGetTest) { + ENDPOINT_ASYNC("POST", "test/multipart/{chunk-size}", MultipartTest) { - ENDPOINT_ASYNC_INIT(MultipartGetTest) + ENDPOINT_ASYNC_INIT(MultipartTest) + + v_int32 m_chunkSize; + std::shared_ptr m_multipart; Action act() override { - auto multipart = std::make_shared("0--qwerty1234--0"); + m_chunkSize = oatpp::utils::conversion::strToInt32(request->getPathVariable("chunk-size")->c_str()); - { - oatpp::web::mime::multipart::Headers partHeaders; - auto part = std::make_shared(partHeaders); - multipart->addPart(part); - part->putHeader("Content-Disposition", "form-data; name=\"part1\""); -// oatpp::String data = ""; -// part->setDataInfo(std::make_shared(data)); - } + m_multipart = std::make_shared(request->getHeaders()); + auto multipartReader = std::make_shared(m_multipart); - { - oatpp::web::mime::multipart::Headers partHeaders; - auto part = std::make_shared(partHeaders); - multipart->addPart(part); - part->putHeader("Content-Disposition", "form-data; filename=\"file2.txt\""); - oatpp::String data = "World"; - part->setDataInfo(std::make_shared(data)); - } + return request->transferBodyAsync(multipartReader).next(yieldTo(&MultipartTest::respond)); - auto body = std::make_shared(multipart); + } - return _return(OutgoingResponse::createShared(Status::CODE_200, body)); + Action respond() { + + auto responseBody = std::make_shared(m_multipart, m_chunkSize); + return _return(OutgoingResponse::createShared(Status::CODE_200, responseBody)); }