Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
WebService/Sources/WebServer.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include "Stroika/Foundation/Characters/FloatConversion.h"
7#include "Stroika/Foundation/Characters/String2Int.h"
9#include "Stroika/Foundation/Common/Property.h"
11#include "Stroika/Foundation/IO/Network/HTTP/Exception.h"
12#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
13#include "Stroika/Foundation/IO/Network/HTTP/Methods.h"
15
16#include "Stroika/Frameworks/WebServer/ConnectionManager.h"
17#include "Stroika/Frameworks/WebServer/Router.h"
18#include "Stroika/Frameworks/WebService/Server/Basic.h"
19#include "Stroika/Frameworks/WebService/Server/ObjectRequestHandler.h"
20#include "Stroika/Frameworks/WebService/Server/VariantValue.h"
21
22#include "WebServer.h"
23
24#include "AppVersion.h"
25
26using namespace std;
27
28using namespace Stroika::Foundation;
31using namespace Stroika::Foundation::Execution;
33using namespace Stroika::Foundation::Traversal;
34
35using namespace Stroika::Frameworks::WebServer;
36using namespace Stroika::Frameworks::WebService;
37using namespace Stroika::Frameworks::WebService::Server;
38using namespace Stroika::Frameworks::WebService::Server::VariantValue;
39
40using Memory::BLOB;
41using Memory::MakeSharedPtr;
43using Stroika::Frameworks::WebServer::Response;
45
46using namespace StroikaSample::WebServices;
47
48namespace {
49 const Headers kDefaultResponseHeaders_{[] () {
50 Headers h;
51 h.server = "Stroika-Sample-WebServices/"_k + AppVersion::kVersion.AsMajorMinorString ();
52 return h;
53 }()};
54}
55
56namespace {
57 const ObjectRequestHandler::Options kBinaryOpObjRequestOptions_ = {.fObjectMapper = Model::kMapper,
58 .fExtractVariantValueFromRequest = ExtractArgumentsAsVariantValue::FromRequest,
59 .fTreatBodyAsListOfArguments = Iterable<String>{"arg1"sv, "arg2"sv}};
60}
61
62/*
63 * It's often helpful to structure together, routes, special interceptors, with your connection manager, to package up
64 * all the logic /options for HTTP interface.
65 *
66 * This particular organization also makes it easy to save instance variables with the webserver (like a pointer to a handler)
67 * and access them from the Route handler functions.
68 */
69class WebServer::Rep_ {
70public:
71 const Sequence<Route> kRoutes_; // rules saying how to map urls to code
72 shared_ptr<IWSAPI> fWSImpl_; // application logic actually handling webservices
73 ConnectionManager fConnectionMgr_; // manage http connection objects, thread pool, etc
74
75 static const WebServiceMethodDescription kVariables_;
76
77 static const WebServiceMethodDescription kPlus_;
78 static const WebServiceMethodDescription kMinus;
79 static const WebServiceMethodDescription kTimes;
80 static const WebServiceMethodDescription kDivide;
81
82 Rep_ (uint16_t portNumber, const shared_ptr<IWSAPI>& wsImpl)
83 : kRoutes_{
84 Route{""_RegEx, DefaultPage_},
85
86 Route{HTTP::MethodsRegEx::kPost, "SetAppState"_RegEx, SetAppState_},
87
88 Route{"FRED"_RegEx,
89 [] (Request&, Response& response) {
90 response.write ("FRED");
91 response.contentType = InternetMediaTypes::kText_PLAIN;
92 }},
93
94 /*
95 * the 'variable' API demonstrates a typical REST style CRUD usage - where the 'arguments' mainly come from
96 * the URL itself.
97 */
98 Route{"variables(/?)"_RegEx,
99 [this] (Message& m) { WriteResponse (m.rwResponse (), kVariables_, kMapper.FromObject (fWSImpl_->Variables_GET ())); }},
100 Route{"variables/(.+)"_RegEx,
101 [this] (Message& m, const String& varName) {
102 WriteResponse (m.rwResponse (), kVariables_, kMapper.FromObject (fWSImpl_->Variables_GET (varName)));
103 }},
104 Route{HTTP::MethodsRegEx::kPostOrPut, "variables/(.+)"_RegEx,
105 [this] (Message& m, const String& varName) {
106 optional<Number> number;
107 // demo getting argument from the body
108 if (not number) {
109 // read if content-type is text (not json)
110 if (m.request ().contentType () and
111 InternetMediaTypeRegistry::sThe->IsA (InternetMediaTypes::kText_PLAIN, *m.request ().contentType ())) {
112 String argsAsString = Streams::BinaryToText::Reader::New (m.rwRequest ().GetBody ()).ReadAll ();
113 number = kMapper.ToObject<Number> (DataExchange::VariantValue{argsAsString});
114 }
115 }
116 // demo getting argument from the query argument
117 if (not number) {
118 static const String kValueParamName_ = "value"sv;
119 Mapping<String, DataExchange::VariantValue> args = PickoutParamValuesFromURL (m.request ());
120 number = Model::kMapper.ToObject<Number> (args.LookupValue (kValueParamName_));
121 }
122 // demo getting either query arg, or url encoded arg
123 if (not number) {
124 static const String kValueParamName_ = "value"sv;
125 // NOTE - PickoutParamValues combines PickoutParamValuesFromURL, and PickoutParamValuesFromBody. You can use
126 // Either one of those instead. PickoutParamValuesFromURL assumes you know the name of the parameter, and its
127 // encoded in the query string. PickoutParamValuesFromBody assumes you have something equivalent you can parse ouf
128 // of the body, either json encoded or form-encoded (as of 2.1d23, only json encoded supported)
129 Mapping<String, DataExchange::VariantValue> args = PickoutParamValues (m.rwRequest ());
130 number = Model::kMapper.ToObject<Number> (args.LookupValue (kValueParamName_));
131 }
132 if (not number) {
133 Throw (HTTP::ClientErrorException{"Expected argument to PUT/POST variable"sv});
134 }
135 fWSImpl_->Variables_SET (varName, *number);
136 WriteResponse (m.rwResponse (), kVariables_);
137 }},
138 Route{HTTP::MethodsRegEx::kDelete, "variables/(.+)"_RegEx,
139 /*
140 * varName - the first string argument to the request handler - comes from the first (and only) capture in the regexp.
141 */
142 [this] (Message& m, const String& varName) {
143 fWSImpl_->Variables_DELETE (varName);
144 WriteResponse (m.rwResponse (), kVariables_);
145 }},
146
147 /*
148 * plus, minus, times, and divide, test-void-return all all demonstrate passing in variables through either the POST body, or query-arguments.
149 */
150 Route{HTTP::MethodsRegEx::kPost, "plus"_RegEx,
151 ObjectRequestHandler::Factory{kBinaryOpObjRequestOptions_,
152 [this] (Number arg1, Number arg2) { return fWSImpl_->plus (arg1, arg2); }}},
153 Route{HTTP::MethodsRegEx::kPost, "minus"_RegEx,
154 ObjectRequestHandler::Factory{kBinaryOpObjRequestOptions_,
155 [this] (Number arg1, Number arg2) { return fWSImpl_->minus (arg1, arg2); }}},
156 Route{HTTP::MethodsRegEx::kPost, "times"_RegEx,
157 ObjectRequestHandler::Factory{kBinaryOpObjRequestOptions_,
158 [this] (Number arg1, Number arg2) { return fWSImpl_->times (arg1, arg2); }}},
159 Route{HTTP::MethodsRegEx::kPost, "divide"_RegEx,
160 ObjectRequestHandler::Factory{kBinaryOpObjRequestOptions_,
161 [this] (Number arg1, Number arg2) { return fWSImpl_->divide (arg1, arg2); }}},
162 Route{HTTP::MethodsRegEx::kPost, "test-void-return"_RegEx,
164 .fTreatBodyAsListOfArguments = Iterable<String>{"err-if-more-than-10"sv}},
165 [] (double check) {
166 if (check > 10) {
167 Throw (Exception{"more than 10"sv});
168 }
169 }}}}
170 , fWSImpl_{wsImpl}
171 , fConnectionMgr_{SocketAddresses (InternetAddresses_Any (), portNumber), kRoutes_,
172 ConnectionManager::Options{.fDefaultResponseHeaders = kDefaultResponseHeaders_}}
173 {
174 }
175 // Can declare arguments as Request&,Response&
176 static void DefaultPage_ (Request&, Response& response)
177 {
178 WriteDocsPage (response,
180 kVariables_,
181 kPlus_,
182 kMinus,
183 kTimes,
184 kDivide,
185 },
186 DocsOptions{"Stroika Sample WebService - Web Methods"_k, "Note - curl lines all in bash quoting syntax"_k});
187 }
188 static void SetAppState_ (Message& message)
189 {
190 String argsAsString = Streams::BinaryToText::Reader::New (message.rwRequest ().GetBody ()).ReadAll ();
191 message.rwResponse ().writeln ("<html><body><p>Hi SetAppState ("sv + argsAsString + ")</p></body></html>"sv);
192 message.rwResponse ().contentType = InternetMediaTypes::kHTML;
193 }
194};
195
196/*
197 * Documentation on WSAPIs
198 */
199const WebServiceMethodDescription WebServer::Rep_::kVariables_{
200 "variables"_k,
201 Set<String>{HTTP::Methods::kGet, HTTP::Methods::kPost, HTTP::Methods::kDelete},
202 InternetMediaTypes::kJSON,
203 {},
204 Sequence<String>{"curl http://localhost:8080/variables -v --output -", "curl http://localhost:8080/variables/x -v --output -",
205 "curl -X POST http://localhost:8080/variables/x -v --output -",
206 "curl -H \"Content-Type: application/json\" -X POST -d '{\"value\": 3}' http://localhost:8080/variables/x --output -",
207 "curl -H \"Content-Type: text/plain\" -X POST -d 3 http://localhost:8080/variables/x --output -"},
208 Sequence<String>{"@todo - this is a rough draft (but functional). It could use alot of cleanup and review to see WHICH way I recommend "
209 "using, and just provide the recommended ways in samples"},
210};
211
212const WebServiceMethodDescription WebServer::Rep_::kPlus_{
213 "plus"_k,
214 Set<String>{HTTP::Methods::kPost},
215 InternetMediaTypes::kJSON,
216 {},
218 "curl -H \"Content-Type: application/json\" -X POST -d '{\"arg1\": 3, \"arg2\": 5 }' http://localhost:8080/plus --output -",
219 "curl -X POST 'http://localhost:8080/plus?arg1=3&arg2=5' --output -",
220 },
221 Sequence<String>{"add the two argument numbers"},
222};
223const WebServiceMethodDescription WebServer::Rep_::kMinus{
224 "minus"_k,
225 Set<String>{HTTP::Methods::kPost},
226 InternetMediaTypes::kJSON,
227 {},
229 "curl -H \"Content-Type: application/json\" -X POST -d '{\"arg1\": 4.5, \"arg2\": -3.23 }' http://localhost:8080/minus --output -",
230 },
231 Sequence<String>{"subtract the two argument numbers"},
232};
233const WebServiceMethodDescription WebServer::Rep_::kTimes{
234 "times"_k,
235 Set<String>{HTTP::Methods::kPost},
236 InternetMediaTypes::kJSON,
237 {},
239 "curl -H \"Content-Type: application/json\" -X POST -d '{\"arg1\":\"2 + 4i\", \"arg2\": 3.2 }' http://localhost:8080/times "
240 "--output -",
241 "curl -H \"Content-Type: application/json\" -X POST -d '{\"arg1\":\"2 + i\", \"arg2\": \"2 - i\" }' http://localhost:8080/times "
242 "--output -",
243 },
244 Sequence<String>{"multiply the two argument numbers"},
245};
246const WebServiceMethodDescription WebServer::Rep_::kDivide{
247 "divide"_k,
248 Set<String>{HTTP::Methods::kPost},
249 InternetMediaTypes::kJSON,
250 {},
252 "curl -H \"Content-Type: application/json\" -X POST -d '{\"arg1\":\"2 + i\", \"arg2\": 0 }' http://localhost:8080/divide --output "
253 "-",
254 },
255 Sequence<String>{"divide the two argument numbers"},
256};
257
258WebServer::WebServer (uint16_t portNumber, const shared_ptr<IWSAPI>& wsImpl)
259 : fRep_{MakeSharedPtr<Rep_> (portNumber, wsImpl)}
260{
261}
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual mapped_type LookupValue(ArgByValueType< key_type > key, ArgByValueType< mapped_type > defaultValue=mapped_type{}) const
Definition Mapping.inl:166
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
static Execution::Synchronized< InternetMediaTypeRegistry > sThe
nonvirtual VariantValue FromObject(const T &from) const
nonvirtual T ToObject(const VariantValue &v) const
Simple variant-value (case variant union) object, with (variant) basic types analogous to a value in ...
Exception<> is a replacement (subclass) for any std c++ exception class (e.g. the default 'std::excep...
Definition Exceptions.h:157
ClientErrorException is to capture exceptions caused by a bad (e.g ill-formed) request.
roughly equivalent to Association<String,String>, except that the class is smart about certain keys a...
Definition Headers.h:129
Common::Property< optional< String > > server
Definition Headers.h:404
nonvirtual String ReadAll(size_t upTo=numeric_limits< size_t >::max()) const
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:238
Common::ReadOnlyProperty< Response & > rwResponse
Definition Message.h:93
Common::ReadOnlyProperty< Request & > rwRequest
Definition Message.h:80
this represents a HTTP request object for the WebServer module
ObjectRequestHandler::Factory is a way to construct a WebServer::RequestHandler from an ObjectVariant...
void Throw(T &&e2Throw)
identical to builtin C++ 'throw' except that it does helpful, type dependent DbgTrace() messages firs...
Definition Throw.inl:43
Traversal::Iterable< SocketAddress > SocketAddresses(const Traversal::Iterable< InternetAddress > &internetAddresses, PortType portNumber)
Traversal::Iterable< InternetAddress > InternetAddresses_Any(InternetProtocol::IP::IPVersionSupport ipSupport=InternetProtocol::IP::IPVersionSupport::eDEFAULT)
Ptr New(const InputStream::Ptr< byte > &src, optional< AutomaticCodeCvtFlags > codeCvtFlags={}, optional< SeekableFlag > seekable={}, ReadAhead readAhead=eReadAheadAllowed)
Create an InputStream::Ptr<Character> from the arguments (usually binary source) - which can be used ...
STL namespace.
ConnectionManager::Options specify things like default headers, caching policies, binding flags (not ...
Options for ObjectRequestHandler - mostly the ObjectVariantMapper, but also a few others depending on...