# Oat++ 1.3.0 Previous release - [1.2.5](1.2.5.md) Feel free to ask questions - [Chat on Gitter!](https://gitter.im/oatpp-framework/Lobby) Contents: - [The New oatpp::String](#the-new-oatppstring) - [ConnectionPool::get() Timeout](#connectionpoolget-timeout) - [JSON Serializer Escape Flags](#json-serializer-escape-flags) - [Headers Stored In unordered_multimap](#headers-stored-in-unordered_multimap) - [QueryParameters Stored In unordered_multimap](#queryparameters-stored-in-unordered_multimap) - [Polymorphic DTO_FIELD](#polymorphic-dto_field) - [ConnectionMonitor](#connectionmonitor) - [Request Data Bundle](#request-data-bundle) - [ConnectionProviderSwitch](#connectionproviderswitch) - [Proper Server Stoppage](#proper-server-stoppage) - [TemporaryFile](#temporaryfile) - [Better Multipart](#better-multipart) - [Response::getBody()](#responsegetbody) - [data::stream::FIFOStream](#datastreamfifostream) - [data::stream::BufferedInputStream](#datastreambufferedinputstream) - [oatpp::parser::json::mapping::Serializer::Config::alwaysIncludeRequired](#oatppparserjsonmappingserializerconfigalwaysincluderequired) ## The New oatpp::String Now it's much easier to use `oatpp::String` since `oatpp::String` is now wrapper over `std::string` ```cpp { std::string s1 = Hello; oatpp::String s2 = s1; } { oatpp::String s1 = "Hello"; std::string s2 = *s1; // *s1 returns a reference to the internal std::string object } { oatpp::String s1 = "Hello"; std::string s2 = s1; // implicit cast } { oatpp::String s1 = nullptr; std::string s2 = s1; // implicit cast from null-value throws runtime_error } { oatpp::String s1 = "Hello"; bool b = s1 == "Hello"; // compare s1 with const char* assert(b); } { oatpp::String s1 = "Hello"; std::stringg s2 = "Hello"; bool b = s1 == s2; // compare s1 with std::string assert(b); } { oatpp::String s1 = "Hello"; std::string s2 = "World"; oatpp::String s3 = s1 + " " + s2; // concat oatpp::String with const char* and std::string directly OATPP_LOGD("TEST", "str='%s'", s3->c_str()) // prints 'Hello World' } { oatpp::String s1 = nullptr; oatpp::String s2 = "hello"; OATPP_ASSERT(s1.getValue("default") == "default") OATPP_ASSERT(s2.getValue("default") == "hello") } ``` ## ConnectionPool::get() Timeout [#408](https://github.com/oatpp/oatpp/issues/408) ```cpp { auto connectionProvider = oatpp::network::tcp::client::ConnectionProvider::createShared({"httpbin.org", 80}); auto pool = oatpp::network::ClientConnectionPool::createShared(connectionProvider, 1, std::chrono::seconds(10), std::chrono::seconds(5)); OATPP_LOGD("TEST", "start") auto c1 = pool->get(); //<--- this one will succeed OATPP_LOGD("TEST", "c1=%llu", c1.get()) auto c2 = pool->get(); //<--- this one will fail in 5 sec. Since Max-Resources is 1, Pool timeout is 5 sec. And c1 is not freed. OATPP_LOGD("TEST", "c2=%llu", c2.get()) } ``` Output: ``` D |2021-08-04 01:32:56 1628029976986744| TEST:start D |2021-08-04 01:32:57 1628029977126940| TEST:c1=140716915331208 D |2021-08-04 01:33:02 1628029982128324| TEST:c2=0 ``` ## JSON Serializer Escape Flags [#381](https://github.com/oatpp/oatpp/issues/381) Now you can control if solidus is escaped or not. ### Default Behavior ```cpp oatpp::parser::json::mapping::ObjectMapper mapper; // mapper.getSerializer()->getConfig()->escapeFlags = 0; // by default FLAG_ESCAPE_SOLIDUS is ON auto res = mapper.writeToString(oatpp::String("https://oatpp.io/")); OATPP_LOGD("TEST", "res='%s'", res->c_str()) ``` Output: ``` res='"https:\/\/oatpp.io\/"' # by default, solidus is escaped ``` ### Clear Escape Flags ```cpp oatpp::parser::json::mapping::ObjectMapper mapper; mapper.getSerializer()->getConfig()->escapeFlags = 0; auto res = mapper.writeToString(oatpp::String("https://oatpp.io/")); OATPP_LOGD("TEST", "res='%s'", res->c_str()) ``` Output: ``` res='"https://oatpp.io/"' # solidus isn't escaped ``` ## Headers Stored In unordered_multimap Headers are now stored using [std::unordered_multimap](https://en.cppreference.com/w/cpp/container/unordered_multimap). Put multiple headers: ```cpp auto response = createResponse(Status::CODE_200, ""); response->putHeader("Set-Cookie", "..."); response->putHeader("Set-Cookie", "..."); return response; ``` Log all "Set-Cookie" headers: ```cpp const auto& map = headers.getAll(); auto bucket = map.bucket("Set-Cookie"); auto bucketBegin = map.begin(bucket); auto bucketEnd = map.end(bucket); for(auto it = bucketBegin; it != bucketEnd; it ++) { oatpp::String value = it->second.toString(); OATPP_LOGD("Header", "Set-Cookie: %s", value->c_str()) } ``` ## QueryParameters Stored In unordered_multimap QueryParameters are now stored using [std::unordered_multimap](https://en.cppreference.com/w/cpp/container/unordered_multimap). Log all entries of "userId" query parameter: ```cpp const auto& map = request->getQueryParameters().getAll(); auto bucket = map.bucket("userId"); auto bucketBegin = map.begin(bucket); auto bucketEnd = map.end(bucket); for(auto it = bucketBegin; it != bucketEnd; it ++) { oatpp::String value = it->second.toString(); OATPP_LOGD("QueryParameter", "userId: %s", value->c_str()) } ``` ## Polymorphic DTO_FIELD Now, when used inside of a DTO, we can specify exact types that `oatpp::Any` can store by specifying `DTO_FIELD_TYPE_SELECTOR`: ```cpp /* Possible type of a DTO_FIELD */ class ClassA : public oatpp::DTO { DTO_INIT(ClassA, DTO) DTO_FIELD(String, value); }; /* Possible type of a DTO_FIELD */ class ClassB : public oatpp::DTO { DTO_INIT(ClassB, DTO) DTO_FIELD(Vector, values); }; /* enum of possible DTO_FIELD types */ ENUM(ClassType, v_int32, VALUE(CLASS_TYPE_A, 0), VALUE(CLASS_TYPE_B, 1) ) /* our DTO */ class ResponseDto : public oatpp::DTO { DTO_INIT(ResponseDto, DTO) /* type control field */ DTO_FIELD(Enum::AsString, payloadType); /* polymorphic field */ DTO_FIELD(Any, payload); /* type selector */ DTO_FIELD_TYPE_SELECTOR(payload) { if(!payloadType) return Void::Class::getType(); switch (*payloadType) { case ClassType::CLASS_TYPE_A: return Object::Class::getType(); case ClassType::CLASS_TYPE_B: return Object::Class::getType(); } } }; ... /* send polymorphic payload to client */ ENDPOINT("GET", "payload", getPayload) { auto payload = ClassB::createShared(); payload->values = {"value1", "value2", "value3"}; auto r = ResponseDto::createShared(); r->payloadType = ClassType::CLASS_TYPE_B; r->payload = payload; return createDtoResponse(Status::CODE_200, r); } /* receive polymorphic payload from client */ ENDPOINT("POST", "payload", postPayload, BODY_DTO(oatpp::Object, r)) { /* check type-control field and retrieve value of the corresponding type */ if(r->payloadType == ClassType::CLASS_TYPE_B) { auto payload = r->payload.retrieve>(); for(auto& value : *payload->values) { OATPP_LOGD("VALUE", "%s", value->c_str()) } } return createResponse(Status::CODE_200, "OK"); } ``` ## ConnectionMonitor `oatpp::network::monitor::ConnectionMonitor` is a middleman who's able to monitor provided connections and close those ones that not satisfy selected rules. ```cpp OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionProvider)([] { auto connectionProvider = oatpp::network::tcp::server::ConnectionProvider::createShared({"0.0.0.0", 8000, oatpp::network::Address::IP_4}); auto monitor = std::make_shared(connectionProvider); /* close all connections that stay opened for more than 120 seconds */ monitor->addMetricsChecker( std::make_shared( std::chrono::seconds(120) ) ); /* close all connections that have had no successful reads and writes for longer than 5 seconds */ monitor->addMetricsChecker( std::make_shared( std::chrono::seconds(5), std::chrono::seconds(5), ) ); return monitor; }()); ``` **Note:** `ConnectionMonitor` also works with `ClientConnectionProvider` as well. ## Request Data Bundle Now there is a data bundle associated with the Request and the Response which makes it easy to pass data through middleware interceptors and endpoints. Example: ```cpp class MyAuthInterceptor : public oatpp::web::server::interceptor::RequestInterceptor { public: std::shared_ptr intercept(const std::shared_ptr& request) override { /* authorize request and get auth data */ oatpp::Object authData = authorize(request); if(!authData) { return OutgoingResponse::createShared(Status::CODE_401, nullptr); } /* put auth data to bundle for later use at an endpoint */ request->putBundleData("auth", authData); return nullptr; // continue processing } }; ... ENDPOINT("GET", "videos/{videoId}", getVideoById, PATH(String, videoId), BUNDLE(oatpp::Object, authData, "auth")) { ... } ``` ## ConnectionProviderSwitch [#483](https://github.com/oatpp/oatpp/issues/483) `oatpp::network::ConnectionProviderSwitch` can be used to change connection providers on the go, ex.: when you want to reload an SSL certificate without stopping the server. ```cpp /* create server connection provider component */ /* use ConnectionProviderSwitch instead of a regular ServerConnectionProvider */ OATPP_CREATE_COMPONENT(std::shared_ptr, serverConnectionProvider)([this] { /* create SSL provider */ auto sslProvider = oatpp::libressl::server::ConnectionProvider::createShared(...); /* create oatpp::network::ConnectionProviderSwitch*/ return std::make_shared(sslProvider /* current provider */); }()); ... void reloadCert() { /* get server connection provider component */ OATPP_COMPONENT(std::shared_ptr, providerSwitch); /* create new SSL provider with new cert */ auto sslProvider = oatpp::libressl::server::ConnectionProvider::createShared(...); /* set new provider */ providerSwitch->resetProvider(sslProvider); } ``` Additionally, resource invalidation is no longer supported by ConnectionProvider. Please use either ResourceHandleTemplate::invalidate() or Invalidator::invalidate(resource) directly. ## Proper Server Stoppage Fix to [#476](https://github.com/oatpp/oatpp/issues/476), [#269](https://github.com/oatpp/oatpp/issues/269) Now call to `HttpConnectionHandler::stop()`, `AsyncHttpConnectionHandler::stop()` will shutdown all opened connections and will wait until all request handlers exit. ## TemporaryFile Introduce `oatpp::data::resource::TemporaryFile`. Use-case: Temporary file resolves concurrency issues during file uploads. Also, a temporary file ensures that partially uploaded (due to errors/exceptions) resources will be automatically deleted at the end of the block. ```cpp #include "oatpp/core/data/resource/TemporaryFile.hpp" ... ENDPOINT("POST", "/upload", upload, REQUEST(std::shared_ptr, request)) { /* create random file in '/tmp' folder */ oatpp::data::resource::TemporaryFile tmp("/tmp"); /* transfer body to temporary file */ request->transferBody(tmp.openOutputStream()); /* move file to permanent storage */ OATPP_ASSERT_HTTP(tmp.moveFile("/path/to/permanent/storage/avatar.png"), Status::CODE_500, "Failed to save file") /* return 200 */ return createResponse(Status::CODE_200, "OK"); } ``` ## Better Multipart Multipart API has been changed and improved. Now it's possible to upload multiple files using `TemporaryFile` and keep track of all parts and their corresponding data resources. ```cpp #include "oatpp/web/mime/multipart/TemporaryFileProvider.hpp" #include "oatpp/web/mime/multipart/Reader.hpp" #include "oatpp/web/mime/multipart/PartList.hpp" ... namespace multipart = oatpp::web::mime::multipart; ... ENDPOINT("POST", "upload", upload, REQUEST(std::shared_ptr, request)) { /* create multipart object */ multipart::PartList multipart(request->getHeaders()); /* create multipart reader */ multipart::Reader multipartReader(&multipart); /* setup reader to stream parts to a temporary files by default */ multipartReader.setDefaultPartReader(multipart::createTemporaryFilePartReader("/tmp" /* /tmp directory */)); /* upload multipart data */ request->transferBody(&multipartReader); /* list all parts and locations to corresponding temporary files */ auto parts = multipart.getAllParts(); for(auto& p : parts) { OATPP_LOGD("part", "name=%s, location=%s", p->getName()->c_str(), p->getPayload()->getLocation()->c_str()) } /* return 200 */ return createResponse(Status::CODE_200, "OK"); } ``` ## Response::getBody() `oatpp::web::protocol::http::outgoing::Response` has a new method `getBody()` to retrieve the body of the response. This is handy for response interceptors. ## data::stream::FIFOStream The new `FIFOStream` stream is a buffered [`InputStream`](https://oatpp.io/api/latest/oatpp/core/data/stream/Stream/#inputstream) with an [`WriteCallback`](https://oatpp.io/api/latest/oatpp/core/data/stream/Stream/#writecallback). Check the corresponding documentation on how to use these interfaces. Instead of using a static buffer like `BufferInputStream` it is build upon `data::buffer::FIFOBuffer` and is able to dynamically grow when data is written to it that would surpass its capacity. It is especially useful if you need to buffer data from a stream upfront or have multiple data sources that should be buffered in a single stream. However, it is not synchronized, so be careful when using `FIFOStream` in a multithreaded manner. You need to implement your own locking. ## data::stream::BufferedInputStream `FIFOStream` also introduced a new interface [`BufferedInputStream`](https://oatpp.io/api/latest/oatpp/core/data/stream/Stream/#bufferedinputstream) which unifies the buffered-stream-interface all existing buffered streams (`InputStreamBufferedProxy`, `BufferInputStream`, `FIFOStream`) to allow for generalisation. ## oatpp::parser::json::mapping::Serializer::Config::alwaysIncludeRequired If `oatpp::parser::json::mapping::Serializer::Config::includeNullFields == false` there might still be the requirement to include some fields even if they are `nullptr`, because they are required by the deserializing end. Consider the following DTO and endpoint-snippet. ```c++ class StatusDto : public oatpp::DTO { DTO_INIT(StatusDto, DTO) DTO_FIELD_INFO(status) { info->required = true; } DTO_FIELD(String, status); DTO_FIELD(Int32, code); DTO_FIELD(String, message); }; // endpoint code: ENDPOINT("GET", "/status", status) { auto dto = StatusDto::createShared(); dto->code = 200; return createDtoResponse(Status::CODE_200, dto); } ``` With a serializer with its config set to `Serializer::Config::includeNullFields = false`, the snippet would just yield `{"code":200}`. However, `status` is a required field. Now, one can set `Serializer::Config::alwaysIncludeRequired = true`. With `alwaysIncludeRequired == true`, the same snippet would yield `{"status":null,"code":200}`, even with `includeNullFields == false`.