Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
WebServer/Sources/WebServer.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <iostream>
7
8#include "Stroika/Foundation/Characters/String2Int.h"
12#include "Stroika/Foundation/Execution/CommandLine.h"
13#include "Stroika/Foundation/Execution/Module.h"
14#include "Stroika/Foundation/Execution/SignalHandlers.h"
15#include "Stroika/Foundation/Execution/TimeOutException.h"
16#include "Stroika/Foundation/Execution/WaitableEvent.h"
17#include "Stroika/Foundation/IO/Network/HTTP/Exception.h"
18#include "Stroika/Foundation/IO/Network/HTTP/Headers.h"
19#include "Stroika/Foundation/IO/Network/HTTP/Methods.h"
22
23#include "Stroika/Frameworks/WebServer/ConnectionManager.h"
24#include "Stroika/Frameworks/WebServer/FileSystemRequestHandler.h"
25#include "Stroika/Frameworks/WebServer/Router.h"
26
27#include "AppVersion.h"
28
29using namespace std;
30
31using namespace Stroika::Foundation;
33using namespace Stroika::Foundation::Execution;
35using namespace Stroika::Frameworks::WebServer;
36
38using Memory::BLOB;
39
42using Stroika::Frameworks::WebServer::Response;
43using Time::Duration;
44
45namespace {
46
47 /**
48 * You don't need to specify any of this, but it maybe helpful to specify caching control policies to
49 * get the best web-server performance.
50 */
51 const FileSystemRequestHandler::Options kFileSystemRouterOptions_{[] () {
53 {RegularExpression{".*\\.gif", eCaseInsensitive}, CacheControl{.fMaxAge = Duration{5min}.As<int32_t> ()}}};
54 return FileSystemRequestHandler::Options{.fURLPrefix2Strip = "/Files/"_k,
55 .fDefaultIndexFileNames = Sequence<filesystem::path>{"index.html"sv},
56 .fCacheControlSettings = cacheControlSettings};
57 }()};
58
59 /**
60 * You don't need to specify any of this, but its a good idea to properly identify your application.
61 */
62 const Headers kDefaultResponseHeaders_{[] () {
63 Headers h;
64 h.server = "Stroika-Sample-WebServer/"_k + AppVersion::kVersion.AsMajorMinorString ();
65 return h;
66 }()};
67
68 /*
69 * It's often helpful to structure together, routes, special interceptors, with your connection manager, to package up
70 * all the logic /options for HTTP interface.
71 *
72 * This particular organization also makes it easy to save instance variables with the webserver (like a pointer to a handler)
73 * and access them from the Route handler functions.
74 */
75 struct MyWebServer_ {
76
77 /**
78 * Routes specify the 'handlers' for the various web URLs your webserver will support.
79 */
80 const Sequence<Route> kRoutes_;
81
82 /**
83 * The connectionMgr specifies parameters that govern the procedural behavior of your webserver.
84 * For example, caching settings go here, thread pool settings, network bindings, and so on.
85 */
86 ConnectionManager fConnectionMgr_;
87
88 MyWebServer_ (uint16_t portNumber)
89 : kRoutes_{
90 /*
91 * Define the 'routes' for your webserver - the dispatch of URLs to callbacks (usually lambdas).
92 * But - sometimes the callbacks are complex objects, like the filesystem handler.
93 *
94 * \see the ../../../WebService, or ../../../HTMLUI samples for more complex examples of routers.
95 */
96
97 Route{""_RegEx, DefaultPage_}
98
99 /*
100 * You can put the code for a route into a separate procedure
101 */
102 , Route{HTTP::MethodsRegEx::kPost, "SetAppState"_RegEx, SetAppState_}
103
104 /*
105 * Or you can put the code for a route into a lambda directly - whichever you find easier/more maintainable.
106 */
107 , Route{"FRED/?"_RegEx,
108 [] (Request&, Response& response) {
109 response.contentType = DataExchange::InternetMediaTypes::kText_PLAIN;
110 response.write ("FRED"sv);
111 }}
112
113 /*
114 * Or use complex, pre-built handlers that do something complicated (like feed content from filesystem).
115 */
116 , Route{"Files/.*"_RegEx, FileSystemRequestHandler{GetEXEDir () / "html", kFileSystemRouterOptions_}}
117
118 /*
119 * Access webserver (connection manager) statistics, and print them (health check?)
120 */
121 , Route{"stats"_RegEx, [this] (Message& m) { PrintServerStats_ (m);} }
122
123 }
124 , fConnectionMgr_{SocketAddresses (InternetAddresses_Any (), portNumber), kRoutes_,
125 ConnectionManager::Options{.fBindFlags = Socket::BindFlags{}, .fDefaultResponseHeaders = kDefaultResponseHeaders_}}
126 {
127 cerr << "Listening on {}..."_f(fConnectionMgr_.bindings ()) << endl;
128 }
129
130 // Can declare arguments as Request&,Response&, or Message&, or many other options - see WebServer::RequestHandler constructor for details
131 static void DefaultPage_ (Request& request, Response& response)
132 {
133 /*
134 * Control aspects of the response, like chunked transfer (before writing).
135 */
136 if (request.url ().LookupQueryArg ("useChunked"sv) == "true"sv) {
137 response.automaticTransferChunkSize = 25;
138 }
139 else if (request.url ().LookupQueryArg ("useChunked"sv) == "false"sv) {
140 response.automaticTransferChunkSize = Response::kNoChunkedTransfer;
141 }
142 else {
143 // use the default if requester didn't specify
144 }
145
146 response.contentType = DataExchange::InternetMediaTypes::kHTML;
147 /*
148 * \note can also use HTMLViewCompiler to generate 'server side html' using more standard html syntax...
149 */
150 response.writeln ("<html><body>"sv);
151 response.writeln ("<p>Run the service (under the debugger if you wish)</p>"sv);
152 response.writeln ("<ul>"sv);
153 response.writeln ("<li>curl -v http://localhost:8080/ OR</li>"sv);
154 response.writeln ("<li>curl -v http://localhost:8080/?useChunked=true OR</li>"sv);
155 response.writeln ("<li>curl -v http://localhost:8080/FRED OR (to see error handling)</li>"sv);
156 response.writeln ("<li>curl -v -H \"Content-Type: application/json\" -X POST -d '{\"AppState\":\"Start\"}' http://localhost:8080/SetAppState</li>"sv);
157 response.writeln ("<li>curl -v http://localhost:8080/Files/index.html</li>"sv);
158 response.writeln ("<li>curl -v http://localhost:8080/stats OR</li>"sv);
159 response.writeln ("</ul>"sv);
160 response.writeln ("</body></html>"sv);
161 }
162 void PrintServerStats_ (Message& m)
163 {
164 auto stats = this->fConnectionMgr_.statistics ();
165 auto connections = this->fConnectionMgr_.connections (); // but sometimes when debugging/exploring....
166 Response& r = m.rwResponse ();
167 r.contentType = DataExchange::InternetMediaTypes::kText_PLAIN;
168 r.writeln ("statistics: {");
169 r.writeln (" {},"_f(stats));
170 r.writeln ("}");
171 r.writeln ("connections: [");
172 for (auto s : connections) {
173 // could check if slow-handled-connection:
174 // return s.fMostRecentMessage and s.fMostRecentMessage->ReplaceEnd (min (s.fMostRecentMessage->GetUpperBound (), now)).GetDistanceSpanned () > threshold
175 r.writeln (" {},"_f(s));
176 }
177 r.writeln ("]");
178 }
179 // Can declare arguments as Message& message
180 static void SetAppState_ (Message& message)
181 {
182 message.rwResponse ().contentType = DataExchange::InternetMediaTypes::kHTML;
183 String argsAsString = Streams::BinaryToText::Reader::New (message.rwRequest ().GetBody ()).ReadAll ();
184 message.rwResponse ().writeln ("<html><body><p>Hi SetAppState ("sv + argsAsString + ")</p></body></html>");
185 }
186 };
187}
188
189int main (int argc, const char* argv[])
190{
191 CommandLine cmdLine{argc, argv};
192 Debug::TraceContextBumper ctx{"main", "argv={}"_f, cmdLine};
194#if qStroika_Foundation_Common_Platform_POSIX
196#endif
197
198 uint16_t portNumber = 8080;
199
200 const CommandLine::Option kPortO_{
201 .fLongName = "port"sv, .fSupportsArgument = true, .fHelpOptionText = "specify webserver listen port (default {})"_f(portNumber)};
202 const CommandLine::Option kQuitAfterO_{
203 .fLongName = "quit-after"sv, .fSupportsArgument = true, .fHelpOptionText = "automatically quit after <argument> seconds"sv};
204 const Sequence<CommandLine::Option> kAllOptions_{StandardCommandLineOptions::kHelp, kPortO_, kQuitAfterO_};
205
206 try {
207 cmdLine.Validate (kAllOptions_);
208 }
209 catch (const InvalidCommandLineArgument&) {
210 cerr << Characters::ToString (current_exception ()).AsNarrowSDKString () << endl;
211 cerr << cmdLine.GenerateUsage (kAllOptions_).AsNarrowSDKString () << endl;
212 return EXIT_FAILURE;
213 }
214 if (cmdLine.Has (StandardCommandLineOptions::kHelp)) {
215 cerr << cmdLine.GenerateUsage (kAllOptions_).AsNarrowSDKString () << endl;
216 return EXIT_SUCCESS;
217 }
218
219 try {
220 Time::DurationSeconds quitAfter = Time::kInfinity;
221 if (auto o = cmdLine.GetArgument (kPortO_)) {
222 portNumber = Characters::String2Int<uint16_t> (*o);
223 }
224 if (auto o = cmdLine.GetArgument (kQuitAfterO_)) {
225 quitAfter = Time::DurationSeconds{Characters::FloatConversion::ToFloat<Time::DurationSeconds::rep> (*o)};
226 }
227 MyWebServer_ myWebServer{portNumber}; // listen and dispatch while this object exists
228 WaitableEvent{}.Wait (quitAfter); // wait quitAfter seconds, or til user hits ctrl-c
229 }
230 catch (const TimeOutException&) {
231 cerr << "Timed out - so - exiting..." << endl;
232 return EXIT_SUCCESS;
233 }
234 catch (...) {
235 cerr << "Error encountered: " << Characters::ToString (current_exception ()).AsNarrowSDKString () << " - terminating..." << endl;
236 return EXIT_FAILURE;
237 }
238 return EXIT_SUCCESS;
239}
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
RegularExpression is a compiled regular expression which can be used to match on a String class.
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual void Wait(Time::DurationSeconds timeout=Time::kInfinity)
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
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
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
filesystem::path GetEXEDir()
Definition Module.cpp: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 ...