# 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) - [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 ``` ## 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`.