The OpenD Programming Language

1 // FIXME: if an exception is thrown, we shouldn't necessarily cache...
2 // FIXME: there's some annoying duplication of code in the various versioned mains
3 
4 // add the Range header in there too. should return 206
5 
6 // FIXME: cgi per-request arena allocator
7 
8 // i need to add a bunch of type templates for validations... mayne @NotNull or NotNull!
9 
10 // FIXME: I might make a cgi proxy class which can change things; the underlying one is still immutable
11 // but the later one can edit and simplify the api. You'd have to use the subclass tho!
12 
13 /*
14 void foo(int f, @("test") string s) {}
15 
16 void main() {
17 	static if(is(typeof(foo) Params == __parameters))
18 		//pragma(msg, __traits(getAttributes, Params[0]));
19 		pragma(msg, __traits(getAttributes, Params[1..2]));
20 	else
21 		pragma(msg, "fail");
22 }
23 */
24 
25 // Note: spawn-fcgi can help with fastcgi on nginx
26 
27 // FIXME: to do: add openssl optionally
28 // make sure embedded_httpd doesn't send two answers if one writes() then dies
29 
30 // future direction: websocket as a separate process that you can sendfile to for an async passoff of those long-lived connections
31 
32 /*
33 	Session manager process: it spawns a new process, passing a
34 	command line argument, to just be a little key/value store
35 	of some serializable struct. On Windows, it CreateProcess.
36 	On Linux, it can just fork or maybe fork/exec. The session
37 	key is in a cookie.
38 
39 	Server-side event process: spawns an async manager. You can
40 	push stuff out to channel ids and the clients listen to it.
41 
42 	websocket process: spawns an async handler. They can talk to
43 	each other or get info from a cgi request.
44 
45 	Tempting to put web.d 2.0 in here. It would:
46 		* map urls and form generation to functions
47 		* have data presentation magic
48 		* do the skeleton stuff like 1.0
49 		* auto-cache generated stuff in files (at least if pure?)
50 		* introspect functions in json for consumers
51 
52 
53 	https://linux.die.net/man/3/posix_spawn
54 */
55 
56 /++
57 	Provides a uniform server-side API for CGI, FastCGI, SCGI, and HTTP web applications. Offers both lower- and higher- level api options among other common (optional) things like websocket and event source serving support, session management, and job scheduling.
58 
59 	---
60 	import arsd.cgi;
61 
62 	// Instead of writing your own main(), you should write a function
63 	// that takes a Cgi param, and use mixin GenericMain
64 	// for maximum compatibility with different web servers.
65 	void hello(Cgi cgi) {
66 		cgi.setResponseContentType("text/plain");
67 
68 		if("name" in cgi.get)
69 			cgi.write("Hello, " ~ cgi.get["name"]);
70 		else
71 			cgi.write("Hello, world!");
72 	}
73 
74 	mixin GenericMain!hello;
75 	---
76 
77 	Or:
78 	---
79 	import arsd.cgi;
80 
81 	class MyApi : WebObject {
82 		@UrlName("")
83 		string hello(string name = null) {
84 			if(name is null)
85 				return "Hello, world!";
86 			else
87 				return "Hello, " ~ name;
88 		}
89 	}
90 	mixin DispatcherMain!(
91 		"/".serveApi!MyApi
92 	);
93 	---
94 
95 	$(NOTE
96 		Please note that using the higher-level api will add a dependency on arsd.dom and arsd.jsvar to your application.
97 		If you use `dmd -i` or `ldc2 -i` to build, it will just work, but with dub, you will have do `dub add arsd-official:jsvar`
98 		and `dub add arsd-official:dom` yourself.
99 	)
100 
101 	Test on console (works in any interface mode):
102 	$(CONSOLE
103 		$ ./cgi_hello GET / name=whatever
104 	)
105 
106 	If using http version (default on `dub` builds, or on custom builds when passing `-version=embedded_httpd` to dmd):
107 	$(CONSOLE
108 		$ ./cgi_hello --port 8080
109 		# now you can go to http://localhost:8080/?name=whatever
110 	)
111 
112 	Please note: the default port for http is 8085 and for scgi is 4000. I recommend you set your own by the command line argument in a startup script instead of relying on any hard coded defaults. It is possible though to code your own with [RequestServer], however.
113 
114 
115 	Build_Configurations:
116 
117 	cgi.d tries to be flexible to meet your needs. It is possible to configure it both at runtime (by writing your own `main` function and constructing a [RequestServer] object) or at compile time using the `version` switch to the compiler or a dub `subConfiguration`.
118 
119 	If you are using `dub`, use:
120 
121 	```sdlang
122 	subConfiguration "arsd-official:cgi" "VALUE_HERE"
123 	```
124 
125 	or to dub.json:
126 
127 	```json
128         	"subConfigurations": {"arsd-official:cgi": "VALUE_HERE"}
129 	```
130 
131 	to change versions. The possible options for `VALUE_HERE` are:
132 
133 	$(LIST
134 		* `embedded_httpd` for the embedded httpd version (built-in web server). This is the default for dub builds. You can run the program then connect directly to it from your browser. Note: prior to version 11, this would be embedded_httpd_processes on Linux and embedded_httpd_threads everywhere else. It now means embedded_httpd_hybrid everywhere supported and embedded_httpd_threads everywhere else.
135 		* `cgi` for traditional cgi binaries. These are run by an outside web server as-needed to handle requests.
136 		* `fastcgi` for FastCGI builds. FastCGI is managed from an outside helper, there's one built into Microsoft IIS, Apache httpd, and Lighttpd, and a generic program you can use with nginx called `spawn-fcgi`. If you don't already know how to use it, I suggest you use one of the other modes.
137 		* `scgi` for SCGI builds. SCGI is a simplified form of FastCGI, where you run the server as an application service which is proxied by your outside webserver. Please note: on nginx make sure you add `scgi_param  PATH_INFO          $document_uri;` to the config!
138 		* `stdio_http` for speaking raw http over stdin and stdout. This is made for systemd services. See [RequestServer.serveSingleHttpConnectionOnStdio] for more information.
139 	)
140 
141 	With dmd, use:
142 
143 	$(TABLE_ROWS
144 
145 		* + Interfaces
146 		  + (mutually exclusive)
147 
148 		* - `-version=plain_cgi`
149 			- The default building the module alone without dub - a traditional, plain CGI executable will be generated.
150 		* - `-version=embedded_httpd`
151 			- A HTTP server will be embedded in the generated executable. This is default when building with dub.
152 		* - `-version=fastcgi`
153 			- A FastCGI executable will be generated.
154 		* - `-version=scgi`
155 			- A SCGI (SimpleCGI) executable will be generated.
156 		* - `-version=embedded_httpd_hybrid`
157 			- A HTTP server that uses a combination of processes, threads, and fibers to better handle large numbers of idle connections. Recommended if you are going to serve websockets in a non-local application.
158 		* - `-version=embedded_httpd_threads`
159 			- The embedded HTTP server will use a single process with a thread pool. (use instead of plain `embedded_httpd` if you want this specific implementation)
160 		* - `-version=embedded_httpd_processes`
161 			- The embedded HTTP server will use a prefork style process pool. (use instead of plain `embedded_httpd` if you want this specific implementation)
162 		* - `-version=embedded_httpd_processes_accept_after_fork`
163 			- It will call accept() in each child process, after forking. This is currently the only option, though I am experimenting with other ideas. You probably should NOT specify this right now.
164 		* - `-version=stdio_http`
165 			- The embedded HTTP server will be spoken over stdin and stdout.
166 
167 		* + Tweaks
168 		  + (can be used together with others)
169 
170 		* - `-version=cgi_with_websocket`
171 			- The CGI class has websocket server support. (This is on by default now.)
172 
173 		* - `-version=with_openssl`
174 			- not currently used
175 		* - `-version=cgi_embedded_sessions`
176 			- The session server will be embedded in the cgi.d server process
177 		* - `-version=cgi_session_server_process`
178 			- The session will be provided in a separate process, provided by cgi.d.
179 	)
180 
181 	For example,
182 
183 	For CGI, `dmd yourfile.d cgi.d` then put the executable in your cgi-bin directory.
184 
185 	For FastCGI: `dmd yourfile.d cgi.d -version=fastcgi` and run it. spawn-fcgi helps on nginx. You can put the file in the directory for Apache. On IIS, run it with a port on the command line (this causes it to call FCGX_OpenSocket, which can work on nginx too).
186 
187 	For SCGI: `dmd yourfile.d cgi.d -version=scgi` and run the executable, providing a port number on the command line.
188 
189 	For an embedded HTTP server, run `dmd yourfile.d cgi.d -version=embedded_httpd` and run the generated program. It listens on port 8085 by default. You can change this on the command line with the --port option when running your program.
190 
191 	Simulating_requests:
192 
193 	If you are using one of the [GenericMain] or [DispatcherMain] mixins, or main with your own call to [RequestServer.trySimulatedRequest], you can simulate requests from your command-ine shell. Call the program like this:
194 
195 	$(CONSOLE
196 	./yourprogram GET / name=adr
197 	)
198 
199 	And it will print the result to stdout instead of running a server, regardless of build more..
200 
201 	CGI_Setup_tips:
202 
203 	On Apache, you may do `SetHandler cgi-script` in your `.htaccess` file to set a particular file to be run through the cgi program. Note that all "subdirectories" of it also run the program; if you configure `/foo` to be a cgi script, then going to `/foo/bar` will call your cgi handler function with `cgi.pathInfo == "/bar"`.
204 
205 	Overview_Of_Basic_Concepts:
206 
207 	cgi.d offers both lower-level handler apis as well as higher-level auto-dispatcher apis. For a lower-level handler function, you'll probably want to review the following functions:
208 
209 		Input: [Cgi.get], [Cgi.post], [Cgi.request], [Cgi.files], [Cgi.cookies], [Cgi.pathInfo], [Cgi.requestMethod],
210 		       and HTTP headers ([Cgi.headers], [Cgi.userAgent], [Cgi.referrer], [Cgi.accept], [Cgi.authorization], [Cgi.lastEventId])
211 
212 		Output: [Cgi.write], [Cgi.header], [Cgi.setResponseStatus], [Cgi.setResponseContentType], [Cgi.gzipResponse]
213 
214 		Cookies: [Cgi.setCookie], [Cgi.clearCookie], [Cgi.cookie], [Cgi.cookies]
215 
216 		Caching: [Cgi.setResponseExpires], [Cgi.updateResponseExpires], [Cgi.setCache]
217 
218 		Redirections: [Cgi.setResponseLocation]
219 
220 		Other Information: [Cgi.remoteAddress], [Cgi.https], [Cgi.port], [Cgi.scriptName], [Cgi.requestUri], [Cgi.getCurrentCompleteUri], [Cgi.onRequestBodyDataReceived]
221 
222 		Websockets: [Websocket], [websocketRequested], [acceptWebsocket]. For websockets, use the `embedded_httpd_hybrid` build mode for best results, because it is optimized for handling large numbers of idle connections compared to the other build modes.
223 
224 		Overriding behavior for special cases streaming input data: see the virtual functions [Cgi.handleIncomingDataChunk], [Cgi.prepareForIncomingDataChunks], [Cgi.cleanUpPostDataState]
225 
226 	A basic program using the lower-level api might look like:
227 
228 		---
229 		import arsd.cgi;
230 
231 		// you write a request handler which always takes a Cgi object
232 		void handler(Cgi cgi) {
233 			/+
234 				when the user goes to your site, suppose you are being hosted at http://example.com/yourapp
235 
236 				If the user goes to http://example.com/yourapp/test?name=value
237 				then the url will be parsed out into the following pieces:
238 
239 					cgi.pathInfo == "/test". This is everything after yourapp's name. (If you are doing an embedded http server, your app's name is blank, so pathInfo will be the whole path of the url.)
240 
241 					cgi.scriptName == "yourapp". With an embedded http server, this will be blank.
242 
243 					cgi.host == "example.com"
244 
245 					cgi.https == false
246 
247 					cgi.queryString == "name=value" (there's also cgi.search, which will be "?name=value", including the ?)
248 
249 					The query string is further parsed into the `get` and `getArray` members, so:
250 
251 					cgi.get == ["name": "value"], meaning you can do `cgi.get["name"] == "value"`
252 
253 					And
254 
255 					cgi.getArray == ["name": ["value"]].
256 
257 					Why is there both `get` and `getArray`? The standard allows names to be repeated. This can be very useful,
258 					it is how http forms naturally pass multiple items like a set of checkboxes. So `getArray` is the complete data
259 					if you need it. But since so often you only care about one value, the `get` member provides more convenient access.
260 
261 				We can use these members to process the request and build link urls. Other info from the request are in other members, we'll look at them later.
262 			+/
263 			switch(cgi.pathInfo) {
264 				// the home page will be a small html form that can set a cookie.
265 				case "/":
266 					cgi.write(`<!DOCTYPE html>
267 					<html>
268 					<body>
269 						<form method="POST" action="set-cookie">
270 							<label>Your name: <input type="text" name="name" /></label>
271 							<input type="submit" value="Submit" />
272 						</form>
273 					</body>
274 					</html>
275 					`, true); // the , true tells it that this is the one, complete response i want to send, allowing some optimizations.
276 				break;
277 				// POSTing to this will set a cookie with our submitted name
278 				case "/set-cookie":
279 					// HTTP has a number of request methods (also called "verbs") to tell
280 					// what you should do with the given resource.
281 					// The most common are GET and POST, the ones used in html forms.
282 					// You can check which one was used with the `cgi.requestMethod` property.
283 					if(cgi.requestMethod == Cgi.RequestMethod.POST) {
284 
285 						// headers like redirections need to be set before we call `write`
286 						cgi.setResponseLocation("read-cookie");
287 
288 						// just like how url params go into cgi.get/getArray, form data submitted in a POST
289 						// body go to cgi.post/postArray. Please note that a POST request can also have get
290 						// params in addition to post params.
291 						//
292 						// There's also a convenience function `cgi.request("name")` which checks post first,
293 						// then get if it isn't found there, and then returns a default value if it is in neither.
294 						if("name" in cgi.post) {
295 							// we can set cookies with a method too
296 							// again, cookies need to be set before calling `cgi.write`, since they
297 							// are a kind of header.
298 							cgi.setCookie("name" , cgi.post["name"]);
299 						}
300 
301 						// the user will probably never see this, since the response location
302 						// is an automatic redirect, but it is still best to say something anyway
303 						cgi.write("Redirecting you to see the cookie...", true);
304 					} else {
305 						// you can write out response codes and headers
306 						// as well as response bodies
307 						//
308 						// But always check the cgi docs before using the generic
309 						// `header` method - if there is a specific method for your
310 						// header, use it before resorting to the generic one to avoid
311 						// a header value from being sent twice.
312 						cgi.setResponseLocation("405 Method Not Allowed");
313 						// there is no special accept member, so you can use the generic header function
314 						cgi.header("Accept: POST");
315 						// but content type does have a method, so prefer to use it:
316 						cgi.setResponseContentType("text/plain");
317 
318 						// all the headers are buffered, and will be sent upon the first body
319 						// write. you can actually modify some of them before sending if need be.
320 						cgi.write("You must use the POST http verb on this resource.", true);
321 					}
322 				break;
323 				// and GETting this will read the cookie back out
324 				case "/read-cookie":
325 					// I did NOT pass `,true` here because this is writing a partial response.
326 					// It is possible to stream data to the user in chunks by writing partial
327 					// responses the calling `cgi.flush();` to send the partial response immediately.
328 					// normally, you'd only send partial chunks if you have to - it is better to build
329 					// a response as a whole and send it as a whole whenever possible - but here I want
330 					// to demo that you can.
331 					cgi.write("Hello, ");
332 					if("name" in cgi.cookies) {
333 						import arsd.dom; // dom.d provides a lot of helpers for html
334 						// since the cookie is set, we need to write it out properly to
335 						// avoid cross-site scripting attacks.
336 						//
337 						// Getting this stuff right automatically is a benefit of using the higher
338 						// level apis, but this demo is to show the fundamental building blocks, so
339 						// we're responsible to take care of it.
340 						cgi.write(htmlEntitiesEncode(cgi.cookies["name"]));
341 					} else {
342 						cgi.write("friend");
343 					}
344 
345 					// note that I never called cgi.setResponseContentType, since the default is text/html.
346 					// it doesn't hurt to do it explicitly though, just remember to do it before any cgi.write
347 					// calls.
348 				break;
349 				default:
350 					// no path matched
351 					cgi.setResponseStatus("404 Not Found");
352 					cgi.write("Resource not found.", true);
353 			}
354 		}
355 
356 		// and this adds the boilerplate to set up a server according to the
357 		// compile version configuration and call your handler as requests come in
358 		mixin GenericMain!handler; // the `handler` here is the name of your function
359 		---
360 
361 	Even if you plan to always use the higher-level apis, I still recommend you at least familiarize yourself with the lower level functions, since they provide the lightest weight, most flexible options to get down to business if you ever need them.
362 
363 	In the lower-level api, the [Cgi] object represents your HTTP transaction. It has functions to describe the request and for you to send your response. It leaves the details of how you o it up to you. The general guideline though is to avoid depending any variables outside your handler function, since there's no guarantee they will survive to another handler. You can use global vars as a lazy initialized cache, but you should always be ready in case it is empty. (One exception: if you use `-version=embedded_httpd_threads -version=cgi_no_fork`, then you can rely on it more, but you should still really write things assuming your function won't have anything survive beyond its return for max scalability and compatibility.)
364 
365 	A basic program using the higher-level apis might look like:
366 
367 		---
368 		/+
369 		import arsd.cgi;
370 
371 		struct LoginData {
372 			string currentUser;
373 		}
374 
375 		class AppClass : WebObject {
376 			string foo() {}
377 		}
378 
379 		mixin DispatcherMain!(
380 			"/assets/.serveStaticFileDirectory("assets/", true), // serve the files in the assets subdirectory
381 			"/".serveApi!AppClass,
382 			"/thing/".serveRestObject,
383 		);
384 		+/
385 		---
386 
387 	Guide_for_PHP_users:
388 		(Please note: I wrote this section in 2008. A lot of PHP hosts still ran 4.x back then, so it was common to avoid using classes - introduced in php 5 - to maintain compatibility! If you're coming from php more recently, this may not be relevant anymore, but still might help you.)
389 
390 		If you are coming from old-style PHP, here's a quick guide to help you get started:
391 
392 		$(SIDE_BY_SIDE
393 			$(COLUMN
394 				```php
395 				<?php
396 					$foo = $_POST["foo"];
397 					$bar = $_GET["bar"];
398 					$baz = $_COOKIE["baz"];
399 
400 					$user_ip = $_SERVER["REMOTE_ADDR"];
401 					$host = $_SERVER["HTTP_HOST"];
402 					$path = $_SERVER["PATH_INFO"];
403 
404 					setcookie("baz", "some value");
405 
406 					echo "hello!";
407 				?>
408 				```
409 			)
410 			$(COLUMN
411 				---
412 				import arsd.cgi;
413 				void app(Cgi cgi) {
414 					string foo = cgi.post["foo"];
415 					string bar = cgi.get["bar"];
416 					string baz = cgi.cookies["baz"];
417 
418 					string user_ip = cgi.remoteAddress;
419 					string host = cgi.host;
420 					string path = cgi.pathInfo;
421 
422 					cgi.setCookie("baz", "some value");
423 
424 					cgi.write("hello!");
425 				}
426 
427 				mixin GenericMain!app
428 				---
429 			)
430 		)
431 
432 		$(H3 Array elements)
433 
434 
435 		In PHP, you can give a form element a name like `"something[]"`, and then
436 		`$_POST["something"]` gives an array. In D, you can use whatever name
437 		you want, and access an array of values with the `cgi.getArray["name"]` and
438 		`cgi.postArray["name"]` members.
439 
440 		$(H3 Databases)
441 
442 		PHP has a lot of stuff in its standard library. cgi.d doesn't include most
443 		of these, but the rest of my arsd repository has much of it. For example,
444 		to access a MySQL database, download `database.d` and `mysql.d` from my
445 		github repo, and try this code (assuming, of course, your database is
446 		set up):
447 
448 		---
449 		import arsd.cgi;
450 		import arsd.mysql;
451 
452 		void app(Cgi cgi) {
453 			auto database = new MySql("localhost", "username", "password", "database_name");
454 			foreach(row; mysql.query("SELECT count(id) FROM people"))
455 				cgi.write(row[0] ~ " people in database");
456 		}
457 
458 		mixin GenericMain!app;
459 		---
460 
461 		Similar modules are available for PostgreSQL, Microsoft SQL Server, and SQLite databases,
462 		implementing the same basic interface.
463 
464 	See_Also:
465 
466 	You may also want to see [arsd.dom], [arsd.webtemplate], and maybe some functions from my old [arsd.html] for more code for making
467 	web applications. dom and webtemplate are used by the higher-level api here in cgi.d.
468 
469 	For working with json, try [arsd.jsvar].
470 
471 	[arsd.database], [arsd.mysql], [arsd.postgres], [arsd.mssql], and [arsd.sqlite] can help in
472 	accessing databases.
473 
474 	If you are looking to access a web application via HTTP, try [arsd.http2].
475 
476 	Copyright:
477 
478 	cgi.d copyright 2008-2023, Adam D. Ruppe. Provided under the Boost Software License.
479 
480 	Yes, this file is old, and yes, it is still actively maintained and used.
481 
482 	History:
483 		An import of `arsd.core` was added on March 21, 2023 (dub v11.0). Prior to this, the module's default configuration was completely stand-alone. You must now include the `core.d` file in your builds with `cgi.d`.
484 
485 		This change is primarily to integrate the event loops across the library, allowing you to more easily use cgi.d along with my other libraries like simpledisplay and http2.d. Previously, you'd have to run separate helper threads. Now, they can all automatically work together.
486 +/
487 module arsd.cgi;
488 
489 // FIXME: Nullable!T can be a checkbox that enables/disables the T on the automatic form
490 // and a SumType!(T, R) can be a radio box to pick between T and R to disclose the extra boxes on the automatic form
491 
492 /++
493 	This micro-example uses the [dispatcher] api to act as a simple http file server, serving files found in the current directory and its children.
494 +/
495 version(Demo)
496 unittest {
497 	import arsd.cgi;
498 
499 	mixin DispatcherMain!(
500 		"/".serveStaticFileDirectory(null, true)
501 	);
502 }
503 
504 /++
505 	Same as the previous example, but written out long-form without the use of [DispatcherMain] nor [GenericMain].
506 +/
507 version(Demo)
508 unittest {
509 	import arsd.cgi;
510 
511 	void requestHandler(Cgi cgi) {
512 		cgi.dispatcher!(
513 			"/".serveStaticFileDirectory(null, true)
514 		);
515 	}
516 
517 	// mixin GenericMain!requestHandler would add this function:
518 	void main(string[] args) {
519 		// this is all the content of [cgiMainImpl] which you can also call
520 
521 		// cgi.d embeds a few add on functions like real time event forwarders
522 		// and session servers it can run in other processes. this spawns them, if needed.
523 		if(tryAddonServers(args))
524 			return;
525 
526 		// cgi.d allows you to easily simulate http requests from the command line,
527 		// without actually starting a server. this function will do that.
528 		if(trySimulatedRequest!(requestHandler, Cgi)(args))
529 			return;
530 
531 		RequestServer server;
532 		// you can change the default port here if you like
533 		// server.listeningPort = 9000;
534 
535 		// then call this to let the command line args override your default
536 		server.configureFromCommandLine(args);
537 
538 		// here is where you could print out the listeningPort to the user if you wanted
539 
540 		// and serve the request(s) according to the compile configuration
541 		server.serve!(requestHandler)();
542 
543 		// or you could explicitly choose a serve mode like this:
544 		// server.serveEmbeddedHttp!requestHandler();
545 	}
546 }
547 
548 /++
549 	 cgi.d has built-in testing helpers too. These will provide mock requests and mock sessions that
550 	 otherwise run through the rest of the internal mechanisms to call your functions without actually
551 	 spinning up a server.
552 +/
553 version(Demo)
554 unittest {
555 	import arsd.cgi;
556 
557 	void requestHandler(Cgi cgi) {
558 
559 	}
560 
561 	// D doesn't let me embed a unittest inside an example unittest
562 	// so this is a function, but you can do it however in your real program
563 	/* unittest */ void runTests() {
564 		auto tester = new CgiTester(&requestHandler);
565 
566 		auto response = tester.GET("/");
567 		assert(response.code == 200);
568 	}
569 }
570 
571 /++
572 	The session system works via a built-in spawnable server.
573 
574 	Bugs:
575 		Requires addon servers, which are not implemented yet on Windows.
576 +/
577 version(Posix)
578 version(Demo)
579 unittest {
580 	import arsd.cgi;
581 
582 	struct SessionData {
583 		string userId;
584 	}
585 
586 	void handler(Cgi cgi) {
587 		auto session = cgi.getSessionObject!SessionData;
588 
589 		if(cgi.pathInfo == "/login") {
590 			session.userId = cgi.queryString;
591 			cgi.setResponseLocation("view");
592 		} else {
593 			cgi.write(session.userId);
594 		}
595 	}
596 
597 	mixin GenericMain!handler;
598 }
599 
600 static import std.file;
601 
602 static import arsd.core;
603 version(Posix)
604 import arsd.core : makeNonBlocking;
605 
606 import arsd.core : encodeUriComponent, decodeUriComponent;
607 
608 
609 // for a single thread, linear request thing, use:
610 // -version=embedded_httpd_threads -version=cgi_no_threads
611 
612 version(Posix) {
613 	version(CRuntime_Musl) {
614 
615 	} else version(minimal) {
616 
617 	} else {
618 		version(FreeBSD) {
619 			// I never implemented the fancy stuff there either
620 		} else {
621 			version=with_breaking_cgi_features;
622 			version=with_sendfd;
623 			version=with_addon_servers;
624 		}
625 	}
626 }
627 
628 version(Windows) {
629 	version(minimal) {
630 
631 	} else {
632 		// not too concerned about gdc here since the mingw version is fairly new as well
633 		version=with_breaking_cgi_features;
634 	}
635 }
636 
637 // FIXME: can use the arsd.core function now but it is trivial anyway tbh
638 void cloexec(int fd) {
639 	version(Posix) {
640 		import core.sys.posix.fcntl;
641 		fcntl(fd, F_SETFD, FD_CLOEXEC);
642 	}
643 }
644 
645 void cloexec(Socket s) {
646 	version(Posix) {
647 		import core.sys.posix.fcntl;
648 		fcntl(s.handle, F_SETFD, FD_CLOEXEC);
649 	}
650 }
651 
652 // the servers must know about the connections to talk to them; the interfaces are vital
653 version(with_addon_servers)
654 	version=with_addon_servers_connections;
655 
656 version(embedded_httpd) {
657 	version(OSX)
658 		version = embedded_httpd_threads;
659 	else
660 		version=embedded_httpd_hybrid;
661 	/*
662 	version(with_openssl) {
663 		pragma(lib, "crypto");
664 		pragma(lib, "ssl");
665 	}
666 	*/
667 }
668 
669 version(embedded_httpd_hybrid) {
670 	version=embedded_httpd_threads;
671 	version(cgi_no_fork) {} else version(Posix)
672 		version=cgi_use_fork;
673 	version=cgi_use_fiber;
674 }
675 
676 version(cgi_use_fork)
677 	enum cgi_use_fork_default = true;
678 else
679 	enum cgi_use_fork_default = false;
680 
681 version(embedded_httpd_processes)
682 	version=embedded_httpd_processes_accept_after_fork; // I am getting much better average performance on this, so just keeping it. But the other way MIGHT help keep the variation down so i wanna keep the code to play with later
683 
684 version(embedded_httpd_threads) {
685 	//  unless the user overrides the default..
686 	version(cgi_session_server_process)
687 		{}
688 	else
689 		version=cgi_embedded_sessions;
690 }
691 version(scgi) {
692 	//  unless the user overrides the default..
693 	version(cgi_session_server_process)
694 		{}
695 	else
696 		version=cgi_embedded_sessions;
697 }
698 
699 // fall back if the other is not defined so we can cleanly version it below
700 version(cgi_embedded_sessions) {}
701 else version=cgi_session_server_process;
702 
703 
704 version=cgi_with_websocket;
705 
706 enum long defaultMaxContentLength = 5_000_000;
707 
708 /*
709 
710 	To do a file download offer in the browser:
711 
712     cgi.setResponseContentType("text/csv");
713     cgi.header("Content-Disposition: attachment; filename=\"customers.csv\"");
714 */
715 
716 // FIXME: the location header is supposed to be an absolute url I guess.
717 
718 // FIXME: would be cool to flush part of a dom document before complete
719 // somehow in here and dom.d.
720 
721 
722 // these are public so you can mixin GenericMain.
723 // FIXME: use a function level import instead!
724 public import std.string;
725 public import std.stdio;
726 public import std.conv;
727 import std.uni;
728 import std.algorithm.comparison;
729 import std.algorithm.searching;
730 import std.exception;
731 import std.base64;
732 static import std.algorithm;
733 import std.datetime;
734 import std.range;
735 
736 import std.process;
737 
738 import std.zlib;
739 
740 
741 T[] consume(T)(T[] range, int count) {
742 	if(count > range.length)
743 		count = range.length;
744 	return range[count..$];
745 }
746 
747 int locationOf(T)(T[] data, string item) {
748 	const(ubyte[]) d = cast(const(ubyte[])) data;
749 	const(ubyte[]) i = cast(const(ubyte[])) item;
750 
751 	// this is a vague sanity check to ensure we aren't getting insanely
752 	// sized input that will infinite loop below. it should never happen;
753 	// even huge file uploads ought to come in smaller individual pieces.
754 	if(d.length > (int.max/2))
755 		throw new Exception("excessive block of input");
756 
757 	for(int a = 0; a < d.length; a++) {
758 		if(a + i.length > d.length)
759 			return -1;
760 		if(d[a..a+i.length] == i)
761 			return a;
762 	}
763 
764 	return -1;
765 }
766 
767 /// If you are doing a custom cgi class, mixing this in can take care of
768 /// the required constructors for you
769 mixin template ForwardCgiConstructors() {
770 	this(long maxContentLength = defaultMaxContentLength,
771 		string[string] env = null,
772 		const(ubyte)[] delegate() readdata = null,
773 		void delegate(const(ubyte)[]) _rawDataOutput = null,
774 		void delegate() _flush = null
775 		) { super(maxContentLength, env, readdata, _rawDataOutput, _flush); }
776 
777 	this(string[] args) { super(args); }
778 
779 	this(
780 		BufferedInputRange inputData,
781 		string address, ushort _port,
782 		int pathInfoStarts = 0,
783 		bool _https = false,
784 		void delegate(const(ubyte)[]) _rawDataOutput = null,
785 		void delegate() _flush = null,
786 		// this pointer tells if the connection is supposed to be closed after we handle this
787 		bool* closeConnection = null)
788 	{
789 		super(inputData, address, _port, pathInfoStarts, _https, _rawDataOutput, _flush, closeConnection);
790 	}
791 
792 	this(BufferedInputRange ir, bool* closeConnection) { super(ir, closeConnection); }
793 }
794 
795 /// thrown when a connection is closed remotely while we waiting on data from it
796 class ConnectionClosedException : Exception {
797 	this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
798 		super(message, file, line, next);
799 	}
800 }
801 
802 
803 version(Windows) {
804 // FIXME: ugly hack to solve stdin exception problems on Windows:
805 // reading stdin results in StdioException (Bad file descriptor)
806 // this is probably due to http://d.puremagic.com/issues/show_bug.cgi?id=3425
807 private struct stdin {
808 	struct ByChunk { // Replicates std.stdio.ByChunk
809 	private:
810 		ubyte[] chunk_;
811 	public:
812 		this(size_t size)
813 		in {
814 			assert(size, "size must be larger than 0");
815 		}
816 		do {
817 			chunk_ = new ubyte[](size);
818 			popFront();
819 		}
820 
821 		@property bool empty() const {
822 			return !std.stdio.stdin.isOpen || std.stdio.stdin.eof; // Ugly, but seems to do the job
823 		}
824 		@property nothrow ubyte[] front() {	return chunk_; }
825 		void popFront()	{
826 			enforce(!empty, "Cannot call popFront on empty range");
827 			chunk_ = stdin.rawRead(chunk_);
828 		}
829 	}
830 
831 	import core.sys.windows.windows;
832 static:
833 
834 	T[] rawRead(T)(T[] buf) {
835 		uint bytesRead;
836 		auto result = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buf.ptr, cast(int) (buf.length * T.sizeof), &bytesRead, null);
837 
838 		if (!result) {
839 			auto err = GetLastError();
840 			if (err == 38/*ERROR_HANDLE_EOF*/ || err == 109/*ERROR_BROKEN_PIPE*/) // 'good' errors meaning end of input
841 				return buf[0..0];
842 			// Some other error, throw it
843 
844 			char* buffer;
845 			scope(exit) LocalFree(buffer);
846 
847 			// FORMAT_MESSAGE_ALLOCATE_BUFFER	= 0x00000100
848 			// FORMAT_MESSAGE_FROM_SYSTEM		= 0x00001000
849 			FormatMessageA(0x1100, null, err, 0, cast(char*)&buffer, 256, null);
850 			throw new Exception(to!string(buffer));
851 		}
852 		enforce(!(bytesRead % T.sizeof), "I/O error");
853 		return buf[0..bytesRead / T.sizeof];
854 	}
855 
856 	auto byChunk(size_t sz) { return ByChunk(sz); }
857 
858 	void close() {
859 		std.stdio.stdin.close;
860 	}
861 }
862 }
863 
864 /// The main interface with the web request
865 class Cgi {
866   public:
867 	/// the methods a request can be
868 	enum RequestMethod { GET, HEAD, POST, PUT, DELETE, // GET and POST are the ones that really work
869 		// these are defined in the standard, but idk if they are useful for anything
870 		OPTIONS, TRACE, CONNECT,
871 		// These seem new, I have only recently seen them
872 		PATCH, MERGE,
873 		// this is an extension for when the method is not specified and you want to assume
874 		CommandLine }
875 
876 
877 	/+
878 
879 	ubyte[] perRequestMemoryPool;
880 	void[] perRequestMemoryPoolWithPointers;
881 	// might want to just slice the buffer itself too when we happened to have gotten a full request inside it and don't need to decode
882 	// then the buffer also can be recycled if it is set.
883 
884 	// we might also be able to set memory recyclable true by default, but then the property getters set it to false. but not all the things are property getters. but realistically anything except benchmarks are gonna get something lol so meh.
885 
886 	/+
887 	struct VariableCollection {
888 		string[] opIndex(string name) {
889 
890 		}
891 	}
892 
893 	/++
894 		Call this to indicate that you've not retained any reference to the request-local memory (including all strings returned from the Cgi object) outside the request (you can .idup anything you need to store) and it is thus free to be freed or reused by another request.
895 
896 		Most handlers should be able to call this; retaining memory is the exception in any cgi program, but since I can't prove it from inside the library, it plays it safe and lets the GC manage it unless you opt into this behavior. All cgi.d functions will duplicate strings if needed (e.g. session ids from cookies) so unless you're doing something yourself, this should be ok.
897 
898 		History:
899 			Added
900 	+/
901 	public void recycleMemory() {
902 
903 	}
904 	+/
905 
906 
907 	/++
908 		Cgi provides a per-request memory pool
909 
910 	+/
911 	void[] allocateMemory(size_t nBytes) {
912 
913 	}
914 
915 	/// ditto
916 	void[] reallocateMemory(void[] old, size_t nBytes) {
917 
918 	}
919 
920 	/// ditto
921 	void freeMemory(void[] memory) {
922 
923 	}
924 	+/
925 
926 
927 /*
928 	import core.runtime;
929 	auto args = Runtime.args();
930 
931 	we can call the app a few ways:
932 
933 	1) set up the environment variables and call the app (manually simulating CGI)
934 	2) simulate a call automatically:
935 		./app method 'uri'
936 
937 		for example:
938 			./app get /path?arg arg2=something
939 
940 	  Anything on the uri is treated as query string etc
941 
942 	  on get method, further args are appended to the query string (encoded automatically)
943 	  on post method, further args are done as post
944 
945 
946 	  @name means import from file "name". if name == -, it uses stdin
947 	  (so info=@- means set info to the value of stdin)
948 
949 
950 	  Other arguments include:
951 	  	--cookie name=value (these are all concated together)
952 		--header 'X-Something: cool'
953 		--referrer 'something'
954 		--port 80
955 		--remote-address some.ip.address.here
956 		--https yes
957 		--user-agent 'something'
958 		--userpass 'user:pass'
959 		--authorization 'Basic base64encoded_user:pass'
960 		--accept 'content' // FIXME: better example
961 		--last-event-id 'something'
962 		--host 'something.com'
963 		--session name=value (these are added to a mock session, changes to the session are printed out as dummy response headers)
964 
965 	  Non-simulation arguments:
966 	  	--port xxx listening port for non-cgi things (valid for the cgi interfaces)
967 		--listening-host  the ip address the application should listen on, or if you want to use unix domain sockets, it is here you can set them: `--listening-host unix:filename` or, on Linux, `--listening-host abstract:name`.
968 
969 */
970 
971 	/** Initializes it with command line arguments (for easy testing) */
972 	this(string[] args, void delegate(const(ubyte)[]) _rawDataOutput = null) {
973 		rawDataOutput = _rawDataOutput;
974 		// these are all set locally so the loop works
975 		// without triggering errors in dmd 2.064
976 		// we go ahead and set them at the end of it to the this version
977 		int port;
978 		string referrer;
979 		string remoteAddress;
980 		string userAgent;
981 		string authorization;
982 		string origin;
983 		string accept;
984 		string lastEventId;
985 		bool https;
986 		string host;
987 		RequestMethod requestMethod;
988 		string requestUri;
989 		string pathInfo;
990 		string queryString;
991 
992 		bool lookingForMethod;
993 		bool lookingForUri;
994 		string nextArgIs;
995 
996 		string _cookie;
997 		string _queryString;
998 		string[][string] _post;
999 		string[string] _headers;
1000 
1001 		string[] breakUp(string s) {
1002 			string k, v;
1003 			auto idx = s.indexOf("=");
1004 			if(idx == -1) {
1005 				k = s;
1006 			} else {
1007 				k = s[0 .. idx];
1008 				v = s[idx + 1 .. $];
1009 			}
1010 
1011 			return [k, v];
1012 		}
1013 
1014 		lookingForMethod = true;
1015 
1016 		scriptName = args[0];
1017 		scriptFileName = args[0];
1018 
1019 		environmentVariables = cast(const) environment.toAA;
1020 
1021 		foreach(arg; args[1 .. $]) {
1022 			if(arg.startsWith("--")) {
1023 				nextArgIs = arg[2 .. $];
1024 			} else if(nextArgIs.length) {
1025 				if (nextArgIs == "cookie") {
1026 					auto info = breakUp(arg);
1027 					if(_cookie.length)
1028 						_cookie ~= "; ";
1029 					_cookie ~= encodeUriComponent(info[0]) ~ "=" ~ encodeUriComponent(info[1]);
1030 				}
1031 				if (nextArgIs == "session") {
1032 					auto info = breakUp(arg);
1033 					_commandLineSession[info[0]] = info[1];
1034 				}
1035 
1036 				else if (nextArgIs == "port") {
1037 					port = to!int(arg);
1038 				}
1039 				else if (nextArgIs == "referrer") {
1040 					referrer = arg;
1041 				}
1042 				else if (nextArgIs == "remote-address") {
1043 					remoteAddress = arg;
1044 				}
1045 				else if (nextArgIs == "user-agent") {
1046 					userAgent = arg;
1047 				}
1048 				else if (nextArgIs == "authorization") {
1049 					authorization = arg;
1050 				}
1051 				else if (nextArgIs == "userpass") {
1052 					authorization = "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (arg)).idup;
1053 				}
1054 				else if (nextArgIs == "origin") {
1055 					origin = arg;
1056 				}
1057 				else if (nextArgIs == "accept") {
1058 					accept = arg;
1059 				}
1060 				else if (nextArgIs == "last-event-id") {
1061 					lastEventId = arg;
1062 				}
1063 				else if (nextArgIs == "https") {
1064 					if(arg == "yes")
1065 						https = true;
1066 				}
1067 				else if (nextArgIs == "header") {
1068 					string thing, other;
1069 					auto idx = arg.indexOf(":");
1070 					if(idx == -1)
1071 						throw new Exception("need a colon in a http header");
1072 					thing = arg[0 .. idx];
1073 					other = arg[idx + 1.. $];
1074 					_headers[thing.strip.toLower()] = other.strip;
1075 				}
1076 				else if (nextArgIs == "host") {
1077 					host = arg;
1078 				}
1079 				// else
1080 				// skip, we don't know it but that's ok, it might be used elsewhere so no error
1081 
1082 				nextArgIs = null;
1083 			} else if(lookingForMethod) {
1084 				lookingForMethod = false;
1085 				lookingForUri = true;
1086 
1087 				if(arg.asLowerCase().equal("commandline"))
1088 					requestMethod = RequestMethod.CommandLine;
1089 				else
1090 					requestMethod = to!RequestMethod(arg.toUpper());
1091 			} else if(lookingForUri) {
1092 				lookingForUri = false;
1093 
1094 				requestUri = arg;
1095 
1096 				auto idx = arg.indexOf("?");
1097 				if(idx == -1)
1098 					pathInfo = arg;
1099 				else {
1100 					pathInfo = arg[0 .. idx];
1101 					_queryString = arg[idx + 1 .. $];
1102 				}
1103 			} else {
1104 				// it is an argument of some sort
1105 				if(requestMethod == Cgi.RequestMethod.POST || requestMethod == Cgi.RequestMethod.PATCH || requestMethod == Cgi.RequestMethod.PUT || requestMethod == Cgi.RequestMethod.CommandLine) {
1106 					auto parts = breakUp(arg);
1107 					_post[parts[0]] ~= parts[1];
1108 					allPostNamesInOrder ~= parts[0];
1109 					allPostValuesInOrder ~= parts[1];
1110 				} else {
1111 					if(_queryString.length)
1112 						_queryString ~= "&";
1113 					auto parts = breakUp(arg);
1114 					_queryString ~= encodeUriComponent(parts[0]) ~ "=" ~ encodeUriComponent(parts[1]);
1115 				}
1116 			}
1117 		}
1118 
1119 		acceptsGzip = false;
1120 		keepAliveRequested = false;
1121 		requestHeaders = cast(immutable) _headers;
1122 
1123 		cookie = _cookie;
1124 		cookiesArray =  getCookieArray();
1125 		cookies = keepLastOf(cookiesArray);
1126 
1127 		queryString = _queryString;
1128 		getArray = cast(immutable) decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
1129 		get = keepLastOf(getArray);
1130 
1131 		postArray = cast(immutable) _post;
1132 		post = keepLastOf(_post);
1133 
1134 		// FIXME
1135 		filesArray = null;
1136 		files = null;
1137 
1138 		isCalledWithCommandLineArguments = true;
1139 
1140 		this.port = port;
1141 		this.referrer = referrer;
1142 		this.remoteAddress = remoteAddress;
1143 		this.userAgent = userAgent;
1144 		this.authorization = authorization;
1145 		this.origin = origin;
1146 		this.accept = accept;
1147 		this.lastEventId = lastEventId;
1148 		this.https = https;
1149 		this.host = host;
1150 		this.requestMethod = requestMethod;
1151 		this.requestUri = requestUri;
1152 		this.pathInfo = pathInfo;
1153 		this.queryString = queryString;
1154 		this.postBody = null;
1155 		this.requestContentType = null;
1156 	}
1157 
1158 	private {
1159 		string[] allPostNamesInOrder;
1160 		string[] allPostValuesInOrder;
1161 		string[] allGetNamesInOrder;
1162 		string[] allGetValuesInOrder;
1163 	}
1164 
1165 	CgiConnectionHandle getOutputFileHandle() {
1166 		return _outputFileHandle;
1167 	}
1168 
1169 	CgiConnectionHandle _outputFileHandle = INVALID_CGI_CONNECTION_HANDLE;
1170 
1171 	/** Initializes it using a CGI or CGI-like interface */
1172 	this(long maxContentLength = defaultMaxContentLength,
1173 		// use this to override the environment variable listing
1174 		in string[string] env = null,
1175 		// and this should return a chunk of data. return empty when done
1176 		const(ubyte)[] delegate() readdata = null,
1177 		// finally, use this to do custom output if needed
1178 		void delegate(const(ubyte)[]) _rawDataOutput = null,
1179 		// to flush teh custom output
1180 		void delegate() _flush = null
1181 		)
1182 	{
1183 
1184 		// these are all set locally so the loop works
1185 		// without triggering errors in dmd 2.064
1186 		// we go ahead and set them at the end of it to the this version
1187 		int port;
1188 		string referrer;
1189 		string remoteAddress;
1190 		string userAgent;
1191 		string authorization;
1192 		string origin;
1193 		string accept;
1194 		string lastEventId;
1195 		bool https;
1196 		string host;
1197 		RequestMethod requestMethod;
1198 		string requestUri;
1199 		string pathInfo;
1200 		string queryString;
1201 
1202 
1203 
1204 		isCalledWithCommandLineArguments = false;
1205 		rawDataOutput = _rawDataOutput;
1206 		flushDelegate = _flush;
1207 		auto getenv = delegate string(string var) {
1208 			if(env is null)
1209 				return std.process.environment.get(var);
1210 			auto e = var in env;
1211 			if(e is null)
1212 				return null;
1213 			return *e;
1214 		};
1215 
1216 		environmentVariables = env is null ?
1217 			cast(const) environment.toAA :
1218 			env;
1219 
1220 		// fetching all the request headers
1221 		string[string] requestHeadersHere;
1222 		foreach(k, v; env is null ? cast(const) environment.toAA() : env) {
1223 			if(k.startsWith("HTTP_")) {
1224 				requestHeadersHere[replace(k["HTTP_".length .. $].toLower(), "_", "-")] = v;
1225 			}
1226 		}
1227 
1228 		this.requestHeaders = assumeUnique(requestHeadersHere);
1229 
1230 		requestUri = getenv("REQUEST_URI");
1231 
1232 		cookie = getenv("HTTP_COOKIE");
1233 		cookiesArray = getCookieArray();
1234 		cookies = keepLastOf(cookiesArray);
1235 
1236 		referrer = getenv("HTTP_REFERER");
1237 		userAgent = getenv("HTTP_USER_AGENT");
1238 		remoteAddress = getenv("REMOTE_ADDR");
1239 		host = getenv("HTTP_HOST");
1240 		pathInfo = getenv("PATH_INFO");
1241 
1242 		queryString = getenv("QUERY_STRING");
1243 		scriptName = getenv("SCRIPT_NAME");
1244 		{
1245 			import core.runtime;
1246 			auto sfn = getenv("SCRIPT_FILENAME");
1247 			scriptFileName = sfn.length ? sfn : (Runtime.args.length ? Runtime.args[0] : null);
1248 		}
1249 
1250 		bool iis = false;
1251 
1252 		// Because IIS doesn't pass requestUri, we simulate it here if it's empty.
1253 		if(requestUri.length == 0) {
1254 			// IIS sometimes includes the script name as part of the path info - we don't want that
1255 			if(pathInfo.length >= scriptName.length && (pathInfo[0 .. scriptName.length] == scriptName))
1256 				pathInfo = pathInfo[scriptName.length .. $];
1257 
1258 			requestUri = scriptName ~ pathInfo ~ (queryString.length ? ("?" ~ queryString) : "");
1259 
1260 			iis = true; // FIXME HACK - used in byChunk below - see bugzilla 6339
1261 
1262 			// FIXME: this works for apache and iis... but what about others?
1263 		}
1264 
1265 
1266 		auto ugh = decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
1267 		getArray = assumeUnique(ugh);
1268 		get = keepLastOf(getArray);
1269 
1270 
1271 		// NOTE: on shitpache, you need to specifically forward this
1272 		authorization = getenv("HTTP_AUTHORIZATION");
1273 		// this is a hack because Apache is a shitload of fuck and
1274 		// refuses to send the real header to us. Compatible
1275 		// programs should send both the standard and X- versions
1276 
1277 		// NOTE: if you have access to .htaccess or httpd.conf, you can make this
1278 		// unnecessary with mod_rewrite, so it is commented
1279 
1280 		//if(authorization.length == 0) // if the std is there, use it
1281 		//	authorization = getenv("HTTP_X_AUTHORIZATION");
1282 
1283 		// the REDIRECT_HTTPS check is here because with an Apache hack, the port can become wrong
1284 		if(getenv("SERVER_PORT").length && getenv("REDIRECT_HTTPS") != "on")
1285 			port = to!int(getenv("SERVER_PORT"));
1286 		else
1287 			port = 0; // this was probably called from the command line
1288 
1289 		auto ae = getenv("HTTP_ACCEPT_ENCODING");
1290 		if(ae.length && ae.indexOf("gzip") != -1)
1291 			acceptsGzip = true;
1292 
1293 		accept = getenv("HTTP_ACCEPT");
1294 		lastEventId = getenv("HTTP_LAST_EVENT_ID");
1295 
1296 		auto ka = getenv("HTTP_CONNECTION");
1297 		if(ka.length && ka.asLowerCase().canFind("keep-alive"))
1298 			keepAliveRequested = true;
1299 
1300 		auto or = getenv("HTTP_ORIGIN");
1301 			origin = or;
1302 
1303 		auto rm = getenv("REQUEST_METHOD");
1304 		if(rm.length)
1305 			requestMethod = to!RequestMethod(getenv("REQUEST_METHOD"));
1306 		else
1307 			requestMethod = RequestMethod.CommandLine;
1308 
1309 						// FIXME: hack on REDIRECT_HTTPS; this is there because the work app uses mod_rewrite which loses the https flag! So I set it with [E=HTTPS=%HTTPS] or whatever but then it gets translated to here so i want it to still work. This is arguably wrong but meh.
1310 		https = (getenv("HTTPS") == "on" || getenv("REDIRECT_HTTPS") == "on");
1311 
1312 		// FIXME: DOCUMENT_ROOT?
1313 
1314 		// FIXME: what about PUT?
1315 		if(requestMethod == RequestMethod.POST || requestMethod == Cgi.RequestMethod.PATCH || requestMethod == Cgi.RequestMethod.PUT || requestMethod == Cgi.RequestMethod.CommandLine) {
1316 			version(preserveData) // a hack to make forwarding simpler
1317 				immutable(ubyte)[] data;
1318 			size_t amountReceived = 0;
1319 			auto contentType = getenv("CONTENT_TYPE");
1320 
1321 			// FIXME: is this ever not going to be set? I guess it depends
1322 			// on if the server de-chunks and buffers... seems like it has potential
1323 			// to be slow if they did that. The spec says it is always there though.
1324 			// And it has worked reliably for me all year in the live environment,
1325 			// but some servers might be different.
1326 			auto cls = getenv("CONTENT_LENGTH");
1327 			auto contentLength = to!size_t(cls.length ? cls : "0");
1328 
1329 			immutable originalContentLength = contentLength;
1330 			if(contentLength) {
1331 				if(maxContentLength > 0 && contentLength > maxContentLength) {
1332 					setResponseStatus("413 Request entity too large");
1333 					write("You tried to upload a file that is too large.");
1334 					close();
1335 					throw new Exception("POST too large");
1336 				}
1337 				prepareForIncomingDataChunks(contentType, contentLength);
1338 
1339 
1340 				int processChunk(in ubyte[] chunk) {
1341 					if(chunk.length > contentLength) {
1342 						handleIncomingDataChunk(chunk[0..contentLength]);
1343 						amountReceived += contentLength;
1344 						contentLength = 0;
1345 						return 1;
1346 					} else {
1347 						handleIncomingDataChunk(chunk);
1348 						contentLength -= chunk.length;
1349 						amountReceived += chunk.length;
1350 					}
1351 					if(contentLength == 0)
1352 						return 1;
1353 
1354 					onRequestBodyDataReceived(amountReceived, originalContentLength);
1355 					return 0;
1356 				}
1357 
1358 
1359 				if(readdata is null) {
1360 					foreach(ubyte[] chunk; stdin.byChunk(iis ? contentLength : 4096))
1361 						if(processChunk(chunk))
1362 							break;
1363 				} else {
1364 					// we have a custom data source..
1365 					auto chunk = readdata();
1366 					while(chunk.length) {
1367 						if(processChunk(chunk))
1368 							break;
1369 						chunk = readdata();
1370 					}
1371 				}
1372 
1373 				onRequestBodyDataReceived(amountReceived, originalContentLength);
1374 				postArray = assumeUnique(pps._post);
1375 				filesArray = assumeUnique(pps._files);
1376 				files = keepLastOf(filesArray);
1377 				post = keepLastOf(postArray);
1378 				this.postBody = pps.postBody;
1379 				this.requestContentType = contentType;
1380 				cleanUpPostDataState();
1381 			}
1382 
1383 			version(preserveData)
1384 				originalPostData = data;
1385 		}
1386 		// fixme: remote_user script name
1387 
1388 
1389 		this.port = port;
1390 		this.referrer = referrer;
1391 		this.remoteAddress = remoteAddress;
1392 		this.userAgent = userAgent;
1393 		this.authorization = authorization;
1394 		this.origin = origin;
1395 		this.accept = accept;
1396 		this.lastEventId = lastEventId;
1397 		this.https = https;
1398 		this.host = host;
1399 		this.requestMethod = requestMethod;
1400 		this.requestUri = requestUri;
1401 		this.pathInfo = pathInfo;
1402 		this.queryString = queryString;
1403 	}
1404 
1405 	/// Cleans up any temporary files. Do not use the object
1406 	/// after calling this.
1407 	///
1408 	/// NOTE: it is called automatically by GenericMain
1409 	// FIXME: this should be called if the constructor fails too, if it has created some garbage...
1410 	void dispose() {
1411 		foreach(file; files) {
1412 			if(!file.contentInMemory)
1413 				if(std.file.exists(file.contentFilename))
1414 					std.file.remove(file.contentFilename);
1415 		}
1416 	}
1417 
1418 	private {
1419 		struct PostParserState {
1420 			string contentType;
1421 			string boundary;
1422 			string localBoundary; // the ones used at the end or something lol
1423 			bool isMultipart;
1424 			bool needsSavedBody;
1425 
1426 			ulong expectedLength;
1427 			ulong contentConsumed;
1428 			immutable(ubyte)[] buffer;
1429 
1430 			// multipart parsing state
1431 			int whatDoWeWant;
1432 			bool weHaveAPart;
1433 			string[] thisOnesHeaders;
1434 			immutable(ubyte)[] thisOnesData;
1435 
1436 			string postBody;
1437 
1438 			UploadedFile piece;
1439 			bool isFile = false;
1440 
1441 			size_t memoryCommitted;
1442 
1443 			// do NOT keep mutable references to these anywhere!
1444 			// I assume they are unique in the constructor once we're all done getting data.
1445 			string[][string] _post;
1446 			UploadedFile[][string] _files;
1447 		}
1448 
1449 		PostParserState pps;
1450 	}
1451 
1452 	/// This represents a file the user uploaded via a POST request.
1453 	static struct UploadedFile {
1454 		/// If you want to create one of these structs for yourself from some data,
1455 		/// use this function.
1456 		static UploadedFile fromData(immutable(void)[] data, string name = null) {
1457 			Cgi.UploadedFile f;
1458 			f.filename = name;
1459 			f.content = cast(immutable(ubyte)[]) data;
1460 			f.contentInMemory = true;
1461 			return f;
1462 		}
1463 
1464 		string name; 		/// The name of the form element.
1465 		string filename; 	/// The filename the user set.
1466 		string contentType; 	/// The MIME type the user's browser reported. (Not reliable.)
1467 
1468 		/**
1469 			For small files, cgi.d will buffer the uploaded file in memory, and make it
1470 			directly accessible to you through the content member. I find this very convenient
1471 			and somewhat efficient, since it can avoid hitting the disk entirely. (I
1472 			often want to inspect and modify the file anyway!)
1473 
1474 			I find the file is very large, it is undesirable to eat that much memory just
1475 			for a file buffer. In those cases, if you pass a large enough value for maxContentLength
1476 			to the constructor so they are accepted, cgi.d will write the content to a temporary
1477 			file that you can re-read later.
1478 
1479 			You can override this behavior by subclassing Cgi and overriding the protected
1480 			handlePostChunk method. Note that the object is not initialized when you
1481 			write that method - the http headers are available, but the cgi.post method
1482 			is not. You may parse the file as it streams in using this method.
1483 
1484 
1485 			Anyway, if the file is small enough to be in memory, contentInMemory will be
1486 			set to true, and the content is available in the content member.
1487 
1488 			If not, contentInMemory will be set to false, and the content saved in a file,
1489 			whose name will be available in the contentFilename member.
1490 
1491 
1492 			Tip: if you know you are always dealing with small files, and want the convenience
1493 			of ignoring this member, construct Cgi with a small maxContentLength. Then, if
1494 			a large file comes in, it simply throws an exception (and HTTP error response)
1495 			instead of trying to handle it.
1496 
1497 			The default value of maxContentLength in the constructor is for small files.
1498 		*/
1499 		bool contentInMemory = true; // the default ought to always be true
1500 		immutable(ubyte)[] content; /// The actual content of the file, if contentInMemory == true
1501 		string contentFilename; /// the file where we dumped the content, if contentInMemory == false. Note that if you want to keep it, you MUST move the file, since otherwise it is considered garbage when cgi is disposed.
1502 
1503 		///
1504 		ulong fileSize() const {
1505 			if(contentInMemory)
1506 				return content.length;
1507 			import std.file;
1508 			return std.file.getSize(contentFilename);
1509 
1510 		}
1511 
1512 		///
1513 		void writeToFile(string filenameToSaveTo) const {
1514 			import std.file;
1515 			if(contentInMemory)
1516 				std.file.write(filenameToSaveTo, content);
1517 			else
1518 				std.file.rename(contentFilename, filenameToSaveTo);
1519 		}
1520 	}
1521 
1522 	// given a content type and length, decide what we're going to do with the data..
1523 	protected void prepareForIncomingDataChunks(string contentType, ulong contentLength) {
1524 		pps.expectedLength = contentLength;
1525 
1526 		auto terminator = contentType.indexOf(";");
1527 		if(terminator == -1)
1528 			terminator = contentType.length;
1529 
1530 		pps.contentType = contentType[0 .. terminator];
1531 		auto b = contentType[terminator .. $];
1532 		if(b.length) {
1533 			auto idx = b.indexOf("boundary=");
1534 			if(idx != -1) {
1535 				pps.boundary = b[idx + "boundary=".length .. $];
1536 				pps.localBoundary = "\r\n--" ~ pps.boundary;
1537 			}
1538 		}
1539 
1540 		// while a content type SHOULD be sent according to the RFC, it is
1541 		// not required. We're told we SHOULD guess by looking at the content
1542 		// but it seems to me that this only happens when it is urlencoded.
1543 		if(pps.contentType == "application/x-www-form-urlencoded" || pps.contentType == "") {
1544 			pps.isMultipart = false;
1545 			pps.needsSavedBody = false;
1546 		} else if(pps.contentType == "multipart/form-data") {
1547 			pps.isMultipart = true;
1548 			enforce(pps.boundary.length, "no boundary");
1549 		} else if(pps.contentType == "text/xml") { // FIXME: could this be special and load the post params
1550 			// save the body so the application can handle it
1551 			pps.isMultipart = false;
1552 			pps.needsSavedBody = true;
1553 		} else if(pps.contentType == "application/json") { // FIXME: this could prolly try to load post params too
1554 			// save the body so the application can handle it
1555 			pps.needsSavedBody = true;
1556 			pps.isMultipart = false;
1557 		} else {
1558 			// the rest is 100% handled by the application. just save the body and send it to them
1559 			pps.needsSavedBody = true;
1560 			pps.isMultipart = false;
1561 		}
1562 	}
1563 
1564 	// handles streaming POST data. If you handle some other content type, you should
1565 	// override this. If the data isn't the content type you want, you ought to call
1566 	// super.handleIncomingDataChunk so regular forms and files still work.
1567 
1568 	// FIXME: I do some copying in here that I'm pretty sure is unnecessary, and the
1569 	// file stuff I'm sure is inefficient. But, my guess is the real bottleneck is network
1570 	// input anyway, so I'm not going to get too worked up about it right now.
1571 	protected void handleIncomingDataChunk(const(ubyte)[] chunk) {
1572 		if(chunk.length == 0)
1573 			return;
1574 		assert(chunk.length <= 32 * 1024 * 1024); // we use chunk size as a memory constraint thing, so
1575 							// if we're passed big chunks, it might throw unnecessarily.
1576 							// just pass it smaller chunks at a time.
1577 		if(pps.isMultipart) {
1578 			// multipart/form-data
1579 
1580 
1581 			// FIXME: this might want to be factored out and factorized
1582 			// need to make sure the stream hooks actually work.
1583 			void pieceHasNewContent() {
1584 				// we just grew the piece's buffer. Do we have to switch to file backing?
1585 				if(pps.piece.contentInMemory) {
1586 					if(pps.piece.content.length <= 10 * 1024 * 1024)
1587 						// meh, I'm ok with it.
1588 						return;
1589 					else {
1590 						// this is too big.
1591 						if(!pps.isFile)
1592 							throw new Exception("Request entity too large"); // a variable this big is kinda ridiculous, just reject it.
1593 						else {
1594 							// a file this large is probably acceptable though... let's use a backing file.
1595 							pps.piece.contentInMemory = false;
1596 							// FIXME: say... how do we intend to delete these things? cgi.dispose perhaps.
1597 
1598 							int count = 0;
1599 							pps.piece.contentFilename = getTempDirectory() ~ "arsd_cgi_uploaded_file_" ~ to!string(getUtcTime()) ~ "-" ~ to!string(count);
1600 							// odds are this loop will never be entered, but we want it just in case.
1601 							while(std.file.exists(pps.piece.contentFilename)) {
1602 								count++;
1603 								pps.piece.contentFilename = getTempDirectory() ~ "arsd_cgi_uploaded_file_" ~ to!string(getUtcTime()) ~ "-" ~ to!string(count);
1604 							}
1605 							// I hope this creates the file pretty quickly, or the loop might be useless...
1606 							// FIXME: maybe I should write some kind of custom transaction here.
1607 							std.file.write(pps.piece.contentFilename, pps.piece.content);
1608 
1609 							pps.piece.content = null;
1610 						}
1611 					}
1612 				} else {
1613 					// it's already in a file, so just append it to what we have
1614 					if(pps.piece.content.length) {
1615 						// FIXME: this is surely very inefficient... we'll be calling this by 4kb chunk...
1616 						std.file.append(pps.piece.contentFilename, pps.piece.content);
1617 						pps.piece.content = null;
1618 					}
1619 				}
1620 			}
1621 
1622 
1623 			void commitPart() {
1624 				if(!pps.weHaveAPart)
1625 					return;
1626 
1627 				pieceHasNewContent(); // be sure the new content is handled every time
1628 
1629 				if(pps.isFile) {
1630 					// I'm not sure if other environments put files in post or not...
1631 					// I used to not do it, but I think I should, since it is there...
1632 					pps._post[pps.piece.name] ~= pps.piece.filename;
1633 					pps._files[pps.piece.name] ~= pps.piece;
1634 
1635 					allPostNamesInOrder ~= pps.piece.name;
1636 					allPostValuesInOrder ~= pps.piece.filename;
1637 				} else {
1638 					pps._post[pps.piece.name] ~= cast(string) pps.piece.content;
1639 
1640 					allPostNamesInOrder ~= pps.piece.name;
1641 					allPostValuesInOrder ~= cast(string) pps.piece.content;
1642 				}
1643 
1644 				/*
1645 				stderr.writeln("RECEIVED: ", pps.piece.name, "=",
1646 					pps.piece.content.length < 1000
1647 					?
1648 					to!string(pps.piece.content)
1649 					:
1650 					"too long");
1651 				*/
1652 
1653 				// FIXME: the limit here
1654 				pps.memoryCommitted += pps.piece.content.length;
1655 
1656 				pps.weHaveAPart = false;
1657 				pps.whatDoWeWant = 1;
1658 				pps.thisOnesHeaders = null;
1659 				pps.thisOnesData = null;
1660 
1661 				pps.piece = UploadedFile.init;
1662 				pps.isFile = false;
1663 			}
1664 
1665 			void acceptChunk() {
1666 				pps.buffer ~= chunk;
1667 				chunk = null; // we've consumed it into the buffer, so keeping it just brings confusion
1668 			}
1669 
1670 			immutable(ubyte)[] consume(size_t howMuch) {
1671 				pps.contentConsumed += howMuch;
1672 				auto ret = pps.buffer[0 .. howMuch];
1673 				pps.buffer = pps.buffer[howMuch .. $];
1674 				return ret;
1675 			}
1676 
1677 			dataConsumptionLoop: do {
1678 			switch(pps.whatDoWeWant) {
1679 				default: assert(0);
1680 				case 0:
1681 					acceptChunk();
1682 					// the format begins with two extra leading dashes, then we should be at the boundary
1683 					if(pps.buffer.length < 2)
1684 						return;
1685 					assert(pps.buffer[0] == '-', "no leading dash");
1686 					consume(1);
1687 					assert(pps.buffer[0] == '-', "no second leading dash");
1688 					consume(1);
1689 
1690 					pps.whatDoWeWant = 1;
1691 					goto case 1;
1692 				/* fallthrough */
1693 				case 1: // looking for headers
1694 					// here, we should be lined up right at the boundary, which is followed by a \r\n
1695 
1696 					// want to keep the buffer under control in case we're under attack
1697 					//stderr.writeln("here once");
1698 					//if(pps.buffer.length + chunk.length > 70 * 1024) // they should be < 1 kb really....
1699 					//	throw new Exception("wtf is up with the huge mime part headers");
1700 
1701 					acceptChunk();
1702 
1703 					if(pps.buffer.length < pps.boundary.length)
1704 						return; // not enough data, since there should always be a boundary here at least
1705 
1706 					if(pps.contentConsumed + pps.boundary.length + 6 == pps.expectedLength) {
1707 						assert(pps.buffer.length == pps.boundary.length + 4 + 2); // --, --, and \r\n
1708 						// we *should* be at the end here!
1709 						assert(pps.buffer[0] == '-');
1710 						consume(1);
1711 						assert(pps.buffer[0] == '-');
1712 						consume(1);
1713 
1714 						// the message is terminated by --BOUNDARY--\r\n (after a \r\n leading to the boundary)
1715 						assert(pps.buffer[0 .. pps.boundary.length] == cast(const(ubyte[])) pps.boundary,
1716 							"not lined up on boundary " ~ pps.boundary);
1717 						consume(pps.boundary.length);
1718 
1719 						assert(pps.buffer[0] == '-');
1720 						consume(1);
1721 						assert(pps.buffer[0] == '-');
1722 						consume(1);
1723 
1724 						assert(pps.buffer[0] == '\r');
1725 						consume(1);
1726 						assert(pps.buffer[0] == '\n');
1727 						consume(1);
1728 
1729 						assert(pps.buffer.length == 0);
1730 						assert(pps.contentConsumed == pps.expectedLength);
1731 						break dataConsumptionLoop; // we're done!
1732 					} else {
1733 						// we're not done yet. We should be lined up on a boundary.
1734 
1735 						// But, we want to ensure the headers are here before we consume anything!
1736 						auto headerEndLocation = locationOf(pps.buffer, "\r\n\r\n");
1737 						if(headerEndLocation == -1)
1738 							return; // they *should* all be here, so we can handle them all at once.
1739 
1740 						assert(pps.buffer[0 .. pps.boundary.length] == cast(const(ubyte[])) pps.boundary,
1741 							"not lined up on boundary " ~ pps.boundary);
1742 
1743 						consume(pps.boundary.length);
1744 						// the boundary is always followed by a \r\n
1745 						assert(pps.buffer[0] == '\r');
1746 						consume(1);
1747 						assert(pps.buffer[0] == '\n');
1748 						consume(1);
1749 					}
1750 
1751 					// re-running since by consuming the boundary, we invalidate the old index.
1752 					auto headerEndLocation = locationOf(pps.buffer, "\r\n\r\n");
1753 					assert(headerEndLocation >= 0, "no header");
1754 					auto thisOnesHeaders = pps.buffer[0..headerEndLocation];
1755 
1756 					consume(headerEndLocation + 4); // The +4 is the \r\n\r\n that caps it off
1757 
1758 					pps.thisOnesHeaders = split(cast(string) thisOnesHeaders, "\r\n");
1759 
1760 					// now we'll parse the headers
1761 					foreach(h; pps.thisOnesHeaders) {
1762 						auto p = h.indexOf(":");
1763 						assert(p != -1, "no colon in header, got " ~ to!string(pps.thisOnesHeaders));
1764 						string hn = h[0..p];
1765 						string hv = h[p+2..$];
1766 
1767 						switch(hn.toLower) {
1768 							default: assert(0);
1769 							case "content-disposition":
1770 								auto info = hv.split("; ");
1771 								foreach(i; info[1..$]) { // skipping the form-data
1772 									auto o = i.split("="); // FIXME
1773 									string pn = o[0];
1774 									string pv = o[1][1..$-1];
1775 
1776 									if(pn == "name") {
1777 										pps.piece.name = pv;
1778 									} else if (pn == "filename") {
1779 										pps.piece.filename = pv;
1780 										pps.isFile = true;
1781 									}
1782 								}
1783 							break;
1784 							case "content-type":
1785 								pps.piece.contentType = hv;
1786 							break;
1787 						}
1788 					}
1789 
1790 					pps.whatDoWeWant++; // move to the next step - the data
1791 				break;
1792 				case 2:
1793 					// when we get here, pps.buffer should contain our first chunk of data
1794 
1795 					if(pps.buffer.length + chunk.length > 8 * 1024 * 1024) // we might buffer quite a bit but not much
1796 						throw new Exception("wtf is up with the huge mime part buffer");
1797 
1798 					acceptChunk();
1799 
1800 					// so the trick is, we want to process all the data up to the boundary,
1801 					// but what if the chunk's end cuts the boundary off? If we're unsure, we
1802 					// want to wait for the next chunk. We start by looking for the whole boundary
1803 					// in the buffer somewhere.
1804 
1805 					auto boundaryLocation = locationOf(pps.buffer, pps.localBoundary);
1806 					// assert(boundaryLocation != -1, "should have seen "~to!string(cast(ubyte[]) pps.localBoundary)~" in " ~ to!string(pps.buffer));
1807 					if(boundaryLocation != -1) {
1808 						// this is easy - we can see it in it's entirety!
1809 
1810 						pps.piece.content ~= consume(boundaryLocation);
1811 
1812 						assert(pps.buffer[0] == '\r');
1813 						consume(1);
1814 						assert(pps.buffer[0] == '\n');
1815 						consume(1);
1816 						assert(pps.buffer[0] == '-');
1817 						consume(1);
1818 						assert(pps.buffer[0] == '-');
1819 						consume(1);
1820 						// the boundary here is always preceded by \r\n--, which is why we used localBoundary instead of boundary to locate it. Cut that off.
1821 						pps.weHaveAPart = true;
1822 						pps.whatDoWeWant = 1; // back to getting headers for the next part
1823 
1824 						commitPart(); // we're done here
1825 					} else {
1826 						// we can't see the whole thing, but what if there's a partial boundary?
1827 
1828 						enforce(pps.localBoundary.length < 128); // the boundary ought to be less than a line...
1829 						assert(pps.localBoundary.length > 1); // should already be sane but just in case
1830 						bool potentialBoundaryFound = false;
1831 
1832 						boundaryCheck: for(int a = 1; a < pps.localBoundary.length; a++) {
1833 							// we grow the boundary a bit each time. If we think it looks the
1834 							// same, better pull another chunk to be sure it's not the end.
1835 							// Starting small because exiting the loop early is desirable, since
1836 							// we're not keeping any ambiguity and 1 / 256 chance of exiting is
1837 							// the best we can do.
1838 							if(a > pps.buffer.length)
1839 								break; // FIXME: is this right?
1840 							assert(a <= pps.buffer.length);
1841 							assert(a > 0);
1842 							if(std.algorithm.endsWith(pps.buffer, pps.localBoundary[0 .. a])) {
1843 								// ok, there *might* be a boundary here, so let's
1844 								// not treat the end as data yet. The rest is good to
1845 								// use though, since if there was a boundary there, we'd
1846 								// have handled it up above after locationOf.
1847 
1848 								pps.piece.content ~= pps.buffer[0 .. $ - a];
1849 								consume(pps.buffer.length - a);
1850 								pieceHasNewContent();
1851 								potentialBoundaryFound = true;
1852 								break boundaryCheck;
1853 							}
1854 						}
1855 
1856 						if(!potentialBoundaryFound) {
1857 							// we can consume the whole thing
1858 							pps.piece.content ~= pps.buffer;
1859 							pieceHasNewContent();
1860 							consume(pps.buffer.length);
1861 						} else {
1862 							// we found a possible boundary, but there was
1863 							// insufficient data to be sure.
1864 							assert(pps.buffer == cast(const(ubyte[])) pps.localBoundary[0 .. pps.buffer.length]);
1865 
1866 							return; // wait for the next chunk.
1867 						}
1868 					}
1869 			}
1870 			} while(pps.buffer.length);
1871 
1872 			// btw all boundaries except the first should have a \r\n before them
1873 		} else {
1874 			// application/x-www-form-urlencoded and application/json
1875 
1876 				// not using maxContentLength because that might be cranked up to allow
1877 				// large file uploads. We can handle them, but a huge post[] isn't any good.
1878 			if(pps.buffer.length + chunk.length > 8 * 1024 * 1024) // surely this is plenty big enough
1879 				throw new Exception("wtf is up with such a gigantic form submission????");
1880 
1881 			pps.buffer ~= chunk;
1882 
1883 			// simple handling, but it works... until someone bombs us with gigabytes of crap at least...
1884 			if(pps.buffer.length == pps.expectedLength) {
1885 				if(pps.needsSavedBody)
1886 					pps.postBody = cast(string) pps.buffer;
1887 				else
1888 					pps._post = decodeVariables(cast(string) pps.buffer, "&", &allPostNamesInOrder, &allPostValuesInOrder);
1889 				version(preserveData)
1890 					originalPostData = pps.buffer;
1891 			} else {
1892 				// just for debugging
1893 			}
1894 		}
1895 	}
1896 
1897 	protected void cleanUpPostDataState() {
1898 		pps = PostParserState.init;
1899 	}
1900 
1901 	/// you can override this function to somehow react
1902 	/// to an upload in progress.
1903 	///
1904 	/// Take note that parts of the CGI object is not yet
1905 	/// initialized! Stuff from HTTP headers, including get[], is usable.
1906 	/// But, none of post[] is usable, and you cannot write here. That's
1907 	/// why this method is const - mutating the object won't do much anyway.
1908 	///
1909 	/// My idea here was so you can output a progress bar or
1910 	/// something to a cooperative client (see arsd.rtud for a potential helper)
1911 	///
1912 	/// The default is to do nothing. Subclass cgi and use the
1913 	/// CustomCgiMain mixin to do something here.
1914 	void onRequestBodyDataReceived(size_t receivedSoFar, size_t totalExpected) const {
1915 		// This space intentionally left blank.
1916 	}
1917 
1918 	/// Initializes the cgi from completely raw HTTP data. The ir must have a Socket source.
1919 	/// *closeConnection will be set to true if you should close the connection after handling this request
1920 	this(BufferedInputRange ir, bool* closeConnection) {
1921 		isCalledWithCommandLineArguments = false;
1922 		import al = std.algorithm;
1923 
1924 		immutable(ubyte)[] data;
1925 
1926 		void rdo(const(ubyte)[] d) {
1927 		//import std.stdio; writeln(d);
1928 			sendAll(ir.source, d);
1929 		}
1930 
1931 		auto ira = ir.source.remoteAddress();
1932 		auto irLocalAddress = ir.source.localAddress();
1933 
1934 		ushort port = 80;
1935 		if(auto ia = cast(InternetAddress) irLocalAddress) {
1936 			port = ia.port;
1937 		} else if(auto ia = cast(Internet6Address) irLocalAddress) {
1938 			port = ia.port;
1939 		}
1940 
1941 		// that check for UnixAddress is to work around a Phobos bug
1942 		// see: https://github.com/dlang/phobos/pull/7383
1943 		// but this might be more useful anyway tbh for this case
1944 		version(Posix)
1945 		this(ir, ira is null ? null : cast(UnixAddress) ira ? "unix:" : ira.toString(), port, 0, false, &rdo, null, closeConnection);
1946 		else
1947 		this(ir, ira is null ? null : ira.toString(), port, 0, false, &rdo, null, closeConnection);
1948 	}
1949 
1950 	/**
1951 		Initializes it from raw HTTP request data. GenericMain uses this when you compile with -version=embedded_httpd.
1952 
1953 		NOTE: If you are behind a reverse proxy, the values here might not be what you expect.... it will use X-Forwarded-For for remote IP and X-Forwarded-Host for host
1954 
1955 		Params:
1956 			inputData = the incoming data, including headers and other raw http data.
1957 				When the constructor exits, it will leave this range exactly at the start of
1958 				the next request on the connection (if there is one).
1959 
1960 			address = the IP address of the remote user
1961 			_port = the port number of the connection
1962 			pathInfoStarts = the offset into the path component of the http header where the SCRIPT_NAME ends and the PATH_INFO begins.
1963 			_https = if this connection is encrypted (note that the input data must not actually be encrypted)
1964 			_rawDataOutput = delegate to accept response data. It should write to the socket or whatever; Cgi does all the needed processing to speak http.
1965 			_flush = if _rawDataOutput buffers, this delegate should flush the buffer down the wire
1966 			closeConnection = if the request asks to close the connection, *closeConnection == true.
1967 	*/
1968 	this(
1969 		BufferedInputRange inputData,
1970 //		string[] headers, immutable(ubyte)[] data,
1971 		string address, ushort _port,
1972 		int pathInfoStarts = 0, // use this if you know the script name, like if this is in a folder in a bigger web environment
1973 		bool _https = false,
1974 		void delegate(const(ubyte)[]) _rawDataOutput = null,
1975 		void delegate() _flush = null,
1976 		// this pointer tells if the connection is supposed to be closed after we handle this
1977 		bool* closeConnection = null)
1978 	{
1979 		// these are all set locally so the loop works
1980 		// without triggering errors in dmd 2.064
1981 		// we go ahead and set them at the end of it to the this version
1982 		int port;
1983 		string referrer;
1984 		string remoteAddress;
1985 		string userAgent;
1986 		string authorization;
1987 		string origin;
1988 		string accept;
1989 		string lastEventId;
1990 		bool https;
1991 		string host;
1992 		RequestMethod requestMethod;
1993 		string requestUri;
1994 		string pathInfo;
1995 		string queryString;
1996 		string scriptName;
1997 		string[string] get;
1998 		string[][string] getArray;
1999 		bool keepAliveRequested;
2000 		bool acceptsGzip;
2001 		string cookie;
2002 
2003 
2004 
2005 		environmentVariables = cast(const) environment.toAA;
2006 
2007 		idlol = inputData;
2008 
2009 		isCalledWithCommandLineArguments = false;
2010 
2011 		https = _https;
2012 		port = _port;
2013 
2014 		rawDataOutput = _rawDataOutput;
2015 		flushDelegate = _flush;
2016 		nph = true;
2017 
2018 		remoteAddress = address;
2019 
2020 		// streaming parser
2021 		import al = std.algorithm;
2022 
2023 			// FIXME: tis cast is technically wrong, but Phobos deprecated al.indexOf... for some reason.
2024 		auto idx = indexOf(cast(string) inputData.front(), "\r\n\r\n");
2025 		while(idx == -1) {
2026 			inputData.popFront(0);
2027 			idx = indexOf(cast(string) inputData.front(), "\r\n\r\n");
2028 		}
2029 
2030 		assert(idx != -1);
2031 
2032 
2033 		string contentType = "";
2034 		string[string] requestHeadersHere;
2035 
2036 		size_t contentLength;
2037 
2038 		bool isChunked;
2039 
2040 		{
2041 			import core.runtime;
2042 			scriptFileName = Runtime.args.length ? Runtime.args[0] : null;
2043 		}
2044 
2045 
2046 		int headerNumber = 0;
2047 		foreach(line; al.splitter(inputData.front()[0 .. idx], "\r\n"))
2048 		if(line.length) {
2049 			headerNumber++;
2050 			auto header = cast(string) line.idup;
2051 			if(headerNumber == 1) {
2052 				// request line
2053 				auto parts = al.splitter(header, " ");
2054 				if(parts.front == "PRI") {
2055 					// this is an HTTP/2.0 line - "PRI * HTTP/2.0" - which indicates their payload will follow
2056 					// we're going to immediately refuse this, im not interested in implementing http2 (it is unlikely
2057 					// to bring me benefit)
2058 					throw new HttpVersionNotSupportedException();
2059 				}
2060 				requestMethod = to!RequestMethod(parts.front);
2061 				parts.popFront();
2062 				requestUri = parts.front;
2063 
2064 				// FIXME:  the requestUri could be an absolute path!!! should I rename it or something?
2065 				scriptName = requestUri[0 .. pathInfoStarts];
2066 
2067 				auto question = requestUri.indexOf("?");
2068 				if(question == -1) {
2069 					queryString = "";
2070 					// FIXME: double check, this might be wrong since it could be url encoded
2071 					pathInfo = requestUri[pathInfoStarts..$];
2072 				} else {
2073 					queryString = requestUri[question+1..$];
2074 					pathInfo = requestUri[pathInfoStarts..question];
2075 				}
2076 
2077 				auto ugh = decodeVariables(queryString, "&", &allGetNamesInOrder, &allGetValuesInOrder);
2078 				getArray = cast(string[][string]) assumeUnique(ugh);
2079 
2080 				if(header.indexOf("HTTP/1.0") != -1) {
2081 					http10 = true;
2082 					autoBuffer = true;
2083 					if(closeConnection) {
2084 						// on http 1.0, close is assumed (unlike http/1.1 where we assume keep alive)
2085 						*closeConnection = true;
2086 					}
2087 				}
2088 			} else {
2089 				// other header
2090 				auto colon = header.indexOf(":");
2091 				if(colon == -1)
2092 					throw new Exception("HTTP headers should have a colon!");
2093 				string name = header[0..colon].toLower;
2094 				string value = header[colon+2..$]; // skip the colon and the space
2095 
2096 				requestHeadersHere[name] = value;
2097 
2098 				if (name == "accept") {
2099 					accept = value;
2100 				}
2101 				else if (name == "origin") {
2102 					origin = value;
2103 				}
2104 				else if (name == "connection") {
2105 					if(value == "close" && closeConnection)
2106 						*closeConnection = true;
2107 					if(value.asLowerCase().canFind("keep-alive")) {
2108 						keepAliveRequested = true;
2109 
2110 						// on http 1.0, the connection is closed by default,
2111 						// but not if they request keep-alive. then we don't close
2112 						// anymore - undoing the set above
2113 						if(http10 && closeConnection) {
2114 							*closeConnection = false;
2115 						}
2116 					}
2117 				}
2118 				else if (name == "transfer-encoding") {
2119 					if(value == "chunked")
2120 						isChunked = true;
2121 				}
2122 				else if (name == "last-event-id") {
2123 					lastEventId = value;
2124 				}
2125 				else if (name == "authorization") {
2126 					authorization = value;
2127 				}
2128 				else if (name == "content-type") {
2129 					contentType = value;
2130 				}
2131 				else if (name == "content-length") {
2132 					contentLength = to!size_t(value);
2133 				}
2134 				else if (name == "x-forwarded-for") {
2135 					remoteAddress = value;
2136 				}
2137 				else if (name == "x-forwarded-host" || name == "host") {
2138 					if(name != "host" || host is null)
2139 						host = value;
2140 				}
2141 				// FIXME: https://tools.ietf.org/html/rfc7239
2142 				else if (name == "accept-encoding") {
2143 					if(value.indexOf("gzip") != -1)
2144 						acceptsGzip = true;
2145 				}
2146 				else if (name == "user-agent") {
2147 					userAgent = value;
2148 				}
2149 				else if (name == "referer") {
2150 					referrer = value;
2151 				}
2152 				else if (name == "cookie") {
2153 					cookie ~= value;
2154 				} else if(name == "expect") {
2155 					if(value == "100-continue") {
2156 						// FIXME we should probably give user code a chance
2157 						// to process and reject but that needs to be virtual,
2158 						// perhaps part of the CGI redesign.
2159 
2160 						// FIXME: if size is > max content length it should
2161 						// also fail at this point.
2162 						_rawDataOutput(cast(ubyte[]) "HTTP/1.1 100 Continue\r\n\r\n");
2163 
2164 						// FIXME: let the user write out 103 early hints too
2165 					}
2166 				}
2167 				// else
2168 				// ignore it
2169 
2170 			}
2171 		}
2172 
2173 		inputData.consume(idx + 4);
2174 		// done
2175 
2176 		requestHeaders = assumeUnique(requestHeadersHere);
2177 
2178 		ByChunkRange dataByChunk;
2179 
2180 		// reading Content-Length type data
2181 		// We need to read up the data we have, and write it out as a chunk.
2182 		if(!isChunked) {
2183 			dataByChunk = byChunk(inputData, contentLength);
2184 		} else {
2185 			// chunked requests happen, but not every day. Since we need to know
2186 			// the content length (for now, maybe that should change), we'll buffer
2187 			// the whole thing here instead of parse streaming. (I think this is what Apache does anyway in cgi modes)
2188 			auto data = dechunk(inputData);
2189 
2190 			// set the range here
2191 			dataByChunk = byChunk(data);
2192 			contentLength = data.length;
2193 		}
2194 
2195 		assert(dataByChunk !is null);
2196 
2197 		if(contentLength) {
2198 			prepareForIncomingDataChunks(contentType, contentLength);
2199 			foreach(dataChunk; dataByChunk) {
2200 				handleIncomingDataChunk(dataChunk);
2201 			}
2202 			postArray = assumeUnique(pps._post);
2203 			filesArray = assumeUnique(pps._files);
2204 			files = keepLastOf(filesArray);
2205 			post = keepLastOf(postArray);
2206 			postBody = pps.postBody;
2207 			this.requestContentType = contentType;
2208 
2209 			cleanUpPostDataState();
2210 		}
2211 
2212 		this.port = port;
2213 		this.referrer = referrer;
2214 		this.remoteAddress = remoteAddress;
2215 		this.userAgent = userAgent;
2216 		this.authorization = authorization;
2217 		this.origin = origin;
2218 		this.accept = accept;
2219 		this.lastEventId = lastEventId;
2220 		this.https = https;
2221 		this.host = host;
2222 		this.requestMethod = requestMethod;
2223 		this.requestUri = requestUri;
2224 		this.pathInfo = pathInfo;
2225 		this.queryString = queryString;
2226 
2227 		this.scriptName = scriptName;
2228 		this.get = keepLastOf(getArray);
2229 		this.getArray = cast(immutable) getArray;
2230 		this.keepAliveRequested = keepAliveRequested;
2231 		this.acceptsGzip = acceptsGzip;
2232 		this.cookie = cookie;
2233 
2234 		cookiesArray = getCookieArray();
2235 		cookies = keepLastOf(cookiesArray);
2236 
2237 	}
2238 	BufferedInputRange idlol;
2239 
2240 	private immutable(string[string]) keepLastOf(in string[][string] arr) {
2241 		string[string] ca;
2242 		foreach(k, v; arr)
2243 			ca[k] = v[$-1];
2244 
2245 		return assumeUnique(ca);
2246 	}
2247 
2248 	// FIXME duplication
2249 	private immutable(UploadedFile[string]) keepLastOf(in UploadedFile[][string] arr) {
2250 		UploadedFile[string] ca;
2251 		foreach(k, v; arr)
2252 			ca[k] = v[$-1];
2253 
2254 		return assumeUnique(ca);
2255 	}
2256 
2257 
2258 	private immutable(string[][string]) getCookieArray() {
2259 		auto forTheLoveOfGod = decodeVariables(cookie, "; ");
2260 		return assumeUnique(forTheLoveOfGod);
2261 	}
2262 
2263 	/++
2264 		Very simple method to require a basic auth username and password.
2265 		If the http request doesn't include the required credentials, it throws a
2266 		HTTP 401 error, and an exception to cancel your handler. Do NOT catch the
2267 		`AuthorizationRequiredException` exception thrown by this if you want the
2268 		http basic auth prompt to work for the user!
2269 
2270 		Note: basic auth does not provide great security, especially over unencrypted HTTP;
2271 		the user's credentials are sent in plain text on every request.
2272 
2273 		If you are using Apache, the HTTP_AUTHORIZATION variable may not be sent to the
2274 		application. Either use Apache's built in methods for basic authentication, or add
2275 		something along these lines to your server configuration:
2276 
2277 		     ```
2278 		     RewriteEngine On
2279 		     RewriteCond %{HTTP:Authorization} ^(.*)
2280 		     RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1]
2281 		     ```
2282 
2283 		To ensure the necessary data is available to cgi.d.
2284 	+/
2285 	void requireBasicAuth(string user, string pass, string message = null, string file = __FILE__, size_t line = __LINE__) {
2286 		if(authorization != "Basic " ~ Base64.encode(cast(immutable(ubyte)[]) (user ~ ":" ~ pass))) {
2287 			throw new AuthorizationRequiredException("Basic", message, file, line);
2288 		}
2289 	}
2290 
2291 	/// Very simple caching controls - setCache(false) means it will never be cached. Good for rapidly updated or sensitive sites.
2292 	/// setCache(true) means it will always be cached for as long as possible. Best for static content.
2293 	/// Use setResponseExpires and updateResponseExpires for more control
2294 	void setCache(bool allowCaching) {
2295 		noCache = !allowCaching;
2296 	}
2297 
2298 	/// Set to true and use cgi.write(data, true); to send a gzipped response to browsers
2299 	/// who can accept it
2300 	bool gzipResponse;
2301 
2302 	immutable bool acceptsGzip;
2303 	immutable bool keepAliveRequested;
2304 
2305 	/// Set to true if and only if this was initialized with command line arguments
2306 	immutable bool isCalledWithCommandLineArguments;
2307 
2308 	/// This gets a full url for the current request, including port, protocol, host, path, and query
2309 	string getCurrentCompleteUri() const {
2310 		ushort defaultPort = https ? 443 : 80;
2311 
2312 		string uri = "http";
2313 		if(https)
2314 			uri ~= "s";
2315 		uri ~= "://";
2316 		uri ~= host;
2317 		/+ // the host has the port so p sure this never needed, cgi on apache and embedded http all do the right hting now
2318 		version(none)
2319 		if(!(!port || port == defaultPort)) {
2320 			uri ~= ":";
2321 			uri ~= to!string(port);
2322 		}
2323 		+/
2324 		uri ~= requestUri;
2325 		return uri;
2326 	}
2327 
2328 	/// You can override this if your site base url isn't the same as the script name
2329 	string logicalScriptName() const {
2330 		return scriptName;
2331 	}
2332 
2333 	/++
2334 		Sets the HTTP status of the response. For example, "404 File Not Found" or "500 Internal Server Error".
2335 		It assumes "200 OK", and automatically changes to "302 Found" if you call setResponseLocation().
2336 		Note setResponseStatus() must be called *before* you write() any data to the output.
2337 
2338 		History:
2339 			The `int` overload was added on January 11, 2021.
2340 	+/
2341 	void setResponseStatus(string status) {
2342 		assert(!outputtedResponseData);
2343 		responseStatus = status;
2344 	}
2345 	/// ditto
2346 	void setResponseStatus(int statusCode) {
2347 		setResponseStatus(getHttpCodeText(statusCode));
2348 	}
2349 	private string responseStatus = null;
2350 
2351 	/// Returns true if it is still possible to output headers
2352 	bool canOutputHeaders() {
2353 		return !isClosed && !outputtedResponseData;
2354 	}
2355 
2356 	/// Sets the location header, which the browser will redirect the user to automatically.
2357 	/// Note setResponseLocation() must be called *before* you write() any data to the output.
2358 	/// The optional important argument is used if it's a default suggestion rather than something to insist upon.
2359 	void setResponseLocation(string uri, bool important = true, string status = null) {
2360 		if(!important && isCurrentResponseLocationImportant)
2361 			return; // important redirects always override unimportant ones
2362 
2363 		if(uri is null) {
2364 			responseStatus = "200 OK";
2365 			responseLocation = null;
2366 			isCurrentResponseLocationImportant = important;
2367 			return; // this just cancels the redirect
2368 		}
2369 
2370 		assert(!outputtedResponseData);
2371 		if(status is null)
2372 			responseStatus = "302 Found";
2373 		else
2374 			responseStatus = status;
2375 
2376 		responseLocation = uri.strip;
2377 		isCurrentResponseLocationImportant = important;
2378 	}
2379 	protected string responseLocation = null;
2380 	private bool isCurrentResponseLocationImportant = false;
2381 
2382 	/// Sets the Expires: http header. See also: updateResponseExpires, setPublicCaching
2383 	/// The parameter is in unix_timestamp * 1000. Try setResponseExpires(getUTCtime() + SOME AMOUNT) for normal use.
2384 	/// Note: the when parameter is different than setCookie's expire parameter.
2385 	void setResponseExpires(long when, bool isPublic = false) {
2386 		responseExpires = when;
2387 		setCache(true); // need to enable caching so the date has meaning
2388 
2389 		responseIsPublic = isPublic;
2390 		responseExpiresRelative = false;
2391 	}
2392 
2393 	/// Sets a cache-control max-age header for whenFromNow, in seconds.
2394 	void setResponseExpiresRelative(int whenFromNow, bool isPublic = false) {
2395 		responseExpires = whenFromNow;
2396 		setCache(true); // need to enable caching so the date has meaning
2397 
2398 		responseIsPublic = isPublic;
2399 		responseExpiresRelative = true;
2400 	}
2401 	private long responseExpires = long.min;
2402 	private bool responseIsPublic = false;
2403 	private bool responseExpiresRelative = false;
2404 
2405 	/// This is like setResponseExpires, but it can be called multiple times. The setting most in the past is the one kept.
2406 	/// If you have multiple functions, they all might call updateResponseExpires about their own return value. The program
2407 	/// output as a whole is as cacheable as the least cachable part in the chain.
2408 
2409 	/// setCache(false) always overrides this - it is, by definition, the strictest anti-cache statement available. If your site outputs sensitive user data, you should probably call setCache(false) when you do, to ensure no other functions will cache the content, as it may be a privacy risk.
2410 	/// Conversely, setting here overrides setCache(true), since any expiration date is in the past of infinity.
2411 	void updateResponseExpires(long when, bool isPublic) {
2412 		if(responseExpires == long.min)
2413 			setResponseExpires(when, isPublic);
2414 		else if(when < responseExpires)
2415 			setResponseExpires(when, responseIsPublic && isPublic); // if any part of it is private, it all is
2416 	}
2417 
2418 	/*
2419 	/// Set to true if you want the result to be cached publically - that is, is the content shared?
2420 	/// Should generally be false if the user is logged in. It assumes private cache only.
2421 	/// setCache(true) also turns on public caching, and setCache(false) sets to private.
2422 	void setPublicCaching(bool allowPublicCaches) {
2423 		publicCaching = allowPublicCaches;
2424 	}
2425 	private bool publicCaching = false;
2426 	*/
2427 
2428 	/++
2429 		History:
2430 			Added January 11, 2021
2431 	+/
2432 	enum SameSitePolicy {
2433 		Lax,
2434 		Strict,
2435 		None
2436 	}
2437 
2438 	/++
2439 		Sets an HTTP cookie, automatically encoding the data to the correct string.
2440 		expiresIn is how many milliseconds in the future the cookie will expire.
2441 		TIP: to make a cookie accessible from subdomains, set the domain to .yourdomain.com.
2442 		Note setCookie() must be called *before* you write() any data to the output.
2443 
2444 		History:
2445 			Parameter `sameSitePolicy` was added on January 11, 2021.
2446 	+/
2447 	void setCookie(string name, string data, long expiresIn = 0, string path = null, string domain = null, bool httpOnly = false, bool secure = false, SameSitePolicy sameSitePolicy = SameSitePolicy.Lax) {
2448 		assert(!outputtedResponseData);
2449 		string cookie = encodeUriComponent(name) ~ "=";
2450 		cookie ~= encodeUriComponent(data);
2451 		if(path !is null)
2452 			cookie ~= "; path=" ~ path;
2453 		// FIXME: should I just be using max-age here? (also in cache below)
2454 		if(expiresIn != 0)
2455 			cookie ~= "; expires=" ~ printDate(cast(DateTime) Clock.currTime(UTC()) + dur!"msecs"(expiresIn));
2456 		if(domain !is null)
2457 			cookie ~= "; domain=" ~ domain;
2458 		if(secure == true)
2459 			cookie ~= "; Secure";
2460 		if(httpOnly == true )
2461 			cookie ~= "; HttpOnly";
2462 		final switch(sameSitePolicy) {
2463 			case SameSitePolicy.Lax:
2464 				cookie ~= "; SameSite=Lax";
2465 			break;
2466 			case SameSitePolicy.Strict:
2467 				cookie ~= "; SameSite=Strict";
2468 			break;
2469 			case SameSitePolicy.None:
2470 				cookie ~= "; SameSite=None";
2471 				assert(secure); // cookie spec requires this now, see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
2472 			break;
2473 		}
2474 
2475 		if(auto idx = name in cookieIndexes) {
2476 			responseCookies[*idx] = cookie;
2477 		} else {
2478 			cookieIndexes[name] = responseCookies.length;
2479 			responseCookies ~= cookie;
2480 		}
2481 	}
2482 	private string[] responseCookies;
2483 	private size_t[string] cookieIndexes;
2484 
2485 	/// Clears a previously set cookie with the given name, path, and domain.
2486 	void clearCookie(string name, string path = null, string domain = null) {
2487 		assert(!outputtedResponseData);
2488 		setCookie(name, "", 1, path, domain);
2489 	}
2490 
2491 	/// Sets the content type of the response, for example "text/html" (the default) for HTML, or "image/png" for a PNG image
2492 	void setResponseContentType(string ct) {
2493 		assert(!outputtedResponseData);
2494 		responseContentType = ct;
2495 	}
2496 	private string responseContentType = null;
2497 
2498 	/// Adds a custom header. It should be the name: value, but without any line terminator.
2499 	/// For example: header("X-My-Header: Some value");
2500 	/// Note you should use the specialized functions in this object if possible to avoid
2501 	/// duplicates in the output.
2502 	void header(string h) {
2503 		customHeaders ~= h;
2504 	}
2505 
2506 	/++
2507 		I named the original function `header` after PHP, but this pattern more fits
2508 		the rest of the Cgi object.
2509 
2510 		Either name are allowed.
2511 
2512 		History:
2513 			Alias added June 17, 2022.
2514 	+/
2515 	alias setResponseHeader = header;
2516 
2517 	private string[] customHeaders;
2518 	private bool websocketMode;
2519 
2520 	void flushHeaders(const(void)[] t, bool isAll = false) {
2521 		StackBuffer buffer = StackBuffer(0);
2522 
2523 		prepHeaders(t, isAll, &buffer);
2524 
2525 		if(rawDataOutput !is null)
2526 			rawDataOutput(cast(const(ubyte)[]) buffer.get());
2527 		else {
2528 			stdout.rawWrite(buffer.get());
2529 		}
2530 	}
2531 
2532 	private void prepHeaders(const(void)[] t, bool isAll, StackBuffer* buffer) {
2533 		string terminator = "\n";
2534 		if(rawDataOutput !is null)
2535 			terminator = "\r\n";
2536 
2537 		if(responseStatus !is null) {
2538 			if(nph) {
2539 				if(http10)
2540 					buffer.add("HTTP/1.0 ", responseStatus, terminator);
2541 				else
2542 					buffer.add("HTTP/1.1 ", responseStatus, terminator);
2543 			} else
2544 				buffer.add("Status: ", responseStatus, terminator);
2545 		} else if (nph) {
2546 			if(http10)
2547 				buffer.add("HTTP/1.0 200 OK", terminator);
2548 			else
2549 				buffer.add("HTTP/1.1 200 OK", terminator);
2550 		}
2551 
2552 		if(websocketMode)
2553 			goto websocket;
2554 
2555 		if(nph) { // we're responsible for setting the date too according to http 1.1
2556 			char[29] db = void;
2557 			printDateToBuffer(cast(DateTime) Clock.currTime(UTC()), db[]);
2558 			buffer.add("Date: ", db[], terminator);
2559 		}
2560 
2561 		// FIXME: what if the user wants to set his own content-length?
2562 		// The custom header function can do it, so maybe that's best.
2563 		// Or we could reuse the isAll param.
2564 		if(responseLocation !is null) {
2565 			buffer.add("Location: ", responseLocation, terminator);
2566 		}
2567 		if(!noCache && responseExpires != long.min) { // an explicit expiration date is set
2568 			if(responseExpiresRelative) {
2569 				buffer.add("Cache-Control: ", responseIsPublic ? "public" : "private", ", max-age=");
2570 				buffer.add(responseExpires);
2571 				buffer.add(", no-cache=\"set-cookie, set-cookie2\"", terminator);
2572 			} else {
2573 				auto expires = SysTime(unixTimeToStdTime(cast(int)(responseExpires / 1000)), UTC());
2574 				char[29] db = void;
2575 				printDateToBuffer(cast(DateTime) expires, db[]);
2576 				buffer.add("Expires: ", db[], terminator);
2577 				// FIXME: assuming everything is private unless you use nocache - generally right for dynamic pages, but not necessarily
2578 				buffer.add("Cache-Control: ", (responseIsPublic ? "public" : "private"), ", no-cache=\"set-cookie, set-cookie2\"");
2579 				buffer.add(terminator);
2580 			}
2581 		}
2582 		if(responseCookies !is null && responseCookies.length > 0) {
2583 			foreach(c; responseCookies)
2584 				buffer.add("Set-Cookie: ", c, terminator);
2585 		}
2586 		if(noCache) { // we specifically do not want caching (this is actually the default)
2587 			buffer.add("Cache-Control: private, no-cache=\"set-cookie\"", terminator);
2588 			buffer.add("Expires: 0", terminator);
2589 			buffer.add("Pragma: no-cache", terminator);
2590 		} else {
2591 			if(responseExpires == long.min) { // caching was enabled, but without a date set - that means assume cache forever
2592 				buffer.add("Cache-Control: public", terminator);
2593 				buffer.add("Expires: Tue, 31 Dec 2030 14:00:00 GMT", terminator); // FIXME: should not be more than one year in the future
2594 			}
2595 		}
2596 		if(responseContentType !is null) {
2597 			buffer.add("Content-Type: ", responseContentType, terminator);
2598 		} else
2599 			buffer.add("Content-Type: text/html; charset=utf-8", terminator);
2600 
2601 		if(gzipResponse && acceptsGzip && isAll) { // FIXME: isAll really shouldn't be necessary
2602 			buffer.add("Content-Encoding: gzip", terminator);
2603 		}
2604 
2605 
2606 		if(!isAll) {
2607 			if(nph && !http10) {
2608 				buffer.add("Transfer-Encoding: chunked", terminator);
2609 				responseChunked = true;
2610 			}
2611 		} else {
2612 			buffer.add("Content-Length: ");
2613 			buffer.add(t.length);
2614 			buffer.add(terminator);
2615 			if(nph && keepAliveRequested) {
2616 				buffer.add("Connection: Keep-Alive", terminator);
2617 			}
2618 		}
2619 
2620 		websocket:
2621 
2622 		foreach(hd; customHeaders)
2623 			buffer.add(hd, terminator);
2624 
2625 		// FIXME: what about duplicated headers?
2626 
2627 		// end of header indicator
2628 		buffer.add(terminator);
2629 
2630 		outputtedResponseData = true;
2631 	}
2632 
2633 	/// Writes the data to the output, flushing headers if they have not yet been sent.
2634 	void write(const(void)[] t, bool isAll = false, bool maybeAutoClose = true) {
2635 		assert(!closed, "Output has already been closed");
2636 
2637 		StackBuffer buffer = StackBuffer(0);
2638 
2639 		if(gzipResponse && acceptsGzip && isAll) { // FIXME: isAll really shouldn't be necessary
2640 			// actually gzip the data here
2641 
2642 			auto c = new Compress(HeaderFormat.gzip); // want gzip
2643 
2644 			auto data = c.compress(t);
2645 			data ~= c.flush();
2646 
2647 			// std.file.write("/tmp/last-item", data);
2648 
2649 			t = data;
2650 		}
2651 
2652 		if(!outputtedResponseData && (!autoBuffer || isAll)) {
2653 			prepHeaders(t, isAll, &buffer);
2654 		}
2655 
2656 		if(requestMethod != RequestMethod.HEAD && t.length > 0) {
2657 			if (autoBuffer && !isAll) {
2658 				outputBuffer ~= cast(ubyte[]) t;
2659 			}
2660 			if(!autoBuffer || isAll) {
2661 				if(rawDataOutput !is null)
2662 					if(nph && responseChunked) {
2663 						//rawDataOutput(makeChunk(cast(const(ubyte)[]) t));
2664 						// we're making the chunk here instead of in a function
2665 						// to avoid unneeded gc pressure
2666 						buffer.add(toHex(t.length));
2667 						buffer.add("\r\n");
2668 						buffer.add(cast(char[]) t, "\r\n");
2669 					} else {
2670 						buffer.add(cast(char[]) t);
2671 					}
2672 				else
2673 					buffer.add(cast(char[]) t);
2674 			}
2675 		}
2676 
2677 		if(rawDataOutput !is null)
2678 			rawDataOutput(cast(const(ubyte)[]) buffer.get());
2679 		else
2680 			stdout.rawWrite(buffer.get());
2681 
2682 		if(maybeAutoClose && isAll)
2683 			close(); // if you say it is all, that means we're definitely done
2684 				// maybeAutoClose can be false though to avoid this (important if you call from inside close()!
2685 	}
2686 
2687 	/++
2688 		Convenience method to set content type to json and write the string as the complete response.
2689 
2690 		History:
2691 			Added January 16, 2020
2692 	+/
2693 	void writeJson(string json) {
2694 		this.setResponseContentType("application/json");
2695 		this.write(json, true);
2696 	}
2697 
2698 	/// Flushes the pending buffer, leaving the connection open so you can send more.
2699 	void flush() {
2700 		if(rawDataOutput is null)
2701 			stdout.flush();
2702 		else if(flushDelegate !is null)
2703 			flushDelegate();
2704 	}
2705 
2706 	version(autoBuffer)
2707 		bool autoBuffer = true;
2708 	else
2709 		bool autoBuffer = false;
2710 	ubyte[] outputBuffer;
2711 
2712 	/// Flushes the buffers to the network, signifying that you are done.
2713 	/// You should always call this explicitly when you are done outputting data.
2714 	void close() {
2715 		if(closed)
2716 			return; // don't double close
2717 
2718 		if(!outputtedResponseData)
2719 			write("", true, false);
2720 
2721 		// writing auto buffered data
2722 		if(requestMethod != RequestMethod.HEAD && autoBuffer) {
2723 			if(!nph)
2724 				stdout.rawWrite(outputBuffer);
2725 			else
2726 				write(outputBuffer, true, false); // tell it this is everything
2727 		}
2728 
2729 		// closing the last chunk...
2730 		if(nph && rawDataOutput !is null && responseChunked)
2731 			rawDataOutput(cast(const(ubyte)[]) "0\r\n\r\n");
2732 
2733 		if(flushDelegate)
2734 			flushDelegate();
2735 
2736 		closed = true;
2737 	}
2738 
2739 	// Closes without doing anything, shouldn't be used often
2740 	void rawClose() {
2741 		closed = true;
2742 	}
2743 
2744 	/++
2745 		Gets a request variable as a specific type, or the default value of it isn't there
2746 		or isn't convertible to the request type.
2747 
2748 		Checks both GET and POST variables, preferring the POST variable, if available.
2749 
2750 		A nice trick is using the default value to choose the type:
2751 
2752 		---
2753 			/*
2754 				The return value will match the type of the default.
2755 				Here, I gave 10 as a default, so the return value will
2756 				be an int.
2757 
2758 				If the user-supplied value cannot be converted to the
2759 				requested type, you will get the default value back.
2760 			*/
2761 			int a = cgi.request("number", 10);
2762 
2763 			if(cgi.get["number"] == "11")
2764 				assert(a == 11); // conversion succeeds
2765 
2766 			if("number" !in cgi.get)
2767 				assert(a == 10); // no value means you can't convert - give the default
2768 
2769 			if(cgi.get["number"] == "twelve")
2770 				assert(a == 10); // conversion from string to int would fail, so we get the default
2771 		---
2772 
2773 		You can use an enum as an easy whitelist, too:
2774 
2775 		---
2776 			enum Operations {
2777 				add, remove, query
2778 			}
2779 
2780 			auto op = cgi.request("op", Operations.query);
2781 
2782 			if(cgi.get["op"] == "add")
2783 				assert(op == Operations.add);
2784 			if(cgi.get["op"] == "remove")
2785 				assert(op == Operations.remove);
2786 			if(cgi.get["op"] == "query")
2787 				assert(op == Operations.query);
2788 
2789 			if(cgi.get["op"] == "random string")
2790 				assert(op == Operations.query); // the value can't be converted to the enum, so we get the default
2791 		---
2792 	+/
2793 	T request(T = string)(in string name, in T def = T.init) const nothrow {
2794 		try {
2795 			return
2796 				(name in post) ? to!T(post[name]) :
2797 				(name in get)  ? to!T(get[name]) :
2798 				def;
2799 		} catch(Exception e) { return def; }
2800 	}
2801 
2802 	/// Is the output already closed?
2803 	bool isClosed() const {
2804 		return closed;
2805 	}
2806 
2807 	private SessionObject commandLineSessionObject;
2808 
2809 	/++
2810 		Gets a session object associated with the `cgi` request. You can use different type throughout your application.
2811 	+/
2812 	Session!Data getSessionObject(Data)() {
2813 		if(testInProcess !is null) {
2814 			// test mode
2815 			auto obj = testInProcess.getSessionOverride(typeid(typeof(return)));
2816 			if(obj !is null)
2817 				return cast(typeof(return)) obj;
2818 			else {
2819 				auto o = new MockSession!Data();
2820 				testInProcess.setSessionOverride(typeid(typeof(return)), o);
2821 				return o;
2822 			}
2823 		} else {
2824 			// FIXME: the changes are not printed out at the end!
2825 			if(_commandLineSession !is null) {
2826 				if(commandLineSessionObject is null) {
2827 					auto clso = new MockSession!Data();
2828 					commandLineSessionObject = clso;
2829 
2830 
2831 					foreach(memberName; __traits(allMembers, Data)) {
2832 						if(auto str = memberName in _commandLineSession)
2833 							__traits(getMember, clso.store_, memberName) = to!(typeof(__traits(getMember, Data, memberName)))(*str);
2834 					}
2835 				}
2836 
2837 				return cast(typeof(return)) commandLineSessionObject;
2838 			}
2839 
2840 			// normal operation
2841 			return new BasicDataServerSession!Data(this);
2842 		}
2843 	}
2844 
2845 	// if it is in test mode; triggers mock sessions. Used by CgiTester
2846 	version(with_breaking_cgi_features)
2847 	private CgiTester testInProcess;
2848 
2849 	/* Hooks for redirecting input and output */
2850 	private void delegate(const(ubyte)[]) rawDataOutput = null;
2851 	private void delegate() flushDelegate = null;
2852 
2853 	/* This info is used when handling a more raw HTTP protocol */
2854 	private bool nph;
2855 	private bool http10;
2856 	private bool closed;
2857 	private bool responseChunked = false;
2858 
2859 	version(preserveData) // note: this can eat lots of memory; don't use unless you're sure you need it.
2860 	immutable(ubyte)[] originalPostData;
2861 
2862 	/++
2863 		This holds the posted body data if it has not been parsed into [post] and [postArray].
2864 
2865 		It is intended to be used for JSON and XML request content types, but also may be used
2866 		for other content types your application can handle. But it will NOT be populated
2867 		for content types application/x-www-form-urlencoded or multipart/form-data, since those are
2868 		parsed into the post and postArray members.
2869 
2870 		Remember that anything beyond your `maxContentLength` param when setting up [GenericMain], etc.,
2871 		will be discarded to the client with an error. This helps keep this array from being exploded in size
2872 		and consuming all your server's memory (though it may still be possible to eat excess ram from a concurrent
2873 		client in certain build modes.)
2874 
2875 		History:
2876 			Added January 5, 2021
2877 			Documented February 21, 2023 (dub v11.0)
2878 	+/
2879 	public immutable string postBody;
2880 	alias postJson = postBody; // old name
2881 
2882 	/++
2883 		The content type header of the request. The [postBody] member may hold the actual data (see [postBody] for details).
2884 
2885 		History:
2886 			Added January 26, 2024 (dub v11.4)
2887 	+/
2888 	public immutable string requestContentType;
2889 
2890 	/* Internal state flags */
2891 	private bool outputtedResponseData;
2892 	private bool noCache = true;
2893 
2894 	const(string[string]) environmentVariables;
2895 
2896 	/** What follows is data gotten from the HTTP request. It is all fully immutable,
2897 	    partially because it logically is (your code doesn't change what the user requested...)
2898 	    and partially because I hate how bad programs in PHP change those superglobals to do
2899 	    all kinds of hard to follow ugliness. I don't want that to ever happen in D.
2900 
2901 	    For some of these, you'll want to refer to the http or cgi specs for more details.
2902 	*/
2903 	immutable(string[string]) requestHeaders; /// All the raw headers in the request as name/value pairs. The name is stored as all lower case, but otherwise the same as it is in HTTP; words separated by dashes. For example, "cookie" or "accept-encoding". Many HTTP headers have specialized variables below for more convenience and static name checking; you should generally try to use them.
2904 
2905 	immutable(char[]) host; 	/// The hostname in the request. If one program serves multiple domains, you can use this to differentiate between them.
2906 	immutable(char[]) origin; 	/// The origin header in the request, if present. Some HTML5 cross-domain apis set this and you should check it on those cross domain requests and websockets.
2907 	immutable(char[]) userAgent; 	/// The browser's user-agent string. Can be used to identify the browser.
2908 	immutable(char[]) pathInfo; 	/// This is any stuff sent after your program's name on the url, but before the query string. For example, suppose your program is named "app". If the user goes to site.com/app, pathInfo is empty. But, he can also go to site.com/app/some/sub/path; treating your program like a virtual folder. In this case, pathInfo == "/some/sub/path".
2909 	immutable(char[]) scriptName;   /// The full base path of your program, as seen by the user. If your program is located at site.com/programs/apps, scriptName == "/programs/apps".
2910 	immutable(char[]) scriptFileName;   /// The physical filename of your script
2911 	immutable(char[]) authorization; /// The full authorization string from the header, undigested. Useful for implementing auth schemes such as OAuth 1.0. Note that some web servers do not forward this to the app without taking extra steps. See requireBasicAuth's comment for more info.
2912 	immutable(char[]) accept; 	/// The HTTP accept header is the user agent telling what content types it is willing to accept. This is often */*; they accept everything, so it's not terribly useful. (The similar sounding Accept-Encoding header is handled automatically for chunking and gzipping. Simply set gzipResponse = true and cgi.d handles the details, zipping if the user's browser is willing to accept it.)
2913 	immutable(char[]) lastEventId; 	/// The HTML 5 draft includes an EventSource() object that connects to the server, and remains open to take a stream of events. My arsd.rtud module can help with the server side part of that. The Last-Event-Id http header is defined in the draft to help handle loss of connection. When the browser reconnects to you, it sets this header to the last event id it saw, so you can catch it up. This member has the contents of that header.
2914 
2915 	immutable(RequestMethod) requestMethod; /// The HTTP request verb: GET, POST, etc. It is represented as an enum in cgi.d (which, like many enums, you can convert back to string with std.conv.to()). A HTTP GET is supposed to, according to the spec, not have side effects; a user can GET something over and over again and always have the same result. On all requests, the get[] and getArray[] members may be filled in. The post[] and postArray[] members are only filled in on POST methods.
2916 	immutable(char[]) queryString; 	/// The unparsed content of the request query string - the stuff after the ? in your URL. See get[] and getArray[] for a parse view of it. Sometimes, the unparsed string is useful though if you want a custom format of data up there (probably not a good idea, unless it is really simple, like "?username" perhaps.)
2917 	immutable(char[]) cookie; 	/// The unparsed content of the Cookie: header in the request. See also the cookies[string] member for a parsed view of the data.
2918 	/** The Referer header from the request. (It is misspelled in the HTTP spec, and thus the actual request and cgi specs too, but I spelled the word correctly here because that's sane. The spec's misspelling is an implementation detail.) It contains the site url that referred the user to your program; the site that linked to you, or if you're serving images, the site that has you as an image. Also, if you're in an iframe, the referrer is the site that is framing you.
2919 
2920 	Important note: if the user copy/pastes your url, this is blank, and, just like with all other user data, their browsers can also lie to you. Don't rely on it for real security.
2921 	*/
2922 	immutable(char[]) referrer;
2923 	immutable(char[]) requestUri; 	/// The full url if the current request, excluding the protocol and host. requestUri == scriptName ~ pathInfo ~ (queryString.length ? "?" ~ queryString : "");
2924 
2925 	immutable(char[]) remoteAddress; /// The IP address of the user, as we see it. (Might not match the IP of the user's computer due to things like proxies and NAT.)
2926 
2927 	immutable bool https; 	/// Was the request encrypted via https?
2928 	immutable int port; 	/// On what TCP port number did the server receive the request?
2929 
2930 	/** Here come the parsed request variables - the things that come close to PHP's _GET, _POST, etc. superglobals in content. */
2931 
2932 	immutable(string[string]) get; 	/// The data from your query string in the url, only showing the last string of each name. If you want to handle multiple values with the same name, use getArray. This only works right if the query string is x-www-form-urlencoded; the default you see on the web with name=value pairs separated by the & character.
2933 	immutable(string[string]) post; /// The data from the request's body, on POST requests. It parses application/x-www-form-urlencoded data (used by most web requests, including typical forms), and multipart/form-data requests (used by file uploads on web forms) into the same container, so you can always access them the same way. It makes no attempt to parse other content types. If you want to accept an XML Post body (for a web api perhaps), you'll need to handle the raw data yourself.
2934 	immutable(string[string]) cookies; /// Separates out the cookie header into individual name/value pairs (which is how you set them!)
2935 
2936 	/// added later
2937 	alias query = get;
2938 
2939 	/**
2940 		Represents user uploaded files.
2941 
2942 		When making a file upload form, be sure to follow the standard: set method="POST" and enctype="multipart/form-data" in your html <form> tag attributes. The key into this array is the name attribute on your input tag, just like with other post variables. See the comments on the UploadedFile struct for more information about the data inside, including important notes on max size and content location.
2943 	*/
2944 	immutable(UploadedFile[][string]) filesArray;
2945 	immutable(UploadedFile[string]) files;
2946 
2947 	/// Use these if you expect multiple items submitted with the same name. btw, assert(get[name] is getArray[name][$-1); should pass. Same for post and cookies.
2948 	/// the order of the arrays is the order the data arrives
2949 	immutable(string[][string]) getArray; /// like get, but an array of values per name
2950 	immutable(string[][string]) postArray; /// ditto for post
2951 	immutable(string[][string]) cookiesArray; /// ditto for cookies
2952 
2953 	private string[string] _commandLineSession;
2954 
2955 	// convenience function for appending to a uri without extra ?
2956 	// matches the name and effect of javascript's location.search property
2957 	string search() const {
2958 		if(queryString.length)
2959 			return "?" ~ queryString;
2960 		return "";
2961 	}
2962 
2963 	// FIXME: what about multiple files with the same name?
2964   private:
2965 	//RequestMethod _requestMethod;
2966 }
2967 
2968 /// use this for testing or other isolated things when you want it to be no-ops
2969 Cgi dummyCgi(Cgi.RequestMethod method = Cgi.RequestMethod.GET, string url = null, in ubyte[] data = null, void delegate(const(ubyte)[]) outputSink = null) {
2970 	// we want to ignore, not use stdout
2971 	if(outputSink is null)
2972 		outputSink = delegate void(const(ubyte)[]) { };
2973 
2974 	string[string] env;
2975 	env["REQUEST_METHOD"] = to!string(method);
2976 	env["CONTENT_LENGTH"] = to!string(data.length);
2977 
2978 	auto cgi = new Cgi(
2979 		0,
2980 		env,
2981 		{ return data; },
2982 		outputSink,
2983 		null);
2984 
2985 	return cgi;
2986 }
2987 
2988 /++
2989 	A helper test class for request handler unittests.
2990 +/
2991 version(with_breaking_cgi_features)
2992 class CgiTester {
2993 	private {
2994 		SessionObject[TypeInfo] mockSessions;
2995 		SessionObject getSessionOverride(TypeInfo ti) {
2996 			if(auto o = ti in mockSessions)
2997 				return *o;
2998 			else
2999 				return null;
3000 		}
3001 		void setSessionOverride(TypeInfo ti, SessionObject so) {
3002 			mockSessions[ti] = so;
3003 		}
3004 	}
3005 
3006 	/++
3007 		Gets (and creates if necessary) a mock session object for this test. Note
3008 		it will be the same one used for any test operations through this CgiTester instance.
3009 	+/
3010 	Session!Data getSessionObject(Data)() {
3011 		auto obj = getSessionOverride(typeid(typeof(return)));
3012 		if(obj !is null)
3013 			return cast(typeof(return)) obj;
3014 		else {
3015 			auto o = new MockSession!Data();
3016 			setSessionOverride(typeid(typeof(return)), o);
3017 			return o;
3018 		}
3019 	}
3020 
3021 	/++
3022 		Pass a reference to your request handler when creating the tester.
3023 	+/
3024 	this(void function(Cgi) requestHandler) {
3025 		this.requestHandler = requestHandler;
3026 	}
3027 
3028 	/++
3029 		You can check response information with these methods after you call the request handler.
3030 	+/
3031 	struct Response {
3032 		int code;
3033 		string[string] headers;
3034 		string responseText;
3035 		ubyte[] responseBody;
3036 	}
3037 
3038 	/++
3039 		Executes a test request on your request handler, and returns the response.
3040 
3041 		Params:
3042 			url = The URL to test. Should be an absolute path, but excluding domain. e.g. `"/test"`.
3043 			args = additional arguments. Same format as cgi's command line handler.
3044 	+/
3045 	Response GET(string url, string[] args = null) {
3046 		return executeTest("GET", url, args);
3047 	}
3048 	/// ditto
3049 	Response POST(string url, string[] args = null) {
3050 		return executeTest("POST", url, args);
3051 	}
3052 
3053 	/// ditto
3054 	Response executeTest(string method, string url, string[] args) {
3055 		ubyte[] outputtedRawData;
3056 		void outputSink(const(ubyte)[] data) {
3057 			outputtedRawData ~= data;
3058 		}
3059 		auto cgi = new Cgi(["test", method, url] ~ args, &outputSink);
3060 		cgi.testInProcess = this;
3061 		scope(exit) cgi.dispose();
3062 
3063 		requestHandler(cgi);
3064 
3065 		cgi.close();
3066 
3067 		Response response;
3068 
3069 		if(outputtedRawData.length) {
3070 			enum LINE = "\r\n";
3071 
3072 			auto idx = outputtedRawData.locationOf(LINE ~ LINE);
3073 			assert(idx != -1, to!string(outputtedRawData));
3074 			auto headers = cast(string) outputtedRawData[0 .. idx];
3075 			response.code = 200;
3076 			while(headers.length) {
3077 				auto i = headers.locationOf(LINE);
3078 				if(i == -1) i = cast(int) headers.length;
3079 
3080 				auto header = headers[0 .. i];
3081 
3082 				auto c = header.locationOf(":");
3083 				if(c != -1) {
3084 					auto name = header[0 .. c];
3085 					auto value = header[c + 2 ..$];
3086 
3087 					if(name == "Status")
3088 						response.code = value[0 .. value.locationOf(" ")].to!int;
3089 
3090 					response.headers[name] = value;
3091 				} else {
3092 					assert(0);
3093 				}
3094 
3095 				if(i != headers.length)
3096 					i += 2;
3097 				headers = headers[i .. $];
3098 			}
3099 			response.responseBody = outputtedRawData[idx + 4 .. $];
3100 			response.responseText = cast(string) response.responseBody;
3101 		}
3102 
3103 		return response;
3104 	}
3105 
3106 	private void function(Cgi) requestHandler;
3107 }
3108 
3109 
3110 // should this be a separate module? Probably, but that's a hassle.
3111 
3112 /// Makes a data:// uri that can be used as links in most newer browsers (IE8+).
3113 string makeDataUrl(string mimeType, in void[] data) {
3114 	auto data64 = Base64.encode(cast(const(ubyte[])) data);
3115 	return "data:" ~ mimeType ~ ";base64," ~ assumeUnique(data64);
3116 }
3117 
3118 // FIXME: I don't think this class correctly decodes/encodes the individual parts
3119 /// Represents a url that can be broken down or built up through properties
3120 struct Uri {
3121 	alias toString this; // blargh idk a url really is a string, but should it be implicit?
3122 
3123 	// scheme//userinfo@host:port/path?query#fragment
3124 
3125 	string scheme; /// e.g. "http" in "http://example.com/"
3126 	string userinfo; /// the username (and possibly a password) in the uri
3127 	string host; /// the domain name
3128 	int port; /// port number, if given. Will be zero if a port was not explicitly given
3129 	string path; /// e.g. "/folder/file.html" in "http://example.com/folder/file.html"
3130 	string query; /// the stuff after the ? in a uri
3131 	string fragment; /// the stuff after the # in a uri.
3132 
3133 	// idk if i want to keep these, since the functions they wrap are used many, many, many times in existing code, so this is either an unnecessary alias or a gratuitous break of compatibility
3134 	// the decode ones need to keep different names anyway because we can't overload on return values...
3135 	static string encode(string s) { return encodeUriComponent(s); }
3136 	static string encode(string[string] s) { return encodeVariables(s); }
3137 	static string encode(string[][string] s) { return encodeVariables(s); }
3138 
3139 	/// Breaks down a uri string to its components
3140 	this(string uri) {
3141 		reparse(uri);
3142 	}
3143 
3144 	private void reparse(string uri) {
3145 		// from RFC 3986
3146 		// the ctRegex triples the compile time and makes ugly errors for no real benefit
3147 		// it was a nice experiment but just not worth it.
3148 		// enum ctr = ctRegex!r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?";
3149 		/*
3150 			Captures:
3151 				0 = whole url
3152 				1 = scheme, with :
3153 				2 = scheme, no :
3154 				3 = authority, with //
3155 				4 = authority, no //
3156 				5 = path
3157 				6 = query string, with ?
3158 				7 = query string, no ?
3159 				8 = anchor, with #
3160 				9 = anchor, no #
3161 		*/
3162 		// Yikes, even regular, non-CT regex is also unacceptably slow to compile. 1.9s on my computer!
3163 		// instead, I will DIY and cut that down to 0.6s on the same computer.
3164 		/*
3165 
3166 				Note that authority is
3167 					user:password@domain:port
3168 				where the user:password@ part is optional, and the :port is optional.
3169 
3170 				Regex translation:
3171 
3172 				Scheme cannot have :, /, ?, or # in it, and must have one or more chars and end in a :. It is optional, but must be first.
3173 				Authority must start with //, but cannot have any other /, ?, or # in it. It is optional.
3174 				Path cannot have any ? or # in it. It is optional.
3175 				Query must start with ? and must not have # in it. It is optional.
3176 				Anchor must start with # and can have anything else in it to end of string. It is optional.
3177 		*/
3178 
3179 		this = Uri.init; // reset all state
3180 
3181 		// empty uri = nothing special
3182 		if(uri.length == 0) {
3183 			return;
3184 		}
3185 
3186 		size_t idx;
3187 
3188 		scheme_loop: foreach(char c; uri[idx .. $]) {
3189 			switch(c) {
3190 				case ':':
3191 				case '/':
3192 				case '?':
3193 				case '#':
3194 					break scheme_loop;
3195 				default:
3196 			}
3197 			idx++;
3198 		}
3199 
3200 		if(idx == 0 && uri[idx] == ':') {
3201 			// this is actually a path! we skip way ahead
3202 			goto path_loop;
3203 		}
3204 
3205 		if(idx == uri.length) {
3206 			// the whole thing is a path, apparently
3207 			path = uri;
3208 			return;
3209 		}
3210 
3211 		if(idx > 0 && uri[idx] == ':') {
3212 			scheme = uri[0 .. idx];
3213 			idx++;
3214 		} else {
3215 			// we need to rewind; it found a / but no :, so the whole thing is prolly a path...
3216 			idx = 0;
3217 		}
3218 
3219 		if(idx + 2 < uri.length && uri[idx .. idx + 2] == "//") {
3220 			// we have an authority....
3221 			idx += 2;
3222 
3223 			auto authority_start = idx;
3224 			authority_loop: foreach(char c; uri[idx .. $]) {
3225 				switch(c) {
3226 					case '/':
3227 					case '?':
3228 					case '#':
3229 						break authority_loop;
3230 					default:
3231 				}
3232 				idx++;
3233 			}
3234 
3235 			auto authority = uri[authority_start .. idx];
3236 
3237 			auto idx2 = authority.indexOf("@");
3238 			if(idx2 != -1) {
3239 				userinfo = authority[0 .. idx2];
3240 				authority = authority[idx2 + 1 .. $];
3241 			}
3242 
3243 			if(authority.length && authority[0] == '[') {
3244 				// ipv6 address special casing
3245 				idx2 = authority.indexOf(']');
3246 				if(idx2 != -1) {
3247 					auto end = authority[idx2 + 1 .. $];
3248 					if(end.length && end[0] == ':')
3249 						idx2 = idx2 + 1;
3250 					else
3251 						idx2 = -1;
3252 				}
3253 			} else {
3254 				idx2 = authority.indexOf(":");
3255 			}
3256 
3257 			if(idx2 == -1) {
3258 				port = 0; // 0 means not specified; we should use the default for the scheme
3259 				host = authority;
3260 			} else {
3261 				host = authority[0 .. idx2];
3262 				if(idx2 + 1 < authority.length)
3263 					port = to!int(authority[idx2 + 1 .. $]);
3264 				else
3265 					port = 0;
3266 			}
3267 		}
3268 
3269 		path_loop:
3270 		auto path_start = idx;
3271 
3272 		foreach(char c; uri[idx .. $]) {
3273 			if(c == '?' || c == '#')
3274 				break;
3275 			idx++;
3276 		}
3277 
3278 		path = uri[path_start .. idx];
3279 
3280 		if(idx == uri.length)
3281 			return; // nothing more to examine...
3282 
3283 		if(uri[idx] == '?') {
3284 			idx++;
3285 			auto query_start = idx;
3286 			foreach(char c; uri[idx .. $]) {
3287 				if(c == '#')
3288 					break;
3289 				idx++;
3290 			}
3291 			query = uri[query_start .. idx];
3292 		}
3293 
3294 		if(idx < uri.length && uri[idx] == '#') {
3295 			idx++;
3296 			fragment = uri[idx .. $];
3297 		}
3298 
3299 		// uriInvalidated = false;
3300 	}
3301 
3302 	private string rebuildUri() const {
3303 		string ret;
3304 		if(scheme.length)
3305 			ret ~= scheme ~ ":";
3306 		if(userinfo.length || host.length)
3307 			ret ~= "//";
3308 		if(userinfo.length)
3309 			ret ~= userinfo ~ "@";
3310 		if(host.length)
3311 			ret ~= host;
3312 		if(port)
3313 			ret ~= ":" ~ to!string(port);
3314 
3315 		ret ~= path;
3316 
3317 		if(query.length)
3318 			ret ~= "?" ~ query;
3319 
3320 		if(fragment.length)
3321 			ret ~= "#" ~ fragment;
3322 
3323 		// uri = ret;
3324 		// uriInvalidated = false;
3325 		return ret;
3326 	}
3327 
3328 	/// Converts the broken down parts back into a complete string
3329 	string toString() const {
3330 		// if(uriInvalidated)
3331 			return rebuildUri();
3332 	}
3333 
3334 	/// Returns a new absolute Uri given a base. It treats this one as
3335 	/// relative where possible, but absolute if not. (If protocol, domain, or
3336 	/// other info is not set, the new one inherits it from the base.)
3337 	///
3338 	/// Browsers use a function like this to figure out links in html.
3339 	Uri basedOn(in Uri baseUrl) const {
3340 		Uri n = this; // copies
3341 		if(n.scheme == "data")
3342 			return n;
3343 		// n.uriInvalidated = true; // make sure we regenerate...
3344 
3345 		// userinfo is not inherited... is this wrong?
3346 
3347 		// if anything is given in the existing url, we don't use the base anymore.
3348 		if(n.scheme.empty) {
3349 			n.scheme = baseUrl.scheme;
3350 			if(n.host.empty) {
3351 				n.host = baseUrl.host;
3352 				if(n.port == 0) {
3353 					n.port = baseUrl.port;
3354 					if(n.path.length > 0 && n.path[0] != '/') {
3355 						auto b = baseUrl.path[0 .. baseUrl.path.lastIndexOf("/") + 1];
3356 						if(b.length == 0)
3357 							b = "/";
3358 						n.path = b ~ n.path;
3359 					} else if(n.path.length == 0) {
3360 						n.path = baseUrl.path;
3361 					}
3362 				}
3363 			}
3364 		}
3365 
3366 		n.removeDots();
3367 
3368 		return n;
3369 	}
3370 
3371 	void removeDots() {
3372 		auto parts = this.path.split("/");
3373 		string[] toKeep;
3374 		foreach(part; parts) {
3375 			if(part == ".") {
3376 				continue;
3377 			} else if(part == "..") {
3378 				//if(toKeep.length > 1)
3379 					toKeep = toKeep[0 .. $-1];
3380 				//else
3381 					//toKeep = [""];
3382 				continue;
3383 			} else {
3384 				//if(toKeep.length && toKeep[$-1].length == 0 && part.length == 0)
3385 					//continue; // skip a `//` situation
3386 				toKeep ~= part;
3387 			}
3388 		}
3389 
3390 		auto path = toKeep.join("/");
3391 		if(path.length && path[0] != '/')
3392 			path = "/" ~ path;
3393 
3394 		this.path = path;
3395 	}
3396 
3397 	unittest {
3398 		auto uri = Uri("test.html");
3399 		assert(uri.path == "test.html");
3400 		uri = Uri("path/1/lol");
3401 		assert(uri.path == "path/1/lol");
3402 		uri = Uri("http://me@example.com");
3403 		assert(uri.scheme == "http");
3404 		assert(uri.userinfo == "me");
3405 		assert(uri.host == "example.com");
3406 		uri = Uri("http://example.com/#a");
3407 		assert(uri.scheme == "http");
3408 		assert(uri.host == "example.com");
3409 		assert(uri.fragment == "a");
3410 		uri = Uri("#foo");
3411 		assert(uri.fragment == "foo");
3412 		uri = Uri("?lol");
3413 		assert(uri.query == "lol");
3414 		uri = Uri("#foo?lol");
3415 		assert(uri.fragment == "foo?lol");
3416 		uri = Uri("?lol#foo");
3417 		assert(uri.fragment == "foo");
3418 		assert(uri.query == "lol");
3419 
3420 		uri = Uri("http://127.0.0.1/");
3421 		assert(uri.host == "127.0.0.1");
3422 		assert(uri.port == 0);
3423 
3424 		uri = Uri("http://127.0.0.1:123/");
3425 		assert(uri.host == "127.0.0.1");
3426 		assert(uri.port == 123);
3427 
3428 		uri = Uri("http://[ff:ff::0]/");
3429 		assert(uri.host == "[ff:ff::0]");
3430 
3431 		uri = Uri("http://[ff:ff::0]:123/");
3432 		assert(uri.host == "[ff:ff::0]");
3433 		assert(uri.port == 123);
3434 	}
3435 
3436 	// This can sometimes be a big pain in the butt for me, so lots of copy/paste here to cover
3437 	// the possibilities.
3438 	unittest {
3439 		auto url = Uri("cool.html"); // checking relative links
3440 
3441 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/what/cool.html");
3442 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/what/cool.html");
3443 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/what/cool.html");
3444 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/cool.html");
3445 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/cool.html");
3446 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/what/cool.html");
3447 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/what/cool.html");
3448 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/what/cool.html");
3449 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/cool.html");
3450 
3451 		url = Uri("/something/cool.html"); // same server, different path
3452 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/something/cool.html");
3453 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/something/cool.html");
3454 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/something/cool.html");
3455 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/something/cool.html");
3456 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/something/cool.html");
3457 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/something/cool.html");
3458 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/something/cool.html");
3459 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/something/cool.html");
3460 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com/something/cool.html");
3461 
3462 		url = Uri("?query=answer"); // same path. server, protocol, and port, just different query string and fragment
3463 		assert(url.basedOn(Uri("http://test.com/what/test.html")) == "http://test.com/what/test.html?query=answer");
3464 		assert(url.basedOn(Uri("https://test.com/what/test.html")) == "https://test.com/what/test.html?query=answer");
3465 		assert(url.basedOn(Uri("http://test.com/what/")) == "http://test.com/what/?query=answer");
3466 		assert(url.basedOn(Uri("http://test.com/")) == "http://test.com/?query=answer");
3467 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com?query=answer");
3468 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b")) == "http://test.com/what/test.html?query=answer");
3469 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d")) == "http://test.com/what/test.html?query=answer");
3470 		assert(url.basedOn(Uri("http://test.com/what/test.html?a=b&c=d#what")) == "http://test.com/what/test.html?query=answer");
3471 		assert(url.basedOn(Uri("http://test.com")) == "http://test.com?query=answer");
3472 
3473 		url = Uri("/test/bar");
3474 		assert(Uri("./").basedOn(url) == "/test/", Uri("./").basedOn(url));
3475 		assert(Uri("../").basedOn(url) == "/");
3476 
3477 		url = Uri("http://example.com/");
3478 		assert(Uri("../foo").basedOn(url) == "http://example.com/foo");
3479 
3480 		//auto uriBefore = url;
3481 		url = Uri("#anchor"); // everything should remain the same except the anchor
3482 		//uriBefore.anchor = "anchor");
3483 		//assert(url == uriBefore);
3484 
3485 		url = Uri("//example.com"); // same protocol, but different server. the path here should be blank.
3486 
3487 		url = Uri("//example.com/example.html"); // same protocol, but different server and path
3488 
3489 		url = Uri("http://example.com/test.html"); // completely absolute link should never be modified
3490 
3491 		url = Uri("http://example.com"); // completely absolute link should never be modified, even if it has no path
3492 
3493 		// FIXME: add something for port too
3494 	}
3495 
3496 	// these are like javascript's location.search and location.hash
3497 	string search() const {
3498 		return query.length ? ("?" ~ query) : "";
3499 	}
3500 	string hash() const {
3501 		return fragment.length ? ("#" ~ fragment) : "";
3502 	}
3503 }
3504 
3505 
3506 /*
3507 	for session, see web.d
3508 */
3509 
3510 /// breaks down a url encoded string
3511 string[][string] decodeVariables(string data, string separator = "&", string[]* namesInOrder = null, string[]* valuesInOrder = null) {
3512 	auto vars = data.split(separator);
3513 	string[][string] _get;
3514 	foreach(var; vars) {
3515 		auto equal = var.indexOf("=");
3516 		string name;
3517 		string value;
3518 		if(equal == -1) {
3519 			name = decodeUriComponent(var);
3520 			value = "";
3521 		} else {
3522 			//_get[decodeUriComponent(var[0..equal])] ~= decodeUriComponent(var[equal + 1 .. $].replace("+", " "));
3523 			// stupid + -> space conversion.
3524 			name = decodeUriComponent(var[0..equal].replace("+", " "));
3525 			value = decodeUriComponent(var[equal + 1 .. $].replace("+", " "));
3526 		}
3527 
3528 		_get[name] ~= value;
3529 		if(namesInOrder)
3530 			(*namesInOrder) ~= name;
3531 		if(valuesInOrder)
3532 			(*valuesInOrder) ~= value;
3533 	}
3534 	return _get;
3535 }
3536 
3537 /// breaks down a url encoded string, but only returns the last value of any array
3538 string[string] decodeVariablesSingle(string data) {
3539 	string[string] va;
3540 	auto varArray = decodeVariables(data);
3541 	foreach(k, v; varArray)
3542 		va[k] = v[$-1];
3543 
3544 	return va;
3545 }
3546 
3547 /// url encodes the whole string
3548 string encodeVariables(in string[string] data) {
3549 	string ret;
3550 
3551 	bool outputted = false;
3552 	foreach(k, v; data) {
3553 		if(outputted)
3554 			ret ~= "&";
3555 		else
3556 			outputted = true;
3557 
3558 		ret ~= encodeUriComponent(k) ~ "=" ~ encodeUriComponent(v);
3559 	}
3560 
3561 	return ret;
3562 }
3563 
3564 /// url encodes a whole string
3565 string encodeVariables(in string[][string] data) {
3566 	string ret;
3567 
3568 	bool outputted = false;
3569 	foreach(k, arr; data) {
3570 		foreach(v; arr) {
3571 			if(outputted)
3572 				ret ~= "&";
3573 			else
3574 				outputted = true;
3575 			ret ~= encodeUriComponent(k) ~ "=" ~ encodeUriComponent(v);
3576 		}
3577 	}
3578 
3579 	return ret;
3580 }
3581 
3582 /// Encodes all but the explicitly unreserved characters per rfc 3986
3583 /// Alphanumeric and -_.~ are the only ones left unencoded
3584 /// name is borrowed from php
3585 string rawurlencode(in char[] data) {
3586 	string ret;
3587 	ret.reserve(data.length * 2);
3588 	foreach(char c; data) {
3589 		if(
3590 			(c >= 'a' && c <= 'z') ||
3591 			(c >= 'A' && c <= 'Z') ||
3592 			(c >= '0' && c <= '9') ||
3593 			c == '-' || c == '_' || c == '.' || c == '~')
3594 		{
3595 			ret ~= c;
3596 		} else {
3597 			ret ~= '%';
3598 			// since we iterate on char, this should give us the octets of the full utf8 string
3599 			ret ~= toHexUpper(c);
3600 		}
3601 	}
3602 
3603 	return ret;
3604 }
3605 
3606 
3607 // http helper functions
3608 
3609 // for chunked responses (which embedded http does whenever possible)
3610 version(none) // this is moved up above to avoid making a copy of the data
3611 const(ubyte)[] makeChunk(const(ubyte)[] data) {
3612 	const(ubyte)[] ret;
3613 
3614 	ret = cast(const(ubyte)[]) toHex(data.length);
3615 	ret ~= cast(const(ubyte)[]) "\r\n";
3616 	ret ~= data;
3617 	ret ~= cast(const(ubyte)[]) "\r\n";
3618 
3619 	return ret;
3620 }
3621 
3622 string toHex(long num) {
3623 	string ret;
3624 	while(num) {
3625 		int v = num % 16;
3626 		num /= 16;
3627 		char d = cast(char) ((v < 10) ? v + '0' : (v-10) + 'a');
3628 		ret ~= d;
3629 	}
3630 
3631 	return to!string(array(ret.retro));
3632 }
3633 
3634 string toHexUpper(long num) {
3635 	string ret;
3636 	while(num) {
3637 		int v = num % 16;
3638 		num /= 16;
3639 		char d = cast(char) ((v < 10) ? v + '0' : (v-10) + 'A');
3640 		ret ~= d;
3641 	}
3642 
3643 	if(ret.length == 1)
3644 		ret ~= "0"; // url encoding requires two digits and that's what this function is used for...
3645 
3646 	return to!string(array(ret.retro));
3647 }
3648 
3649 
3650 // the generic mixins
3651 
3652 /++
3653 	Use this instead of writing your own main
3654 
3655 	It ultimately calls [cgiMainImpl] which creates a [RequestServer] for you.
3656 +/
3657 mixin template GenericMain(alias fun, long maxContentLength = defaultMaxContentLength) {
3658 	mixin CustomCgiMain!(Cgi, fun, maxContentLength);
3659 }
3660 
3661 /++
3662 	Boilerplate mixin for a main function that uses the [dispatcher] function.
3663 
3664 	You can send `typeof(null)` as the `Presenter` argument to use a generic one.
3665 
3666 	History:
3667 		Added July 9, 2021
3668 +/
3669 mixin template DispatcherMain(Presenter, DispatcherArgs...) {
3670 	/// forwards to [CustomCgiDispatcherMain] with default args
3671 	mixin CustomCgiDispatcherMain!(Cgi, defaultMaxContentLength, Presenter, DispatcherArgs);
3672 }
3673 
3674 /// ditto
3675 mixin template DispatcherMain(DispatcherArgs...) if(!is(DispatcherArgs[0] : WebPresenter!T, T)) {
3676 	class GenericPresenter : WebPresenter!GenericPresenter {}
3677 	mixin DispatcherMain!(GenericPresenter, DispatcherArgs);
3678 }
3679 
3680 /++
3681 	Allows for a generic [DispatcherMain] with custom arguments. Note you can use [defaultMaxContentLength] as the second argument if you like.
3682 
3683 	History:
3684 		Added May 13, 2023 (dub v11.0)
3685 +/
3686 mixin template CustomCgiDispatcherMain(CustomCgi, size_t maxContentLength, Presenter, DispatcherArgs...) {
3687 	/++
3688 		Handler to the generated presenter you can use from your objects, etc.
3689 	+/
3690 	Presenter activePresenter;
3691 
3692 	/++
3693 		Request handler that creates the presenter then forwards to the [dispatcher] function.
3694 		Renders 404 if the dispatcher did not handle the request.
3695 
3696 		Will automatically serve the presenter.style and presenter.script as "style.css" and "script.js"
3697 	+/
3698 	void handler(Cgi cgi) {
3699 		auto presenter = new Presenter;
3700 		activePresenter = presenter;
3701 		scope(exit) activePresenter = null;
3702 
3703 		if(cgi.pathInfo.length == 0) {
3704 			cgi.setResponseLocation(cgi.scriptName ~ "/");
3705 			return;
3706 		}
3707 
3708 		if(cgi.dispatcher!DispatcherArgs(presenter))
3709 			return;
3710 
3711 		switch(cgi.pathInfo) {
3712 			case "/style.css":
3713 				cgi.setCache(true);
3714 				cgi.setResponseContentType("text/css");
3715 				cgi.write(presenter.style(), true);
3716 			break;
3717 			case "/script.js":
3718 				cgi.setCache(true);
3719 				cgi.setResponseContentType("application/javascript");
3720 				cgi.write(presenter.script(), true);
3721 			break;
3722 			default:
3723 				presenter.renderBasicError(cgi, 404);
3724 		}
3725 	}
3726 	mixin CustomCgiMain!(CustomCgi, handler, maxContentLength);
3727 }
3728 
3729 /// ditto
3730 mixin template CustomCgiDispatcherMain(CustomCgi, size_t maxContentLength, DispatcherArgs...) if(!is(DispatcherArgs[0] : WebPresenter!T, T)) {
3731 	class GenericPresenter : WebPresenter!GenericPresenter {}
3732 	mixin CustomCgiDispatcherMain!(CustomCgi, maxContentLength, GenericPresenter, DispatcherArgs);
3733 
3734 }
3735 
3736 private string simpleHtmlEncode(string s) {
3737 	return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace("\n", "<br />\n");
3738 }
3739 
3740 string messageFromException(Throwable t) {
3741 	string message;
3742 	if(t !is null) {
3743 		debug message = t.toString();
3744 		else  message = "An unexpected error has occurred.";
3745 	} else {
3746 		message = "Unknown error";
3747 	}
3748 	return message;
3749 }
3750 
3751 string plainHttpError(bool isCgi, string type, Throwable t) {
3752 	auto message = messageFromException(t);
3753 	message = simpleHtmlEncode(message);
3754 
3755 	return format("%s %s\r\nContent-Length: %s\r\nConnection: close\r\n\r\n%s",
3756 		isCgi ? "Status:" : "HTTP/1.1",
3757 		type, message.length, message);
3758 }
3759 
3760 // returns true if we were able to recover reasonably
3761 bool handleException(Cgi cgi, Throwable t) {
3762 	if(cgi.isClosed) {
3763 		// if the channel has been explicitly closed, we can't handle it here
3764 		return true;
3765 	}
3766 
3767 	if(cgi.outputtedResponseData) {
3768 		// the headers are sent, but the channel is open... since it closes if all was sent, we can append an error message here.
3769 		return false; // but I don't want to, since I don't know what condition the output is in; I don't want to inject something (nor check the content-type for that matter. So we say it was not a clean handling.
3770 	} else {
3771 		// no headers are sent, we can send a full blown error and recover
3772 		cgi.setCache(false);
3773 		cgi.setResponseContentType("text/html");
3774 		cgi.setResponseLocation(null); // cancel the redirect
3775 		cgi.setResponseStatus("500 Internal Server Error");
3776 		cgi.write(simpleHtmlEncode(messageFromException(t)));
3777 		cgi.close();
3778 		return true;
3779 	}
3780 }
3781 
3782 bool isCgiRequestMethod(string s) {
3783 	s = s.toUpper();
3784 	if(s == "COMMANDLINE")
3785 		return true;
3786 	foreach(member; __traits(allMembers, Cgi.RequestMethod))
3787 		if(s == member)
3788 			return true;
3789 	return false;
3790 }
3791 
3792 /// If you want to use a subclass of Cgi with generic main, use this mixin.
3793 mixin template CustomCgiMain(CustomCgi, alias fun, long maxContentLength = defaultMaxContentLength) if(is(CustomCgi : Cgi)) {
3794 	// kinda hacky - the T... is passed to Cgi's constructor in standard cgi mode, and ignored elsewhere
3795 	void main(string[] args) {
3796 		cgiMainImpl!(fun, CustomCgi, maxContentLength)(args);
3797 	}
3798 }
3799 
3800 version(embedded_httpd_processes)
3801 	__gshared int processPoolSize = 8;
3802 
3803 // Returns true if run. You should exit the program after that.
3804 bool tryAddonServers(string[] args) {
3805 	if(args.length > 1) {
3806 		// run the special separate processes if needed
3807 		switch(args[1]) {
3808 			case "--websocket-server":
3809 				version(with_addon_servers)
3810 					websocketServers[args[2]](args[3 .. $]);
3811 				else
3812 					printf("Add-on servers not compiled in.\n");
3813 				return true;
3814 			case "--websocket-servers":
3815 				import core.demangle;
3816 				version(with_addon_servers_connections)
3817 				foreach(k, v; websocketServers)
3818 					writeln(k, "\t", demangle(k));
3819 				return true;
3820 			case "--session-server":
3821 				version(with_addon_servers)
3822 					runSessionServer();
3823 				else
3824 					printf("Add-on servers not compiled in.\n");
3825 				return true;
3826 			case "--event-server":
3827 				version(with_addon_servers)
3828 					runEventServer();
3829 				else
3830 					printf("Add-on servers not compiled in.\n");
3831 				return true;
3832 			case "--timer-server":
3833 				version(with_addon_servers)
3834 					runTimerServer();
3835 				else
3836 					printf("Add-on servers not compiled in.\n");
3837 				return true;
3838 			case "--timed-jobs":
3839 				import core.demangle;
3840 				version(with_addon_servers_connections)
3841 				foreach(k, v; scheduledJobHandlers)
3842 					writeln(k, "\t", demangle(k));
3843 				return true;
3844 			case "--timed-job":
3845 				scheduledJobHandlers[args[2]](args[3 .. $]);
3846 				return true;
3847 			default:
3848 				// intentionally blank - do nothing and carry on to run normally
3849 		}
3850 	}
3851 	return false;
3852 }
3853 
3854 /// Tries to simulate a request from the command line. Returns true if it does, false if it didn't find the args.
3855 bool trySimulatedRequest(alias fun, CustomCgi = Cgi)(string[] args) if(is(CustomCgi : Cgi)) {
3856 	// we support command line thing for easy testing everywhere
3857 	// it needs to be called ./app method uri [other args...]
3858 	if(args.length >= 3 && isCgiRequestMethod(args[1])) {
3859 		Cgi cgi = new CustomCgi(args);
3860 		scope(exit) cgi.dispose();
3861 		try {
3862 			fun(cgi);
3863 			cgi.close();
3864 		} catch(AuthorizationRequiredException are) {
3865 			cgi.setResponseStatus("401 Authorization Required");
3866 			cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
3867 			cgi.close();
3868 		}
3869 		writeln(); // just to put a blank line before the prompt cuz it annoys me
3870 		// FIXME: put in some footers to show what changes happened in the session
3871 		// could make the MockSession be some kind of ReflectableSessionObject or something
3872 		return true;
3873 	}
3874 	return false;
3875 }
3876 
3877 /++
3878 	A server control and configuration struct, as a potential alternative to calling [GenericMain] or [cgiMainImpl]. See the source of [cgiMainImpl] for a complete, up-to-date, example of how it is used internally.
3879 
3880 	As of version 11 (released August 2023), you can also make things like this:
3881 
3882 	---
3883 		// listens on both a unix domain socket called `foo` and on the loopback interfaces port 8080
3884 		RequestServer server = RequestServer(["http://unix:foo", "http://localhost:8080"]);
3885 
3886 		// can also:
3887 		// RequestServer server = RequestServer(0); // listen on an OS-provided port on all interfaces
3888 
3889 		// NOT IMPLEMENTED YET
3890 		// server.initialize(); // explicit initialization will populate any "any port" things and throw if a bind failed
3891 
3892 		foreach(listenSpec; server.listenSpecs) {
3893 			// you can check what it actually bound to here and see your assigned ports
3894 		}
3895 
3896 		// NOT IMPLEMENTED YET
3897 		// server.start!handler(); // starts and runs in the arsd.core event loop
3898 
3899 		server.serve!handler(); // blocks the thread until the server exits
3900 	---
3901 
3902 	History:
3903 		Added Sept 26, 2020 (release version 8.5).
3904 
3905 		The `listenSpec` member was added July 31, 2023.
3906 +/
3907 struct RequestServer {
3908 	/++
3909 		Sets the host and port the server will listen on. This is semi-deprecated; the new (as of July 31, 2023) [listenSpec] parameter obsoletes these. You cannot use both together; the listeningHost and listeningPort are ONLY used if listenSpec is null.
3910 	+/
3911 	string listeningHost = defaultListeningHost();
3912 	/// ditto
3913 	ushort listeningPort = defaultListeningPort();
3914 
3915 	static struct ListenSpec {
3916 		enum Protocol {
3917 			http,
3918 			https,
3919 			scgi
3920 		}
3921 		Protocol protocol;
3922 
3923 		enum AddressType {
3924 			ip,
3925 			unix,
3926 			abstract_
3927 		}
3928 		AddressType addressType;
3929 
3930 		string address;
3931 		ushort port;
3932 	}
3933 
3934 	/++
3935 		The array of addresses you want to listen on. The format looks like a url but has a few differences.
3936 
3937 		This ONLY works on embedded_httpd_threads, embedded_httpd_hybrid, and scgi builds at this time.
3938 
3939 		`http://localhost:8080`
3940 
3941 		`http://unix:filename/here`
3942 
3943 		`scgi://abstract:/name/here`
3944 
3945 		`http://[::1]:4444`
3946 
3947 		Note that IPv6 addresses must be enclosed in brackets. If you want to listen on an interface called `unix` or `abstract`, contact me, that is not supported but I could add some kind of escape mechanism.
3948 
3949 		If you leave off the protocol, it assumes the default based on compile flags. If you only give a number, it is assumed to be a port on any tcp interface.
3950 
3951 		`localhost:8080` serves the default protocol.
3952 
3953 		`8080` or `:8080` assumes default protocol on localhost.
3954 
3955 		The protocols can be `http:`, `https:`, and `scgi:`. Original `cgi` is not supported with this, since it is transactional with a single process.
3956 
3957 		Valid hosts are an IPv4 address (with a mandatory port), an IPv6 address (with a mandatory port), just a port alone, `unix:/path/to/unix/socket` (which may be a relative path without a leading slash), or `abstract:/path/to/linux/abstract/namespace`.
3958 
3959 		`http://unix:foo` will serve http over the unix domain socket named `foo` in the current working directory.
3960 
3961 		$(PITFALL
3962 			If you set this to anything non-null (including a non-null, zero-length array) any `listenSpec` entries, [listeningHost] and [listeningPort] are ignored.
3963 		)
3964 
3965 		Bugs:
3966 			The implementation currently ignores the protocol spec in favor of the default compiled in option.
3967 
3968 		History:
3969 			Added July 31, 2023 (dub v11.0)
3970 	+/
3971 	string[] listenSpec;
3972 
3973 	/++
3974 		Uses a fork() call, if available, to provide additional crash resiliency and possibly improved performance. On the
3975 		other hand, if you fork, you must not assume any memory is shared between requests (you shouldn't be anyway though! But
3976 		if you have to, you probably want to set this to false and use an explicit threaded server with [serveEmbeddedHttp]) and
3977 		[stop] may not work as well.
3978 
3979 		History:
3980 			Added August 12, 2022  (dub v10.9). Previously, this was only configurable through the `-version=cgi_no_fork`
3981 			argument to dmd. That version still defines the value of `cgi_use_fork_default`, used to initialize this, for
3982 			compatibility.
3983 	+/
3984 	bool useFork = cgi_use_fork_default;
3985 
3986 	/++
3987 		Determines the number of worker threads to spawn per process, for server modes that use worker threads. 0 will use a
3988 		default based on the number of cpus modified by the server mode.
3989 
3990 		History:
3991 			Added August 12, 2022 (dub v10.9)
3992 	+/
3993 	int numberOfThreads = 0;
3994 
3995 	/++
3996 		Creates a server configured to listen to multiple URLs.
3997 
3998 		History:
3999 			Added July 31, 2023 (dub v11.0)
4000 	+/
4001 	this(string[] listenTo) {
4002 		this.listenSpec = listenTo;
4003 	}
4004 
4005 	/// Creates a server object configured to listen on a single host and port.
4006 	this(string defaultHost, ushort defaultPort) {
4007 		this.listeningHost = defaultHost;
4008 		this.listeningPort = defaultPort;
4009 	}
4010 
4011 	/// ditto
4012 	this(ushort defaultPort) {
4013 		listeningPort = defaultPort;
4014 	}
4015 
4016 	/++
4017 		Reads the command line arguments into the values here.
4018 
4019 		Possible arguments are `--listen` (can appear multiple times), `--listening-host`, `--listening-port` (or `--port`), `--uid`, and `--gid`.
4020 
4021 		Please note you cannot combine `--listen` with `--listening-host` or `--listening-port` / `--port`. Use one or the other style.
4022 	+/
4023 	void configureFromCommandLine(string[] args) {
4024 		bool portOrHostFound = false;
4025 
4026 		bool foundPort = false;
4027 		bool foundHost = false;
4028 		bool foundUid = false;
4029 		bool foundGid = false;
4030 		bool foundListen = false;
4031 		foreach(arg; args) {
4032 			if(foundPort) {
4033 				listeningPort = to!ushort(arg);
4034 				portOrHostFound = true;
4035 				foundPort = false;
4036 				continue;
4037 			}
4038 			if(foundHost) {
4039 				listeningHost = arg;
4040 				portOrHostFound = true;
4041 				foundHost = false;
4042 				continue;
4043 			}
4044 			if(foundUid) {
4045 				privilegesDropToUid = to!uid_t(arg);
4046 				foundUid = false;
4047 				continue;
4048 			}
4049 			if(foundGid) {
4050 				privilegesDropToGid = to!gid_t(arg);
4051 				foundGid = false;
4052 				continue;
4053 			}
4054 			if(foundListen) {
4055 				this.listenSpec ~= arg;
4056 				foundListen = false;
4057 				continue;
4058 			}
4059 			if(arg == "--listening-host" || arg == "-h" || arg == "/listening-host")
4060 				foundHost = true;
4061 			else if(arg == "--port" || arg == "-p" || arg == "/port" || arg == "--listening-port")
4062 				foundPort = true;
4063 			else if(arg == "--uid")
4064 				foundUid = true;
4065 			else if(arg == "--gid")
4066 				foundGid = true;
4067 			else if(arg == "--listen")
4068 				foundListen = true;
4069 		}
4070 
4071 		if(portOrHostFound && listenSpec.length) {
4072 			throw new Exception("You passed both a --listening-host or --listening-port and a --listen argument. You should fix your script to ONLY use --listen arguments.");
4073 		}
4074 	}
4075 
4076 	version(Windows) {
4077 		private alias uid_t = int;
4078 		private alias gid_t = int;
4079 	}
4080 
4081 	/// user (uid) to drop privileges to
4082 	/// 0 … do nothing
4083 	uid_t privilegesDropToUid = 0;
4084 	/// group (gid) to drop privileges to
4085 	/// 0 … do nothing
4086 	gid_t privilegesDropToGid = 0;
4087 
4088 	private void dropPrivileges() {
4089 		version(Posix) {
4090 			import core.sys.posix.unistd;
4091 
4092 			if (privilegesDropToGid != 0 && setgid(privilegesDropToGid) != 0)
4093 				throw new Exception("Dropping privileges via setgid() failed.");
4094 
4095 			if (privilegesDropToUid != 0 && setuid(privilegesDropToUid) != 0)
4096 				throw new Exception("Dropping privileges via setuid() failed.");
4097 		}
4098 		else {
4099 			// FIXME: Windows?
4100 			//pragma(msg, "Dropping privileges is not implemented for this platform");
4101 		}
4102 
4103 		// done, set zero
4104 		privilegesDropToGid = 0;
4105 		privilegesDropToUid = 0;
4106 	}
4107 
4108 	/++
4109 		Serves a single HTTP request on this thread, with an embedded server, then stops. Designed for cases like embedded oauth responders
4110 
4111 		History:
4112 			Added Oct 10, 2020.
4113 		Example:
4114 
4115 		---
4116 		import arsd.cgi;
4117 		void main() {
4118 			RequestServer server = RequestServer("127.0.0.1", 6789);
4119 			string oauthCode;
4120 			string oauthScope;
4121 			server.serveHttpOnce!((cgi) {
4122 				oauthCode = cgi.request("code");
4123 				oauthScope = cgi.request("scope");
4124 				cgi.write("Thank you, please return to the application.");
4125 			});
4126 			// use the code and scope given
4127 		}
4128 		---
4129 	+/
4130 	void serveHttpOnce(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4131 		import std.socket;
4132 
4133 		bool tcp;
4134 		void delegate() cleanup;
4135 		auto socket = startListening(listeningHost, listeningPort, tcp, cleanup, 1, &dropPrivileges);
4136 		auto connection = socket.accept();
4137 		doThreadHttpConnectionGuts!(CustomCgi, fun, true)(connection);
4138 
4139 		if(cleanup)
4140 			cleanup();
4141 	}
4142 
4143 	/++
4144 		Starts serving requests according to the current configuration.
4145 	+/
4146 	void serve(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4147 		version(netman_httpd) {
4148 			// Obsolete!
4149 
4150 			import arsd.httpd;
4151 			// what about forwarding the other constructor args?
4152 			// this probably needs a whole redoing...
4153 			serveHttp!CustomCgi(&fun, listeningPort);//5005);
4154 			return;
4155 		} else
4156 		version(embedded_httpd_processes) {
4157 			serveEmbeddedHttpdProcesses!(fun, CustomCgi)(this);
4158 		} else
4159 		version(embedded_httpd_threads) {
4160 			serveEmbeddedHttp!(fun, CustomCgi, maxContentLength)();
4161 		} else
4162 		version(scgi) {
4163 			serveScgi!(fun, CustomCgi, maxContentLength)();
4164 		} else
4165 		version(fastcgi) {
4166 			serveFastCgi!(fun, CustomCgi, maxContentLength)(this);
4167 		} else
4168 		version(stdio_http) {
4169 			serveSingleHttpConnectionOnStdio!(fun, CustomCgi, maxContentLength)();
4170 		} else {
4171 			//version=plain_cgi;
4172 			handleCgiRequest!(fun, CustomCgi, maxContentLength)();
4173 		}
4174 	}
4175 
4176 	/++
4177 		Runs the embedded HTTP thread server specifically, regardless of which build configuration you have.
4178 
4179 		If you want the forking worker process server, you do need to compile with the embedded_httpd_processes config though.
4180 	+/
4181 	void serveEmbeddedHttp(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(ThisFor!fun _this) {
4182 		globalStopFlag = false;
4183 		static if(__traits(isStaticFunction, fun))
4184 			alias funToUse = fun;
4185 		else
4186 			void funToUse(CustomCgi cgi) {
4187 				static if(__VERSION__ > 2097)
4188 					__traits(child, _this, fun)(cgi);
4189 				else static assert(0, "Not implemented in your compiler version!");
4190 			}
4191 		auto manager = this.listenSpec is null ?
4192 			new ListeningConnectionManager(listeningHost, listeningPort, &doThreadHttpConnection!(CustomCgi, funToUse), null, useFork, numberOfThreads) :
4193 			new ListeningConnectionManager(this.listenSpec, &doThreadHttpConnection!(CustomCgi, funToUse), null, useFork, numberOfThreads);
4194 		manager.listen();
4195 	}
4196 
4197 	/++
4198 		Runs the embedded SCGI server specifically, regardless of which build configuration you have.
4199 	+/
4200 	void serveScgi(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4201 		globalStopFlag = false;
4202 		auto manager = this.listenSpec is null ?
4203 			new ListeningConnectionManager(listeningHost, listeningPort, &doThreadScgiConnection!(CustomCgi, fun, maxContentLength), null, useFork, numberOfThreads) :
4204 			new ListeningConnectionManager(this.listenSpec, &doThreadScgiConnection!(CustomCgi, fun, maxContentLength), null, useFork, numberOfThreads);
4205 		manager.listen();
4206 	}
4207 
4208 	/++
4209 		Serves a single "connection", but the connection is spoken on stdin and stdout instead of on a socket.
4210 
4211 		Intended for cases like working from systemd, like discussed here: [https://forum.dlang.org/post/avmkfdiitirnrenzljwc@forum.dlang.org]
4212 
4213 		History:
4214 			Added May 29, 2021
4215 	+/
4216 	void serveSingleHttpConnectionOnStdio(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4217 		doThreadHttpConnectionGuts!(CustomCgi, fun, true)(new FakeSocketForStdin());
4218 	}
4219 
4220 	/++
4221 		The [stop] function sets a flag that request handlers can (and should) check periodically. If a handler doesn't
4222 		respond to this flag, the library will force the issue. This determines when and how the issue will be forced.
4223 	+/
4224 	enum ForceStop {
4225 		/++
4226 			Stops accepting new requests, but lets ones already in the queue start and complete before exiting.
4227 		+/
4228 		afterQueuedRequestsComplete,
4229 		/++
4230 			Finishes requests already started their handlers, but drops any others in the queue. Streaming handlers
4231 			should cooperate and exit gracefully, but if they don't, it will continue waiting for them.
4232 		+/
4233 		afterCurrentRequestsComplete,
4234 		/++
4235 			Partial response writes will throw an exception, cancelling any streaming response, but complete
4236 			writes will continue to process. Request handlers that respect the stop token will also gracefully cancel.
4237 		+/
4238 		cancelStreamingRequestsEarly,
4239 		/++
4240 			All writes will throw.
4241 		+/
4242 		cancelAllRequestsEarly,
4243 		/++
4244 			Use OS facilities to forcibly kill running threads. The server process will be in an undefined state after this call (if this call ever returns).
4245 		+/
4246 		forciblyTerminate,
4247 	}
4248 
4249 	version(embedded_httpd_processes) {} else
4250 	/++
4251 		Stops serving after the current requests are completed.
4252 
4253 		Bugs:
4254 			Not implemented on version=embedded_httpd_processes, version=fastcgi on any system, or embedded_httpd on Windows (it does work on embedded_httpd_hybrid
4255 			on Windows however). Only partially implemented on non-Linux posix systems.
4256 
4257 			You might also try SIGINT perhaps.
4258 
4259 			The stopPriority is not yet fully implemented.
4260 	+/
4261 	static void stop(ForceStop stopPriority = ForceStop.afterCurrentRequestsComplete) {
4262 		globalStopFlag = true;
4263 
4264 		version(Posix) {
4265 			if(cancelfd > 0) {
4266 				ulong a = 1;
4267 				core.sys.posix.unistd.write(cancelfd, &a, a.sizeof);
4268 			}
4269 		}
4270 		version(Windows) {
4271 			if(iocp) {
4272 				foreach(i; 0 .. 16) // FIXME
4273 				PostQueuedCompletionStatus(iocp, 0, cast(ULONG_PTR) null, null);
4274 			}
4275 		}
4276 	}
4277 }
4278 
4279 class AuthorizationRequiredException : Exception {
4280 	string type;
4281 	string realm;
4282 	this(string type, string realm, string file, size_t line) {
4283 		this.type = type;
4284 		this.realm = realm;
4285 
4286 		super("Authorization Required", file, line);
4287 	}
4288 }
4289 
4290 private alias AliasSeq(T...) = T;
4291 
4292 version(with_breaking_cgi_features)
4293 mixin(q{
4294 	template ThisFor(alias t) {
4295 		static if(__traits(isStaticFunction, t)) {
4296 			alias ThisFor = AliasSeq!();
4297 		} else {
4298 			alias ThisFor = __traits(parent, t);
4299 		}
4300 	}
4301 });
4302 else
4303 	alias ThisFor(alias t) = AliasSeq!();
4304 
4305 private __gshared bool globalStopFlag = false;
4306 
4307 version(embedded_httpd_processes)
4308 void serveEmbeddedHttpdProcesses(alias fun, CustomCgi = Cgi)(RequestServer params) {
4309 	import core.sys.posix.unistd;
4310 	import core.sys.posix.sys.socket;
4311 	import core.sys.posix.netinet.in_;
4312 	//import std.c.linux.socket;
4313 
4314 	int sock = socket(AF_INET, SOCK_STREAM, 0);
4315 	if(sock == -1)
4316 		throw new Exception("socket");
4317 
4318 	cloexec(sock);
4319 
4320 	{
4321 
4322 		sockaddr_in addr;
4323 		addr.sin_family = AF_INET;
4324 		addr.sin_port = htons(params.listeningPort);
4325 		auto lh = params.listeningHost;
4326 		if(lh.length) {
4327 			if(inet_pton(AF_INET, lh.toStringz(), &addr.sin_addr.s_addr) != 1)
4328 				throw new Exception("bad listening host given, please use an IP address.\nExample: --listening-host 127.0.0.1 means listen only on Localhost.\nExample: --listening-host 0.0.0.0 means listen on all interfaces.\nOr you can pass any other single numeric IPv4 address.");
4329 		} else
4330 			addr.sin_addr.s_addr = INADDR_ANY;
4331 
4332 		// HACKISH
4333 		int on = 1;
4334 		setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, on.sizeof);
4335 		// end hack
4336 
4337 
4338 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
4339 			close(sock);
4340 			throw new Exception("bind");
4341 		}
4342 
4343 		// FIXME: if this queue is full, it will just ignore it
4344 		// and wait for the client to retransmit it. This is an
4345 		// obnoxious timeout condition there.
4346 		if(sock.listen(128) == -1) {
4347 			close(sock);
4348 			throw new Exception("listen");
4349 		}
4350 		params.dropPrivileges();
4351 	}
4352 
4353 	version(embedded_httpd_processes_accept_after_fork) {} else {
4354 		int pipeReadFd;
4355 		int pipeWriteFd;
4356 
4357 		{
4358 			int[2] pipeFd;
4359 			if(socketpair(AF_UNIX, SOCK_DGRAM, 0, pipeFd)) {
4360 				import core.stdc.errno;
4361 				throw new Exception("pipe failed " ~ to!string(errno));
4362 			}
4363 
4364 			pipeReadFd = pipeFd[0];
4365 			pipeWriteFd = pipeFd[1];
4366 		}
4367 	}
4368 
4369 
4370 	int processCount;
4371 	pid_t newPid;
4372 	reopen:
4373 	while(processCount < processPoolSize) {
4374 		newPid = fork();
4375 		if(newPid == 0) {
4376 			// start serving on the socket
4377 			//ubyte[4096] backingBuffer;
4378 			for(;;) {
4379 				bool closeConnection;
4380 				uint i;
4381 				sockaddr addr;
4382 				i = addr.sizeof;
4383 				version(embedded_httpd_processes_accept_after_fork) {
4384 					int s = accept(sock, &addr, &i);
4385 					int opt = 1;
4386 					import core.sys.posix.netinet.tcp;
4387 					// the Cgi class does internal buffering, so disabling this
4388 					// helps with latency in many cases...
4389 					setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
4390 					cloexec(s);
4391 				} else {
4392 					int s;
4393 					auto readret = read_fd(pipeReadFd, &s, s.sizeof, &s);
4394 					if(readret != s.sizeof) {
4395 						import core.stdc.errno;
4396 						throw new Exception("pipe read failed " ~ to!string(errno));
4397 					}
4398 
4399 					//writeln("process ", getpid(), " got socket ", s);
4400 				}
4401 
4402 				try {
4403 
4404 					if(s == -1)
4405 						throw new Exception("accept");
4406 
4407 					scope(failure) close(s);
4408 					//ubyte[__traits(classInstanceSize, BufferedInputRange)] bufferedRangeContainer;
4409 					auto ir = new BufferedInputRange(s);
4410 					//auto ir = emplace!BufferedInputRange(bufferedRangeContainer, s, backingBuffer);
4411 
4412 					while(!ir.empty) {
4413 						//ubyte[__traits(classInstanceSize, CustomCgi)] cgiContainer;
4414 
4415 						Cgi cgi;
4416 						try {
4417 							cgi = new CustomCgi(ir, &closeConnection);
4418 							cgi._outputFileHandle = cast(CgiConnectionHandle) s;
4419 							// if we have a single process and the browser tries to leave the connection open while concurrently requesting another, it will block everything an deadlock since there's no other server to accept it. By closing after each request in this situation, it tells the browser to serialize for us.
4420 							if(processPoolSize <= 1)
4421 								closeConnection = true;
4422 							//cgi = emplace!CustomCgi(cgiContainer, ir, &closeConnection);
4423 						} catch(HttpVersionNotSupportedException he) {
4424 							sendAll(ir.source, plainHttpError(false, "505 HTTP Version Not Supported", he));
4425 							closeConnection = true;
4426 							break;
4427 						} catch(Throwable t) {
4428 							// a construction error is either bad code or bad request; bad request is what it should be since this is bug free :P
4429 							// anyway let's kill the connection
4430 							version(CRuntime_Musl) {
4431 								// LockingTextWriter fails here
4432 								// so working around it
4433 								auto estr = t.toString();
4434 								stderr.rawWrite(estr);
4435 								stderr.rawWrite("\n");
4436 							} else
4437 								stderr.writeln(t.toString());
4438 							sendAll(ir.source, plainHttpError(false, "400 Bad Request", t));
4439 							closeConnection = true;
4440 							break;
4441 						}
4442 						assert(cgi !is null);
4443 						scope(exit)
4444 							cgi.dispose();
4445 
4446 						try {
4447 							fun(cgi);
4448 							cgi.close();
4449 							if(cgi.websocketMode)
4450 								closeConnection = true;
4451 
4452 						} catch(AuthorizationRequiredException are) {
4453 							cgi.setResponseStatus("401 Authorization Required");
4454 							cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
4455 							cgi.close();
4456 						} catch(ConnectionException ce) {
4457 							closeConnection = true;
4458 						} catch(Throwable t) {
4459 							// a processing error can be recovered from
4460 							version(CRuntime_Musl) {
4461 								// LockingTextWriter fails here
4462 								// so working around it
4463 								auto estr = t.toString();
4464 								stderr.rawWrite(estr);
4465 							} else {
4466 								stderr.writeln(t.toString);
4467 							}
4468 							if(!handleException(cgi, t))
4469 								closeConnection = true;
4470 						}
4471 
4472 						if(closeConnection) {
4473 							ir.source.close();
4474 							break;
4475 						} else {
4476 							if(!ir.empty)
4477 								ir.popFront(); // get the next
4478 							else if(ir.sourceClosed) {
4479 								ir.source.close();
4480 							}
4481 						}
4482 					}
4483 
4484 					ir.source.close();
4485 				} catch(Throwable t) {
4486 					version(CRuntime_Musl) {} else
4487 						debug writeln(t);
4488 					// most likely cause is a timeout
4489 				}
4490 			}
4491 		} else if(newPid < 0) {
4492 			throw new Exception("fork failed");
4493 		} else {
4494 			processCount++;
4495 		}
4496 	}
4497 
4498 	// the parent should wait for its children...
4499 	if(newPid) {
4500 		import core.sys.posix.sys.wait;
4501 
4502 		version(embedded_httpd_processes_accept_after_fork) {} else {
4503 			import core.sys.posix.sys.select;
4504 			int[] fdQueue;
4505 			while(true) {
4506 				// writeln("select call");
4507 				int nfds = pipeWriteFd;
4508 				if(sock > pipeWriteFd)
4509 					nfds = sock;
4510 				nfds += 1;
4511 				fd_set read_fds;
4512 				fd_set write_fds;
4513 				FD_ZERO(&read_fds);
4514 				FD_ZERO(&write_fds);
4515 				FD_SET(sock, &read_fds);
4516 				if(fdQueue.length)
4517 					FD_SET(pipeWriteFd, &write_fds);
4518 				auto ret = select(nfds, &read_fds, &write_fds, null, null);
4519 				if(ret == -1) {
4520 					import core.stdc.errno;
4521 					if(errno == EINTR)
4522 						goto try_wait;
4523 					else
4524 						throw new Exception("wtf select");
4525 				}
4526 
4527 				int s = -1;
4528 				if(FD_ISSET(sock, &read_fds)) {
4529 					uint i;
4530 					sockaddr addr;
4531 					i = addr.sizeof;
4532 					s = accept(sock, &addr, &i);
4533 					cloexec(s);
4534 					import core.sys.posix.netinet.tcp;
4535 					int opt = 1;
4536 					setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof);
4537 				}
4538 
4539 				if(FD_ISSET(pipeWriteFd, &write_fds)) {
4540 					if(s == -1 && fdQueue.length) {
4541 						s = fdQueue[0];
4542 						fdQueue = fdQueue[1 .. $]; // FIXME reuse buffer
4543 					}
4544 					write_fd(pipeWriteFd, &s, s.sizeof, s);
4545 					close(s); // we are done with it, let the other process take ownership
4546 				} else
4547 					fdQueue ~= s;
4548 			}
4549 		}
4550 
4551 		try_wait:
4552 
4553 		int status;
4554 		while(-1 != wait(&status)) {
4555 			version(CRuntime_Musl) {} else {
4556 				import std.stdio; writeln("Process died ", status);
4557 			}
4558 			processCount--;
4559 			goto reopen;
4560 		}
4561 		close(sock);
4562 	}
4563 }
4564 
4565 version(fastcgi)
4566 void serveFastCgi(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(RequestServer params) {
4567 	//         SetHandler fcgid-script
4568 	FCGX_Stream* input, output, error;
4569 	FCGX_ParamArray env;
4570 
4571 
4572 
4573 	const(ubyte)[] getFcgiChunk() {
4574 		const(ubyte)[] ret;
4575 		while(FCGX_HasSeenEOF(input) != -1)
4576 			ret ~= cast(ubyte) FCGX_GetChar(input);
4577 		return ret;
4578 	}
4579 
4580 	void writeFcgi(const(ubyte)[] data) {
4581 		FCGX_PutStr(data.ptr, data.length, output);
4582 	}
4583 
4584 	void doARequest() {
4585 		string[string] fcgienv;
4586 
4587 		for(auto e = env; e !is null && *e !is null; e++) {
4588 			string cur = to!string(*e);
4589 			auto idx = cur.indexOf("=");
4590 			string name, value;
4591 			if(idx == -1)
4592 				name = cur;
4593 			else {
4594 				name = cur[0 .. idx];
4595 				value = cur[idx + 1 .. $];
4596 			}
4597 
4598 			fcgienv[name] = value;
4599 		}
4600 
4601 		void flushFcgi() {
4602 			FCGX_FFlush(output);
4603 		}
4604 
4605 		Cgi cgi;
4606 		try {
4607 			cgi = new CustomCgi(maxContentLength, fcgienv, &getFcgiChunk, &writeFcgi, &flushFcgi);
4608 		} catch(Throwable t) {
4609 			FCGX_PutStr(cast(ubyte*) t.msg.ptr, t.msg.length, error);
4610 			writeFcgi(cast(const(ubyte)[]) plainHttpError(true, "400 Bad Request", t));
4611 			return; //continue;
4612 		}
4613 		assert(cgi !is null);
4614 		scope(exit) cgi.dispose();
4615 		try {
4616 			fun(cgi);
4617 			cgi.close();
4618 		} catch(AuthorizationRequiredException are) {
4619 			cgi.setResponseStatus("401 Authorization Required");
4620 			cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
4621 			cgi.close();
4622 		} catch(Throwable t) {
4623 			// log it to the error stream
4624 			FCGX_PutStr(cast(ubyte*) t.msg.ptr, t.msg.length, error);
4625 			// handle it for the user, if we can
4626 			if(!handleException(cgi, t))
4627 				return; // continue;
4628 		}
4629 	}
4630 
4631 	auto lp = params.listeningPort;
4632 	auto host = params.listeningHost;
4633 
4634 	FCGX_Request request;
4635 	if(lp || !host.empty) {
4636 		// if a listening port was specified on the command line, we want to spawn ourself
4637 		// (needed for nginx without spawn-fcgi, e.g. on Windows)
4638 		FCGX_Init();
4639 
4640 		int sock;
4641 
4642 		if(host.startsWith("unix:")) {
4643 			sock = FCGX_OpenSocket(toStringz(params.listeningHost["unix:".length .. $]), 12);
4644 		} else if(host.startsWith("abstract:")) {
4645 			sock = FCGX_OpenSocket(toStringz("\0" ~ params.listeningHost["abstract:".length .. $]), 12);
4646 		} else {
4647 			sock = FCGX_OpenSocket(toStringz(params.listeningHost ~ ":" ~ to!string(lp)), 12);
4648 		}
4649 
4650 		if(sock < 0)
4651 			throw new Exception("Couldn't listen on the port");
4652 		FCGX_InitRequest(&request, sock, 0);
4653 		while(FCGX_Accept_r(&request) >= 0) {
4654 			input = request.inStream;
4655 			output = request.outStream;
4656 			error = request.errStream;
4657 			env = request.envp;
4658 			doARequest();
4659 		}
4660 	} else {
4661 		// otherwise, assume the httpd is doing it (the case for Apache, IIS, and Lighttpd)
4662 		// using the version with a global variable since we are separate processes anyway
4663 		while(FCGX_Accept(&input, &output, &error, &env) >= 0) {
4664 			doARequest();
4665 		}
4666 	}
4667 }
4668 
4669 /// Returns the default listening port for the current cgi configuration. 8085 for embedded httpd, 4000 for scgi, irrelevant for others.
4670 ushort defaultListeningPort() @safe {
4671 	version(netman_httpd)
4672 		return 8080;
4673 	else version(embedded_httpd_processes)
4674 		return 8085;
4675 	else version(embedded_httpd_threads)
4676 		return 8085;
4677 	else version(scgi)
4678 		return 4000;
4679 	else
4680 		return 0;
4681 }
4682 
4683 /// Default host for listening. 127.0.0.1 for scgi, null (aka all interfaces) for all others. If you want the server directly accessible from other computers on the network, normally use null. If not, 127.0.0.1 is a bit better. Settable with default handlers with --listening-host command line argument.
4684 string defaultListeningHost() @safe {
4685 	version(netman_httpd)
4686 		return null;
4687 	else version(embedded_httpd_processes)
4688 		return null;
4689 	else version(embedded_httpd_threads)
4690 		return null;
4691 	else version(scgi)
4692 		return "127.0.0.1";
4693 	else
4694 		return null;
4695 
4696 }
4697 
4698 /++
4699 	This is the function [GenericMain] calls. View its source for some simple boilerplate you can copy/paste and modify, or you can call it yourself from your `main`.
4700 
4701 	Please note that this may spawn other helper processes that will call `main` again. It does this currently for the timer server and event source server (and the quasi-deprecated web socket server).
4702 
4703 	Params:
4704 		fun = Your request handler
4705 		CustomCgi = a subclass of Cgi, if you wise to customize it further
4706 		maxContentLength = max POST size you want to allow
4707 		args = command-line arguments
4708 
4709 	History:
4710 		Documented Sept 26, 2020.
4711 +/
4712 void cgiMainImpl(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)(string[] args) if(is(CustomCgi : Cgi)) {
4713 	if(tryAddonServers(args))
4714 		return;
4715 
4716 	if(trySimulatedRequest!(fun, CustomCgi)(args))
4717 		return;
4718 
4719 	RequestServer server;
4720 	// you can change the port here if you like
4721 	// server.listeningPort = 9000;
4722 
4723 	// then call this to let the command line args override your default
4724 	server.configureFromCommandLine(args);
4725 
4726 	// and serve the request(s).
4727 	server.serve!(fun, CustomCgi, maxContentLength)();
4728 }
4729 
4730 //version(plain_cgi)
4731 void handleCgiRequest(alias fun, CustomCgi = Cgi, long maxContentLength = defaultMaxContentLength)() {
4732 	// standard CGI is the default version
4733 
4734 
4735 	// Set stdin to binary mode if necessary to avoid mangled newlines
4736 	// the fact that stdin is global means this could be trouble but standard cgi request
4737 	// handling is one per process anyway so it shouldn't actually be threaded here or anything.
4738 	version(Windows) {
4739 		version(Win64)
4740 		_setmode(std.stdio.stdin.fileno(), 0x8000);
4741 		else
4742 		setmode(std.stdio.stdin.fileno(), 0x8000);
4743 	}
4744 
4745 	Cgi cgi;
4746 	try {
4747 		cgi = new CustomCgi(maxContentLength);
4748 		version(Posix)
4749 			cgi._outputFileHandle = cast(CgiConnectionHandle) 1; // stdout
4750 		else version(Windows)
4751 			cgi._outputFileHandle = cast(CgiConnectionHandle) GetStdHandle(STD_OUTPUT_HANDLE);
4752 		else static assert(0);
4753 	} catch(Throwable t) {
4754 		version(CRuntime_Musl) {
4755 			// LockingTextWriter fails here
4756 			// so working around it
4757 			auto s = t.toString();
4758 			stderr.rawWrite(s);
4759 			stdout.rawWrite(plainHttpError(true, "400 Bad Request", t));
4760 		} else {
4761 			stderr.writeln(t.msg);
4762 			// the real http server will probably handle this;
4763 			// most likely, this is a bug in Cgi. But, oh well.
4764 			stdout.write(plainHttpError(true, "400 Bad Request", t));
4765 		}
4766 		return;
4767 	}
4768 	assert(cgi !is null);
4769 	scope(exit) cgi.dispose();
4770 
4771 	try {
4772 		fun(cgi);
4773 		cgi.close();
4774 	} catch(AuthorizationRequiredException are) {
4775 		cgi.setResponseStatus("401 Authorization Required");
4776 		cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
4777 		cgi.close();
4778 	} catch (Throwable t) {
4779 		version(CRuntime_Musl) {
4780 			// LockingTextWriter fails here
4781 			// so working around it
4782 			auto s = t.msg;
4783 			stderr.rawWrite(s);
4784 		} else {
4785 			stderr.writeln(t.msg);
4786 		}
4787 		if(!handleException(cgi, t))
4788 			return;
4789 	}
4790 }
4791 
4792 private __gshared int cancelfd = -1;
4793 
4794 /+
4795 	The event loop for embedded_httpd_threads will prolly fiber dispatch
4796 	cgi constructors too, so slow posts will not monopolize a worker thread.
4797 
4798 	May want to provide the worker task system just need to ensure all the fibers
4799 	has a big enough stack for real work... would also ideally like to reuse them.
4800 
4801 
4802 	So prolly bir would switch it to nonblocking. If it would block, it epoll
4803 	registers one shot with this existing fiber to take it over.
4804 
4805 		new connection comes in. it picks a fiber off the free list,
4806 		or if there is none, it creates a new one. this fiber handles
4807 		this connection the whole time.
4808 
4809 		epoll triggers the fiber when something comes in. it is called by
4810 		a random worker thread, it might change at any time. at least during
4811 		the constructor. maybe into the main body it will stay tied to a thread
4812 		just so TLS stuff doesn't randomly change in the middle. but I could
4813 		specify if you yield all bets are off.
4814 
4815 		when the request is finished, if there's more data buffered, it just
4816 		keeps going. if there is no more data buffered, it epoll ctls to
4817 		get triggered when more data comes in. all one shot.
4818 
4819 		when a connection is closed, the fiber returns and is then reset
4820 		and added to the free list. if the free list is full, the fiber is
4821 		just freed, this means it will balloon to a certain size but not generally
4822 		grow beyond that unless the activity keeps going.
4823 
4824 		256 KB stack i thnk per fiber. 4,000 active fibers per gigabyte of memory.
4825 
4826 	So the fiber has its own magic methods to read and write. if they would block, it registers
4827 	for epoll and yields. when it returns, it read/writes and then returns back normal control.
4828 
4829 	basically you issue the command and it tells you when it is done
4830 
4831 	it needs to DEL the epoll thing when it is closed. add it when opened. mod it when anther thing issued
4832 
4833 +/
4834 
4835 /++
4836 	The stack size when a fiber is created. You can set this from your main or from a shared static constructor
4837 	to optimize your memory use if you know you don't need this much space. Be careful though, some functions use
4838 	more stack space than you realize and a recursive function (including ones like in dom.d) can easily grow fast!
4839 
4840 	History:
4841 		Added July 10, 2021. Previously, it used the druntime default of 16 KB.
4842 +/
4843 version(cgi_use_fiber)
4844 __gshared size_t fiberStackSize = 4096 * 100;
4845 
4846 version(cgi_use_fiber)
4847 class CgiFiber : Fiber {
4848 	private void function(Socket) f_handler;
4849 	private void f_handler_dg(Socket s) { // to avoid extra allocation w/ function
4850 		f_handler(s);
4851 	}
4852 	this(void function(Socket) handler) {
4853 		this.f_handler = handler;
4854 		this(&f_handler_dg);
4855 	}
4856 
4857 	this(void delegate(Socket) handler) {
4858 		this.handler = handler;
4859 		super(&run, fiberStackSize);
4860 	}
4861 
4862 	Socket connection;
4863 	void delegate(Socket) handler;
4864 
4865 	void run() {
4866 		handler(connection);
4867 	}
4868 
4869 	void delegate() postYield;
4870 
4871 	private void setPostYield(scope void delegate() py) @nogc {
4872 		postYield = cast(void delegate()) py;
4873 	}
4874 
4875 	void proceed() {
4876 		try {
4877 			call();
4878 			auto py = postYield;
4879 			postYield = null;
4880 			if(py !is null)
4881 				py();
4882 		} catch(Exception e) {
4883 			if(connection)
4884 				connection.close();
4885 			goto terminate;
4886 		}
4887 
4888 		if(state == State.TERM) {
4889 			terminate:
4890 			import core.memory;
4891 			GC.removeRoot(cast(void*) this);
4892 		}
4893 	}
4894 }
4895 
4896 version(cgi_use_fiber)
4897 version(Windows) {
4898 
4899 extern(Windows) private {
4900 
4901 	import core.sys.windows.mswsock;
4902 
4903 	alias GROUP=uint;
4904 	alias LPWSAPROTOCOL_INFOW = void*;
4905 	SOCKET WSASocketW(int af, int type, int protocol, LPWSAPROTOCOL_INFOW lpProtocolInfo, GROUP g, DWORD dwFlags);
4906 	alias WSASend = arsd.core.WSASend;
4907 	alias WSARecv = arsd.core.WSARecv;
4908 	alias WSABUF = arsd.core.WSABUF;
4909 
4910 	/+
4911 	int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
4912 	int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
4913 
4914 	struct WSABUF {
4915 		ULONG len;
4916 		CHAR  *buf;
4917 	}
4918 	+/
4919 	alias LPWSABUF = WSABUF*;
4920 
4921 	alias WSAOVERLAPPED = OVERLAPPED;
4922 	alias LPWSAOVERLAPPED = LPOVERLAPPED;
4923 	/+
4924 
4925 	alias LPFN_ACCEPTEX =
4926 		BOOL
4927 		function(
4928 				SOCKET sListenSocket,
4929 				SOCKET sAcceptSocket,
4930 				//_Out_writes_bytes_(dwReceiveDataLength+dwLocalAddressLength+dwRemoteAddressLength) PVOID lpOutputBuffer,
4931 				void* lpOutputBuffer,
4932 				WORD dwReceiveDataLength,
4933 				WORD dwLocalAddressLength,
4934 				WORD dwRemoteAddressLength,
4935 				LPDWORD lpdwBytesReceived,
4936 				LPOVERLAPPED lpOverlapped
4937 			);
4938 
4939 	enum WSAID_ACCEPTEX = GUID([0xb5367df1,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]]);
4940 	+/
4941 
4942 	enum WSAID_GETACCEPTEXSOCKADDRS = GUID(0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]);
4943 }
4944 
4945 private class PseudoblockingOverlappedSocket : Socket {
4946 	SOCKET handle;
4947 
4948 	CgiFiber fiber;
4949 
4950 	this(AddressFamily af, SocketType st) {
4951 		auto handle = WSASocketW(af, st, 0, null, 0, 1 /*WSA_FLAG_OVERLAPPED*/);
4952 		if(!handle)
4953 			throw new Exception("WSASocketW");
4954 		this.handle = handle;
4955 
4956 		iocp = CreateIoCompletionPort(cast(HANDLE) handle, iocp, cast(ULONG_PTR) cast(void*) this, 0);
4957 
4958 		if(iocp is null) {
4959 			writeln(GetLastError());
4960 			throw new Exception("CreateIoCompletionPort");
4961 		}
4962 
4963 		super(cast(socket_t) handle, af);
4964 	}
4965 	this() pure nothrow @trusted { assert(0); }
4966 
4967 	override void blocking(bool) {} // meaningless to us, just ignore it.
4968 
4969 	protected override Socket accepting() pure nothrow {
4970 		assert(0);
4971 	}
4972 
4973 	bool addressesParsed;
4974 	Address la;
4975 	Address ra;
4976 
4977 	private void populateAddresses() {
4978 		if(addressesParsed)
4979 			return;
4980 		addressesParsed = true;
4981 
4982 		int lalen, ralen;
4983 
4984 		sockaddr_in* la;
4985 		sockaddr_in* ra;
4986 
4987 		lpfnGetAcceptExSockaddrs(
4988 			scratchBuffer.ptr,
4989 			0, // same as in the AcceptEx call!
4990 			sockaddr_in.sizeof + 16,
4991 			sockaddr_in.sizeof + 16,
4992 			cast(sockaddr**) &la,
4993 			&lalen,
4994 			cast(sockaddr**) &ra,
4995 			&ralen
4996 		);
4997 
4998 		if(la)
4999 			this.la = new InternetAddress(*la);
5000 		if(ra)
5001 			this.ra = new InternetAddress(*ra);
5002 
5003 	}
5004 
5005 	override @property @trusted Address localAddress() {
5006 		populateAddresses();
5007 		return la;
5008 	}
5009 	override @property @trusted Address remoteAddress() {
5010 		populateAddresses();
5011 		return ra;
5012 	}
5013 
5014 	PseudoblockingOverlappedSocket accepted;
5015 
5016 	__gshared static LPFN_ACCEPTEX lpfnAcceptEx;
5017 	__gshared static typeof(&GetAcceptExSockaddrs) lpfnGetAcceptExSockaddrs;
5018 
5019 	override Socket accept() @trusted {
5020 		__gshared static LPFN_ACCEPTEX lpfnAcceptEx;
5021 
5022 		if(lpfnAcceptEx is null) {
5023 			DWORD dwBytes;
5024 			GUID GuidAcceptEx = WSAID_ACCEPTEX;
5025 
5026 			auto iResult = WSAIoctl(handle, 0xc8000006 /*SIO_GET_EXTENSION_FUNCTION_POINTER*/,
5027 					&GuidAcceptEx, GuidAcceptEx.sizeof,
5028 					&lpfnAcceptEx, lpfnAcceptEx.sizeof,
5029 					&dwBytes, null, null);
5030 
5031 			GuidAcceptEx = WSAID_GETACCEPTEXSOCKADDRS;
5032 			iResult = WSAIoctl(handle, 0xc8000006 /*SIO_GET_EXTENSION_FUNCTION_POINTER*/,
5033 					&GuidAcceptEx, GuidAcceptEx.sizeof,
5034 					&lpfnGetAcceptExSockaddrs, lpfnGetAcceptExSockaddrs.sizeof,
5035 					&dwBytes, null, null);
5036 
5037 		}
5038 
5039 		auto pfa = new PseudoblockingOverlappedSocket(AddressFamily.INET, SocketType.STREAM);
5040 		accepted = pfa;
5041 
5042 		SOCKET pendingForAccept = pfa.handle;
5043 		DWORD ignored;
5044 
5045 		auto ret = lpfnAcceptEx(handle,
5046 			pendingForAccept,
5047 			// buffer to receive up front
5048 			pfa.scratchBuffer.ptr,
5049 			0,
5050 			// size of local and remote addresses. normally + 16.
5051 			sockaddr_in.sizeof + 16,
5052 			sockaddr_in.sizeof + 16,
5053 			&ignored, // bytes would be given through the iocp instead but im not even requesting the thing
5054 			&overlapped
5055 		);
5056 
5057 		return pfa;
5058 	}
5059 
5060 	override void connect(Address to) { assert(0); }
5061 
5062 	DWORD lastAnswer;
5063 	ubyte[1024] scratchBuffer;
5064 	static assert(scratchBuffer.length > sockaddr_in.sizeof * 2 + 32);
5065 
5066 	WSABUF[1] buffer;
5067 	OVERLAPPED overlapped;
5068 	override ptrdiff_t send(scope const(void)[] buf, SocketFlags flags) @trusted {
5069 		overlapped = overlapped.init;
5070 		buffer[0].len = cast(DWORD) buf.length;
5071 		buffer[0].buf = cast(ubyte*) buf.ptr;
5072 		fiber.setPostYield( () {
5073 			if(!WSASend(handle, buffer.ptr, cast(DWORD) buffer.length, null, 0, &overlapped, null)) {
5074 				if(GetLastError() != 997) {
5075 					//throw new Exception("WSASend fail");
5076 				}
5077 			}
5078 		});
5079 
5080 		Fiber.yield();
5081 		return lastAnswer;
5082 	}
5083 	override ptrdiff_t receive(scope void[] buf, SocketFlags flags) @trusted {
5084 		overlapped = overlapped.init;
5085 		buffer[0].len = cast(DWORD) buf.length;
5086 		buffer[0].buf = cast(ubyte*) buf.ptr;
5087 
5088 		DWORD flags2 = 0;
5089 
5090 		fiber.setPostYield(() {
5091 			if(!WSARecv(handle, buffer.ptr, cast(DWORD) buffer.length, null, &flags2 /* flags */, &overlapped, null)) {
5092 				if(GetLastError() != 997) {
5093 					//writeln("WSARecv ", WSAGetLastError());
5094 					//throw new Exception("WSARecv fail");
5095 				}
5096 			}
5097 		});
5098 
5099 		Fiber.yield();
5100 		return lastAnswer;
5101 	}
5102 
5103 	// I might go back and implement these for udp things.
5104 	override ptrdiff_t receiveFrom(scope void[] buf, SocketFlags flags, ref Address from) @trusted {
5105 		assert(0);
5106 	}
5107 	override ptrdiff_t receiveFrom(scope void[] buf, SocketFlags flags) @trusted {
5108 		assert(0);
5109 	}
5110 	override ptrdiff_t sendTo(scope const(void)[] buf, SocketFlags flags, Address to) @trusted {
5111 		assert(0);
5112 	}
5113 	override ptrdiff_t sendTo(scope const(void)[] buf, SocketFlags flags) @trusted {
5114 		assert(0);
5115 	}
5116 
5117 	// lol overload sets
5118 	alias send = typeof(super).send;
5119 	alias receive = typeof(super).receive;
5120 	alias sendTo = typeof(super).sendTo;
5121 	alias receiveFrom = typeof(super).receiveFrom;
5122 
5123 }
5124 }
5125 
5126 void doThreadHttpConnection(CustomCgi, alias fun)(Socket connection) {
5127 	assert(connection !is null);
5128 	version(cgi_use_fiber) {
5129 		auto fiber = new CgiFiber(&doThreadHttpConnectionGuts!(CustomCgi, fun));
5130 
5131 		version(Windows) {
5132 			(cast(PseudoblockingOverlappedSocket) connection).fiber = fiber;
5133 		}
5134 
5135 		import core.memory;
5136 		GC.addRoot(cast(void*) fiber);
5137 		fiber.connection = connection;
5138 		fiber.proceed();
5139 	} else {
5140 		doThreadHttpConnectionGuts!(CustomCgi, fun)(connection);
5141 	}
5142 }
5143 
5144 /+
5145 
5146 /+
5147 	The represents a recyclable per-task arena allocator. The default is to let the GC manage the whole block as a large array, meaning if a reference into it is escaped, it waste memory but is not dangerous. If you don't escape any references to it and don't do anything special, the GC collects it.
5148 
5149 	But, if you call `cgi.recyclable = true`, the memory is retained for the next request on the thread. If a reference is escaped, it is the user's problem; it can be modified (and break the `immutable` guarantees!) and thus be memory unsafe. They're taking responsibility for doing it right when they call `escape`. But if they do it right and opt into recycling, the memory is all reused to give a potential boost without requiring the GC's involvement.
5150 
5151 	What if one request used an abnormally large amount of memory though? Will recycling it keep that pinned forever? No, that's why it keeps track of some stats. If a working set was significantly above average and not fully utilized for a while, it will just let the GC have it again despite your suggestion to recycle it.
5152 
5153 	Be warned that growing the memory block may release the old, smaller block for garbage collection. If you retained references to it, it may not be collectable and lead to some unnecessary memory growth. It is probably best to try to keep the things sized in a continuous block that doesn't have to grow often.
5154 
5155 	Internally, it is broken up into a few blocks:
5156 		* the request block. This holds the incoming request and associated data (parsed headers, variables, etc).
5157 		* the scannable block. this holds pointers arrays, classes, etc. associated with this request, so named because the GC scans it.
5158 		* the response block. This holds the output buffer.
5159 
5160 	And I may add more later if I decide to open this up to outside user code.
5161 
5162 	The scannable block is separate to limit the amount of work the GC has to do; no point asking it to scan that which need not be scanned.
5163 
5164 	The request and response blocks are separated because they will have different typical sizes, with the request likely being less predictable. Being able to release one to the GC while recycling the other might help, and having them grow independently (if needed) may also prevent some pain.
5165 
5166 	All of this are internal implementation details subject to change at any time without notice. It is valid for my recycle method to do absolutely nothing; the GC also eventually recycles memory!
5167 
5168 	Each active task can have its own recyclable memory object. When you recycle it, it is added to a thread-local freelist. If the list is excessively large, entries maybe discarded at random and left for the GC to prevent a temporary burst of activity from leading to a permanent waste of memory.
5169 +/
5170 struct RecyclableMemory {
5171 	private ubyte[] inputBuffer;
5172 	private ubyte[] processedRequestBlock;
5173 	private void[] scannableBlock;
5174 	private ubyte[] outputBuffer;
5175 
5176 	RecyclableMemory* next;
5177 }
5178 
5179 /++
5180 	This emulates the D associative array interface with a different internal implementation.
5181 
5182 	string s = cgi.get["foo"]; // just does cgi.getArray[x][$-1];
5183 	string[] arr = cgi.getArray["foo"];
5184 
5185 	"foo" in cgi.get
5186 
5187 	foreach(k, v; cgi.get)
5188 
5189 	cgi.get.toAA // for compatibility
5190 
5191 	// and this can urldecode lazily tbh... in-place even, since %xx is always longer than a single char thing it turns into...
5192 		... but how does it mark that it has already been processed in-place? it'd have to just add it to the index then.
5193 
5194 	deprecated alias toAA this;
5195 +/
5196 struct VariableCollection {
5197 	private VariableArrayCollection* vac;
5198 
5199 	const(char[]) opIndex(scope const char[] key) {
5200 		return (*vac)[key][$-1];
5201 	}
5202 
5203 	const(char[]*) opBinaryRight(string op : "in")(scope const char[] key) {
5204 		return key in (*vac);
5205 	}
5206 
5207 	int opApply(int delegate(scope const(char)[] key, scope const(char)[] value) dg) {
5208 		foreach(k, v; *vac) {
5209 			if(auto res = dg(k, v[$-1]))
5210 				return res;
5211 		}
5212 		return 0;
5213 	}
5214 
5215 	immutable(string[string]) toAA() {
5216 		string[string] aa;
5217 		foreach(k, v; *vac)
5218 			aa[k.idup] = v[$-1].idup;
5219 		return aa;
5220 	}
5221 
5222 	deprecated alias toAA this;
5223 }
5224 
5225 struct VariableArrayCollection {
5226 	/+
5227 		This needs the actual implementation of looking it up. As it pulls data, it should
5228 		decode and index for later.
5229 
5230 		The index will go into a block attached to the cgi object and it should prolly be sorted
5231 		something like
5232 
5233 		[count of names]
5234 		[slice to name][count of values][slice to value, decoded in-place, ...]
5235 		...
5236 	+/
5237 	private Cgi cgi;
5238 
5239 	const(char[][]) opIndex(scope const char[] key) {
5240 		return null;
5241 	}
5242 
5243 	const(char[][]*) opBinaryRight(string op : "in")(scope const char[] key) {
5244 		return null;
5245 	}
5246 
5247 	// int opApply(int delegate(scope const(char)[] key, scope const(char)[][] value) dg)
5248 
5249 	immutable(string[string]) toAA() {
5250 		return null;
5251 	}
5252 
5253 	deprecated alias toAA this;
5254 
5255 }
5256 
5257 struct HeaderCollection {
5258 
5259 }
5260 +/
5261 
5262 void doThreadHttpConnectionGuts(CustomCgi, alias fun, bool alwaysCloseConnection = false)(Socket connection) {
5263 	scope(failure) {
5264 		// catch all for other errors
5265 		try {
5266 			sendAll(connection, plainHttpError(false, "500 Internal Server Error", null));
5267 			connection.close();
5268 		} catch(Exception e) {} // swallow it, we're aborting anyway.
5269 	}
5270 
5271 	bool closeConnection = alwaysCloseConnection;
5272 
5273 	/+
5274 	ubyte[4096] inputBuffer = void;
5275 	ubyte[__traits(classInstanceSize, BufferedInputRange)] birBuffer = void;
5276 	ubyte[__traits(classInstanceSize, CustomCgi)] cgiBuffer = void;
5277 
5278 	birBuffer[] = cast(ubyte[]) typeid(BufferedInputRange).initializer()[];
5279 	BufferedInputRange ir = cast(BufferedInputRange) cast(void*) birBuffer.ptr;
5280 	ir.__ctor(connection, inputBuffer[], true);
5281 	+/
5282 
5283 	auto ir = new BufferedInputRange(connection);
5284 
5285 	while(!ir.empty) {
5286 
5287 		if(ir.view.length == 0) {
5288 			ir.popFront();
5289 			if(ir.sourceClosed) {
5290 				connection.close();
5291 				closeConnection = true;
5292 				break;
5293 			}
5294 		}
5295 
5296 		Cgi cgi;
5297 		try {
5298 			cgi = new CustomCgi(ir, &closeConnection);
5299 			// There's a bunch of these casts around because the type matches up with
5300 			// the -version=.... specifiers, just you can also create a RequestServer
5301 			// and instantiate the things where the types don't match up. It isn't exactly
5302 			// correct but I also don't care rn. Might FIXME and either remove it later or something.
5303 			cgi._outputFileHandle = cast(CgiConnectionHandle) connection.handle;
5304 		} catch(ConnectionClosedException ce) {
5305 			closeConnection = true;
5306 			break;
5307 		} catch(ConnectionException ce) {
5308 			// broken pipe or something, just abort the connection
5309 			closeConnection = true;
5310 			break;
5311 		} catch(HttpVersionNotSupportedException ve) {
5312 			sendAll(connection, plainHttpError(false, "505 HTTP Version Not Supported", ve));
5313 			closeConnection = true;
5314 			break;
5315 		} catch(Throwable t) {
5316 			// a construction error is either bad code or bad request; bad request is what it should be since this is bug free :P
5317 			// anyway let's kill the connection
5318 			version(CRuntime_Musl) {
5319 				stderr.rawWrite(t.toString());
5320 				stderr.rawWrite("\n");
5321 			} else {
5322 				stderr.writeln(t.toString());
5323 			}
5324 			sendAll(connection, plainHttpError(false, "400 Bad Request", t));
5325 			closeConnection = true;
5326 			break;
5327 		}
5328 		assert(cgi !is null);
5329 		scope(exit)
5330 			cgi.dispose();
5331 
5332 		try {
5333 			fun(cgi);
5334 			cgi.close();
5335 			if(cgi.websocketMode)
5336 				closeConnection = true;
5337 		} catch(AuthorizationRequiredException are) {
5338 			cgi.setResponseStatus("401 Authorization Required");
5339 			cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
5340 			cgi.close();
5341 		} catch(ConnectionException ce) {
5342 			// broken pipe or something, just abort the connection
5343 			closeConnection = true;
5344 		} catch(ConnectionClosedException ce) {
5345 			// broken pipe or something, just abort the connection
5346 			closeConnection = true;
5347 		} catch(Throwable t) {
5348 			// a processing error can be recovered from
5349 			version(CRuntime_Musl) {} else
5350 			stderr.writeln(t.toString);
5351 			if(!handleException(cgi, t))
5352 				closeConnection = true;
5353 		}
5354 
5355 		if(globalStopFlag)
5356 			closeConnection = true;
5357 
5358 		if(closeConnection || alwaysCloseConnection) {
5359 			connection.shutdown(SocketShutdown.BOTH);
5360 			connection.close();
5361 			ir.dispose();
5362 			closeConnection = false; // don't reclose after loop
5363 			break;
5364 		} else {
5365 			if(ir.front.length) {
5366 				ir.popFront(); // we can't just discard the buffer, so get the next bit and keep chugging along
5367 			} else if(ir.sourceClosed) {
5368 				ir.source.shutdown(SocketShutdown.BOTH);
5369 				ir.source.close();
5370 				ir.dispose();
5371 				closeConnection = false;
5372 			} else {
5373 				continue;
5374 				// break; // this was for a keepalive experiment
5375 			}
5376 		}
5377 	}
5378 
5379 	if(closeConnection) {
5380 		connection.shutdown(SocketShutdown.BOTH);
5381 		connection.close();
5382 		ir.dispose();
5383 	}
5384 
5385 	// I am otherwise NOT closing it here because the parent thread might still be able to make use of the keep-alive connection!
5386 }
5387 
5388 void doThreadScgiConnection(CustomCgi, alias fun, long maxContentLength)(Socket connection) {
5389 	// and now we can buffer
5390 	scope(failure)
5391 		connection.close();
5392 
5393 	import al = std.algorithm;
5394 
5395 	size_t size;
5396 
5397 	string[string] headers;
5398 
5399 	auto range = new BufferedInputRange(connection);
5400 	more_data:
5401 	auto chunk = range.front();
5402 	// waiting for colon for header length
5403 	auto idx = indexOf(cast(string) chunk, ':');
5404 	if(idx == -1) {
5405 		try {
5406 			range.popFront();
5407 		} catch(Exception e) {
5408 			// it is just closed, no big deal
5409 			connection.close();
5410 			return;
5411 		}
5412 		goto more_data;
5413 	}
5414 
5415 	size = to!size_t(cast(string) chunk[0 .. idx]);
5416 	chunk = range.consume(idx + 1);
5417 	// reading headers
5418 	if(chunk.length < size)
5419 		range.popFront(0, size + 1);
5420 	// we are now guaranteed to have enough
5421 	chunk = range.front();
5422 	assert(chunk.length > size);
5423 
5424 	idx = 0;
5425 	string key;
5426 	string value;
5427 	foreach(part; al.splitter(chunk, '\0')) {
5428 		if(idx & 1) { // odd is value
5429 			value = cast(string)(part.idup);
5430 			headers[key] = value; // commit
5431 		} else
5432 			key = cast(string)(part.idup);
5433 		idx++;
5434 	}
5435 
5436 	enforce(chunk[size] == ','); // the terminator
5437 
5438 	range.consume(size + 1);
5439 	// reading data
5440 	// this will be done by Cgi
5441 
5442 	const(ubyte)[] getScgiChunk() {
5443 		// we are already primed
5444 		auto data = range.front();
5445 		if(data.length == 0 && !range.sourceClosed) {
5446 			range.popFront(0);
5447 			data = range.front();
5448 		} else if (range.sourceClosed)
5449 			range.source.close();
5450 
5451 		return data;
5452 	}
5453 
5454 	void writeScgi(const(ubyte)[] data) {
5455 		sendAll(connection, data);
5456 	}
5457 
5458 	void flushScgi() {
5459 		// I don't *think* I have to do anything....
5460 	}
5461 
5462 	Cgi cgi;
5463 	try {
5464 		cgi = new CustomCgi(maxContentLength, headers, &getScgiChunk, &writeScgi, &flushScgi);
5465 		cgi._outputFileHandle = cast(CgiConnectionHandle) connection.handle;
5466 	} catch(Throwable t) {
5467 		sendAll(connection, plainHttpError(true, "400 Bad Request", t));
5468 		connection.close();
5469 		return; // this connection is dead
5470 	}
5471 	assert(cgi !is null);
5472 	scope(exit) cgi.dispose();
5473 	try {
5474 		fun(cgi);
5475 		cgi.close();
5476 		connection.close();
5477 
5478 	} catch(AuthorizationRequiredException are) {
5479 		cgi.setResponseStatus("401 Authorization Required");
5480 		cgi.header ("WWW-Authenticate: "~are.type~" realm=\""~are.realm~"\"");
5481 		cgi.close();
5482 	} catch(Throwable t) {
5483 		// no std err
5484 		if(!handleException(cgi, t)) {
5485 			connection.close();
5486 			return;
5487 		} else {
5488 			connection.close();
5489 			return;
5490 		}
5491 	}
5492 }
5493 
5494 string printDate(DateTime date) {
5495 	char[29] buffer = void;
5496 	printDateToBuffer(date, buffer[]);
5497 	return buffer.idup;
5498 }
5499 
5500 int printDateToBuffer(DateTime date, char[] buffer) @nogc {
5501 	assert(buffer.length >= 29);
5502 	// 29 static length ?
5503 
5504 	static immutable daysOfWeek = [
5505 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
5506 	];
5507 
5508 	static immutable months = [
5509 		null, "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
5510 	];
5511 
5512 	buffer[0 .. 3] = daysOfWeek[date.dayOfWeek];
5513 	buffer[3 .. 5] = ", ";
5514 	buffer[5] = date.day / 10 + '0';
5515 	buffer[6] = date.day % 10 + '0';
5516 	buffer[7] = ' ';
5517 	buffer[8 .. 11] = months[date.month];
5518 	buffer[11] = ' ';
5519 	auto y = date.year;
5520 	buffer[12] = cast(char) (y / 1000 + '0'); y %= 1000;
5521 	buffer[13] = cast(char) (y / 100 + '0'); y %= 100;
5522 	buffer[14] = cast(char) (y / 10 + '0'); y %= 10;
5523 	buffer[15] = cast(char) (y + '0');
5524 	buffer[16] = ' ';
5525 	buffer[17] = date.hour / 10 + '0';
5526 	buffer[18] = date.hour % 10 + '0';
5527 	buffer[19] = ':';
5528 	buffer[20] = date.minute / 10 + '0';
5529 	buffer[21] = date.minute % 10 + '0';
5530 	buffer[22] = ':';
5531 	buffer[23] = date.second / 10 + '0';
5532 	buffer[24] = date.second % 10 + '0';
5533 	buffer[25 .. $] = " GMT";
5534 
5535 	return 29;
5536 }
5537 
5538 
5539 // Referencing this gigantic typeid seems to remind the compiler
5540 // to actually put the symbol in the object file. I guess the immutable
5541 // assoc array array isn't actually included in druntime
5542 void hackAroundLinkerError() {
5543       stdout.rawWrite(typeid(const(immutable(char)[][])[immutable(char)[]]).toString());
5544       stdout.rawWrite(typeid(immutable(char)[][][immutable(char)[]]).toString());
5545       stdout.rawWrite(typeid(Cgi.UploadedFile[immutable(char)[]]).toString());
5546       stdout.rawWrite(typeid(Cgi.UploadedFile[][immutable(char)[]]).toString());
5547       stdout.rawWrite(typeid(immutable(Cgi.UploadedFile)[immutable(char)[]]).toString());
5548       stdout.rawWrite(typeid(immutable(Cgi.UploadedFile[])[immutable(char)[]]).toString());
5549       stdout.rawWrite(typeid(immutable(char[])[immutable(char)[]]).toString());
5550       // this is getting kinda ridiculous btw. Moving assoc arrays
5551       // to the library is the pain that keeps on coming.
5552 
5553       // eh this broke the build on the work server
5554       // stdout.rawWrite(typeid(immutable(char)[][immutable(string[])]));
5555       stdout.rawWrite(typeid(immutable(string[])[immutable(char)[]]).toString());
5556 }
5557 
5558 
5559 
5560 
5561 
5562 version(fastcgi) {
5563 	pragma(lib, "fcgi");
5564 
5565 	static if(size_t.sizeof == 8) // 64 bit
5566 		alias long c_int;
5567 	else
5568 		alias int c_int;
5569 
5570 	extern(C) {
5571 		struct FCGX_Stream {
5572 			ubyte* rdNext;
5573 			ubyte* wrNext;
5574 			ubyte* stop;
5575 			ubyte* stopUnget;
5576 			c_int isReader;
5577 			c_int isClosed;
5578 			c_int wasFCloseCalled;
5579 			c_int FCGI_errno;
5580 			void* function(FCGX_Stream* stream) fillBuffProc;
5581 			void* function(FCGX_Stream* stream, c_int doClose) emptyBuffProc;
5582 			void* data;
5583 		}
5584 
5585 		// note: this is meant to be opaque, so don't access it directly
5586 		struct FCGX_Request {
5587 			int requestId;
5588 			int role;
5589 			FCGX_Stream* inStream;
5590 			FCGX_Stream* outStream;
5591 			FCGX_Stream* errStream;
5592 			char** envp;
5593 			void* paramsPtr;
5594 			int ipcFd;
5595 			int isBeginProcessed;
5596 			int keepConnection;
5597 			int appStatus;
5598 			int nWriters;
5599 			int flags;
5600 			int listen_sock;
5601 		}
5602 
5603 		int FCGX_InitRequest(FCGX_Request *request, int sock, int flags);
5604 		void FCGX_Init();
5605 
5606 		int FCGX_Accept_r(FCGX_Request *request);
5607 
5608 
5609 		alias char** FCGX_ParamArray;
5610 
5611 		c_int FCGX_Accept(FCGX_Stream** stdin, FCGX_Stream** stdout, FCGX_Stream** stderr, FCGX_ParamArray* envp);
5612 		c_int FCGX_GetChar(FCGX_Stream* stream);
5613 		c_int FCGX_PutStr(const ubyte* str, c_int n, FCGX_Stream* stream);
5614 		int FCGX_HasSeenEOF(FCGX_Stream* stream);
5615 		c_int FCGX_FFlush(FCGX_Stream *stream);
5616 
5617 		int FCGX_OpenSocket(const char*, int);
5618 	}
5619 }
5620 
5621 
5622 /* This might go int a separate module eventually. It is a network input helper class. */
5623 
5624 import std.socket;
5625 
5626 version(cgi_use_fiber) {
5627 	import core.thread;
5628 
5629 	version(linux) {
5630 		import core.sys.linux.epoll;
5631 
5632 		int epfd = -1; // thread local because EPOLLEXCLUSIVE works much better this way... weirdly.
5633 	} else version(Windows) {
5634 		// declaring the iocp thing below...
5635 	} else static assert(0, "The hybrid fiber server is not implemented on your OS.");
5636 }
5637 
5638 version(Windows)
5639 	__gshared HANDLE iocp;
5640 
5641 version(cgi_use_fiber) {
5642 	version(linux)
5643 	private enum WakeupEvent {
5644 		Read = EPOLLIN,
5645 		Write = EPOLLOUT
5646 	}
5647 	else version(Windows)
5648 	private enum WakeupEvent {
5649 		Read, Write
5650 	}
5651 	else static assert(0);
5652 }
5653 
5654 version(cgi_use_fiber)
5655 private void registerEventWakeup(bool* registered, Socket source, WakeupEvent e) @nogc {
5656 
5657 	// static cast since I know what i have in here and don't want to pay for dynamic cast
5658 	auto f = cast(CgiFiber) cast(void*) Fiber.getThis();
5659 
5660 	version(linux) {
5661 		f.setPostYield = () {
5662 			if(*registered) {
5663 				// rearm
5664 				epoll_event evt;
5665 				evt.events = e | EPOLLONESHOT;
5666 				evt.data.ptr = cast(void*) f;
5667 				if(epoll_ctl(epfd, EPOLL_CTL_MOD, source.handle, &evt) == -1)
5668 					throw new Exception("epoll_ctl");
5669 			} else {
5670 				// initial registration
5671 				*registered = true ;
5672 				int fd = source.handle;
5673 				epoll_event evt;
5674 				evt.events = e | EPOLLONESHOT;
5675 				evt.data.ptr = cast(void*) f;
5676 				if(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &evt) == -1)
5677 					throw new Exception("epoll_ctl");
5678 			}
5679 		};
5680 
5681 		Fiber.yield();
5682 
5683 		f.setPostYield(null);
5684 	} else version(Windows) {
5685 		Fiber.yield();
5686 	}
5687 	else static assert(0);
5688 }
5689 
5690 version(cgi_use_fiber)
5691 void unregisterSource(Socket s) {
5692 	version(linux) {
5693 		epoll_event evt;
5694 		epoll_ctl(epfd, EPOLL_CTL_DEL, s.handle(), &evt);
5695 	} else version(Windows) {
5696 		// intentionally blank
5697 	}
5698 	else static assert(0);
5699 }
5700 
5701 // it is a class primarily for reference semantics
5702 // I might change this interface
5703 /// This is NOT ACTUALLY an input range! It is too different. Historical mistake kinda.
5704 class BufferedInputRange {
5705 	version(Posix)
5706 	this(int source, ubyte[] buffer = null) {
5707 		this(new Socket(cast(socket_t) source, AddressFamily.INET), buffer);
5708 	}
5709 
5710 	this(Socket source, ubyte[] buffer = null, bool allowGrowth = true) {
5711 		// if they connect but never send stuff to us, we don't want it wasting the process
5712 		// so setting a time out
5713 		version(cgi_use_fiber)
5714 			source.blocking = false;
5715 		else
5716 			source.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(3));
5717 
5718 		this.source = source;
5719 		if(buffer is null) {
5720 			underlyingBuffer = new ubyte[4096];
5721 			this.allowGrowth = true;
5722 		} else {
5723 			underlyingBuffer = buffer;
5724 			this.allowGrowth = allowGrowth;
5725 		}
5726 
5727 		assert(underlyingBuffer.length);
5728 
5729 		// we assume view.ptr is always inside underlyingBuffer
5730 		view = underlyingBuffer[0 .. 0];
5731 
5732 		popFront(); // prime
5733 	}
5734 
5735 	version(cgi_use_fiber) {
5736 		bool registered;
5737 	}
5738 
5739 	void dispose() {
5740 		version(cgi_use_fiber) {
5741 			if(registered)
5742 				unregisterSource(source);
5743 		}
5744 	}
5745 
5746 	/**
5747 		A slight difference from regular ranges is you can give it the maximum
5748 		number of bytes to consume.
5749 
5750 		IMPORTANT NOTE: the default is to consume nothing, so if you don't call
5751 		consume() yourself and use a regular foreach, it will infinitely loop!
5752 
5753 		The default is to do what a normal range does, and consume the whole buffer
5754 		and wait for additional input.
5755 
5756 		You can also specify 0, to append to the buffer, or any other number
5757 		to remove the front n bytes and wait for more.
5758 	*/
5759 	void popFront(size_t maxBytesToConsume = 0 /*size_t.max*/, size_t minBytesToSettleFor = 0, bool skipConsume = false) {
5760 		if(sourceClosed)
5761 			throw new ConnectionClosedException("can't get any more data from a closed source");
5762 		if(!skipConsume)
5763 			consume(maxBytesToConsume);
5764 
5765 		// we might have to grow the buffer
5766 		if(minBytesToSettleFor > underlyingBuffer.length || view.length == underlyingBuffer.length) {
5767 			if(allowGrowth) {
5768 			//import std.stdio; writeln("growth");
5769 				auto viewStart = view.ptr - underlyingBuffer.ptr;
5770 				size_t growth = 4096;
5771 				// make sure we have enough for what we're being asked for
5772 				if(minBytesToSettleFor > 0 && minBytesToSettleFor - underlyingBuffer.length > growth)
5773 					growth = minBytesToSettleFor - underlyingBuffer.length;
5774 				//import std.stdio; writeln(underlyingBuffer.length, " ", viewStart, " ", view.length, " ", growth,  " ", minBytesToSettleFor, " ", minBytesToSettleFor - underlyingBuffer.length);
5775 				underlyingBuffer.length += growth;
5776 				view = underlyingBuffer[viewStart .. view.length];
5777 			} else
5778 				throw new Exception("No room left in the buffer");
5779 		}
5780 
5781 		do {
5782 			auto freeSpace = underlyingBuffer[view.ptr - underlyingBuffer.ptr + view.length .. $];
5783 			try_again:
5784 			auto ret = source.receive(freeSpace);
5785 			if(ret == Socket.ERROR) {
5786 				if(wouldHaveBlocked()) {
5787 					version(cgi_use_fiber) {
5788 						registerEventWakeup(&registered, source, WakeupEvent.Read);
5789 						goto try_again;
5790 					} else {
5791 						// gonna treat a timeout here as a close
5792 						sourceClosed = true;
5793 						return;
5794 					}
5795 				}
5796 				version(Posix) {
5797 					import core.stdc.errno;
5798 					if(errno == EINTR || errno == EAGAIN) {
5799 						goto try_again;
5800 					}
5801 					if(errno == ECONNRESET) {
5802 						sourceClosed = true;
5803 						return;
5804 					}
5805 				}
5806 				throw new Exception(lastSocketError); // FIXME
5807 			}
5808 			if(ret == 0) {
5809 				sourceClosed = true;
5810 				return;
5811 			}
5812 
5813 			//import std.stdio; writeln(view.ptr); writeln(underlyingBuffer.ptr); writeln(view.length, " ", ret, " = ", view.length + ret);
5814 			view = underlyingBuffer[view.ptr - underlyingBuffer.ptr .. view.length + ret];
5815 			//import std.stdio; writeln(cast(string) view);
5816 		} while(view.length < minBytesToSettleFor);
5817 	}
5818 
5819 	/// Removes n bytes from the front of the buffer, and returns the new buffer slice.
5820 	/// You might want to idup the data you are consuming if you store it, since it may
5821 	/// be overwritten on the new popFront.
5822 	///
5823 	/// You do not need to call this if you always want to wait for more data when you
5824 	/// consume some.
5825 	ubyte[] consume(size_t bytes) {
5826 		//import std.stdio; writeln("consuime ", bytes, "/", view.length);
5827 		view = view[bytes > $ ? $ : bytes .. $];
5828 		if(view.length == 0) {
5829 			view = underlyingBuffer[0 .. 0]; // go ahead and reuse the beginning
5830 			/*
5831 			writeln("HERE");
5832 			popFront(0, 0, true); // try to load more if we can, checks if the source is closed
5833 			writeln(cast(string)front);
5834 			writeln("DONE");
5835 			*/
5836 		}
5837 		return front;
5838 	}
5839 
5840 	bool empty() {
5841 		return sourceClosed && view.length == 0;
5842 	}
5843 
5844 	ubyte[] front() {
5845 		return view;
5846 	}
5847 
5848 	@system invariant() {
5849 		assert(view.ptr >= underlyingBuffer.ptr);
5850 		// it should never be equal, since if that happens view ought to be empty, and thus reusing the buffer
5851 		assert(view.ptr < underlyingBuffer.ptr + underlyingBuffer.length);
5852 	}
5853 
5854 	ubyte[] underlyingBuffer;
5855 	bool allowGrowth;
5856 	ubyte[] view;
5857 	Socket source;
5858 	bool sourceClosed;
5859 }
5860 
5861 private class FakeSocketForStdin : Socket {
5862 	import std.stdio;
5863 
5864 	this() {
5865 
5866 	}
5867 
5868 	private bool closed;
5869 
5870 	override ptrdiff_t receive(scope void[] buffer, std.socket.SocketFlags) @trusted {
5871 		if(closed)
5872 			throw new Exception("Closed");
5873 		return stdin.rawRead(buffer).length;
5874 	}
5875 
5876 	override ptrdiff_t send(const scope void[] buffer, std.socket.SocketFlags) @trusted {
5877 		if(closed)
5878 			throw new Exception("Closed");
5879 		stdout.rawWrite(buffer);
5880 		return buffer.length;
5881 	}
5882 
5883 	override void close() @trusted scope {
5884 		(cast(void delegate() @nogc nothrow) &realClose)();
5885 	}
5886 
5887 	override void shutdown(SocketShutdown s) {
5888 		// FIXME
5889 	}
5890 
5891 	override void setOption(SocketOptionLevel, SocketOption, scope void[]) {}
5892 	override void setOption(SocketOptionLevel, SocketOption, Duration) {}
5893 
5894 	override @property @trusted Address remoteAddress() { return null; }
5895 	override @property @trusted Address localAddress() { return null; }
5896 
5897 	void realClose() {
5898 		closed = true;
5899 		try {
5900 			stdin.close();
5901 			stdout.close();
5902 		} catch(Exception e) {
5903 
5904 		}
5905 	}
5906 }
5907 
5908 import core.sync.semaphore;
5909 import core.atomic;
5910 
5911 /**
5912 	To use this thing:
5913 
5914 	---
5915 	void handler(Socket s) { do something... }
5916 	auto manager = new ListeningConnectionManager("127.0.0.1", 80, &handler, &delegateThatDropsPrivileges);
5917 	manager.listen();
5918 	---
5919 
5920 	The 4th parameter is optional.
5921 
5922 	I suggest you use BufferedInputRange(connection) to handle the input. As a packet
5923 	comes in, you will get control. You can just continue; though to fetch more.
5924 
5925 
5926 	FIXME: should I offer an event based async thing like netman did too? Yeah, probably.
5927 */
5928 class ListeningConnectionManager {
5929 	Semaphore semaphore;
5930 	Socket[256] queue;
5931 	shared(ubyte) nextIndexFront;
5932 	ubyte nextIndexBack;
5933 	shared(int) queueLength;
5934 
5935 	Socket acceptCancelable() {
5936 		version(Posix) {
5937 			import core.sys.posix.sys.select;
5938 			fd_set read_fds;
5939 			FD_ZERO(&read_fds);
5940 			int max = 0;
5941 			foreach(listener; listeners) {
5942 				FD_SET(listener.handle, &read_fds);
5943 				if(listener.handle > max)
5944 					max = listener.handle;
5945 			}
5946 			if(cancelfd != -1) {
5947 				FD_SET(cancelfd, &read_fds);
5948 				if(cancelfd > max)
5949 					max = cancelfd;
5950 			}
5951 			auto ret = select(max + 1, &read_fds, null, null, null);
5952 			if(ret == -1) {
5953 				import core.stdc.errno;
5954 				if(errno == EINTR)
5955 					return null;
5956 				else
5957 					throw new Exception("wtf select");
5958 			}
5959 
5960 			if(cancelfd != -1 && FD_ISSET(cancelfd, &read_fds)) {
5961 				return null;
5962 			}
5963 
5964 			foreach(listener; listeners) {
5965 				if(FD_ISSET(listener.handle, &read_fds))
5966 					return listener.accept();
5967 			}
5968 
5969 			return null;
5970 		} else {
5971 
5972 			auto check = new SocketSet();
5973 
5974 			keep_looping:
5975 			check.reset();
5976 			foreach(listener; listeners)
5977 				check.add(listener);
5978 
5979 			// just to check the stop flag on a kinda busy loop. i hate this FIXME
5980 			auto got = Socket.select(check, null, null, 3.seconds);
5981 			if(got > 0)
5982 				foreach(listener; listeners)
5983 					if(check.isSet(listener))
5984 						return listener.accept();
5985 			if(globalStopFlag)
5986 				return null;
5987 			else
5988 				goto keep_looping;
5989 		}
5990 	}
5991 
5992 	int defaultNumberOfThreads() {
5993 		import std.parallelism;
5994 		version(cgi_use_fiber) {
5995 			return totalCPUs * 2 + 1; // still chance some will be pointlessly blocked anyway
5996 		} else {
5997 			// I times 4 here because there's a good chance some will be blocked on i/o.
5998 			return totalCPUs * 4;
5999 		}
6000 
6001 	}
6002 
6003 	void listen() {
6004 		shared(int) loopBroken;
6005 
6006 		version(Posix) {
6007 			import core.sys.posix.signal;
6008 			signal(SIGPIPE, SIG_IGN);
6009 		}
6010 
6011 		version(linux) {
6012 			if(cancelfd == -1)
6013 				cancelfd = eventfd(0, 0);
6014 		}
6015 
6016 		version(cgi_no_threads) {
6017 			// NEVER USE THIS
6018 			// it exists only for debugging and other special occasions
6019 
6020 			// the thread mode is faster and less likely to stall the whole
6021 			// thing when a request is slow
6022 			while(!loopBroken && !globalStopFlag) {
6023 				auto sn = acceptCancelable();
6024 				if(sn is null) continue;
6025 				cloexec(sn);
6026 				try {
6027 					handler(sn);
6028 				} catch(Exception e) {
6029 					// if a connection goes wrong, we want to just say no, but try to carry on unless it is an Error of some sort (in which case, we'll die. You might want an external helper program to revive the server when it dies)
6030 					sn.close();
6031 				}
6032 			}
6033 		} else {
6034 
6035 			if(useFork) {
6036 				version(linux) {
6037 					//asm { int 3; }
6038 					fork();
6039 				}
6040 			}
6041 
6042 			version(cgi_use_fiber) {
6043 
6044 				version(Windows) {
6045 					// please note these are overlapped sockets! so the accept just kicks things off
6046 					foreach(listener; listeners)
6047 						listener.accept();
6048 				}
6049 
6050 				WorkerThread[] threads = new WorkerThread[](numberOfThreads);
6051 				foreach(i, ref thread; threads) {
6052 					thread = new WorkerThread(this, handler, cast(int) i);
6053 					thread.start();
6054 				}
6055 
6056 				bool fiber_crash_check() {
6057 					bool hasAnyRunning;
6058 					foreach(thread; threads) {
6059 						if(!thread.isRunning) {
6060 							thread.join();
6061 						} else hasAnyRunning = true;
6062 					}
6063 
6064 					return (!hasAnyRunning);
6065 				}
6066 
6067 
6068 				while(!globalStopFlag) {
6069 					Thread.sleep(1.seconds);
6070 					if(fiber_crash_check())
6071 						break;
6072 				}
6073 
6074 			} else {
6075 				semaphore = new Semaphore();
6076 
6077 				ConnectionThread[] threads = new ConnectionThread[](numberOfThreads);
6078 				foreach(i, ref thread; threads) {
6079 					thread = new ConnectionThread(this, handler, cast(int) i);
6080 					thread.start();
6081 				}
6082 
6083 				while(!loopBroken && !globalStopFlag) {
6084 					Socket sn;
6085 
6086 					bool crash_check() {
6087 						bool hasAnyRunning;
6088 						foreach(thread; threads) {
6089 							if(!thread.isRunning) {
6090 								thread.join();
6091 							} else hasAnyRunning = true;
6092 						}
6093 
6094 						return (!hasAnyRunning);
6095 					}
6096 
6097 
6098 					void accept_new_connection() {
6099 						sn = acceptCancelable();
6100 						if(sn is null) return;
6101 						cloexec(sn);
6102 						if(tcp) {
6103 							// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
6104 							// on the socket because we do some buffering internally. I think this helps,
6105 							// certainly does for small requests, and I think it does for larger ones too
6106 							sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
6107 
6108 							sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
6109 						}
6110 					}
6111 
6112 					void existing_connection_new_data() {
6113 						// wait until a slot opens up
6114 						// int waited = 0;
6115 						while(queueLength >= queue.length) {
6116 							Thread.sleep(1.msecs);
6117 							// waited ++;
6118 						}
6119 						// if(waited) {import std.stdio; writeln(waited);}
6120 						synchronized(this) {
6121 							queue[nextIndexBack] = sn;
6122 							nextIndexBack++;
6123 							atomicOp!"+="(queueLength, 1);
6124 						}
6125 						semaphore.notify();
6126 					}
6127 
6128 
6129 					accept_new_connection();
6130 					if(sn !is null)
6131 						existing_connection_new_data();
6132 					else if(sn is null && globalStopFlag) {
6133 						foreach(thread; threads) {
6134 							semaphore.notify();
6135 						}
6136 						Thread.sleep(50.msecs);
6137 					}
6138 
6139 					if(crash_check())
6140 						break;
6141 				}
6142 			}
6143 
6144 			// FIXME: i typically stop this with ctrl+c which never
6145 			// actually gets here. i need to do a sigint handler.
6146 			if(cleanup)
6147 				cleanup();
6148 		}
6149 	}
6150 
6151 	//version(linux)
6152 		//int epoll_fd;
6153 
6154 	bool tcp;
6155 	void delegate() cleanup;
6156 
6157 	private void function(Socket) fhandler;
6158 	private void dg_handler(Socket s) {
6159 		fhandler(s);
6160 	}
6161 
6162 
6163 	this(string[] listenSpec, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6164 		fhandler = handler;
6165 		this(listenSpec, &dg_handler, dropPrivs, useFork, numberOfThreads);
6166 	}
6167 	this(string[] listenSpec, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6168 		string[] host;
6169 		ushort[] port;
6170 
6171 		foreach(spec; listenSpec) {
6172 			/+
6173 				The format:
6174 
6175 					protocol://
6176 					address_spec
6177 
6178 				Protocol is optional. Must be http, https, scgi, or fastcgi.
6179 
6180 				address_spec is either:
6181 					ipv4 address : port
6182 					[ipv6 address] : port
6183 					unix:filename
6184 					abstract:name
6185 					port <which is tcp but on any interface>
6186 			+/
6187 
6188 			string protocol;
6189 			string address_spec;
6190 
6191 			auto protocolIdx = spec.indexOf("://");
6192 			if(protocolIdx != -1) {
6193 				protocol = spec[0 .. protocolIdx];
6194 				address_spec = spec[protocolIdx + "://".length .. $];
6195 			} else {
6196 				address_spec = spec;
6197 			}
6198 
6199 			if(address_spec.startsWith("unix:") || address_spec.startsWith("abstract:")) {
6200 				host ~= address_spec;
6201 				port ~= 0;
6202 			} else {
6203 				auto idx = address_spec.lastIndexOf(":");
6204 				if(idx == -1) {
6205 					host ~= null;
6206 				} else {
6207 					auto as = address_spec[0 .. idx];
6208 					if(as.length >= 3 && as[0] == '[' && as[$-1] == ']')
6209 						as = as[1 .. $-1];
6210 					host ~= as;
6211 				}
6212 				port ~= address_spec[idx + 1 .. $].to!ushort;
6213 			}
6214 
6215 		}
6216 
6217 		this(host, port, handler, dropPrivs, useFork, numberOfThreads);
6218 	}
6219 
6220 	this(string host, ushort port, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6221 		this([host], [port], handler, dropPrivs, useFork, numberOfThreads);
6222 	}
6223 	this(string host, ushort port, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6224 		this([host], [port], handler, dropPrivs, useFork, numberOfThreads);
6225 	}
6226 
6227 	this(string[] host, ushort[] port, void function(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6228 		fhandler = handler;
6229 		this(host, port, &dg_handler, dropPrivs, useFork, numberOfThreads);
6230 	}
6231 
6232 	this(string[] host, ushort[] port, void delegate(Socket) handler, void delegate() dropPrivs = null, bool useFork = cgi_use_fork_default, int numberOfThreads = 0) {
6233 		assert(host.length == port.length);
6234 
6235 		this.handler = handler;
6236 		this.useFork = useFork;
6237 		this.numberOfThreads = numberOfThreads ? numberOfThreads : defaultNumberOfThreads();
6238 
6239 		listeners.reserve(host.length);
6240 
6241 		foreach(i; 0 .. host.length)
6242 			if(host[i] == "localhost") {
6243 				listeners ~= startListening("127.0.0.1", port[i], tcp, cleanup, 128, dropPrivs);
6244 				listeners ~= startListening("::1", port[i], tcp, cleanup, 128, dropPrivs);
6245 			} else {
6246 				listeners ~= startListening(host[i], port[i], tcp, cleanup, 128, dropPrivs);
6247 			}
6248 
6249 		version(cgi_use_fiber)
6250 		if(useFork) {
6251 			foreach(listener; listeners)
6252 				listener.blocking = false;
6253 		}
6254 
6255 		// this is the UI control thread and thus gets more priority
6256 		Thread.getThis.priority = Thread.PRIORITY_MAX;
6257 	}
6258 
6259 	Socket[] listeners;
6260 	void delegate(Socket) handler;
6261 
6262 	immutable bool useFork;
6263 	int numberOfThreads;
6264 }
6265 
6266 Socket startListening(string host, ushort port, ref bool tcp, ref void delegate() cleanup, int backQueue, void delegate() dropPrivs) {
6267 	Socket listener;
6268 	if(host.startsWith("unix:")) {
6269 		version(Posix) {
6270 			listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
6271 			cloexec(listener);
6272 			string filename = host["unix:".length .. $].idup;
6273 			listener.bind(new UnixAddress(filename));
6274 			cleanup = delegate() {
6275 				listener.close();
6276 				import std.file;
6277 				remove(filename);
6278 			};
6279 			tcp = false;
6280 		} else {
6281 			throw new Exception("unix sockets not supported on this system");
6282 		}
6283 	} else if(host.startsWith("abstract:")) {
6284 		version(linux) {
6285 			listener = new Socket(AddressFamily.UNIX, SocketType.STREAM);
6286 			cloexec(listener);
6287 			string filename = "\0" ~ host["abstract:".length .. $];
6288 			import std.stdio; stderr.writeln("Listening to abstract unix domain socket: ", host["abstract:".length .. $]);
6289 			listener.bind(new UnixAddress(filename));
6290 			tcp = false;
6291 		} else {
6292 			throw new Exception("abstract unix sockets not supported on this system");
6293 		}
6294 	} else {
6295 		auto address = host.length ? parseAddress(host, port) : new InternetAddress(port);
6296 		version(cgi_use_fiber) {
6297 			version(Windows)
6298 				listener = new PseudoblockingOverlappedSocket(AddressFamily.INET, SocketType.STREAM);
6299 			else
6300 				listener = new Socket(address.addressFamily, SocketType.STREAM);
6301 		} else {
6302 			listener = new Socket(address.addressFamily, SocketType.STREAM);
6303 		}
6304 		cloexec(listener);
6305 		listener.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
6306 		if(address.addressFamily == AddressFamily.INET6)
6307 			listener.setOption(SocketOptionLevel.IPV6, SocketOption.IPV6_V6ONLY, true);
6308 		listener.bind(address);
6309 		cleanup = delegate() {
6310 			listener.close();
6311 		};
6312 		tcp = true;
6313 	}
6314 
6315 	listener.listen(backQueue);
6316 
6317 	if (dropPrivs !is null) // can be null, backwards compatibility
6318 		dropPrivs();
6319 
6320 	return listener;
6321 }
6322 
6323 // helper function to send a lot to a socket. Since this blocks for the buffer (possibly several times), you should probably call it in a separate thread or something.
6324 void sendAll(Socket s, const(void)[] data, string file = __FILE__, size_t line = __LINE__) {
6325 	if(data.length == 0) return;
6326 	ptrdiff_t amount;
6327 	//import std.stdio; writeln("***",cast(string) data,"///");
6328 	do {
6329 		amount = s.send(data);
6330 		if(amount == Socket.ERROR) {
6331 			version(cgi_use_fiber) {
6332 				if(wouldHaveBlocked()) {
6333 					bool registered = true;
6334 					registerEventWakeup(&registered, s, WakeupEvent.Write);
6335 					continue;
6336 				}
6337 			}
6338 			throw new ConnectionException(s, lastSocketError, file, line);
6339 		}
6340 		assert(amount > 0);
6341 
6342 		data = data[amount .. $];
6343 	} while(data.length);
6344 }
6345 
6346 class ConnectionException : Exception {
6347 	Socket socket;
6348 	this(Socket s, string msg, string file = __FILE__, size_t line = __LINE__) {
6349 		this.socket = s;
6350 		super(msg, file, line);
6351 	}
6352 }
6353 
6354 class HttpVersionNotSupportedException : Exception {
6355 	this(string file = __FILE__, size_t line = __LINE__) {
6356 		super("HTTP Version Not Supported", file, line);
6357 	}
6358 }
6359 
6360 alias void delegate(Socket) CMT;
6361 
6362 import core.thread;
6363 /+
6364 	cgi.d now uses a hybrid of event i/o and threads at the top level.
6365 
6366 	Top level thread is responsible for accepting sockets and selecting on them.
6367 
6368 	It then indicates to a child that a request is pending, and any random worker
6369 	thread that is free handles it. It goes into blocking mode and handles that
6370 	http request to completion.
6371 
6372 	At that point, it goes back into the waiting queue.
6373 
6374 
6375 	This concept is only implemented on Linux. On all other systems, it still
6376 	uses the worker threads and semaphores (which is perfectly fine for a lot of
6377 	things! Just having a great number of keep-alive connections will break that.)
6378 
6379 
6380 	So the algorithm is:
6381 
6382 	select(accept, event, pending)
6383 		if accept -> send socket to free thread, if any. if not, add socket to queue
6384 		if event -> send the signaling thread a socket from the queue, if not, mark it free
6385 			- event might block until it can be *written* to. it is a fifo sending socket fds!
6386 
6387 	A worker only does one http request at a time, then signals its availability back to the boss.
6388 
6389 	The socket the worker was just doing should be added to the one-off epoll read. If it is closed,
6390 	great, we can get rid of it. Otherwise, it is considered `pending`. The *kernel* manages that; the
6391 	actual FD will not be kept out here.
6392 
6393 	So:
6394 		queue = sockets we know are ready to read now, but no worker thread is available
6395 		idle list = worker threads not doing anything else. they signal back and forth
6396 
6397 	the workers all read off the event fd. This is the semaphore wait
6398 
6399 	the boss waits on accept or other sockets read events (one off! and level triggered). If anything happens wrt ready read,
6400 	it puts it in the queue and writes to the event fd.
6401 
6402 	The child could put the socket back in the epoll thing itself.
6403 
6404 	The child needs to be able to gracefully handle being given a socket that just closed with no work.
6405 +/
6406 class ConnectionThread : Thread {
6407 	this(ListeningConnectionManager lcm, CMT dg, int myThreadNumber) {
6408 		this.lcm = lcm;
6409 		this.dg = dg;
6410 		this.myThreadNumber = myThreadNumber;
6411 		super(&run);
6412 	}
6413 
6414 	void run() {
6415 		while(true) {
6416 			// so if there's a bunch of idle keep-alive connections, it can
6417 			// consume all the worker threads... just sitting there.
6418 			lcm.semaphore.wait();
6419 			if(globalStopFlag)
6420 				return;
6421 			Socket socket;
6422 			synchronized(lcm) {
6423 				auto idx = lcm.nextIndexFront;
6424 				socket = lcm.queue[idx];
6425 				lcm.queue[idx] = null;
6426 				atomicOp!"+="(lcm.nextIndexFront, 1);
6427 				atomicOp!"-="(lcm.queueLength, 1);
6428 			}
6429 			try {
6430 			//import std.stdio; writeln(myThreadNumber, " taking it");
6431 				dg(socket);
6432 				/+
6433 				if(socket.isAlive) {
6434 					// process it more later
6435 					version(linux) {
6436 						import core.sys.linux.epoll;
6437 						epoll_event ev;
6438 						ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
6439 						ev.data.fd = socket.handle;
6440 						import std.stdio; writeln("adding");
6441 						if(epoll_ctl(lcm.epoll_fd, EPOLL_CTL_ADD, socket.handle, &ev) == -1) {
6442 							if(errno == EEXIST) {
6443 								ev.events = EPOLLIN | EPOLLONESHOT | EPOLLET;
6444 								ev.data.fd = socket.handle;
6445 								if(epoll_ctl(lcm.epoll_fd, EPOLL_CTL_MOD, socket.handle, &ev) == -1)
6446 									throw new Exception("epoll_ctl " ~ to!string(errno));
6447 							} else
6448 								throw new Exception("epoll_ctl " ~ to!string(errno));
6449 						}
6450 						//import std.stdio; writeln("keep alive");
6451 						// writing to this private member is to prevent the GC from closing my precious socket when I'm trying to use it later
6452 						__traits(getMember, socket, "sock") = cast(socket_t) -1;
6453 					} else {
6454 						continue; // hope it times out in a reasonable amount of time...
6455 					}
6456 				}
6457 				+/
6458 			} catch(ConnectionClosedException e) {
6459 				// can just ignore this, it is fairly normal
6460 				socket.close();
6461 			} catch(Throwable e) {
6462 				import std.stdio; stderr.rawWrite(e.toString); stderr.rawWrite("\n");
6463 				socket.close();
6464 			}
6465 		}
6466 	}
6467 
6468 	ListeningConnectionManager lcm;
6469 	CMT dg;
6470 	int myThreadNumber;
6471 }
6472 
6473 version(cgi_use_fiber)
6474 class WorkerThread : Thread {
6475 	this(ListeningConnectionManager lcm, CMT dg, int myThreadNumber) {
6476 		this.lcm = lcm;
6477 		this.dg = dg;
6478 		this.myThreadNumber = myThreadNumber;
6479 		super(&run);
6480 	}
6481 
6482 	version(Windows)
6483 	void run() {
6484 		auto timeout = INFINITE;
6485 		PseudoblockingOverlappedSocket key;
6486 		OVERLAPPED* overlapped;
6487 		DWORD bytes;
6488 		while(!globalStopFlag && GetQueuedCompletionStatus(iocp, &bytes, cast(PULONG_PTR) &key, &overlapped, timeout)) {
6489 			if(key is null)
6490 				continue;
6491 			key.lastAnswer = bytes;
6492 			if(key.fiber) {
6493 				key.fiber.proceed();
6494 			} else {
6495 				// we have a new connection, issue the first receive on it and issue the next accept
6496 
6497 				auto sn = key.accepted;
6498 
6499 				key.accept();
6500 
6501 				cloexec(sn);
6502 				if(lcm.tcp) {
6503 					// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
6504 					// on the socket because we do some buffering internally. I think this helps,
6505 					// certainly does for small requests, and I think it does for larger ones too
6506 					sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
6507 
6508 					sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
6509 				}
6510 
6511 				dg(sn);
6512 			}
6513 		}
6514 		//SleepEx(INFINITE, TRUE);
6515 	}
6516 
6517 	version(linux)
6518 	void run() {
6519 
6520 		import core.sys.linux.epoll;
6521 		epfd = epoll_create1(EPOLL_CLOEXEC);
6522 		if(epfd == -1)
6523 			throw new Exception("epoll_create1 " ~ to!string(errno));
6524 		scope(exit) {
6525 			import core.sys.posix.unistd;
6526 			close(epfd);
6527 		}
6528 
6529 		{
6530 			epoll_event ev;
6531 			ev.events = EPOLLIN;
6532 			ev.data.fd = cancelfd;
6533 			epoll_ctl(epfd, EPOLL_CTL_ADD, cancelfd, &ev);
6534 		}
6535 
6536 		foreach(listener; lcm.listeners) {
6537 			epoll_event ev;
6538 			ev.events = EPOLLIN | EPOLLEXCLUSIVE; // EPOLLEXCLUSIVE is only available on kernels since like 2017 but that's prolly good enough.
6539 			ev.data.fd = listener.handle;
6540 			if(epoll_ctl(epfd, EPOLL_CTL_ADD, listener.handle, &ev) == -1)
6541 				throw new Exception("epoll_ctl " ~ to!string(errno));
6542 		}
6543 
6544 
6545 
6546 		while(!globalStopFlag) {
6547 			Socket sn;
6548 
6549 			epoll_event[64] events;
6550 			auto nfds = epoll_wait(epfd, events.ptr, events.length, -1);
6551 			if(nfds == -1) {
6552 				if(errno == EINTR)
6553 					continue;
6554 				throw new Exception("epoll_wait " ~ to!string(errno));
6555 			}
6556 
6557 			outer: foreach(idx; 0 .. nfds) {
6558 				auto flags = events[idx].events;
6559 
6560 				if(cast(size_t) events[idx].data.ptr == cast(size_t) cancelfd) {
6561 					globalStopFlag = true;
6562 					//import std.stdio; writeln("exit heard");
6563 					break;
6564 				} else {
6565 					foreach(listener; lcm.listeners) {
6566 						if(cast(size_t) events[idx].data.ptr == cast(size_t) listener.handle) {
6567 							//import std.stdio; writeln(myThreadNumber, " woken up ", flags);
6568 							// this try/catch is because it is set to non-blocking mode
6569 							// and Phobos' stupid api throws an exception instead of returning
6570 							// if it would block. Why would it block? because a forked process
6571 							// might have beat us to it, but the wakeup event thundered our herds.
6572 								try
6573 								sn = listener.accept(); // don't need to do the acceptCancelable here since the epoll checks it better
6574 								catch(SocketAcceptException e) { continue outer; }
6575 
6576 							cloexec(sn);
6577 							if(lcm.tcp) {
6578 								// disable Nagle's algorithm to avoid a 40ms delay when we send/recv
6579 								// on the socket because we do some buffering internally. I think this helps,
6580 								// certainly does for small requests, and I think it does for larger ones too
6581 								sn.setOption(SocketOptionLevel.TCP, SocketOption.TCP_NODELAY, 1);
6582 
6583 								sn.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"seconds"(10));
6584 							}
6585 
6586 							dg(sn);
6587 							continue outer;
6588 						} else {
6589 							// writeln(events[idx].data.ptr);
6590 						}
6591 					}
6592 
6593 					if(cast(size_t) events[idx].data.ptr < 1024) {
6594 						throw arsd.core.ArsdException!"this doesn't look like a fiber pointer... "(cast(size_t) events[idx].data.ptr);
6595 					}
6596 					auto fiber = cast(CgiFiber) events[idx].data.ptr;
6597 					fiber.proceed();
6598 				}
6599 			}
6600 		}
6601 	}
6602 
6603 	ListeningConnectionManager lcm;
6604 	CMT dg;
6605 	int myThreadNumber;
6606 }
6607 
6608 
6609 /* Done with network helper */
6610 
6611 /* Helpers for doing temporary files. Used both here and in web.d */
6612 
6613 version(Windows) {
6614 	import core.sys.windows.windows;
6615 	extern(Windows) DWORD GetTempPathW(DWORD, LPWSTR);
6616 	alias GetTempPathW GetTempPath;
6617 }
6618 
6619 version(Posix) {
6620 	static import linux = core.sys.posix.unistd;
6621 }
6622 
6623 string getTempDirectory() {
6624 	string path;
6625 	version(Windows) {
6626 		wchar[1024] buffer;
6627 		auto len = GetTempPath(1024, buffer.ptr);
6628 		if(len == 0)
6629 			throw new Exception("couldn't find a temporary path");
6630 
6631 		auto b = buffer[0 .. len];
6632 
6633 		path = to!string(b);
6634 	} else
6635 		path = "/tmp/";
6636 
6637 	return path;
6638 }
6639 
6640 
6641 // I like std.date. These functions help keep my old code and data working with phobos changing.
6642 
6643 long sysTimeToDTime(in SysTime sysTime) {
6644     return convert!("hnsecs", "msecs")(sysTime.stdTime - 621355968000000000L);
6645 }
6646 
6647 long dateTimeToDTime(in DateTime dt) {
6648 	return sysTimeToDTime(cast(SysTime) dt);
6649 }
6650 
6651 long getUtcTime() { // renamed primarily to avoid conflict with std.date itself
6652 	return sysTimeToDTime(Clock.currTime(UTC()));
6653 }
6654 
6655 // NOTE: new SimpleTimeZone(minutes); can perhaps work with the getTimezoneOffset() JS trick
6656 SysTime dTimeToSysTime(long dTime, immutable TimeZone tz = null) {
6657 	immutable hnsecs = convert!("msecs", "hnsecs")(dTime) + 621355968000000000L;
6658 	return SysTime(hnsecs, tz);
6659 }
6660 
6661 
6662 
6663 // this is a helper to read HTTP transfer-encoding: chunked responses
6664 immutable(ubyte[]) dechunk(BufferedInputRange ir) {
6665 	immutable(ubyte)[] ret;
6666 
6667 	another_chunk:
6668 	// If here, we are at the beginning of a chunk.
6669 	auto a = ir.front();
6670 	int chunkSize;
6671 	int loc = locationOf(a, "\r\n");
6672 	while(loc == -1) {
6673 		ir.popFront();
6674 		a = ir.front();
6675 		loc = locationOf(a, "\r\n");
6676 	}
6677 
6678 	string hex;
6679 	hex = "";
6680 	for(int i = 0; i < loc; i++) {
6681 		char c = a[i];
6682 		if(c >= 'A' && c <= 'Z')
6683 			c += 0x20;
6684 		if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'z')) {
6685 			hex ~= c;
6686 		} else {
6687 			break;
6688 		}
6689 	}
6690 
6691 	assert(hex.length);
6692 
6693 	int power = 1;
6694 	int size = 0;
6695 	foreach(cc1; retro(hex)) {
6696 		dchar cc = cc1;
6697 		if(cc >= 'a' && cc <= 'z')
6698 			cc -= 0x20;
6699 		int val = 0;
6700 		if(cc >= '0' && cc <= '9')
6701 			val = cc - '0';
6702 		else
6703 			val = cc - 'A' + 10;
6704 
6705 		size += power * val;
6706 		power *= 16;
6707 	}
6708 
6709 	chunkSize = size;
6710 	assert(size >= 0);
6711 
6712 	if(loc + 2 > a.length) {
6713 		ir.popFront(0, a.length + loc + 2);
6714 		a = ir.front();
6715 	}
6716 
6717 	a = ir.consume(loc + 2);
6718 
6719 	if(chunkSize == 0) { // we're done with the response
6720 		// if we got here, will change must be true....
6721 		more_footers:
6722 		loc = locationOf(a, "\r\n");
6723 		if(loc == -1) {
6724 			ir.popFront();
6725 			a = ir.front;
6726 			goto more_footers;
6727 		} else {
6728 			assert(loc == 0);
6729 			ir.consume(loc + 2);
6730 			goto finish;
6731 		}
6732 	} else {
6733 		// if we got here, will change must be true....
6734 		if(a.length < chunkSize + 2) {
6735 			ir.popFront(0, chunkSize + 2);
6736 			a = ir.front();
6737 		}
6738 
6739 		ret ~= (a[0..chunkSize]);
6740 
6741 		if(!(a.length > chunkSize + 2)) {
6742 			ir.popFront(0, chunkSize + 2);
6743 			a = ir.front();
6744 		}
6745 		assert(a[chunkSize] == 13);
6746 		assert(a[chunkSize+1] == 10);
6747 		a = ir.consume(chunkSize + 2);
6748 		chunkSize = 0;
6749 		goto another_chunk;
6750 	}
6751 
6752 	finish:
6753 	return ret;
6754 }
6755 
6756 // I want to be able to get data from multiple sources the same way...
6757 interface ByChunkRange {
6758 	bool empty();
6759 	void popFront();
6760 	const(ubyte)[] front();
6761 }
6762 
6763 ByChunkRange byChunk(const(ubyte)[] data) {
6764 	return new class ByChunkRange {
6765 		override bool empty() {
6766 			return !data.length;
6767 		}
6768 
6769 		override void popFront() {
6770 			if(data.length > 4096)
6771 				data = data[4096 .. $];
6772 			else
6773 				data = null;
6774 		}
6775 
6776 		override const(ubyte)[] front() {
6777 			return data[0 .. $ > 4096 ? 4096 : $];
6778 		}
6779 	};
6780 }
6781 
6782 ByChunkRange byChunk(BufferedInputRange ir, size_t atMost) {
6783 	const(ubyte)[] f;
6784 
6785 	f = ir.front;
6786 	if(f.length > atMost)
6787 		f = f[0 .. atMost];
6788 
6789 	return new class ByChunkRange {
6790 		override bool empty() {
6791 			return atMost == 0;
6792 		}
6793 
6794 		override const(ubyte)[] front() {
6795 			return f;
6796 		}
6797 
6798 		override void popFront() {
6799 			ir.consume(f.length);
6800 			atMost -= f.length;
6801 			auto a = ir.front();
6802 
6803 			if(a.length <= atMost) {
6804 				f = a;
6805 				atMost -= a.length;
6806 				a = ir.consume(a.length);
6807 				if(atMost != 0)
6808 					ir.popFront();
6809 				if(f.length == 0) {
6810 					f = ir.front();
6811 				}
6812 			} else {
6813 				// we actually have *more* here than we need....
6814 				f = a[0..atMost];
6815 				atMost = 0;
6816 				ir.consume(atMost);
6817 			}
6818 		}
6819 	};
6820 }
6821 
6822 version(cgi_with_websocket) {
6823 	// http://tools.ietf.org/html/rfc6455
6824 
6825 	/++
6826 		WEBSOCKET SUPPORT:
6827 
6828 		Full example:
6829 		---
6830 			import arsd.cgi;
6831 
6832 			void websocketEcho(Cgi cgi) {
6833 				if(cgi.websocketRequested()) {
6834 					if(cgi.origin != "http://arsdnet.net")
6835 						throw new Exception("bad origin");
6836 					auto websocket = cgi.acceptWebsocket();
6837 
6838 					websocket.send("hello");
6839 					websocket.send(" world!");
6840 
6841 					auto msg = websocket.recv();
6842 					while(msg.opcode != WebSocketOpcode.close) {
6843 						if(msg.opcode == WebSocketOpcode.text) {
6844 							websocket.send(msg.textData);
6845 						} else if(msg.opcode == WebSocketOpcode.binary) {
6846 							websocket.send(msg.data);
6847 						}
6848 
6849 						msg = websocket.recv();
6850 					}
6851 
6852 					websocket.close();
6853 				} else {
6854 					cgi.write("You are loading the websocket endpoint in a browser instead of a websocket client. Use a websocket client on this url instead.\n", true);
6855 				}
6856 			}
6857 
6858 			mixin GenericMain!websocketEcho;
6859 		---
6860 	+/
6861 
6862 	class WebSocket {
6863 		Cgi cgi;
6864 
6865 		private bool isClient = false;
6866 
6867 		private this(Cgi cgi) {
6868 			this.cgi = cgi;
6869 
6870 			Socket socket = cgi.idlol.source;
6871 			socket.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, dur!"minutes"(5));
6872 		}
6873 
6874 		// returns true if data available, false if it timed out
6875 		bool recvAvailable(Duration timeout = dur!"msecs"(0)) {
6876 			if(!waitForNextMessageWouldBlock())
6877 				return true;
6878 			if(isDataPending(timeout))
6879 				return true; // this is kinda a lie.
6880 
6881 			return false;
6882 		}
6883 
6884 		public bool lowLevelReceive() {
6885 			auto bfr = cgi.idlol;
6886 			top:
6887 			auto got = bfr.front;
6888 			if(got.length) {
6889 				if(receiveBuffer.length < receiveBufferUsedLength + got.length)
6890 					receiveBuffer.length += receiveBufferUsedLength + got.length;
6891 
6892 				receiveBuffer[receiveBufferUsedLength .. receiveBufferUsedLength + got.length] = got[];
6893 				receiveBufferUsedLength += got.length;
6894 				bfr.consume(got.length);
6895 
6896 				return true;
6897 			}
6898 
6899 			if(bfr.sourceClosed)
6900 				return false;
6901 
6902 			bfr.popFront(0);
6903 			if(bfr.sourceClosed)
6904 				return false;
6905 			goto top;
6906 		}
6907 
6908 
6909 		bool isDataPending(Duration timeout = 0.seconds) {
6910 			Socket socket = cgi.idlol.source;
6911 
6912 			auto check = new SocketSet();
6913 			check.add(socket);
6914 
6915 			auto got = Socket.select(check, null, null, timeout);
6916 			if(got > 0)
6917 				return true;
6918 			return false;
6919 		}
6920 
6921 		// note: this blocks
6922 		WebSocketFrame recv() {
6923 			return waitForNextMessage();
6924 		}
6925 
6926 
6927 
6928 
6929 		private void llclose() {
6930 			cgi.close();
6931 		}
6932 
6933 		private void llsend(ubyte[] data) {
6934 			cgi.write(data);
6935 			cgi.flush();
6936 		}
6937 
6938 		void unregisterActiveSocket(WebSocket) {}
6939 
6940 		/* copy/paste section { */
6941 
6942 		private int readyState_;
6943 		private ubyte[] receiveBuffer;
6944 		private size_t receiveBufferUsedLength;
6945 
6946 		private Config config;
6947 
6948 		enum CONNECTING = 0; /// Socket has been created. The connection is not yet open.
6949 		enum OPEN = 1; /// The connection is open and ready to communicate.
6950 		enum CLOSING = 2; /// The connection is in the process of closing.
6951 		enum CLOSED = 3; /// The connection is closed or couldn't be opened.
6952 
6953 		/++
6954 
6955 		+/
6956 		/// Group: foundational
6957 		static struct Config {
6958 			/++
6959 				These control the size of the receive buffer.
6960 
6961 				It starts at the initial size, will temporarily
6962 				balloon up to the maximum size, and will reuse
6963 				a buffer up to the likely size.
6964 
6965 				Anything larger than the maximum size will cause
6966 				the connection to be aborted and an exception thrown.
6967 				This is to protect you against a peer trying to
6968 				exhaust your memory, while keeping the user-level
6969 				processing simple.
6970 			+/
6971 			size_t initialReceiveBufferSize = 4096;
6972 			size_t likelyReceiveBufferSize = 4096; /// ditto
6973 			size_t maximumReceiveBufferSize = 10 * 1024 * 1024; /// ditto
6974 
6975 			/++
6976 				Maximum combined size of a message.
6977 			+/
6978 			size_t maximumMessageSize = 10 * 1024 * 1024;
6979 
6980 			string[string] cookies; /// Cookies to send with the initial request. cookies[name] = value;
6981 			string origin; /// Origin URL to send with the handshake, if desired.
6982 			string protocol; /// the protocol header, if desired.
6983 
6984 			/++
6985 				Additional headers to put in the HTTP request. These should be formatted `Name: value`, like for example:
6986 
6987 				---
6988 				Config config;
6989 				config.additionalHeaders ~= "Authorization: Bearer your_auth_token_here";
6990 				---
6991 
6992 				History:
6993 					Added February 19, 2021 (included in dub version 9.2)
6994 			+/
6995 			string[] additionalHeaders;
6996 
6997 			/++
6998 				Amount of time (in msecs) of idleness after which to send an automatic ping
6999 
7000 				Please note how this interacts with [timeoutFromInactivity] - a ping counts as activity that
7001 				keeps the socket alive.
7002 			+/
7003 			int pingFrequency = 5000;
7004 
7005 			/++
7006 				Amount of time to disconnect when there's no activity. Note that automatic pings will keep the connection alive; this timeout only occurs if there's absolutely nothing, including no responses to websocket ping frames. Since the default [pingFrequency] is only seconds, this one minute should never elapse unless the connection is actually dead.
7007 
7008 				The one thing to keep in mind is if your program is busy and doesn't check input, it might consider this a time out since there's no activity. The reason is that your program was busy rather than a connection failure, but it doesn't care. You should avoid long processing periods anyway though!
7009 
7010 				History:
7011 					Added March 31, 2021 (included in dub version 9.4)
7012 			+/
7013 			Duration timeoutFromInactivity = 1.minutes;
7014 
7015 			/++
7016 				For https connections, if this is `true`, it will fail to connect if the TLS certificate can not be
7017 				verified. Setting this to `false` will skip this check and allow the connection to continue anyway.
7018 
7019 				History:
7020 					Added April 5, 2022 (dub v10.8)
7021 
7022 					Prior to this, it always used the global (but undocumented) `defaultVerifyPeer` setting, and sometimes
7023 					even if it was true, it would skip the verification. Now, it always respects this local setting.
7024 			+/
7025 			bool verifyPeer = true;
7026 		}
7027 
7028 		/++
7029 			Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED].
7030 		+/
7031 		int readyState() {
7032 			return readyState_;
7033 		}
7034 
7035 		/++
7036 			Closes the connection, sending a graceful teardown message to the other side.
7037 
7038 			Code 1000 is the normal closure code.
7039 
7040 			History:
7041 				The default `code` was changed to 1000 on January 9, 2023. Previously it was 0,
7042 				but also ignored anyway.
7043 		+/
7044 		/// Group: foundational
7045 		void close(int code = 1000, string reason = null)
7046 			//in (reason.length < 123)
7047 			in { assert(reason.length < 123); } do
7048 		{
7049 			if(readyState_ != OPEN)
7050 				return; // it cool, we done
7051 			WebSocketFrame wss;
7052 			wss.fin = true;
7053 			wss.masked = this.isClient;
7054 			wss.opcode = WebSocketOpcode.close;
7055 			wss.data = [ubyte((code >> 8) & 0xff), ubyte(code & 0xff)] ~ cast(ubyte[]) reason.dup;
7056 			wss.send(&llsend);
7057 
7058 			readyState_ = CLOSING;
7059 
7060 			closeCalled = true;
7061 
7062 			llclose();
7063 		}
7064 
7065 		private bool closeCalled;
7066 
7067 		/++
7068 			Sends a ping message to the server. This is done automatically by the library if you set a non-zero [Config.pingFrequency], but you can also send extra pings explicitly as well with this function.
7069 		+/
7070 		/// Group: foundational
7071 		void ping(in ubyte[] data = null) {
7072 			WebSocketFrame wss;
7073 			wss.fin = true;
7074 			wss.masked = this.isClient;
7075 			wss.opcode = WebSocketOpcode.ping;
7076 			if(data !is null) wss.data = data.dup;
7077 			wss.send(&llsend);
7078 		}
7079 
7080 		/++
7081 			Sends a pong message to the server. This is normally done automatically in response to pings.
7082 		+/
7083 		/// Group: foundational
7084 		void pong(in ubyte[] data = null) {
7085 			WebSocketFrame wss;
7086 			wss.fin = true;
7087 			wss.masked = this.isClient;
7088 			wss.opcode = WebSocketOpcode.pong;
7089 			if(data !is null) wss.data = data.dup;
7090 			wss.send(&llsend);
7091 		}
7092 
7093 		/++
7094 			Sends a text message through the websocket.
7095 		+/
7096 		/// Group: foundational
7097 		void send(in char[] textData) {
7098 			WebSocketFrame wss;
7099 			wss.fin = true;
7100 			wss.masked = this.isClient;
7101 			wss.opcode = WebSocketOpcode.text;
7102 			wss.data = cast(ubyte[]) textData.dup;
7103 			wss.send(&llsend);
7104 		}
7105 
7106 		/++
7107 			Sends a binary message through the websocket.
7108 		+/
7109 		/// Group: foundational
7110 		void send(in ubyte[] binaryData) {
7111 			WebSocketFrame wss;
7112 			wss.masked = this.isClient;
7113 			wss.fin = true;
7114 			wss.opcode = WebSocketOpcode.binary;
7115 			wss.data = cast(ubyte[]) binaryData.dup;
7116 			wss.send(&llsend);
7117 		}
7118 
7119 		/++
7120 			Waits for and returns the next complete message on the socket.
7121 
7122 			Note that the onmessage function is still called, right before
7123 			this returns.
7124 		+/
7125 		/// Group: blocking_api
7126 		public WebSocketFrame waitForNextMessage() {
7127 			do {
7128 				auto m = processOnce();
7129 				if(m.populated)
7130 					return m;
7131 			} while(lowLevelReceive());
7132 
7133 			throw new ConnectionClosedException("Websocket receive timed out");
7134 			//return WebSocketFrame.init; // FIXME? maybe.
7135 		}
7136 
7137 		/++
7138 			Tells if [waitForNextMessage] would block.
7139 		+/
7140 		/// Group: blocking_api
7141 		public bool waitForNextMessageWouldBlock() {
7142 			checkAgain:
7143 			if(isMessageBuffered())
7144 				return false;
7145 			if(!isDataPending())
7146 				return true;
7147 
7148 			while(isDataPending()) {
7149 				if(lowLevelReceive() == false)
7150 					throw new ConnectionClosedException("Connection closed in middle of message");
7151 			}
7152 
7153 			goto checkAgain;
7154 		}
7155 
7156 		/++
7157 			Is there a message in the buffer already?
7158 			If `true`, [waitForNextMessage] is guaranteed to return immediately.
7159 			If `false`, check [isDataPending] as the next step.
7160 		+/
7161 		/// Group: blocking_api
7162 		public bool isMessageBuffered() {
7163 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
7164 			auto s = d;
7165 			if(d.length) {
7166 				auto orig = d;
7167 				auto m = WebSocketFrame.read(d);
7168 				// that's how it indicates that it needs more data
7169 				if(d !is orig)
7170 					return true;
7171 			}
7172 
7173 			return false;
7174 		}
7175 
7176 		private ubyte continuingType;
7177 		private ubyte[] continuingData;
7178 		//private size_t continuingDataLength;
7179 
7180 		private WebSocketFrame processOnce() {
7181 			ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength];
7182 			auto s = d;
7183 			// FIXME: handle continuation frames more efficiently. it should really just reuse the receive buffer.
7184 			WebSocketFrame m;
7185 			if(d.length) {
7186 				auto orig = d;
7187 				m = WebSocketFrame.read(d);
7188 				// that's how it indicates that it needs more data
7189 				if(d is orig)
7190 					return WebSocketFrame.init;
7191 				m.unmaskInPlace();
7192 				switch(m.opcode) {
7193 					case WebSocketOpcode.continuation:
7194 						if(continuingData.length + m.data.length > config.maximumMessageSize)
7195 							throw new Exception("message size exceeded");
7196 
7197 						continuingData ~= m.data;
7198 						if(m.fin) {
7199 							if(ontextmessage)
7200 								ontextmessage(cast(char[]) continuingData);
7201 							if(onbinarymessage)
7202 								onbinarymessage(continuingData);
7203 
7204 							continuingData = null;
7205 						}
7206 					break;
7207 					case WebSocketOpcode.text:
7208 						if(m.fin) {
7209 							if(ontextmessage)
7210 								ontextmessage(m.textData);
7211 						} else {
7212 							continuingType = m.opcode;
7213 							//continuingDataLength = 0;
7214 							continuingData = null;
7215 							continuingData ~= m.data;
7216 						}
7217 					break;
7218 					case WebSocketOpcode.binary:
7219 						if(m.fin) {
7220 							if(onbinarymessage)
7221 								onbinarymessage(m.data);
7222 						} else {
7223 							continuingType = m.opcode;
7224 							//continuingDataLength = 0;
7225 							continuingData = null;
7226 							continuingData ~= m.data;
7227 						}
7228 					break;
7229 					case WebSocketOpcode.close:
7230 
7231 						//import std.stdio; writeln("closed ", cast(string) m.data);
7232 
7233 						ushort code = CloseEvent.StandardCloseCodes.noStatusCodePresent;
7234 						const(char)[] reason;
7235 
7236 						if(m.data.length >= 2) {
7237 							code = (m.data[0] << 8) | m.data[1];
7238 							reason = (cast(char[]) m.data[2 .. $]);
7239 						}
7240 
7241 						if(onclose)
7242 							onclose(CloseEvent(code, reason, true));
7243 
7244 						// if we receive one and haven't sent one back we're supposed to echo it back and close.
7245 						if(!closeCalled)
7246 							close(code, reason.idup);
7247 
7248 						readyState_ = CLOSED;
7249 
7250 						unregisterActiveSocket(this);
7251 					break;
7252 					case WebSocketOpcode.ping:
7253 						// import std.stdio; writeln("ping received ", m.data);
7254 						pong(m.data);
7255 					break;
7256 					case WebSocketOpcode.pong:
7257 						// import std.stdio; writeln("pong received ", m.data);
7258 						// just really references it is still alive, nbd.
7259 					break;
7260 					default: // ignore though i could and perhaps should throw too
7261 				}
7262 			}
7263 
7264 			if(d.length) {
7265 				m.data = m.data.dup();
7266 			}
7267 
7268 			import core.stdc.string;
7269 			memmove(receiveBuffer.ptr, d.ptr, d.length);
7270 			receiveBufferUsedLength = d.length;
7271 
7272 			return m;
7273 		}
7274 
7275 		private void autoprocess() {
7276 			// FIXME
7277 			do {
7278 				processOnce();
7279 			} while(lowLevelReceive());
7280 		}
7281 
7282 		/++
7283 			Arguments for the close event. The `code` and `reason` are provided from the close message on the websocket, if they are present. The spec says code 1000 indicates a normal, default reason close, but reserves the code range from 3000-5000 for future definition; the 3000s can be registered with IANA and the 4000's are application private use. The `reason` should be user readable, but not displayed to the end user. `wasClean` is true if the server actually sent a close event, false if it just disconnected.
7284 
7285 			$(PITFALL
7286 				The `reason` argument references a temporary buffer and there's no guarantee it will remain valid once your callback returns. It may be freed and will very likely be overwritten. If you want to keep the reason beyond the callback, make sure you `.idup` it.
7287 			)
7288 
7289 			History:
7290 				Added March 19, 2023 (dub v11.0).
7291 		+/
7292 		static struct CloseEvent {
7293 			ushort code;
7294 			const(char)[] reason;
7295 			bool wasClean;
7296 
7297 			string extendedErrorInformationUnstable;
7298 
7299 			/++
7300 				See https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 for details.
7301 			+/
7302 			enum StandardCloseCodes {
7303 				purposeFulfilled = 1000,
7304 				goingAway = 1001,
7305 				protocolError = 1002,
7306 				unacceptableData = 1003, // e.g. got text message when you can only handle binary
7307 				Reserved = 1004,
7308 				noStatusCodePresent = 1005, // not set by endpoint.
7309 				abnormalClosure = 1006, // not set by endpoint. closed without a Close control. FIXME: maybe keep a copy of errno around for these
7310 				inconsistentData = 1007, // e.g. utf8 validation failed
7311 				genericPolicyViolation = 1008,
7312 				messageTooBig = 1009,
7313 				clientRequiredExtensionMissing = 1010, // only the client should send this
7314 				unnexpectedCondition = 1011,
7315 				unverifiedCertificate = 1015, // not set by client
7316 			}
7317 		}
7318 
7319 		/++
7320 			The `CloseEvent` you get references a temporary buffer that may be overwritten after your handler returns. If you want to keep it or the `event.reason` member, remember to `.idup` it.
7321 
7322 			History:
7323 				The `CloseEvent` was changed to a [arsd.core.FlexibleDelegate] on March 19, 2023 (dub v11.0). Before that, `onclose` was a public member of type `void delegate()`. This change means setters still work with or without the [CloseEvent] argument.
7324 
7325 				Your onclose method is now also called on abnormal terminations. Check the `wasClean` member of the `CloseEvent` to know if it came from a close frame or other cause.
7326 		+/
7327 		arsd.core.FlexibleDelegate!(void delegate(CloseEvent event)) onclose;
7328 		void delegate() onerror; ///
7329 		void delegate(in char[]) ontextmessage; ///
7330 		void delegate(in ubyte[]) onbinarymessage; ///
7331 		void delegate() onopen; ///
7332 
7333 		/++
7334 
7335 		+/
7336 		/// Group: browser_api
7337 		void onmessage(void delegate(in char[]) dg) {
7338 			ontextmessage = dg;
7339 		}
7340 
7341 		/// ditto
7342 		void onmessage(void delegate(in ubyte[]) dg) {
7343 			onbinarymessage = dg;
7344 		}
7345 
7346 	/* } end copy/paste */
7347 
7348 
7349 
7350 	}
7351 
7352 	/++
7353 		Returns true if the request headers are asking for a websocket upgrade.
7354 
7355 		If this returns true, and you want to accept it, call [acceptWebsocket].
7356 	+/
7357 	bool websocketRequested(Cgi cgi) {
7358 		return
7359 			"sec-websocket-key" in cgi.requestHeaders
7360 			&&
7361 			"connection" in cgi.requestHeaders &&
7362 				cgi.requestHeaders["connection"].asLowerCase().canFind("upgrade")
7363 			&&
7364 			"upgrade" in cgi.requestHeaders &&
7365 				cgi.requestHeaders["upgrade"].asLowerCase().equal("websocket")
7366 			;
7367 	}
7368 
7369 	/++
7370 		If [websocketRequested], you can call this to accept it and upgrade the connection. It returns the new [WebSocket] object you use for future communication on this connection; the `cgi` object should no longer be used.
7371 	+/
7372 	WebSocket acceptWebsocket(Cgi cgi) {
7373 		assert(!cgi.closed);
7374 		assert(!cgi.outputtedResponseData);
7375 		cgi.setResponseStatus("101 Switching Protocols");
7376 		cgi.header("Upgrade: WebSocket");
7377 		cgi.header("Connection: upgrade");
7378 
7379 		string key = cgi.requestHeaders["sec-websocket-key"];
7380 		key ~= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // the defined guid from the websocket spec
7381 
7382 		import std.digest.sha;
7383 		auto hash = sha1Of(key);
7384 		auto accept = Base64.encode(hash);
7385 
7386 		cgi.header(("Sec-WebSocket-Accept: " ~ accept).idup);
7387 
7388 		cgi.websocketMode = true;
7389 		cgi.write("");
7390 
7391 		cgi.flush();
7392 
7393 		auto ws = new WebSocket(cgi);
7394 		ws.readyState_ = WebSocket.OPEN;
7395 		return ws;
7396 	}
7397 
7398 	// FIXME get websocket to work on other modes, not just embedded_httpd
7399 
7400 	/* copy/paste in http2.d { */
7401 	enum WebSocketOpcode : ubyte {
7402 		continuation = 0,
7403 		text = 1,
7404 		binary = 2,
7405 		// 3, 4, 5, 6, 7 RESERVED
7406 		close = 8,
7407 		ping = 9,
7408 		pong = 10,
7409 		// 11,12,13,14,15 RESERVED
7410 	}
7411 
7412 	public struct WebSocketFrame {
7413 		private bool populated;
7414 		bool fin;
7415 		bool rsv1;
7416 		bool rsv2;
7417 		bool rsv3;
7418 		WebSocketOpcode opcode; // 4 bits
7419 		bool masked;
7420 		ubyte lengthIndicator; // don't set this when building one to send
7421 		ulong realLength; // don't use when sending
7422 		ubyte[4] maskingKey; // don't set this when sending
7423 		ubyte[] data;
7424 
7425 		static WebSocketFrame simpleMessage(WebSocketOpcode opcode, void[] data) {
7426 			WebSocketFrame msg;
7427 			msg.fin = true;
7428 			msg.opcode = opcode;
7429 			msg.data = cast(ubyte[]) data.dup;
7430 
7431 			return msg;
7432 		}
7433 
7434 		private void send(scope void delegate(ubyte[]) llsend) {
7435 			ubyte[64] headerScratch;
7436 			int headerScratchPos = 0;
7437 
7438 			realLength = data.length;
7439 
7440 			{
7441 				ubyte b1;
7442 				b1 |= cast(ubyte) opcode;
7443 				b1 |= rsv3 ? (1 << 4) : 0;
7444 				b1 |= rsv2 ? (1 << 5) : 0;
7445 				b1 |= rsv1 ? (1 << 6) : 0;
7446 				b1 |= fin  ? (1 << 7) : 0;
7447 
7448 				headerScratch[0] = b1;
7449 				headerScratchPos++;
7450 			}
7451 
7452 			{
7453 				headerScratchPos++; // we'll set header[1] at the end of this
7454 				auto rlc = realLength;
7455 				ubyte b2;
7456 				b2 |= masked ? (1 << 7) : 0;
7457 
7458 				assert(headerScratchPos == 2);
7459 
7460 				if(realLength > 65535) {
7461 					// use 64 bit length
7462 					b2 |= 0x7f;
7463 
7464 					// FIXME: double check endinaness
7465 					foreach(i; 0 .. 8) {
7466 						headerScratch[2 + 7 - i] = rlc & 0x0ff;
7467 						rlc >>>= 8;
7468 					}
7469 
7470 					headerScratchPos += 8;
7471 				} else if(realLength > 125) {
7472 					// use 16 bit length
7473 					b2 |= 0x7e;
7474 
7475 					// FIXME: double check endinaness
7476 					foreach(i; 0 .. 2) {
7477 						headerScratch[2 + 1 - i] = rlc & 0x0ff;
7478 						rlc >>>= 8;
7479 					}
7480 
7481 					headerScratchPos += 2;
7482 				} else {
7483 					// use 7 bit length
7484 					b2 |= realLength & 0b_0111_1111;
7485 				}
7486 
7487 				headerScratch[1] = b2;
7488 			}
7489 
7490 			//assert(!masked, "masking key not properly implemented");
7491 			if(masked) {
7492 				// FIXME: randomize this
7493 				headerScratch[headerScratchPos .. headerScratchPos + 4] = maskingKey[];
7494 				headerScratchPos += 4;
7495 
7496 				// we'll just mask it in place...
7497 				int keyIdx = 0;
7498 				foreach(i; 0 .. data.length) {
7499 					data[i] = data[i] ^ maskingKey[keyIdx];
7500 					if(keyIdx == 3)
7501 						keyIdx = 0;
7502 					else
7503 						keyIdx++;
7504 				}
7505 			}
7506 
7507 			//writeln("SENDING ", headerScratch[0 .. headerScratchPos], data);
7508 			llsend(headerScratch[0 .. headerScratchPos]);
7509 			llsend(data);
7510 		}
7511 
7512 		static WebSocketFrame read(ref ubyte[] d) {
7513 			WebSocketFrame msg;
7514 
7515 			auto orig = d;
7516 
7517 			WebSocketFrame needsMoreData() {
7518 				d = orig;
7519 				return WebSocketFrame.init;
7520 			}
7521 
7522 			if(d.length < 2)
7523 				return needsMoreData();
7524 
7525 			ubyte b = d[0];
7526 
7527 			msg.populated = true;
7528 
7529 			msg.opcode = cast(WebSocketOpcode) (b & 0x0f);
7530 			b >>= 4;
7531 			msg.rsv3 = b & 0x01;
7532 			b >>= 1;
7533 			msg.rsv2 = b & 0x01;
7534 			b >>= 1;
7535 			msg.rsv1 = b & 0x01;
7536 			b >>= 1;
7537 			msg.fin = b & 0x01;
7538 
7539 			b = d[1];
7540 			msg.masked = (b & 0b1000_0000) ? true : false;
7541 			msg.lengthIndicator = b & 0b0111_1111;
7542 
7543 			d = d[2 .. $];
7544 
7545 			if(msg.lengthIndicator == 0x7e) {
7546 				// 16 bit length
7547 				msg.realLength = 0;
7548 
7549 				if(d.length < 2) return needsMoreData();
7550 
7551 				foreach(i; 0 .. 2) {
7552 					msg.realLength |= d[0] << ((1-i) * 8);
7553 					d = d[1 .. $];
7554 				}
7555 			} else if(msg.lengthIndicator == 0x7f) {
7556 				// 64 bit length
7557 				msg.realLength = 0;
7558 
7559 				if(d.length < 8) return needsMoreData();
7560 
7561 				foreach(i; 0 .. 8) {
7562 					msg.realLength |= ulong(d[0]) << ((7-i) * 8);
7563 					d = d[1 .. $];
7564 				}
7565 			} else {
7566 				// 7 bit length
7567 				msg.realLength = msg.lengthIndicator;
7568 			}
7569 
7570 			if(msg.masked) {
7571 
7572 				if(d.length < 4) return needsMoreData();
7573 
7574 				msg.maskingKey = d[0 .. 4];
7575 				d = d[4 .. $];
7576 			}
7577 
7578 			if(msg.realLength > d.length) {
7579 				return needsMoreData();
7580 			}
7581 
7582 			msg.data = d[0 .. cast(size_t) msg.realLength];
7583 			d = d[cast(size_t) msg.realLength .. $];
7584 
7585 			return msg;
7586 		}
7587 
7588 		void unmaskInPlace() {
7589 			if(this.masked) {
7590 				int keyIdx = 0;
7591 				foreach(i; 0 .. this.data.length) {
7592 					this.data[i] = this.data[i] ^ this.maskingKey[keyIdx];
7593 					if(keyIdx == 3)
7594 						keyIdx = 0;
7595 					else
7596 						keyIdx++;
7597 				}
7598 			}
7599 		}
7600 
7601 		char[] textData() {
7602 			return cast(char[]) data;
7603 		}
7604 	}
7605 	/* } */
7606 }
7607 
7608 
7609 version(Windows)
7610 {
7611     version(CRuntime_DigitalMars)
7612     {
7613         extern(C) int setmode(int, int) nothrow @nogc;
7614     }
7615     else version(CRuntime_Microsoft)
7616     {
7617         extern(C) int _setmode(int, int) nothrow @nogc;
7618         alias setmode = _setmode;
7619     }
7620     else static assert(0);
7621 }
7622 
7623 version(Posix) {
7624 	import core.sys.posix.unistd;
7625 	version(CRuntime_Musl) {} else {
7626 		private extern(C) int posix_spawn(pid_t*, const char*, void*, void*, const char**, const char**);
7627 	}
7628 }
7629 
7630 
7631 // FIXME: these aren't quite public yet.
7632 //private:
7633 
7634 // template for laziness
7635 void startAddonServer()(string arg) {
7636 	version(OSX) {
7637 		assert(0, "Not implemented");
7638 	} else version(linux) {
7639 		import core.sys.posix.unistd;
7640 		pid_t pid;
7641 		const(char)*[16] args;
7642 		args[0] = "ARSD_CGI_ADDON_SERVER";
7643 		args[1] = arg.ptr;
7644 		posix_spawn(&pid, "/proc/self/exe",
7645 			null,
7646 			null,
7647 			args.ptr,
7648 			null // env
7649 		);
7650 	} else version(Windows) {
7651 		wchar[2048] filename;
7652 		auto len = GetModuleFileNameW(null, filename.ptr, cast(DWORD) filename.length);
7653 		if(len == 0 || len == filename.length)
7654 			throw new Exception("could not get process name to start helper server");
7655 
7656 		STARTUPINFOW startupInfo;
7657 		startupInfo.cb = cast(DWORD) startupInfo.sizeof;
7658 		PROCESS_INFORMATION processInfo;
7659 
7660 		import std.utf;
7661 
7662 		// I *MIGHT* need to run it as a new job or a service...
7663 		auto ret = CreateProcessW(
7664 			filename.ptr,
7665 			toUTF16z(arg),
7666 			null, // process attributes
7667 			null, // thread attributes
7668 			false, // inherit handles
7669 			0, // creation flags
7670 			null, // environment
7671 			null, // working directory
7672 			&startupInfo,
7673 			&processInfo
7674 		);
7675 
7676 		if(!ret)
7677 			throw new Exception("create process failed");
7678 
7679 		// when done with those, if we set them
7680 		/*
7681 		CloseHandle(hStdInput);
7682 		CloseHandle(hStdOutput);
7683 		CloseHandle(hStdError);
7684 		*/
7685 
7686 	} else static assert(0, "Websocket server not implemented on this system yet (email me, i can prolly do it if you need it)");
7687 }
7688 
7689 // template for laziness
7690 /*
7691 	The websocket server is a single-process, single-thread, event
7692 	I/O thing. It is passed websockets from other CGI processes
7693 	and is then responsible for handling their messages and responses.
7694 	Note that the CGI process is responsible for websocket setup,
7695 	including authentication, etc.
7696 
7697 	It also gets data sent to it by other processes and is responsible
7698 	for distributing that, as necessary.
7699 */
7700 void runWebsocketServer()() {
7701 	assert(0, "not implemented");
7702 }
7703 
7704 void sendToWebsocketServer(WebSocket ws, string group) {
7705 	assert(0, "not implemented");
7706 }
7707 
7708 void sendToWebsocketServer(string content, string group) {
7709 	assert(0, "not implemented");
7710 }
7711 
7712 
7713 void runEventServer()() {
7714 	runAddonServer("/tmp/arsd_cgi_event_server", new EventSourceServerImplementation());
7715 }
7716 
7717 void runTimerServer()() {
7718 	runAddonServer("/tmp/arsd_scheduled_job_server", new ScheduledJobServerImplementation());
7719 }
7720 
7721 version(Posix) {
7722 	alias LocalServerConnectionHandle = int;
7723 	alias CgiConnectionHandle = int;
7724 	alias SocketConnectionHandle = int;
7725 
7726 	enum INVALID_CGI_CONNECTION_HANDLE = -1;
7727 } else version(Windows) {
7728 	alias LocalServerConnectionHandle = HANDLE;
7729 	version(embedded_httpd_threads) {
7730 		alias CgiConnectionHandle = SOCKET;
7731 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
7732 	} else version(fastcgi) {
7733 		alias CgiConnectionHandle = void*; // Doesn't actually work! But I don't want compile to fail pointlessly at this point.
7734 		enum INVALID_CGI_CONNECTION_HANDLE = null;
7735 	} else version(scgi) {
7736 		alias CgiConnectionHandle = SOCKET;
7737 		enum INVALID_CGI_CONNECTION_HANDLE = INVALID_SOCKET;
7738 	} else { /* version(plain_cgi) */
7739 		alias CgiConnectionHandle = HANDLE;
7740 		enum INVALID_CGI_CONNECTION_HANDLE = null;
7741 	}
7742 	alias SocketConnectionHandle = SOCKET;
7743 }
7744 
7745 version(with_addon_servers_connections)
7746 LocalServerConnectionHandle openLocalServerConnection()(string name, string arg) {
7747 	version(Posix) {
7748 		import core.sys.posix.unistd;
7749 		import core.sys.posix.sys.un;
7750 
7751 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
7752 		if(sock == -1)
7753 			throw new Exception("socket " ~ to!string(errno));
7754 
7755 		scope(failure)
7756 			close(sock);
7757 
7758 		cloexec(sock);
7759 
7760 		// add-on server processes are assumed to be local, and thus will
7761 		// use unix domain sockets. Besides, I want to pass sockets to them,
7762 		// so it basically must be local (except for the session server, but meh).
7763 		sockaddr_un addr;
7764 		addr.sun_family = AF_UNIX;
7765 		version(linux) {
7766 			// on linux, we will use the abstract namespace
7767 			addr.sun_path[0] = 0;
7768 			addr.sun_path[1 .. name.length + 1] = cast(typeof(addr.sun_path[])) name[];
7769 		} else {
7770 			// but otherwise, just use a file cuz we must.
7771 			addr.sun_path[0 .. name.length] = cast(typeof(addr.sun_path[])) name[];
7772 		}
7773 
7774 		bool alreadyTried;
7775 
7776 		try_again:
7777 
7778 		if(connect(sock, cast(sockaddr*) &addr, addr.sizeof) == -1) {
7779 			if(!alreadyTried && errno == ECONNREFUSED) {
7780 				// try auto-spawning the server, then attempt connection again
7781 				startAddonServer(arg);
7782 				import core.thread;
7783 				Thread.sleep(50.msecs);
7784 				alreadyTried = true;
7785 				goto try_again;
7786 			} else
7787 				throw new Exception("connect " ~ to!string(errno));
7788 		}
7789 
7790 		return sock;
7791 	} else version(Windows) {
7792 		return null; // FIXME
7793 	}
7794 }
7795 
7796 version(with_addon_servers_connections)
7797 void closeLocalServerConnection(LocalServerConnectionHandle handle) {
7798 	version(Posix) {
7799 		import core.sys.posix.unistd;
7800 		close(handle);
7801 	} else version(Windows)
7802 		CloseHandle(handle);
7803 }
7804 
7805 void runSessionServer()() {
7806 	runAddonServer("/tmp/arsd_session_server", new BasicDataServerImplementation());
7807 }
7808 
7809 import core.stdc.errno;
7810 
7811 struct IoOp {
7812 	@disable this();
7813 	@disable this(this);
7814 
7815 	/*
7816 		So we want to be able to eventually handle generic sockets too.
7817 	*/
7818 
7819 	enum Read = 1;
7820 	enum Write = 2;
7821 	enum Accept = 3;
7822 	enum ReadSocketHandle = 4;
7823 
7824 	// Your handler may be called in a different thread than the one that initiated the IO request!
7825 	// It is also possible to have multiple io requests being called simultaneously. Use proper thread safety caution.
7826 	private bool delegate(IoOp*, int) handler; // returns true if you are done and want it to be closed
7827 	private void delegate(IoOp*) closeHandler;
7828 	private void delegate(IoOp*) completeHandler;
7829 	private int internalFd;
7830 	private int operation;
7831 	private int bufferLengthAllocated;
7832 	private int bufferLengthUsed;
7833 	private ubyte[1] internalBuffer; // it can be overallocated!
7834 
7835 	ubyte[] allocatedBuffer() return {
7836 		return internalBuffer.ptr[0 .. bufferLengthAllocated];
7837 	}
7838 
7839 	ubyte[] usedBuffer() return {
7840 		return allocatedBuffer[0 .. bufferLengthUsed];
7841 	}
7842 
7843 	void reset() {
7844 		bufferLengthUsed = 0;
7845 	}
7846 
7847 	int fd() {
7848 		return internalFd;
7849 	}
7850 }
7851 
7852 IoOp* allocateIoOp(int fd, int operation, int bufferSize, bool delegate(IoOp*, int) handler) {
7853 	import core.stdc.stdlib;
7854 
7855 	auto ptr = calloc(IoOp.sizeof + bufferSize, 1);
7856 	if(ptr is null)
7857 		assert(0); // out of memory!
7858 
7859 	auto op = cast(IoOp*) ptr;
7860 
7861 	op.handler = handler;
7862 	op.internalFd = fd;
7863 	op.operation = operation;
7864 	op.bufferLengthAllocated = bufferSize;
7865 	op.bufferLengthUsed = 0;
7866 
7867 	import core.memory;
7868 
7869 	GC.addRoot(ptr);
7870 
7871 	return op;
7872 }
7873 
7874 void freeIoOp(ref IoOp* ptr) {
7875 
7876 	import core.memory;
7877 	GC.removeRoot(ptr);
7878 
7879 	import core.stdc.stdlib;
7880 	free(ptr);
7881 	ptr = null;
7882 }
7883 
7884 version(Posix)
7885 version(with_addon_servers_connections)
7886 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
7887 
7888 	//import std.stdio : writeln; writeln(cast(string) data);
7889 
7890 	import core.sys.posix.unistd;
7891 
7892 	auto ret = write(connection, data.ptr, data.length);
7893 	if(ret != data.length) {
7894 		if(ret == 0 || (ret == -1 && (errno == EPIPE || errno == ETIMEDOUT))) {
7895 			// the file is closed, remove it
7896 			eis.fileClosed(connection);
7897 		} else
7898 			throw new Exception("alas " ~ to!string(ret) ~ " " ~ to!string(errno)); // FIXME
7899 	}
7900 }
7901 version(Windows)
7902 version(with_addon_servers_connections)
7903 void nonBlockingWrite(EventIoServer eis, int connection, const void[] data) {
7904 	// FIXME
7905 }
7906 
7907 bool isInvalidHandle(CgiConnectionHandle h) {
7908 	return h == INVALID_CGI_CONNECTION_HANDLE;
7909 }
7910 
7911 /+
7912 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsarecv
7913 https://support.microsoft.com/en-gb/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode
7914 https://stackoverflow.com/questions/18018489/should-i-use-iocps-or-overlapped-wsasend-receive
7915 https://docs.microsoft.com/en-us/windows/desktop/fileio/i-o-completion-ports
7916 https://docs.microsoft.com/en-us/windows/desktop/fileio/createiocompletionport
7917 https://docs.microsoft.com/en-us/windows/desktop/api/mswsock/nf-mswsock-acceptex
7918 https://docs.microsoft.com/en-us/windows/desktop/Sync/waitable-timer-objects
7919 https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-setwaitabletimer
7920 https://docs.microsoft.com/en-us/windows/desktop/Sync/using-a-waitable-timer-with-an-asynchronous-procedure-call
7921 https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsagetoverlappedresult
7922 
7923 +/
7924 
7925 /++
7926 	You can customize your server by subclassing the appropriate server. Then, register your
7927 	subclass at compile time with the [registerEventIoServer] template, or implement your own
7928 	main function and call it yourself.
7929 
7930 	$(TIP If you make your subclass a `final class`, there is a slight performance improvement.)
7931 +/
7932 version(with_addon_servers_connections)
7933 interface EventIoServer {
7934 	bool handleLocalConnectionData(IoOp* op, int receivedFd);
7935 	void handleLocalConnectionClose(IoOp* op);
7936 	void handleLocalConnectionComplete(IoOp* op);
7937 	void wait_timeout();
7938 	void fileClosed(int fd);
7939 
7940 	void epoll_fd(int fd);
7941 }
7942 
7943 // the sink should buffer it
7944 private void serialize(T)(scope void delegate(scope ubyte[]) sink, T t) {
7945 	static if(is(T == struct)) {
7946 		foreach(member; __traits(allMembers, T))
7947 			serialize(sink, __traits(getMember, t, member));
7948 	} else static if(is(T : int)) {
7949 		// no need to think of endianness just because this is only used
7950 		// for local, same-machine stuff anyway. thanks private lol
7951 		sink((cast(ubyte*) &t)[0 .. t.sizeof]);
7952 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
7953 		// these are common enough to optimize
7954 		int len = cast(int) t.length; // want length consistent size tho, in case 32 bit program sends to 64 bit server, etc.
7955 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7956 		sink(cast(ubyte[]) t[]);
7957 	} else static if(is(T : A[], A)) {
7958 		// generic array is less optimal but still prolly ok
7959 		int len = cast(int) t.length;
7960 		sink((cast(ubyte*) &len)[0 .. int.sizeof]);
7961 		foreach(item; t)
7962 			serialize(sink, item);
7963 	} else static assert(0, T.stringof);
7964 }
7965 
7966 // all may be stack buffers, so use cautio
7967 private void deserialize(T)(scope ubyte[] delegate(int sz) get, scope void delegate(T) dg) {
7968 	static if(is(T == struct)) {
7969 		T t;
7970 		foreach(member; __traits(allMembers, T))
7971 			deserialize!(typeof(__traits(getMember, T, member)))(get, (mbr) { __traits(getMember, t, member) = mbr; });
7972 		dg(t);
7973 	} else static if(is(T : int)) {
7974 		// no need to think of endianness just because this is only used
7975 		// for local, same-machine stuff anyway. thanks private lol
7976 		T t;
7977 		auto data = get(t.sizeof);
7978 		t = (cast(T[]) data)[0];
7979 		dg(t);
7980 	} else static if(is(T == string) || is(T : const(ubyte)[])) {
7981 		// these are common enough to optimize
7982 		int len;
7983 		auto data = get(len.sizeof);
7984 		len = (cast(int[]) data)[0];
7985 
7986 		/*
7987 		typeof(T[0])[2000] stackBuffer;
7988 		T buffer;
7989 
7990 		if(len < stackBuffer.length)
7991 			buffer = stackBuffer[0 .. len];
7992 		else
7993 			buffer = new T(len);
7994 
7995 		data = get(len * typeof(T[0]).sizeof);
7996 		*/
7997 
7998 		T t = cast(T) get(len * cast(int) typeof(T.init[0]).sizeof);
7999 
8000 		dg(t);
8001 	} else static if(is(T == E[], E)) {
8002 		T t;
8003 		int len;
8004 		auto data = get(len.sizeof);
8005 		len = (cast(int[]) data)[0];
8006 		t.length = len;
8007 		foreach(ref e; t) {
8008 			deserialize!E(get, (ele) { e = ele; });
8009 		}
8010 		dg(t);
8011 	} else static assert(0, T.stringof);
8012 }
8013 
8014 unittest {
8015 	serialize((ubyte[] b) {
8016 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 1); });
8017 	}, 1);
8018 	serialize((ubyte[] b) {
8019 		deserialize!int( sz => b[0 .. sz], (t) { assert(t == 56674); });
8020 	}, 56674);
8021 	ubyte[1000] buffer;
8022 	int bufferPoint;
8023 	void add(scope ubyte[] b) {
8024 		buffer[bufferPoint ..  bufferPoint + b.length] = b[];
8025 		bufferPoint += b.length;
8026 	}
8027 	ubyte[] get(int sz) {
8028 		auto b = buffer[bufferPoint .. bufferPoint + sz];
8029 		bufferPoint += sz;
8030 		return b;
8031 	}
8032 	serialize(&add, "test here");
8033 	bufferPoint = 0;
8034 	deserialize!string(&get, (t) { assert(t == "test here"); });
8035 	bufferPoint = 0;
8036 
8037 	struct Foo {
8038 		int a;
8039 		ubyte c;
8040 		string d;
8041 	}
8042 	serialize(&add, Foo(403, 37, "amazing"));
8043 	bufferPoint = 0;
8044 	deserialize!Foo(&get, (t) {
8045 		assert(t.a == 403);
8046 		assert(t.c == 37);
8047 		assert(t.d == "amazing");
8048 	});
8049 	bufferPoint = 0;
8050 }
8051 
8052 /*
8053 	Here's the way the RPC interface works:
8054 
8055 	You define the interface that lists the functions you can call on the remote process.
8056 	The interface may also have static methods for convenience. These forward to a singleton
8057 	instance of an auto-generated class, which actually sends the args over the pipe.
8058 
8059 	An impl class actually implements it. A receiving server deserializes down the pipe and
8060 	calls methods on the class.
8061 
8062 	I went with the interface to get some nice compiler checking and documentation stuff.
8063 
8064 	I could have skipped the interface and just implemented it all from the server class definition
8065 	itself, but then the usage may call the method instead of rpcing it; I just like having the user
8066 	interface and the implementation separate so you aren't tempted to `new impl` to call the methods.
8067 
8068 
8069 	I fiddled with newlines in the mixin string to ensure the assert line numbers matched up to the source code line number. Idk why dmd didn't do this automatically, but it was important to me.
8070 
8071 	Realistically though the bodies would just be
8072 		connection.call(this.mangleof, args...) sooooo.
8073 
8074 	FIXME: overloads aren't supported
8075 */
8076 
8077 /// Base for storing sessions in an array. Exists primarily for internal purposes and you should generally not use this.
8078 interface SessionObject {}
8079 
8080 private immutable void delegate(string[])[string] scheduledJobHandlers;
8081 private immutable void delegate(string[])[string] websocketServers;
8082 
8083 version(with_breaking_cgi_features)
8084 mixin(q{
8085 
8086 mixin template ImplementRpcClientInterface(T, string serverPath, string cmdArg) {
8087 	static import std.traits;
8088 
8089 	// derivedMembers on an interface seems to give exactly what I want: the virtual functions we need to implement. so I am just going to use it directly without more filtering.
8090 	static foreach(idx, member; __traits(derivedMembers, T)) {
8091 	static if(__traits(isVirtualMethod, __traits(getMember, T, member)))
8092 		mixin( q{
8093 		std.traits.ReturnType!(__traits(getMember, T, member))
8094 		} ~ member ~ q{(std.traits.Parameters!(__traits(getMember, T, member)) params)
8095 		{
8096 			SerializationBuffer buffer;
8097 			auto i = cast(ushort) idx;
8098 			serialize(&buffer.sink, i);
8099 			serialize(&buffer.sink, __traits(getMember, T, member).mangleof);
8100 			foreach(param; params)
8101 				serialize(&buffer.sink, param);
8102 
8103 			auto sendable = buffer.sendable;
8104 
8105 			version(Posix) {{
8106 				auto ret = send(connectionHandle, sendable.ptr, sendable.length, 0);
8107 
8108 				if(ret == -1) {
8109 					throw new Exception("send returned -1, errno: " ~ to!string(errno));
8110 				} else if(ret == 0) {
8111 					throw new Exception("Connection to addon server lost");
8112 				} if(ret < sendable.length)
8113 					throw new Exception("Send failed to send all");
8114 				assert(ret == sendable.length);
8115 			}} // FIXME Windows impl
8116 
8117 			static if(!is(typeof(return) == void)) {
8118 				// there is a return value; we need to wait for it too
8119 				version(Posix) {
8120 					ubyte[3000] revBuffer;
8121 					auto ret = recv(connectionHandle, revBuffer.ptr, revBuffer.length, 0);
8122 					auto got = revBuffer[0 .. ret];
8123 
8124 					int dataLocation;
8125 					ubyte[] grab(int sz) {
8126 						auto dataLocation1 = dataLocation;
8127 						dataLocation += sz;
8128 						return got[dataLocation1 .. dataLocation];
8129 					}
8130 
8131 					typeof(return) retu;
8132 					deserialize!(typeof(return))(&grab, (a) { retu = a; });
8133 					return retu;
8134 				} else {
8135 					// FIXME Windows impl
8136 					return typeof(return).init;
8137 				}
8138 
8139 			}
8140 		}});
8141 	}
8142 
8143 	private static typeof(this) singletonInstance;
8144 	private LocalServerConnectionHandle connectionHandle;
8145 
8146 	static typeof(this) connection() {
8147 		if(singletonInstance is null) {
8148 			singletonInstance = new typeof(this)();
8149 			singletonInstance.connect();
8150 		}
8151 		return singletonInstance;
8152 	}
8153 
8154 	void connect() {
8155 		connectionHandle = openLocalServerConnection(serverPath, cmdArg);
8156 	}
8157 
8158 	void disconnect() {
8159 		closeLocalServerConnection(connectionHandle);
8160 	}
8161 }
8162 
8163 void dispatchRpcServer(Interface, Class)(Class this_, ubyte[] data, int fd) if(is(Class : Interface)) {
8164 	ushort calledIdx;
8165 	string calledFunction;
8166 
8167 	int dataLocation;
8168 	ubyte[] grab(int sz) {
8169 		if(sz == 0) assert(0);
8170 		auto d = data[dataLocation .. dataLocation + sz];
8171 		dataLocation += sz;
8172 		return d;
8173 	}
8174 
8175 	again:
8176 
8177 	deserialize!ushort(&grab, (a) { calledIdx = a; });
8178 	deserialize!string(&grab, (a) { calledFunction = a; });
8179 
8180 	import std.traits;
8181 
8182 	sw: switch(calledIdx) {
8183 		foreach(idx, memberName; __traits(derivedMembers, Interface))
8184 		static if(__traits(isVirtualMethod, __traits(getMember, Interface, memberName))) {
8185 			case idx:
8186 				assert(calledFunction == __traits(getMember, Interface, memberName).mangleof);
8187 
8188 				Parameters!(__traits(getMember, Interface, memberName)) params;
8189 				foreach(ref param; params)
8190 					deserialize!(typeof(param))(&grab, (a) { param = a; });
8191 
8192 				static if(is(ReturnType!(__traits(getMember, Interface, memberName)) == void)) {
8193 					__traits(getMember, this_, memberName)(params);
8194 				} else {
8195 					auto ret = __traits(getMember, this_, memberName)(params);
8196 					SerializationBuffer buffer;
8197 					serialize(&buffer.sink, ret);
8198 
8199 					auto sendable = buffer.sendable;
8200 
8201 					version(Posix) {
8202 						auto r = send(fd, sendable.ptr, sendable.length, 0);
8203 						if(r == -1) {
8204 							throw new Exception("send returned -1, errno: " ~ to!string(errno));
8205 						} else if(r == 0) {
8206 							throw new Exception("Connection to addon client lost");
8207 						} if(r < sendable.length)
8208 							throw new Exception("Send failed to send all");
8209 
8210 					} // FIXME Windows impl
8211 				}
8212 			break sw;
8213 		}
8214 		default: assert(0);
8215 	}
8216 
8217 	if(dataLocation != data.length)
8218 		goto again;
8219 }
8220 
8221 
8222 private struct SerializationBuffer {
8223 	ubyte[2048] bufferBacking;
8224 	int bufferLocation;
8225 	void sink(scope ubyte[] data) {
8226 		bufferBacking[bufferLocation .. bufferLocation + data.length] = data[];
8227 		bufferLocation += data.length;
8228 	}
8229 
8230 	ubyte[] sendable() return {
8231 		return bufferBacking[0 .. bufferLocation];
8232 	}
8233 }
8234 
8235 /*
8236 	FIXME:
8237 		add a version command line arg
8238 		version data in the library
8239 		management gui as external program
8240 
8241 		at server with event_fd for each run
8242 		use .mangleof in the at function name
8243 
8244 		i think the at server will have to:
8245 			pipe args to the child
8246 			collect child output for logging
8247 			get child return value for logging
8248 
8249 			on windows timers work differently. idk how to best combine with the io stuff.
8250 
8251 			will have to have dump and restore too, so i can restart without losing stuff.
8252 */
8253 
8254 /++
8255 	A convenience object for talking to the [BasicDataServer] from a higher level.
8256 	See: [Cgi.getSessionObject].
8257 
8258 	You pass it a `Data` struct describing the data you want saved in the session.
8259 	Then, this class will generate getter and setter properties that allow access
8260 	to that data.
8261 
8262 	Note that each load and store will be done as-accessed; it doesn't front-load
8263 	mutable data nor does it batch updates out of fear of read-modify-write race
8264 	conditions. (In fact, right now it does this for everything, but in the future,
8265 	I might batch load `immutable` members of the Data struct.)
8266 
8267 	At some point in the future, I might also let it do different backends, like
8268 	a client-side cookie store too, but idk.
8269 
8270 	Note that the plain-old-data members of your `Data` struct are wrapped by this
8271 	interface via a static foreach to make property functions.
8272 
8273 	See_Also: [MockSession]
8274 +/
8275 interface Session(Data) : SessionObject {
8276 	@property string sessionId() const;
8277 
8278 	/++
8279 		Starts a new session. Note that a session is also
8280 		implicitly started as soon as you write data to it,
8281 		so if you need to alter these parameters from their
8282 		defaults, be sure to explicitly call this BEFORE doing
8283 		any writes to session data.
8284 
8285 		Params:
8286 			idleLifetime = How long, in seconds, the session
8287 			should remain in memory when not being read from
8288 			or written to. The default is one day.
8289 
8290 			NOT IMPLEMENTED
8291 
8292 			useExtendedLifetimeCookie = The session ID is always
8293 			stored in a HTTP cookie, and by default, that cookie
8294 			is discarded when the user closes their browser.
8295 
8296 			But if you set this to true, it will use a non-perishable
8297 			cookie for the given idleLifetime.
8298 
8299 			NOT IMPLEMENTED
8300 	+/
8301 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false);
8302 
8303 	/++
8304 		Regenerates the session ID and updates the associated
8305 		cookie.
8306 
8307 		This is also your chance to change immutable data
8308 		(not yet implemented).
8309 	+/
8310 	void regenerateId();
8311 
8312 	/++
8313 		Terminates this session, deleting all saved data.
8314 	+/
8315 	void terminate();
8316 
8317 	/++
8318 		Plain-old-data members of your `Data` struct are wrapped here via
8319 		the property getters and setters.
8320 
8321 		If the member is a non-string array, it returns a magical array proxy
8322 		object which allows for atomic appends and replaces via overloaded operators.
8323 		You can slice this to get a range representing a $(B const) view of the array.
8324 		This is to protect you against read-modify-write race conditions.
8325 	+/
8326 	static foreach(memberName; __traits(allMembers, Data))
8327 		static if(is(typeof(__traits(getMember, Data, memberName))))
8328 		mixin(q{
8329 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout;
8330 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value);
8331 		});
8332 
8333 }
8334 
8335 /++
8336 	An implementation of [Session] that works on real cgi connections utilizing the
8337 	[BasicDataServer].
8338 
8339 	As opposed to a [MockSession] which is made for testing purposes.
8340 
8341 	You will not construct one of these directly. See [Cgi.getSessionObject] instead.
8342 +/
8343 class BasicDataServerSession(Data) : Session!Data {
8344 	private Cgi cgi;
8345 	private string sessionId_;
8346 
8347 	public @property string sessionId() const {
8348 		return sessionId_;
8349 	}
8350 
8351 	protected @property string sessionId(string s) {
8352 		return this.sessionId_ = s;
8353 	}
8354 
8355 	private this(Cgi cgi) {
8356 		this.cgi = cgi;
8357 		if(auto ptr = "sessionId" in cgi.cookies)
8358 			sessionId = (*ptr).length ? *ptr : null;
8359 	}
8360 
8361 	void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {
8362 		assert(sessionId is null);
8363 
8364 		// FIXME: what if there is a session ID cookie, but no corresponding session on the server?
8365 
8366 		import std.random, std.conv;
8367 		sessionId = to!string(uniform(1, long.max));
8368 
8369 		BasicDataServer.connection.createSession(sessionId, idleLifetime);
8370 		setCookie();
8371 	}
8372 
8373 	protected void setCookie() {
8374 		cgi.setCookie(
8375 			"sessionId", sessionId,
8376 			0 /* expiration */,
8377 			"/" /* path */,
8378 			null /* domain */,
8379 			true /* http only */,
8380 			cgi.https /* if the session is started on https, keep it there, otherwise, be flexible */);
8381 	}
8382 
8383 	void regenerateId() {
8384 		if(sessionId is null) {
8385 			start();
8386 			return;
8387 		}
8388 		import std.random, std.conv;
8389 		auto oldSessionId = sessionId;
8390 		sessionId = to!string(uniform(1, long.max));
8391 		BasicDataServer.connection.renameSession(oldSessionId, sessionId);
8392 		setCookie();
8393 	}
8394 
8395 	void terminate() {
8396 		BasicDataServer.connection.destroySession(sessionId);
8397 		sessionId = null;
8398 		setCookie();
8399 	}
8400 
8401 	static foreach(memberName; __traits(allMembers, Data))
8402 		static if(is(typeof(__traits(getMember, Data, memberName))))
8403 		mixin(q{
8404 			@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
8405 				if(sessionId is null)
8406 					return typeof(return).init;
8407 
8408 				import std.traits;
8409 				auto v = BasicDataServer.connection.getSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName);
8410 				if(v.length == 0)
8411 					return typeof(return).init;
8412 				import std.conv;
8413 				// why this cast? to doesn't like being given an inout argument. so need to do it without that, then
8414 				// we need to return it and that needed the cast. It should be fine since we basically respect constness..
8415 				// basically. Assuming the session is POD this should be fine.
8416 				return cast(typeof(return)) to!(typeof(__traits(getMember, Data, memberName)))(v);
8417 			}
8418 			@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
8419 				if(sessionId is null)
8420 					start();
8421 				import std.conv;
8422 				import std.traits;
8423 				BasicDataServer.connection.setSessionData(sessionId, fullyQualifiedName!Data ~ "." ~ memberName, to!string(value));
8424 				return value;
8425 			}
8426 		});
8427 }
8428 
8429 /++
8430 	A mock object that works like the real session, but doesn't actually interact with any actual database or http connection.
8431 	Simply stores the data in its instance members.
8432 +/
8433 class MockSession(Data) : Session!Data {
8434 	pure {
8435 		@property string sessionId() const { return "mock"; }
8436 		void start(int idleLifetime = 2600 * 24, bool useExtendedLifetimeCookie = false) {}
8437 		void regenerateId() {}
8438 		void terminate() {}
8439 
8440 		private Data store_;
8441 
8442 		static foreach(memberName; __traits(allMembers, Data))
8443 			static if(is(typeof(__traits(getMember, Data, memberName))))
8444 			mixin(q{
8445 				@property inout(typeof(__traits(getMember, Data, memberName))) } ~ memberName ~ q{ () inout {
8446 					return __traits(getMember, store_, memberName);
8447 				}
8448 				@property typeof(__traits(getMember, Data, memberName)) } ~ memberName ~ q{ (typeof(__traits(getMember, Data, memberName)) value) {
8449 					return __traits(getMember, store_, memberName) = value;
8450 				}
8451 			});
8452 	}
8453 }
8454 
8455 /++
8456 	Direct interface to the basic data add-on server. You can
8457 	typically use [Cgi.getSessionObject] as a more convenient interface.
8458 +/
8459 version(with_addon_servers_connections)
8460 interface BasicDataServer {
8461 	///
8462 	void createSession(string sessionId, int lifetime);
8463 	///
8464 	void renewSession(string sessionId, int lifetime);
8465 	///
8466 	void destroySession(string sessionId);
8467 	///
8468 	void renameSession(string oldSessionId, string newSessionId);
8469 
8470 	///
8471 	void setSessionData(string sessionId, string dataKey, string dataValue);
8472 	///
8473 	string getSessionData(string sessionId, string dataKey);
8474 
8475 	///
8476 	static BasicDataServerConnection connection() {
8477 		return BasicDataServerConnection.connection();
8478 	}
8479 }
8480 
8481 version(with_addon_servers_connections)
8482 class BasicDataServerConnection : BasicDataServer {
8483 	mixin ImplementRpcClientInterface!(BasicDataServer, "/tmp/arsd_session_server", "--session-server");
8484 }
8485 
8486 version(with_addon_servers)
8487 final class BasicDataServerImplementation : BasicDataServer, EventIoServer {
8488 
8489 	void createSession(string sessionId, int lifetime) {
8490 		sessions[sessionId.idup] = Session(lifetime);
8491 	}
8492 	void destroySession(string sessionId) {
8493 		sessions.remove(sessionId);
8494 	}
8495 	void renewSession(string sessionId, int lifetime) {
8496 		sessions[sessionId].lifetime = lifetime;
8497 	}
8498 	void renameSession(string oldSessionId, string newSessionId) {
8499 		sessions[newSessionId.idup] = sessions[oldSessionId];
8500 		sessions.remove(oldSessionId);
8501 	}
8502 	void setSessionData(string sessionId, string dataKey, string dataValue) {
8503 		if(sessionId !in sessions)
8504 			createSession(sessionId, 3600); // FIXME?
8505 		sessions[sessionId].values[dataKey.idup] = dataValue.idup;
8506 	}
8507 	string getSessionData(string sessionId, string dataKey) {
8508 		if(auto session = sessionId in sessions) {
8509 			if(auto data = dataKey in (*session).values)
8510 				return *data;
8511 			else
8512 				return null; // no such data
8513 
8514 		} else {
8515 			return null; // no session
8516 		}
8517 	}
8518 
8519 
8520 	protected:
8521 
8522 	struct Session {
8523 		int lifetime;
8524 
8525 		string[string] values;
8526 	}
8527 
8528 	Session[string] sessions;
8529 
8530 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8531 		auto data = op.usedBuffer;
8532 		dispatchRpcServer!BasicDataServer(this, data, op.fd);
8533 		return false;
8534 	}
8535 
8536 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
8537 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
8538 	void wait_timeout() {}
8539 	void fileClosed(int fd) {} // stateless so irrelevant
8540 	void epoll_fd(int fd) {}
8541 }
8542 
8543 /++
8544 	See [schedule] to make one of these. You then call one of the methods here to set it up:
8545 
8546 	---
8547 		schedule!fn(args).at(DateTime(2019, 8, 7, 12, 00, 00)); // run the function at August 7, 2019, 12 noon UTC
8548 		schedule!fn(args).delay(6.seconds); // run it after waiting 6 seconds
8549 		schedule!fn(args).asap(); // run it in the background as soon as the event loop gets around to it
8550 	---
8551 +/
8552 version(with_addon_servers_connections)
8553 struct ScheduledJobHelper {
8554 	private string func;
8555 	private string[] args;
8556 	private bool consumed;
8557 
8558 	private this(string func, string[] args) {
8559 		this.func = func;
8560 		this.args = args;
8561 	}
8562 
8563 	~this() {
8564 		assert(consumed);
8565 	}
8566 
8567 	/++
8568 		Schedules the job to be run at the given time.
8569 	+/
8570 	void at(DateTime when, immutable TimeZone timezone = UTC()) {
8571 		consumed = true;
8572 
8573 		auto conn = ScheduledJobServerConnection.connection;
8574 		import std.file;
8575 		auto st = SysTime(when, timezone);
8576 		auto jobId = conn.scheduleJob(1, cast(int) st.toUnixTime(), thisExePath, func, args);
8577 	}
8578 
8579 	/++
8580 		Schedules the job to run at least after the specified delay.
8581 	+/
8582 	void delay(Duration delay) {
8583 		consumed = true;
8584 
8585 		auto conn = ScheduledJobServerConnection.connection;
8586 		import std.file;
8587 		auto jobId = conn.scheduleJob(0, cast(int) delay.total!"seconds", thisExePath, func, args);
8588 	}
8589 
8590 	/++
8591 		Runs the job in the background ASAP.
8592 
8593 		$(NOTE It may run in a background thread. Don't segfault!)
8594 	+/
8595 	void asap() {
8596 		consumed = true;
8597 
8598 		auto conn = ScheduledJobServerConnection.connection;
8599 		import std.file;
8600 		auto jobId = conn.scheduleJob(0, 1, thisExePath, func, args);
8601 	}
8602 
8603 	/+
8604 	/++
8605 		Schedules the job to recur on the given pattern.
8606 	+/
8607 	void recur(string spec) {
8608 
8609 	}
8610 	+/
8611 }
8612 
8613 /++
8614 	First step to schedule a job on the scheduled job server.
8615 
8616 	The scheduled job needs to be a top-level function that doesn't read any
8617 	variables from outside its arguments because it may be run in a new process,
8618 	without any context existing later.
8619 
8620 	You MUST set details on the returned object to actually do anything!
8621 +/
8622 template schedule(alias fn, T...) if(is(typeof(fn) == function)) {
8623 	///
8624 	ScheduledJobHelper schedule(T args) {
8625 		// this isn't meant to ever be called, but instead just to
8626 		// get the compiler to type check the arguments passed for us
8627 		auto sample = delegate() {
8628 			fn(args);
8629 		};
8630 		string[] sargs;
8631 		foreach(arg; args)
8632 			sargs ~= to!string(arg);
8633 		return ScheduledJobHelper(fn.mangleof, sargs);
8634 	}
8635 
8636 	shared static this() {
8637 		scheduledJobHandlers[fn.mangleof] = delegate(string[] sargs) {
8638 			import std.traits;
8639 			Parameters!fn args;
8640 			foreach(idx, ref arg; args)
8641 				arg = to!(typeof(arg))(sargs[idx]);
8642 			fn(args);
8643 		};
8644 	}
8645 }
8646 
8647 ///
8648 interface ScheduledJobServer {
8649 	/// Use the [schedule] function for a higher-level interface.
8650 	int scheduleJob(int whenIs, int when, string executable, string func, string[] args);
8651 	///
8652 	void cancelJob(int jobId);
8653 }
8654 
8655 version(with_addon_servers_connections)
8656 class ScheduledJobServerConnection : ScheduledJobServer {
8657 	mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server", "--timer-server");
8658 }
8659 
8660 version(with_addon_servers)
8661 final class ScheduledJobServerImplementation : ScheduledJobServer, EventIoServer {
8662 	// FIXME: we need to handle SIGCHLD in this somehow
8663 	// whenIs is 0 for relative, 1 for absolute
8664 	protected int scheduleJob(int whenIs, int when, string executable, string func, string[] args) {
8665 		auto nj = nextJobId;
8666 		nextJobId++;
8667 
8668 		version(linux) {
8669 			import core.sys.linux.timerfd;
8670 			import core.sys.linux.epoll;
8671 			import core.sys.posix.unistd;
8672 
8673 
8674 			auto fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
8675 			if(fd == -1)
8676 				throw new Exception("fd timer create failed");
8677 
8678 			foreach(ref arg; args)
8679 				arg = arg.idup;
8680 			auto job = Job(executable.idup, func.idup, .dup(args), fd, nj);
8681 
8682 			itimerspec value;
8683 			value.it_value.tv_sec = when;
8684 			value.it_value.tv_nsec = 0;
8685 
8686 			value.it_interval.tv_sec = 0;
8687 			value.it_interval.tv_nsec = 0;
8688 
8689 			if(timerfd_settime(fd, whenIs == 1 ? TFD_TIMER_ABSTIME : 0, &value, null) == -1)
8690 				throw new Exception("couldn't set fd timer");
8691 
8692 			auto op = allocateIoOp(fd, IoOp.Read, 16, (IoOp* op, int fd) {
8693 				jobs.remove(nj);
8694 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, null);
8695 				close(fd);
8696 
8697 
8698 				spawnProcess([job.executable, "--timed-job", job.func] ~ job.args);
8699 
8700 				return true;
8701 			});
8702 			scope(failure)
8703 				freeIoOp(op);
8704 
8705 			epoll_event ev;
8706 			ev.events = EPOLLIN | EPOLLET;
8707 			ev.data.ptr = op;
8708 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1)
8709 				throw new Exception("epoll_ctl " ~ to!string(errno));
8710 
8711 			jobs[nj] = job;
8712 			return nj;
8713 		} else assert(0);
8714 	}
8715 
8716 	protected void cancelJob(int jobId) {
8717 		version(linux) {
8718 			auto job = jobId in jobs;
8719 			if(job is null)
8720 				return;
8721 
8722 			jobs.remove(jobId);
8723 
8724 			version(linux) {
8725 				import core.sys.linux.timerfd;
8726 				import core.sys.linux.epoll;
8727 				import core.sys.posix.unistd;
8728 				epoll_ctl(epoll_fd, EPOLL_CTL_DEL, job.timerfd, null);
8729 				close(job.timerfd);
8730 			}
8731 		}
8732 		jobs.remove(jobId);
8733 	}
8734 
8735 	int nextJobId = 1;
8736 	static struct Job {
8737 		string executable;
8738 		string func;
8739 		string[] args;
8740 		int timerfd;
8741 		int id;
8742 	}
8743 	Job[int] jobs;
8744 
8745 
8746 	// event io server methods below
8747 
8748 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8749 		auto data = op.usedBuffer;
8750 		dispatchRpcServer!ScheduledJobServer(this, data, op.fd);
8751 		return false;
8752 	}
8753 
8754 	void handleLocalConnectionClose(IoOp* op) {} // doesn't really matter, this is a fairly stateless go
8755 	void handleLocalConnectionComplete(IoOp* op) {} // again, irrelevant
8756 	void wait_timeout() {}
8757 	void fileClosed(int fd) {} // stateless so irrelevant
8758 
8759 	int epoll_fd_;
8760 	void epoll_fd(int fd) {this.epoll_fd_ = fd; }
8761 	int epoll_fd() { return epoll_fd_; }
8762 }
8763 
8764 /++
8765 	History:
8766 		Added January 6, 2019
8767 +/
8768 version(with_addon_servers_connections)
8769 interface EventSourceServer {
8770 	/++
8771 		sends this cgi request to the event server so it will be fed events. You should not do anything else with the cgi object after this.
8772 
8773 		See_Also:
8774 			[sendEvent]
8775 
8776 		Bugs:
8777 			Not implemented on Windows!
8778 
8779 		History:
8780 			Officially stabilised on November 23, 2023 (dub v11.4). It actually worked pretty well in its original design.
8781 	+/
8782 	public static void adoptConnection(Cgi cgi, in char[] eventUrl) {
8783 		/*
8784 			If lastEventId is missing or empty, you just get new events as they come.
8785 
8786 			If it is set from something else, it sends all since then (that are still alive)
8787 			down the pipe immediately.
8788 
8789 			The reason it can come from the header is that's what the standard defines for
8790 			browser reconnects. The reason it can come from a query string is just convenience
8791 			in catching up in a user-defined manner.
8792 
8793 			The reason the header overrides the query string is if the browser tries to reconnect,
8794 			it will send the header AND the query (it reconnects to the same url), so we just
8795 			want to do the restart thing.
8796 
8797 			Note that if you ask for "0" as the lastEventId, it will get ALL still living events.
8798 		*/
8799 		string lastEventId = cgi.lastEventId;
8800 		if(lastEventId.length == 0 && "lastEventId" in cgi.get)
8801 			lastEventId = cgi.get["lastEventId"];
8802 
8803 		cgi.setResponseContentType("text/event-stream");
8804 		cgi.write(":\n", false); // to initialize the chunking and send headers before keeping the fd for later
8805 		cgi.flush();
8806 
8807 		cgi.closed = true;
8808 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
8809 		scope(exit)
8810 			closeLocalServerConnection(s);
8811 
8812 		version(fastcgi)
8813 			throw new Exception("sending fcgi connections not supported");
8814 		else {
8815 			auto fd = cgi.getOutputFileHandle();
8816 			if(isInvalidHandle(fd))
8817 				throw new Exception("bad fd from cgi!");
8818 
8819 			EventSourceServerImplementation.SendableEventConnection sec;
8820 			sec.populate(cgi.responseChunked, eventUrl, lastEventId);
8821 
8822 			version(Posix) {
8823 				auto res = write_fd(s, cast(void*) &sec, sec.sizeof, fd);
8824 				assert(res == sec.sizeof);
8825 			} else version(Windows) {
8826 				// FIXME
8827 			}
8828 		}
8829 	}
8830 
8831 	/++
8832 		Sends an event to the event server, starting it if necessary. The event server will distribute it to any listening clients, and store it for `lifetime` seconds for any later listening clients to catch up later.
8833 
8834 		Params:
8835 			url = A string identifying this event "bucket". Listening clients must also connect to this same string. I called it `url` because I envision it being just passed as the url of the request.
8836 			event = the event type string, which is used in the Javascript addEventListener API on EventSource
8837 			data = the event data. Available in JS as `event.data`.
8838 			lifetime = the amount of time to keep this event for replaying on the event server.
8839 
8840 		Bugs:
8841 			Not implemented on Windows!
8842 
8843 		History:
8844 			Officially stabilised on November 23, 2023 (dub v11.4). It actually worked pretty well in its original design.
8845 	+/
8846 	public static void sendEvent(string url, string event, string data, int lifetime) {
8847 		auto s = openLocalServerConnection("/tmp/arsd_cgi_event_server", "--event-server");
8848 		scope(exit)
8849 			closeLocalServerConnection(s);
8850 
8851 		EventSourceServerImplementation.SendableEvent sev;
8852 		sev.populate(url, event, data, lifetime);
8853 
8854 		version(Posix) {
8855 			auto ret = send(s, &sev, sev.sizeof, 0);
8856 			assert(ret == sev.sizeof);
8857 		} else version(Windows) {
8858 			// FIXME
8859 		}
8860 	}
8861 
8862 	/++
8863 		Messages sent to `url` will also be sent to anyone listening on `forwardUrl`.
8864 
8865 		See_Also: [disconnect]
8866 	+/
8867 	void connect(string url, string forwardUrl);
8868 
8869 	/++
8870 		Disconnects `forwardUrl` from `url`
8871 
8872 		See_Also: [connect]
8873 	+/
8874 	void disconnect(string url, string forwardUrl);
8875 }
8876 
8877 ///
8878 version(with_addon_servers)
8879 final class EventSourceServerImplementation : EventSourceServer, EventIoServer {
8880 
8881 	protected:
8882 
8883 	void connect(string url, string forwardUrl) {
8884 		pipes[url] ~= forwardUrl;
8885 	}
8886 	void disconnect(string url, string forwardUrl) {
8887 		auto t = url in pipes;
8888 		if(t is null)
8889 			return;
8890 		foreach(idx, n; (*t))
8891 			if(n == forwardUrl) {
8892 				(*t)[idx] = (*t)[$-1];
8893 				(*t) = (*t)[0 .. $-1];
8894 				break;
8895 			}
8896 	}
8897 
8898 	bool handleLocalConnectionData(IoOp* op, int receivedFd) {
8899 		if(receivedFd != -1) {
8900 			//writeln("GOT FD ", receivedFd, " -- ", op.usedBuffer);
8901 
8902 			//core.sys.posix.unistd.write(receivedFd, "hello".ptr, 5);
8903 
8904 			SendableEventConnection* got = cast(SendableEventConnection*) op.usedBuffer.ptr;
8905 
8906 			auto url = got.url.idup;
8907 			eventConnectionsByUrl[url] ~= EventConnection(receivedFd, got.responseChunked > 0 ? true : false);
8908 
8909 			// FIXME: catch up on past messages here
8910 		} else {
8911 			auto data = op.usedBuffer;
8912 			auto event = cast(SendableEvent*) data.ptr;
8913 
8914 			if(event.magic == 0xdeadbeef) {
8915 				handleInputEvent(event);
8916 
8917 				if(event.url in pipes)
8918 				foreach(pipe; pipes[event.url]) {
8919 					event.url = pipe;
8920 					handleInputEvent(event);
8921 				}
8922 			} else {
8923 				dispatchRpcServer!EventSourceServer(this, data, op.fd);
8924 			}
8925 		}
8926 		return false;
8927 	}
8928 	void handleLocalConnectionClose(IoOp* op) {
8929 		fileClosed(op.fd);
8930 	}
8931 	void handleLocalConnectionComplete(IoOp* op) {}
8932 
8933 	void wait_timeout() {
8934 		// just keeping alive
8935 		foreach(url, connections; eventConnectionsByUrl)
8936 		foreach(connection; connections)
8937 			if(connection.needsChunking)
8938 				nonBlockingWrite(this, connection.fd, "1b\r\nevent: keepalive\ndata: ok\n\n\r\n");
8939 			else
8940 				nonBlockingWrite(this, connection.fd, "event: keepalive\ndata: ok\n\n\r\n");
8941 	}
8942 
8943 	void fileClosed(int fd) {
8944 		outer: foreach(url, ref connections; eventConnectionsByUrl) {
8945 			foreach(idx, conn; connections) {
8946 				if(fd == conn.fd) {
8947 					connections[idx] = connections[$-1];
8948 					connections = connections[0 .. $ - 1];
8949 					continue outer;
8950 				}
8951 			}
8952 		}
8953 	}
8954 
8955 	void epoll_fd(int fd) {}
8956 
8957 
8958 	private:
8959 
8960 
8961 	struct SendableEventConnection {
8962 		ubyte responseChunked;
8963 
8964 		int urlLength;
8965 		char[256] urlBuffer = 0;
8966 
8967 		int lastEventIdLength;
8968 		char[32] lastEventIdBuffer = 0;
8969 
8970 		char[] url() return {
8971 			return urlBuffer[0 .. urlLength];
8972 		}
8973 		void url(in char[] u) {
8974 			urlBuffer[0 .. u.length] = u[];
8975 			urlLength = cast(int) u.length;
8976 		}
8977 		char[] lastEventId() return {
8978 			return lastEventIdBuffer[0 .. lastEventIdLength];
8979 		}
8980 		void populate(bool responseChunked, in char[] url, in char[] lastEventId)
8981 		in {
8982 			assert(url.length < this.urlBuffer.length);
8983 			assert(lastEventId.length < this.lastEventIdBuffer.length);
8984 		}
8985 		do {
8986 			this.responseChunked = responseChunked ? 1 : 0;
8987 			this.urlLength = cast(int) url.length;
8988 			this.lastEventIdLength = cast(int) lastEventId.length;
8989 
8990 			this.urlBuffer[0 .. url.length] = url[];
8991 			this.lastEventIdBuffer[0 .. lastEventId.length] = lastEventId[];
8992 		}
8993 	}
8994 
8995 	struct SendableEvent {
8996 		int magic = 0xdeadbeef;
8997 		int urlLength;
8998 		char[256] urlBuffer = 0;
8999 		int typeLength;
9000 		char[32] typeBuffer = 0;
9001 		int messageLength;
9002 		char[2048 * 4] messageBuffer = 0; // this is an arbitrary limit, it needs to fit comfortably in stack (including in a fiber) and be a single send on the kernel side cuz of the impl... i think this is ok for a unix socket.
9003 		int _lifetime;
9004 
9005 		char[] message() return {
9006 			return messageBuffer[0 .. messageLength];
9007 		}
9008 		char[] type() return {
9009 			return typeBuffer[0 .. typeLength];
9010 		}
9011 		char[] url() return {
9012 			return urlBuffer[0 .. urlLength];
9013 		}
9014 		void url(in char[] u) {
9015 			urlBuffer[0 .. u.length] = u[];
9016 			urlLength = cast(int) u.length;
9017 		}
9018 		int lifetime() {
9019 			return _lifetime;
9020 		}
9021 
9022 		///
9023 		void populate(string url, string type, string message, int lifetime)
9024 		in {
9025 			assert(url.length < this.urlBuffer.length);
9026 			assert(type.length < this.typeBuffer.length);
9027 			assert(message.length < this.messageBuffer.length);
9028 		}
9029 		do {
9030 			this.urlLength = cast(int) url.length;
9031 			this.typeLength = cast(int) type.length;
9032 			this.messageLength = cast(int) message.length;
9033 			this._lifetime = lifetime;
9034 
9035 			this.urlBuffer[0 .. url.length] = url[];
9036 			this.typeBuffer[0 .. type.length] = type[];
9037 			this.messageBuffer[0 .. message.length] = message[];
9038 		}
9039 	}
9040 
9041 	struct EventConnection {
9042 		int fd;
9043 		bool needsChunking;
9044 	}
9045 
9046 	private EventConnection[][string] eventConnectionsByUrl;
9047 	private string[][string] pipes;
9048 
9049 	private void handleInputEvent(scope SendableEvent* event) {
9050 		static int eventId;
9051 
9052 		static struct StoredEvent {
9053 			int id;
9054 			string type;
9055 			string message;
9056 			int lifetimeRemaining;
9057 		}
9058 
9059 		StoredEvent[][string] byUrl;
9060 
9061 		int thisId = ++eventId;
9062 
9063 		if(event.lifetime)
9064 			byUrl[event.url.idup] ~= StoredEvent(thisId, event.type.idup, event.message.idup, event.lifetime);
9065 
9066 		auto connectionsPtr = event.url in eventConnectionsByUrl;
9067 		EventConnection[] connections;
9068 		if(connectionsPtr is null)
9069 			return;
9070 		else
9071 			connections = *connectionsPtr;
9072 
9073 		char[4096] buffer;
9074 		char[] formattedMessage;
9075 
9076 		void append(const char[] a) {
9077 			// the 6's here are to leave room for a HTTP chunk header, if it proves necessary
9078 			buffer[6 + formattedMessage.length .. 6 + formattedMessage.length + a.length] = a[];
9079 			formattedMessage = buffer[6 .. 6 + formattedMessage.length + a.length];
9080 		}
9081 
9082 		import std.algorithm.iteration;
9083 
9084 		if(connections.length) {
9085 			append("id: ");
9086 			append(to!string(thisId));
9087 			append("\n");
9088 
9089 			append("event: ");
9090 			append(event.type);
9091 			append("\n");
9092 
9093 			foreach(line; event.message.splitter("\n")) {
9094 				append("data: ");
9095 				append(line);
9096 				append("\n");
9097 			}
9098 
9099 			append("\n");
9100 		}
9101 
9102 		// chunk it for HTTP!
9103 		auto len = toHex(formattedMessage.length);
9104 		buffer[4 .. 6] = "\r\n"[];
9105 		buffer[4 - len.length .. 4] = len[];
9106 		buffer[6 + formattedMessage.length] = '\r';
9107 		buffer[6 + formattedMessage.length + 1] = '\n';
9108 
9109 		auto chunkedMessage = buffer[4 - len.length .. 6 + formattedMessage.length +2];
9110 		// done
9111 
9112 		// FIXME: send back requests when needed
9113 		// FIXME: send a single ":\n" every 15 seconds to keep alive
9114 
9115 		foreach(connection; connections) {
9116 			if(connection.needsChunking) {
9117 				nonBlockingWrite(this, connection.fd, chunkedMessage);
9118 			} else {
9119 				nonBlockingWrite(this, connection.fd, formattedMessage);
9120 			}
9121 		}
9122 	}
9123 }
9124 
9125 void runAddonServer(EIS)(string localListenerName, EIS eis) if(is(EIS : EventIoServer)) {
9126 	version(Posix) {
9127 
9128 		import core.sys.posix.unistd;
9129 		import core.sys.posix.fcntl;
9130 		import core.sys.posix.sys.un;
9131 
9132 		import core.sys.posix.signal;
9133 		signal(SIGPIPE, SIG_IGN);
9134 
9135 		static extern(C) void sigchldhandler(int) {
9136 			int status;
9137 			import w = core.sys.posix.sys.wait;
9138 			w.wait(&status);
9139 		}
9140 		signal(SIGCHLD, &sigchldhandler);
9141 
9142 		int sock = socket(AF_UNIX, SOCK_STREAM, 0);
9143 		if(sock == -1)
9144 			throw new Exception("socket " ~ to!string(errno));
9145 
9146 		scope(failure)
9147 			close(sock);
9148 
9149 		cloexec(sock);
9150 
9151 		// add-on server processes are assumed to be local, and thus will
9152 		// use unix domain sockets. Besides, I want to pass sockets to them,
9153 		// so it basically must be local (except for the session server, but meh).
9154 		sockaddr_un addr;
9155 		addr.sun_family = AF_UNIX;
9156 		version(linux) {
9157 			// on linux, we will use the abstract namespace
9158 			addr.sun_path[0] = 0;
9159 			addr.sun_path[1 .. localListenerName.length + 1] = cast(typeof(addr.sun_path[])) localListenerName[];
9160 		} else {
9161 			// but otherwise, just use a file cuz we must.
9162 			addr.sun_path[0 .. localListenerName.length] = cast(typeof(addr.sun_path[])) localListenerName[];
9163 		}
9164 
9165 		if(bind(sock, cast(sockaddr*) &addr, addr.sizeof) == -1)
9166 			throw new Exception("bind " ~ to!string(errno));
9167 
9168 		if(listen(sock, 128) == -1)
9169 			throw new Exception("listen " ~ to!string(errno));
9170 
9171 		makeNonBlocking(sock);
9172 
9173 		version(linux) {
9174 			import core.sys.linux.epoll;
9175 			auto epoll_fd = epoll_create1(EPOLL_CLOEXEC);
9176 			if(epoll_fd == -1)
9177 				throw new Exception("epoll_create1 " ~ to!string(errno));
9178 			scope(failure)
9179 				close(epoll_fd);
9180 		} else {
9181 			import core.sys.posix.poll;
9182 		}
9183 
9184 		version(linux)
9185 		eis.epoll_fd = epoll_fd;
9186 
9187 		auto acceptOp = allocateIoOp(sock, IoOp.Read, 0, null);
9188 		scope(exit)
9189 			freeIoOp(acceptOp);
9190 
9191 		version(linux) {
9192 			epoll_event ev;
9193 			ev.events = EPOLLIN | EPOLLET;
9194 			ev.data.ptr = acceptOp;
9195 			if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev) == -1)
9196 				throw new Exception("epoll_ctl " ~ to!string(errno));
9197 
9198 			epoll_event[64] events;
9199 		} else {
9200 			pollfd[] pollfds;
9201 			IoOp*[int] ioops;
9202 			pollfds ~= pollfd(sock, POLLIN);
9203 			ioops[sock] = acceptOp;
9204 		}
9205 
9206 		import core.time : MonoTime, seconds;
9207 
9208 		MonoTime timeout = MonoTime.currTime + 15.seconds;
9209 
9210 		while(true) {
9211 
9212 			// FIXME: it should actually do a timerfd that runs on any thing that hasn't been run recently
9213 
9214 			int timeout_milliseconds = 0; //  -1; // infinite
9215 
9216 			timeout_milliseconds = cast(int) (timeout - MonoTime.currTime).total!"msecs";
9217 			if(timeout_milliseconds < 0)
9218 				timeout_milliseconds = 0;
9219 
9220 			//writeln("waiting for ", name);
9221 
9222 			version(linux) {
9223 				auto nfds = epoll_wait(epoll_fd, events.ptr, events.length, timeout_milliseconds);
9224 				if(nfds == -1) {
9225 					if(errno == EINTR)
9226 						continue;
9227 					throw new Exception("epoll_wait " ~ to!string(errno));
9228 				}
9229 			} else {
9230 				int nfds = poll(pollfds.ptr, cast(int) pollfds.length, timeout_milliseconds);
9231 				size_t lastIdx = 0;
9232 			}
9233 
9234 			if(nfds == 0) {
9235 				eis.wait_timeout();
9236 				timeout += 15.seconds;
9237 			}
9238 
9239 			foreach(idx; 0 .. nfds) {
9240 				version(linux) {
9241 					auto flags = events[idx].events;
9242 					auto ioop = cast(IoOp*) events[idx].data.ptr;
9243 				} else {
9244 					IoOp* ioop;
9245 					foreach(tidx, thing; pollfds[lastIdx .. $]) {
9246 						if(thing.revents) {
9247 							ioop = ioops[thing.fd];
9248 							lastIdx += tidx + 1;
9249 							break;
9250 						}
9251 					}
9252 				}
9253 
9254 				//writeln(flags, " ", ioop.fd);
9255 
9256 				void newConnection() {
9257 					// on edge triggering, it is important that we get it all
9258 					while(true) {
9259 						auto size = cast(socklen_t) addr.sizeof;
9260 						auto ns = accept(sock, cast(sockaddr*) &addr, &size);
9261 						if(ns == -1) {
9262 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9263 								// all done, got it all
9264 								break;
9265 							}
9266 							throw new Exception("accept " ~ to!string(errno));
9267 						}
9268 						cloexec(ns);
9269 
9270 						makeNonBlocking(ns);
9271 						auto niop = allocateIoOp(ns, IoOp.ReadSocketHandle, 4096 * 4, &eis.handleLocalConnectionData);
9272 						niop.closeHandler = &eis.handleLocalConnectionClose;
9273 						niop.completeHandler = &eis.handleLocalConnectionComplete;
9274 						scope(failure) freeIoOp(niop);
9275 
9276 						version(linux) {
9277 							epoll_event nev;
9278 							nev.events = EPOLLIN | EPOLLET;
9279 							nev.data.ptr = niop;
9280 							if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ns, &nev) == -1)
9281 								throw new Exception("epoll_ctl " ~ to!string(errno));
9282 						} else {
9283 							bool found = false;
9284 							foreach(ref pfd; pollfds) {
9285 								if(pfd.fd < 0) {
9286 									pfd.fd = ns;
9287 									found = true;
9288 								}
9289 							}
9290 							if(!found)
9291 								pollfds ~= pollfd(ns, POLLIN);
9292 							ioops[ns] = niop;
9293 						}
9294 					}
9295 				}
9296 
9297 				bool newConnectionCondition() {
9298 					version(linux)
9299 						return ioop.fd == sock && (flags & EPOLLIN);
9300 					else
9301 						return pollfds[idx].fd == sock && (pollfds[idx].revents & POLLIN);
9302 				}
9303 
9304 				if(newConnectionCondition()) {
9305 					newConnection();
9306 				} else if(ioop.operation == IoOp.ReadSocketHandle) {
9307 					while(true) {
9308 						int in_fd;
9309 						auto got = read_fd(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length, &in_fd);
9310 						if(got == -1) {
9311 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9312 								// all done, got it all
9313 								if(ioop.completeHandler)
9314 									ioop.completeHandler(ioop);
9315 								break;
9316 							}
9317 							throw new Exception("recv " ~ to!string(errno));
9318 						}
9319 
9320 						if(got == 0) {
9321 							if(ioop.closeHandler) {
9322 								ioop.closeHandler(ioop);
9323 								version(linux) {} // nothing needed
9324 								else {
9325 									foreach(ref pfd; pollfds) {
9326 										if(pfd.fd == ioop.fd)
9327 											pfd.fd = -1;
9328 									}
9329 								}
9330 							}
9331 							close(ioop.fd);
9332 							freeIoOp(ioop);
9333 							break;
9334 						}
9335 
9336 						ioop.bufferLengthUsed = cast(int) got;
9337 						ioop.handler(ioop, in_fd);
9338 					}
9339 				} else if(ioop.operation == IoOp.Read) {
9340 					while(true) {
9341 						auto got = read(ioop.fd, ioop.allocatedBuffer.ptr, ioop.allocatedBuffer.length);
9342 						if(got == -1) {
9343 							if(errno == EAGAIN || errno == EWOULDBLOCK) {
9344 								// all done, got it all
9345 								if(ioop.completeHandler)
9346 									ioop.completeHandler(ioop);
9347 								break;
9348 							}
9349 							throw new Exception("recv " ~ to!string(ioop.fd) ~ " errno " ~ to!string(errno));
9350 						}
9351 
9352 						if(got == 0) {
9353 							if(ioop.closeHandler)
9354 								ioop.closeHandler(ioop);
9355 							close(ioop.fd);
9356 							freeIoOp(ioop);
9357 							break;
9358 						}
9359 
9360 						ioop.bufferLengthUsed = cast(int) got;
9361 						if(ioop.handler(ioop, ioop.fd)) {
9362 							close(ioop.fd);
9363 							freeIoOp(ioop);
9364 							break;
9365 						}
9366 					}
9367 				}
9368 
9369 				// EPOLLHUP?
9370 			}
9371 		}
9372 	} else version(Windows) {
9373 
9374 		// set up a named pipe
9375 		// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251(v=vs.85).aspx
9376 		// https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-wsaduplicatesocketw
9377 		// https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getnamedpipeserverprocessid
9378 
9379 	} else static assert(0);
9380 }
9381 
9382 
9383 version(with_sendfd)
9384 // copied from the web and ported from C
9385 // see https://stackoverflow.com/questions/2358684/can-i-share-a-file-descriptor-to-another-process-on-linux-or-are-they-local-to-t
9386 ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd) {
9387 	msghdr msg;
9388 	iovec[1] iov;
9389 
9390 	version(OSX) {
9391 		//msg.msg_accrights = cast(cattr_t) &sendfd;
9392 		//msg.msg_accrightslen = int.sizeof;
9393 	} else version(Android) {
9394 	} else {
9395 		union ControlUnion {
9396 			cmsghdr cm;
9397 			char[CMSG_SPACE(int.sizeof)] control;
9398 		}
9399 
9400 		ControlUnion control_un;
9401 		cmsghdr* cmptr;
9402 
9403 		msg.msg_control = control_un.control.ptr;
9404 		msg.msg_controllen = control_un.control.length;
9405 
9406 		cmptr = CMSG_FIRSTHDR(&msg);
9407 		cmptr.cmsg_len = CMSG_LEN(int.sizeof);
9408 		cmptr.cmsg_level = SOL_SOCKET;
9409 		cmptr.cmsg_type = SCM_RIGHTS;
9410 		*(cast(int *) CMSG_DATA(cmptr)) = sendfd;
9411 	}
9412 
9413 	msg.msg_name = null;
9414 	msg.msg_namelen = 0;
9415 
9416 	iov[0].iov_base = ptr;
9417 	iov[0].iov_len = nbytes;
9418 	msg.msg_iov = iov.ptr;
9419 	msg.msg_iovlen = 1;
9420 
9421 	return sendmsg(fd, &msg, 0);
9422 }
9423 
9424 version(with_sendfd)
9425 // copied from the web and ported from C
9426 ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd) {
9427 	msghdr msg;
9428 	iovec[1] iov;
9429 	ssize_t n;
9430 	int newfd;
9431 
9432 	version(OSX) {
9433 		//msg.msg_accrights = cast(cattr_t) recvfd;
9434 		//msg.msg_accrightslen = int.sizeof;
9435 	} else version(Android) {
9436 	} else {
9437 		union ControlUnion {
9438 			cmsghdr cm;
9439 			char[CMSG_SPACE(int.sizeof)] control;
9440 		}
9441 		ControlUnion control_un;
9442 		cmsghdr* cmptr;
9443 
9444 		msg.msg_control = control_un.control.ptr;
9445 		msg.msg_controllen = control_un.control.length;
9446 	}
9447 
9448 	msg.msg_name = null;
9449 	msg.msg_namelen = 0;
9450 
9451 	iov[0].iov_base = ptr;
9452 	iov[0].iov_len = nbytes;
9453 	msg.msg_iov = iov.ptr;
9454 	msg.msg_iovlen = 1;
9455 
9456 	if ( (n = recvmsg(fd, &msg, 0)) <= 0)
9457 		return n;
9458 
9459 	version(OSX) {
9460 		//if(msg.msg_accrightslen != int.sizeof)
9461 			//*recvfd = -1;
9462 	} else version(Android) {
9463 	} else {
9464 		if ( (cmptr = CMSG_FIRSTHDR(&msg)) != null &&
9465 				cmptr.cmsg_len == CMSG_LEN(int.sizeof)) {
9466 			if (cmptr.cmsg_level != SOL_SOCKET)
9467 				throw new Exception("control level != SOL_SOCKET");
9468 			if (cmptr.cmsg_type != SCM_RIGHTS)
9469 				throw new Exception("control type != SCM_RIGHTS");
9470 			*recvfd = *(cast(int *) CMSG_DATA(cmptr));
9471 		} else
9472 			*recvfd = -1;       /* descriptor was not passed */
9473 	}
9474 
9475 	return n;
9476 }
9477 /* end read_fd */
9478 
9479 
9480 /*
9481 	Event source stuff
9482 
9483 	The api is:
9484 
9485 	sendEvent(string url, string type, string data, int timeout = 60*10);
9486 
9487 	attachEventListener(string url, int fd, lastId)
9488 
9489 
9490 	It just sends to all attached listeners, and stores it until the timeout
9491 	for replaying via lastEventId.
9492 */
9493 
9494 /*
9495 	Session process stuff
9496 
9497 	it stores it all. the cgi object has a session object that can grab it
9498 
9499 	session may be done in the same process if possible, there is a version
9500 	switch to choose if you want to override.
9501 */
9502 
9503 struct DispatcherDefinition(alias dispatchHandler, DispatcherDetails = typeof(null)) {// if(is(typeof(dispatchHandler("str", Cgi.init, void) == bool))) { // bool delegate(string urlPrefix, Cgi cgi) dispatchHandler;
9504 	alias handler = dispatchHandler;
9505 	string urlPrefix;
9506 	bool rejectFurther;
9507 	immutable(DispatcherDetails) details;
9508 }
9509 
9510 private string urlify(string name) pure {
9511 	return beautify(name, '-', true);
9512 }
9513 
9514 private string beautify(string name, char space = ' ', bool allLowerCase = false) pure {
9515 	if(name == "id")
9516 		return allLowerCase ? name : "ID";
9517 
9518 	char[160] buffer;
9519 	int bufferIndex = 0;
9520 	bool shouldCap = true;
9521 	bool shouldSpace;
9522 	bool lastWasCap;
9523 	foreach(idx, char ch; name) {
9524 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
9525 
9526 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
9527 			if(lastWasCap) {
9528 				// two caps in a row, don't change. Prolly acronym.
9529 			} else {
9530 				if(idx)
9531 					shouldSpace = true; // new word, add space
9532 			}
9533 
9534 			lastWasCap = true;
9535 		} else {
9536 			lastWasCap = false;
9537 		}
9538 
9539 		if(shouldSpace) {
9540 			buffer[bufferIndex++] = space;
9541 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
9542 			shouldSpace = false;
9543 		}
9544 		if(shouldCap) {
9545 			if(ch >= 'a' && ch <= 'z')
9546 				ch -= 32;
9547 			shouldCap = false;
9548 		}
9549 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
9550 			ch += 32;
9551 		buffer[bufferIndex++] = ch;
9552 	}
9553 	return buffer[0 .. bufferIndex].idup;
9554 }
9555 
9556 /*
9557 string urlFor(alias func)() {
9558 	return __traits(identifier, func);
9559 }
9560 */
9561 
9562 /++
9563 	UDA: The name displayed to the user in auto-generated HTML.
9564 
9565 	Default is `beautify(identifier)`.
9566 +/
9567 struct DisplayName {
9568 	string name;
9569 }
9570 
9571 /++
9572 	UDA: The name used in the URL or web parameter.
9573 
9574 	Default is `urlify(identifier)` for functions and `identifier` for parameters and data members.
9575 +/
9576 struct UrlName {
9577 	string name;
9578 }
9579 
9580 /++
9581 	UDA: default format to respond for this method
9582 +/
9583 struct DefaultFormat { string value; }
9584 
9585 class MissingArgumentException : Exception {
9586 	string functionName;
9587 	string argumentName;
9588 	string argumentType;
9589 
9590 	this(string functionName, string argumentName, string argumentType, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
9591 		this.functionName = functionName;
9592 		this.argumentName = argumentName;
9593 		this.argumentType = argumentType;
9594 
9595 		super("Missing Argument: " ~ this.argumentName, file, line, next);
9596 	}
9597 }
9598 
9599 /++
9600 	You can throw this from an api handler to indicate a 404 response. This is done by the presentExceptionAsHtml function in the presenter.
9601 
9602 	History:
9603 		Added December 15, 2021 (dub v10.5)
9604 +/
9605 class ResourceNotFoundException : Exception {
9606 	string resourceType;
9607 	string resourceId;
9608 
9609 	this(string resourceType, string resourceId, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
9610 		this.resourceType = resourceType;
9611 		this.resourceId = resourceId;
9612 
9613 		super("Resource not found: " ~ resourceType ~ " " ~ resourceId, file, line, next);
9614 	}
9615 
9616 }
9617 
9618 /++
9619 	This can be attached to any constructor or function called from the cgi system.
9620 
9621 	If it is present, the function argument can NOT be set from web params, but instead
9622 	is set to the return value of the given `func`.
9623 
9624 	If `func` can take a parameter of type [Cgi], it will be passed the one representing
9625 	the current request. Otherwise, it must take zero arguments.
9626 
9627 	Any params in your function of type `Cgi` are automatically assumed to take the cgi object
9628 	for the connection. Any of type [Session] (with an argument) is	also assumed to come from
9629 	the cgi object.
9630 
9631 	const arguments are also supported.
9632 +/
9633 struct ifCalledFromWeb(alias func) {}
9634 
9635 // it only looks at query params for GET requests, the rest must be in the body for a function argument.
9636 auto callFromCgi(alias method, T)(T dg, Cgi cgi) {
9637 
9638 	// FIXME: any array of structs should also be settable or gettable from csv as well.
9639 
9640 	// FIXME: think more about checkboxes and bools.
9641 
9642 	import std.traits;
9643 
9644 	Parameters!method params;
9645 	alias idents = ParameterIdentifierTuple!method;
9646 	alias defaults = ParameterDefaults!method;
9647 
9648 	const(string)[] names;
9649 	const(string)[] values;
9650 
9651 	// first, check for missing arguments and initialize to defaults if necessary
9652 
9653 	static if(is(typeof(method) P == __parameters))
9654 	foreach(idx, param; P) {{
9655 		// see: mustNotBeSetFromWebParams
9656 		static if(is(param : Cgi)) {
9657 			static assert(!is(param == immutable));
9658 			cast() params[idx] = cgi;
9659 		} else static if(is(param == Session!D, D)) {
9660 			static assert(!is(param == immutable));
9661 			cast() params[idx] = cgi.getSessionObject!D();
9662 		} else {
9663 			bool populated;
9664 			foreach(uda; __traits(getAttributes, P[idx .. idx + 1])) {
9665 				static if(is(uda == ifCalledFromWeb!func, alias func)) {
9666 					static if(is(typeof(func(cgi))))
9667 						params[idx] = func(cgi);
9668 					else
9669 						params[idx] = func();
9670 
9671 					populated = true;
9672 				}
9673 			}
9674 
9675 			if(!populated) {
9676 				static if(__traits(compiles, { params[idx] = param.getAutomaticallyForCgi(cgi); } )) {
9677 					params[idx] = param.getAutomaticallyForCgi(cgi);
9678 					populated = true;
9679 				}
9680 			}
9681 
9682 			if(!populated) {
9683 				auto ident = idents[idx];
9684 				if(cgi.requestMethod == Cgi.RequestMethod.GET) {
9685 					if(ident !in cgi.get) {
9686 						static if(is(defaults[idx] == void)) {
9687 							static if(is(param == bool))
9688 								params[idx] = false;
9689 							else
9690 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
9691 						} else
9692 							params[idx] = defaults[idx];
9693 					}
9694 				} else {
9695 					if(ident !in cgi.post) {
9696 						static if(is(defaults[idx] == void)) {
9697 							static if(is(param == bool))
9698 								params[idx] = false;
9699 							else
9700 								throw new MissingArgumentException(__traits(identifier, method), ident, param.stringof);
9701 						} else
9702 							params[idx] = defaults[idx];
9703 					}
9704 				}
9705 			}
9706 		}
9707 	}}
9708 
9709 	// second, parse the arguments in order to build up arrays, etc.
9710 
9711 	static bool setVariable(T)(string name, string paramName, T* what, string value) {
9712 		static if(is(T == struct)) {
9713 			if(name == paramName) {
9714 				*what = T.init;
9715 				return true;
9716 			} else {
9717 				// could be a child. gonna allow either obj.field OR obj[field]
9718 
9719 				string afterName;
9720 
9721 				if(name[paramName.length] == '[') {
9722 					int count = 1;
9723 					auto idx = paramName.length + 1;
9724 					while(idx < name.length && count > 0) {
9725 						if(name[idx] == '[')
9726 							count++;
9727 						else if(name[idx] == ']') {
9728 							count--;
9729 							if(count == 0) break;
9730 						}
9731 						idx++;
9732 					}
9733 
9734 					if(idx == name.length)
9735 						return false; // malformed
9736 
9737 					auto insideBrackets = name[paramName.length + 1 .. idx];
9738 					afterName = name[idx + 1 .. $];
9739 
9740 					name = name[0 .. paramName.length];
9741 
9742 					paramName = insideBrackets;
9743 
9744 				} else if(name[paramName.length] == '.') {
9745 					paramName = name[paramName.length + 1 .. $];
9746 					name = paramName;
9747 					int p = 0;
9748 					foreach(ch; paramName) {
9749 						if(ch == '.' || ch == '[')
9750 							break;
9751 						p++;
9752 					}
9753 
9754 					afterName = paramName[p .. $];
9755 					paramName = paramName[0 .. p];
9756 				} else {
9757 					return false;
9758 				}
9759 
9760 				if(paramName.length)
9761 				// set the child member
9762 				switch(paramName) {
9763 					foreach(idx, memberName; __traits(allMembers, T))
9764 					static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
9765 						// data member!
9766 						case memberName:
9767 							return setVariable(name ~ afterName, paramName, &(__traits(getMember, *what, memberName)), value);
9768 					}
9769 					default:
9770 						// ok, not a member
9771 				}
9772 			}
9773 
9774 			return false;
9775 		} else static if(is(T == enum)) {
9776 			*what = to!T(value);
9777 			return true;
9778 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
9779 			*what = to!T(value);
9780 			return true;
9781 		} else static if(is(T == bool)) {
9782 			*what = value == "1" || value == "yes" || value == "t" || value == "true" || value == "on";
9783 			return true;
9784 		} else static if(is(T == K[], K)) {
9785 			K tmp;
9786 			if(name == paramName) {
9787 				// direct - set and append
9788 				if(setVariable(name, paramName, &tmp, value)) {
9789 					(*what) ~= tmp;
9790 					return true;
9791 				} else {
9792 					return false;
9793 				}
9794 			} else {
9795 				// child, append to last element
9796 				// FIXME: what about range violations???
9797 				auto ptr = &(*what)[(*what).length - 1];
9798 				return setVariable(name, paramName, ptr, value);
9799 
9800 			}
9801 		} else static if(is(T == V[K], K, V)) {
9802 			// assoc array, name[key] is valid
9803 			if(name == paramName) {
9804 				// no action necessary
9805 				return true;
9806 			} else if(name[paramName.length] == '[') {
9807 				int count = 1;
9808 				auto idx = paramName.length + 1;
9809 				while(idx < name.length && count > 0) {
9810 					if(name[idx] == '[')
9811 						count++;
9812 					else if(name[idx] == ']') {
9813 						count--;
9814 						if(count == 0) break;
9815 					}
9816 					idx++;
9817 				}
9818 				if(idx == name.length)
9819 					return false; // malformed
9820 
9821 				auto insideBrackets = name[paramName.length + 1 .. idx];
9822 				auto afterName = name[idx + 1 .. $];
9823 
9824 				auto k = to!K(insideBrackets);
9825 				V v;
9826 				if(auto ptr = k in *what)
9827 					v = *ptr;
9828 
9829 				name = name[0 .. paramName.length];
9830 				//writeln(name, afterName, " ", paramName);
9831 
9832 				auto ret = setVariable(name ~ afterName, paramName, &v, value);
9833 				if(ret) {
9834 					(*what)[k] = v;
9835 					return true;
9836 				}
9837 			}
9838 
9839 			return false;
9840 		} else {
9841 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
9842 		}
9843 
9844 		//return false;
9845 	}
9846 
9847 	void setArgument(string name, string value) {
9848 		int p;
9849 		foreach(ch; name) {
9850 			if(ch == '.' || ch == '[')
9851 				break;
9852 			p++;
9853 		}
9854 
9855 		auto paramName = name[0 .. p];
9856 
9857 		sw: switch(paramName) {
9858 			static if(is(typeof(method) P == __parameters))
9859 			foreach(idx, param; P) {
9860 				static if(mustNotBeSetFromWebParams!(P[idx], __traits(getAttributes, P[idx .. idx + 1]))) {
9861 					// cannot be set from the outside
9862 				} else {
9863 					case idents[idx]:
9864 						static if(is(param == Cgi.UploadedFile)) {
9865 							params[idx] = cgi.files[name];
9866 						} else static if(is(param : const Cgi.UploadedFile[])) {
9867 							(cast() params[idx]) = cgi.filesArray[name];
9868 						} else {
9869 							setVariable(name, paramName, &params[idx], value);
9870 						}
9871 					break sw;
9872 				}
9873 			}
9874 			default:
9875 				// ignore; not relevant argument
9876 		}
9877 	}
9878 
9879 	if(cgi.requestMethod == Cgi.RequestMethod.GET) {
9880 		names = cgi.allGetNamesInOrder;
9881 		values = cgi.allGetValuesInOrder;
9882 	} else {
9883 		names = cgi.allPostNamesInOrder;
9884 		values = cgi.allPostValuesInOrder;
9885 	}
9886 
9887 	foreach(idx, name; names) {
9888 		setArgument(name, values[idx]);
9889 	}
9890 
9891 	static if(is(ReturnType!method == void)) {
9892 		typeof(null) ret;
9893 		dg(params);
9894 	} else {
9895 		auto ret = dg(params);
9896 	}
9897 
9898 	// FIXME: format return values
9899 	// options are: json, html, csv.
9900 	// also may need to wrap in envelope format: none, html, or json.
9901 	return ret;
9902 }
9903 
9904 private bool mustNotBeSetFromWebParams(T, attrs...)() {
9905 	static if(is(T : const(Cgi))) {
9906 		return true;
9907 	} else static if(is(T : const(Session!D), D)) {
9908 		return true;
9909 	} else static if(__traits(compiles, T.getAutomaticallyForCgi(Cgi.init))) {
9910 		return true;
9911 	} else {
9912 		foreach(uda; attrs)
9913 			static if(is(uda == ifCalledFromWeb!func, alias func))
9914 				return true;
9915 		return false;
9916 	}
9917 }
9918 
9919 private bool hasIfCalledFromWeb(attrs...)() {
9920 	foreach(uda; attrs)
9921 		static if(is(uda == ifCalledFromWeb!func, alias func))
9922 			return true;
9923 	return false;
9924 }
9925 
9926 /++
9927 	Implies POST path for the thing itself, then GET will get the automatic form.
9928 
9929 	The given customizer, if present, will be called as a filter on the Form object.
9930 
9931 	History:
9932 		Added December 27, 2020
9933 +/
9934 template AutomaticForm(alias customizer) { }
9935 
9936 /++
9937 	This is meant to be returned by a function that takes a form POST submission. You
9938 	want to set the url of the new resource it created, which is set as the http
9939 	Location header for a "201 Created" result, and you can also set a separate
9940 	destination for browser users, which it sets via a "Refresh" header.
9941 
9942 	The `resourceRepresentation` should generally be the thing you just created, and
9943 	it will be the body of the http response when formatted through the presenter.
9944 	The exact thing is up to you - it could just return an id, or the whole object, or
9945 	perhaps a partial object.
9946 
9947 	Examples:
9948 	---
9949 	class Test : WebObject {
9950 		@(Cgi.RequestMethod.POST)
9951 		CreatedResource!int makeThing(string value) {
9952 			return CreatedResource!int(value.to!int, "/resources/id");
9953 		}
9954 	}
9955 	---
9956 
9957 	History:
9958 		Added December 18, 2021
9959 +/
9960 struct CreatedResource(T) {
9961 	static if(!is(T == void))
9962 		T resourceRepresentation;
9963 	string resourceUrl;
9964 	string refreshUrl;
9965 }
9966 
9967 /+
9968 /++
9969 	This can be attached as a UDA to a handler to add a http Refresh header on a
9970 	successful run. (It will not be attached if the function throws an exception.)
9971 	This will refresh the browser the given number of seconds after the page loads,
9972 	to the url returned by `urlFunc`, which can be either a static function or a
9973 	member method of the current handler object.
9974 
9975 	You might use this for a POST handler that is normally used from ajax, but you
9976 	want it to degrade gracefully to a temporarily flashed message before reloading
9977 	the main page.
9978 
9979 	History:
9980 		Added December 18, 2021
9981 +/
9982 struct Refresh(alias urlFunc) {
9983 	int waitInSeconds;
9984 
9985 	string url() {
9986 		static if(__traits(isStaticFunction, urlFunc))
9987 			return urlFunc();
9988 		else static if(is(urlFunc : string))
9989 			return urlFunc;
9990 	}
9991 }
9992 +/
9993 
9994 /+
9995 /++
9996 	Sets a filter to be run before
9997 
9998 	A before function can do validations of params and log and stop the function from running.
9999 +/
10000 template Before(alias b) {}
10001 template After(alias b) {}
10002 +/
10003 
10004 /+
10005 	Argument conversions: for the most part, it is to!Thing(string).
10006 
10007 	But arrays and structs are a bit different. Arrays come from the cgi array. Thus
10008 	they are passed
10009 
10010 	arr=foo&arr=bar <-- notice the same name.
10011 
10012 	Structs are first declared with an empty thing, then have their members set individually,
10013 	with dot notation. The members are not required, just the initial declaration.
10014 
10015 	struct Foo {
10016 		int a;
10017 		string b;
10018 	}
10019 	void test(Foo foo){}
10020 
10021 	foo&foo.a=5&foo.b=str <-- the first foo declares the arg, the others set the members
10022 
10023 	Arrays of structs use this declaration.
10024 
10025 	void test(Foo[] foo) {}
10026 
10027 	foo&foo.a=5&foo.b=bar&foo&foo.a=9
10028 
10029 	You can use a hidden input field in HTML forms to achieve this. The value of the naked name
10030 	declaration is ignored.
10031 
10032 	Mind that order matters! The declaration MUST come first in the string.
10033 
10034 	Arrays of struct members follow this rule recursively.
10035 
10036 	struct Foo {
10037 		int[] a;
10038 	}
10039 
10040 	foo&foo.a=1&foo.a=2&foo&foo.a=1
10041 
10042 
10043 	Associative arrays are formatted with brackets, after a declaration, like structs:
10044 
10045 	foo&foo[key]=value&foo[other_key]=value
10046 
10047 
10048 	Note: for maximum compatibility with outside code, keep your types simple. Some libraries
10049 	do not support the strict ordering requirements to work with these struct protocols.
10050 
10051 	FIXME: also perhaps accept application/json to better work with outside trash.
10052 
10053 
10054 	Return values are also auto-formatted according to user-requested type:
10055 		for json, it loops over and converts.
10056 		for html, basic types are strings. Arrays are <ol>. Structs are <dl>. Arrays of structs are tables!
10057 +/
10058 
10059 /++
10060 	A web presenter is responsible for rendering things to HTML to be usable
10061 	in a web browser.
10062 
10063 	They are passed as template arguments to the base classes of [WebObject]
10064 
10065 	Responsible for displaying stuff as HTML. You can put this into your own aggregate
10066 	and override it. Use forwarding and specialization to customize it.
10067 
10068 	When you inherit from it, pass your own class as the CRTP argument. This lets the base
10069 	class templates and your overridden templates work with each other.
10070 
10071 	---
10072 	class MyPresenter : WebPresenter!(MyPresenter) {
10073 		@Override
10074 		void presentSuccessfulReturnAsHtml(T : CustomType)(Cgi cgi, T ret, typeof(null) meta) {
10075 			// present the CustomType
10076 		}
10077 		@Override
10078 		void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
10079 			// handle everything else via the super class, which will call
10080 			// back to your class when appropriate
10081 			super.presentSuccessfulReturnAsHtml(cgi, ret);
10082 		}
10083 	}
10084 	---
10085 
10086 	The meta argument in there can be overridden by your own facility.
10087 
10088 +/
10089 class WebPresenter(CRTP) {
10090 
10091 	/// A UDA version of the built-in `override`, to be used for static template polymorphism
10092 	/// If you override a plain method, use `override`. If a template, use `@Override`.
10093 	enum Override;
10094 
10095 	string script() {
10096 		return `
10097 		`;
10098 	}
10099 
10100 	string style() {
10101 		return `
10102 			:root {
10103 				--mild-border: #ccc;
10104 				--middle-border: #999;
10105 				--accent-color: #f2f2f2;
10106 				--sidebar-color: #fefefe;
10107 			}
10108 		` ~ genericFormStyling() ~ genericSiteStyling();
10109 	}
10110 
10111 	string genericFormStyling() {
10112 		return
10113 q"css
10114 			table.automatic-data-display {
10115 				border-collapse: collapse;
10116 				border: solid 1px var(--mild-border);
10117 			}
10118 
10119 			table.automatic-data-display td {
10120 				vertical-align: top;
10121 				border: solid 1px var(--mild-border);
10122 				padding: 2px 4px;
10123 			}
10124 
10125 			table.automatic-data-display th {
10126 				border: solid 1px var(--mild-border);
10127 				border-bottom: solid 1px var(--middle-border);
10128 				padding: 2px 4px;
10129 			}
10130 
10131 			ol.automatic-data-display {
10132 				margin: 0px;
10133 				/*
10134 				list-style-position: inside;
10135 				padding: 0px;
10136 				*/
10137 			}
10138 
10139 			dl.automatic-data-display {
10140 
10141 			}
10142 
10143 			.automatic-form {
10144 				max-width: 600px;
10145 			}
10146 
10147 			.form-field {
10148 				margin: 0.5em;
10149 				padding-left: 0.5em;
10150 			}
10151 
10152 			.label-text {
10153 				display: block;
10154 				font-weight: bold;
10155 				margin-left: -0.5em;
10156 			}
10157 
10158 			.submit-button-holder {
10159 				padding-left: 2em;
10160 			}
10161 
10162 			.add-array-button {
10163 
10164 			}
10165 css";
10166 	}
10167 
10168 	string genericSiteStyling() {
10169 		return
10170 q"css
10171 			* { box-sizing: border-box; }
10172 			html, body { margin: 0px; }
10173 			body {
10174 				font-family: sans-serif;
10175 			}
10176 			header {
10177 				background: var(--accent-color);
10178 				height: 64px;
10179 			}
10180 			footer {
10181 				background: var(--accent-color);
10182 				height: 64px;
10183 			}
10184 			#site-container {
10185 				display: flex;
10186 				flex-wrap: wrap;
10187 			}
10188 			main {
10189 				flex: 1 1 auto;
10190 				order: 2;
10191 				min-height: calc(100vh - 64px - 64px);
10192 				min-width: 80ch;
10193 				padding: 4px;
10194 				padding-left: 1em;
10195 			}
10196 			#sidebar {
10197 				flex: 0 0 16em;
10198 				order: 1;
10199 				background: var(--sidebar-color);
10200 			}
10201 css";
10202 	}
10203 
10204 	import arsd.dom;
10205 	Element htmlContainer() {
10206 		auto document = new Document(q"html
10207 <!DOCTYPE html>
10208 <html class="no-script">
10209 <head>
10210 	<script>document.documentElement.classList.remove("no-script");</script>
10211 	<style>.no-script requires-script { display: none; }</style>
10212 	<title>D Application</title>
10213 	<meta name="viewport" content="initial-scale=1, width=device-width" />
10214 	<link rel="stylesheet" href="style.css" />
10215 </head>
10216 <body>
10217 	<header></header>
10218 	<div id="site-container">
10219 		<main></main>
10220 		<div id="sidebar"></div>
10221 	</div>
10222 	<footer></footer>
10223 	<script src="script.js"></script>
10224 </body>
10225 </html>
10226 html", true, true);
10227 
10228 		return document.requireSelector("main");
10229 	}
10230 
10231 	/// Renders a response as an HTTP error with associated html body
10232 	void renderBasicError(Cgi cgi, int httpErrorCode) {
10233 		cgi.setResponseStatus(getHttpCodeText(httpErrorCode));
10234 		auto c = htmlContainer();
10235 		c.innerText = getHttpCodeText(httpErrorCode);
10236 		cgi.setResponseContentType("text/html; charset=utf-8");
10237 		cgi.write(c.parentDocument.toString(), true);
10238 	}
10239 
10240 	template methodMeta(alias method) {
10241 		enum methodMeta = null;
10242 	}
10243 
10244 	void presentSuccessfulReturn(T, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10245 		switch(format) {
10246 			case "html":
10247 				(cast(CRTP) this).presentSuccessfulReturnAsHtml(cgi, ret, meta);
10248 			break;
10249 			case "json":
10250 				import arsd.jsvar;
10251 				static if(is(typeof(ret) == MultipleResponses!Types, Types...)) {
10252 					var json;
10253 					foreach(index, type; Types) {
10254 						if(ret.contains == index)
10255 							json = ret.payload[index];
10256 					}
10257 				} else {
10258 					var json = ret;
10259 				}
10260 				var envelope = json; // var.emptyObject;
10261 				/*
10262 				envelope.success = true;
10263 				envelope.result = json;
10264 				envelope.error = null;
10265 				*/
10266 				cgi.setResponseContentType("application/json");
10267 				cgi.write(envelope.toJson(), true);
10268 			break;
10269 			default:
10270 				cgi.setResponseStatus("406 Not Acceptable"); // not exactly but sort of.
10271 		}
10272 	}
10273 
10274 	/// typeof(null) (which is also used to represent functions returning `void`) do nothing
10275 	/// in the default presenter - allowing the function to have full low-level control over the
10276 	/// response.
10277 	void presentSuccessfulReturn(T : typeof(null), Meta)(Cgi cgi, T ret, Meta meta, string format) {
10278 		// nothing intentionally!
10279 	}
10280 
10281 	/// Redirections are forwarded to [Cgi.setResponseLocation]
10282 	void presentSuccessfulReturn(T : Redirection, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10283 		cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
10284 	}
10285 
10286 	/// [CreatedResource]s send code 201 and will set the given urls, then present the given representation.
10287 	void presentSuccessfulReturn(T : CreatedResource!R, Meta, R)(Cgi cgi, T ret, Meta meta, string format) {
10288 		cgi.setResponseStatus(getHttpCodeText(201));
10289 		if(ret.resourceUrl.length)
10290 			cgi.header("Location: " ~ ret.resourceUrl);
10291 		if(ret.refreshUrl.length)
10292 			cgi.header("Refresh: 0;" ~ ret.refreshUrl);
10293 		static if(!is(R == void))
10294 			presentSuccessfulReturn(cgi, ret.resourceRepresentation, meta, format);
10295 	}
10296 
10297 	/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
10298 	void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
10299 		bool outputted = false;
10300 		foreach(index, type; Types) {
10301 			if(ret.contains == index) {
10302 				assert(!outputted);
10303 				outputted = true;
10304 				(cast(CRTP) this).presentSuccessfulReturn(cgi, ret.payload[index], meta, format);
10305 			}
10306 		}
10307 		if(!outputted)
10308 			assert(0);
10309 	}
10310 
10311 	/++
10312 		An instance of the [arsd.dom.FileResource] interface has its own content type; assume it is a download of some sort if the filename member is non-null of the FileResource interface.
10313 	+/
10314 	void presentSuccessfulReturn(T : FileResource, Meta)(Cgi cgi, T ret, Meta meta, string format) {
10315 		cgi.setCache(true); // not necessarily true but meh
10316 		if(auto fn = ret.filename()) {
10317 			cgi.header("Content-Disposition: attachment; filename="~fn~";");
10318 		}
10319 		cgi.setResponseContentType(ret.contentType);
10320 		cgi.write(ret.getData(), true);
10321 	}
10322 
10323 	/// And the default handler for HTML will call [formatReturnValueAsHtml] and place it inside the [htmlContainer].
10324 	void presentSuccessfulReturnAsHtml(T)(Cgi cgi, T ret, typeof(null) meta) {
10325 		auto container = this.htmlContainer();
10326 		container.appendChild(formatReturnValueAsHtml(ret));
10327 		cgi.write(container.parentDocument.toString(), true);
10328 	}
10329 
10330 	/++
10331 
10332 		History:
10333 			Added January 23, 2023 (dub v11.0)
10334 	+/
10335 	void presentExceptionalReturn(Meta)(Cgi cgi, Throwable t, Meta meta, string format) {
10336 		switch(format) {
10337 			case "html":
10338 				presentExceptionAsHtml(cgi, t, meta);
10339 			break;
10340 			case "json":
10341 				presentExceptionAsJsonImpl(cgi, t);
10342 			break;
10343 			default:
10344 		}
10345 	}
10346 
10347 	private void presentExceptionAsJsonImpl()(Cgi cgi, Throwable t) {
10348 		cgi.setResponseStatus("500 Internal Server Error");
10349 		cgi.setResponseContentType("application/json");
10350 		import arsd.jsvar;
10351 		var v = var.emptyObject;
10352 		v.type = typeid(t).toString;
10353 		v.msg = t.msg;
10354 		v.fullString = t.toString();
10355 		cgi.write(v.toJson(), true);
10356 	}
10357 
10358 
10359 	/++
10360 		If you override this, you will need to cast the exception type `t` dynamically,
10361 		but can then use the template arguments here to refer back to the function.
10362 
10363 		`func` is an alias to the method itself, and `dg` is a callable delegate to the same
10364 		method on the live object. You could, in theory, change arguments and retry, but I
10365 		provide that information mostly with the expectation that you will use them to make
10366 		useful forms or richer error messages for the user.
10367 
10368 		History:
10369 			BREAKING CHANGE on January 23, 2023 (v11.0 ): it previously took an `alias func` and `T dg` to call the function again.
10370 			I removed this in favor of a `Meta` param.
10371 
10372 			Before: `void presentExceptionAsHtml(alias func, T)(Cgi cgi, Throwable t, T dg)`
10373 
10374 			After: `void presentExceptionAsHtml(Meta)(Cgi cgi, Throwable t, Meta meta)`
10375 
10376 			If you used the func for something, move that something into your `methodMeta` template.
10377 
10378 			What is the benefit of this change? Somewhat smaller executables and faster builds thanks to more reused functions, together with
10379 			enabling an easier implementation of [presentExceptionalReturn].
10380 	+/
10381 	void presentExceptionAsHtml(Meta)(Cgi cgi, Throwable t, Meta meta) {
10382 		Form af;
10383 		/+
10384 		foreach(attr; __traits(getAttributes, func)) {
10385 			static if(__traits(isSame, attr, AutomaticForm)) {
10386 				af = createAutomaticFormForFunction!(func)(dg);
10387 			}
10388 		}
10389 		+/
10390 		presentExceptionAsHtmlImpl(cgi, t, af);
10391 	}
10392 
10393 	void presentExceptionAsHtmlImpl(Cgi cgi, Throwable t, Form automaticForm) {
10394 		if(auto e = cast(ResourceNotFoundException) t) {
10395 			auto container = this.htmlContainer();
10396 
10397 			container.addChild("p", e.msg);
10398 
10399 			if(!cgi.outputtedResponseData)
10400 				cgi.setResponseStatus("404 Not Found");
10401 			cgi.write(container.parentDocument.toString(), true);
10402 		} else if(auto mae = cast(MissingArgumentException) t) {
10403 			if(automaticForm is null)
10404 				goto generic;
10405 			auto container = this.htmlContainer();
10406 			if(cgi.requestMethod == Cgi.RequestMethod.POST)
10407 				container.appendChild(Element.make("p", "Argument `" ~ mae.argumentName ~ "` of type `" ~ mae.argumentType ~ "` is missing"));
10408 			container.appendChild(automaticForm);
10409 
10410 			cgi.write(container.parentDocument.toString(), true);
10411 		} else {
10412 			generic:
10413 			auto container = this.htmlContainer();
10414 
10415 			// import std.stdio; writeln(t.toString());
10416 
10417 			container.appendChild(exceptionToElement(t));
10418 
10419 			container.addChild("h4", "GET");
10420 			foreach(k, v; cgi.get) {
10421 				auto deets = container.addChild("details");
10422 				deets.addChild("summary", k);
10423 				deets.addChild("div", v);
10424 			}
10425 
10426 			container.addChild("h4", "POST");
10427 			foreach(k, v; cgi.post) {
10428 				auto deets = container.addChild("details");
10429 				deets.addChild("summary", k);
10430 				deets.addChild("div", v);
10431 			}
10432 
10433 
10434 			if(!cgi.outputtedResponseData)
10435 				cgi.setResponseStatus("500 Internal Server Error");
10436 			cgi.write(container.parentDocument.toString(), true);
10437 		}
10438 	}
10439 
10440 	Element exceptionToElement(Throwable t) {
10441 		auto div = Element.make("div");
10442 		div.addClass("exception-display");
10443 
10444 		div.addChild("p", t.msg);
10445 		div.addChild("p", "Inner code origin: " ~ typeid(t).name ~ "@" ~ t.file ~ ":" ~ to!string(t.line));
10446 
10447 		auto pre = div.addChild("pre");
10448 		string s;
10449 		s = t.toString();
10450 		Element currentBox;
10451 		bool on = false;
10452 		foreach(line; s.splitLines) {
10453 			if(!on && line.startsWith("-----"))
10454 				on = true;
10455 			if(!on) continue;
10456 			if(line.indexOf("arsd/") != -1) {
10457 				if(currentBox is null) {
10458 					currentBox = pre.addChild("details");
10459 					currentBox.addChild("summary", "Framework code");
10460 				}
10461 				currentBox.addChild("span", line ~ "\n");
10462 			} else {
10463 				pre.addChild("span", line ~ "\n");
10464 				currentBox = null;
10465 			}
10466 		}
10467 
10468 		return div;
10469 	}
10470 
10471 	/++
10472 		Returns an element for a particular type
10473 	+/
10474 	Element elementFor(T)(string displayName, string name, Element function() udaSuggestion) {
10475 		import std.traits;
10476 
10477 		auto div = Element.make("div");
10478 		div.addClass("form-field");
10479 
10480 		static if(is(T : const Cgi.UploadedFile)) {
10481 			Element lbl;
10482 			if(displayName !is null) {
10483 				lbl = div.addChild("label");
10484 				lbl.addChild("span", displayName, "label-text");
10485 				lbl.appendText(" ");
10486 			} else {
10487 				lbl = div;
10488 			}
10489 			auto i = lbl.addChild("input", name);
10490 			i.attrs.name = name;
10491 			i.attrs.type = "file";
10492 			i.attrs.multiple = "multiple";
10493 		} else static if(is(T == Cgi.UploadedFile)) {
10494 			Element lbl;
10495 			if(displayName !is null) {
10496 				lbl = div.addChild("label");
10497 				lbl.addChild("span", displayName, "label-text");
10498 				lbl.appendText(" ");
10499 			} else {
10500 				lbl = div;
10501 			}
10502 			auto i = lbl.addChild("input", name);
10503 			i.attrs.name = name;
10504 			i.attrs.type = "file";
10505 		} else static if(is(T == enum)) {
10506 			Element lbl;
10507 			if(displayName !is null) {
10508 				lbl = div.addChild("label");
10509 				lbl.addChild("span", displayName, "label-text");
10510 				lbl.appendText(" ");
10511 			} else {
10512 				lbl = div;
10513 			}
10514 			auto i = lbl.addChild("select", name);
10515 			i.attrs.name = name;
10516 
10517 			foreach(memberName; __traits(allMembers, T))
10518 				i.addChild("option", memberName);
10519 
10520 		} else static if(is(T == struct)) {
10521 			if(displayName !is null)
10522 				div.addChild("span", displayName, "label-text");
10523 			auto fieldset = div.addChild("fieldset");
10524 			fieldset.addChild("legend", beautify(T.stringof)); // FIXME
10525 			fieldset.addChild("input", name);
10526 			foreach(idx, memberName; __traits(allMembers, T))
10527 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
10528 				fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName, null /* FIXME: pull off the UDA */));
10529 			}
10530 		} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
10531 			Element lbl;
10532 			if(displayName !is null) {
10533 				lbl = div.addChild("label");
10534 				lbl.addChild("span", displayName, "label-text");
10535 				lbl.appendText(" ");
10536 			} else {
10537 				lbl = div;
10538 			}
10539 			Element i;
10540 			if(udaSuggestion) {
10541 				i = udaSuggestion();
10542 				lbl.appendChild(i);
10543 			} else {
10544 				i = lbl.addChild("input", name);
10545 			}
10546 			i.attrs.name = name;
10547 			static if(isSomeString!T)
10548 				i.attrs.type = "text";
10549 			else
10550 				i.attrs.type = "number";
10551 			if(i.tagName == "textarea")
10552 				i.textContent = to!string(T.init);
10553 			else
10554 				i.attrs.value = to!string(T.init);
10555 		} else static if(is(T == bool)) {
10556 			Element lbl;
10557 			if(displayName !is null) {
10558 				lbl = div.addChild("label");
10559 				lbl.addChild("span", displayName, "label-text");
10560 				lbl.appendText(" ");
10561 			} else {
10562 				lbl = div;
10563 			}
10564 			auto i = lbl.addChild("input", name);
10565 			i.attrs.type = "checkbox";
10566 			i.attrs.value = "true";
10567 			i.attrs.name = name;
10568 		} else static if(is(T == K[], K)) {
10569 			auto templ = div.addChild("template");
10570 			templ.appendChild(elementFor!(K)(null, name, null /* uda??*/));
10571 			if(displayName !is null)
10572 				div.addChild("span", displayName, "label-text");
10573 			auto btn = div.addChild("button");
10574 			btn.addClass("add-array-button");
10575 			btn.attrs.type = "button";
10576 			btn.innerText = "Add";
10577 			btn.attrs.onclick = q{
10578 				var a = document.importNode(this.parentNode.firstChild.content, true);
10579 				this.parentNode.insertBefore(a, this);
10580 			};
10581 		} else static if(is(T == V[K], K, V)) {
10582 			div.innerText = "assoc array not implemented for automatic form at this time";
10583 		} else {
10584 			static assert(0, "unsupported type for cgi call " ~ T.stringof);
10585 		}
10586 
10587 
10588 		return div;
10589 	}
10590 
10591 	/// creates a form for gathering the function's arguments
10592 	Form createAutomaticFormForFunction(alias method, T)(T dg) {
10593 
10594 		auto form = cast(Form) Element.make("form");
10595 
10596 		form.method = "POST"; // FIXME
10597 
10598 		form.addClass("automatic-form");
10599 
10600 		string formDisplayName = beautify(__traits(identifier, method));
10601 		foreach(attr; __traits(getAttributes, method))
10602 			static if(is(typeof(attr) == DisplayName))
10603 				formDisplayName = attr.name;
10604 		form.addChild("h3", formDisplayName);
10605 
10606 		import std.traits;
10607 
10608 		//Parameters!method params;
10609 		//alias idents = ParameterIdentifierTuple!method;
10610 		//alias defaults = ParameterDefaults!method;
10611 
10612 		static if(is(typeof(method) P == __parameters))
10613 		foreach(idx, _; P) {{
10614 
10615 			alias param = P[idx .. idx + 1];
10616 
10617 			static if(!mustNotBeSetFromWebParams!(param[0], __traits(getAttributes, param))) {
10618 				string displayName = beautify(__traits(identifier, param));
10619 				Element function() element;
10620 				foreach(attr; __traits(getAttributes, param)) {
10621 					static if(is(typeof(attr) == DisplayName))
10622 						displayName = attr.name;
10623 					else static if(is(typeof(attr) : typeof(element))) {
10624 						element = attr;
10625 					}
10626 				}
10627 				auto i = form.appendChild(elementFor!(param)(displayName, __traits(identifier, param), element));
10628 				if(i.querySelector("input[type=file]") !is null)
10629 					form.setAttribute("enctype", "multipart/form-data");
10630 			}
10631 		}}
10632 
10633 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
10634 
10635 		return form;
10636 	}
10637 
10638 	/// creates a form for gathering object members (for the REST object thing right now)
10639 	Form createAutomaticFormForObject(T)(T obj) {
10640 		auto form = cast(Form) Element.make("form");
10641 
10642 		form.addClass("automatic-form");
10643 
10644 		form.addChild("h3", beautify(__traits(identifier, T)));
10645 
10646 		import std.traits;
10647 
10648 		//Parameters!method params;
10649 		//alias idents = ParameterIdentifierTuple!method;
10650 		//alias defaults = ParameterDefaults!method;
10651 
10652 		foreach(idx, memberName; __traits(derivedMembers, T)) {{
10653 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
10654 			string displayName = beautify(memberName);
10655 			Element function() element;
10656 			foreach(attr; __traits(getAttributes,  __traits(getMember, T, memberName)))
10657 				static if(is(typeof(attr) == DisplayName))
10658 					displayName = attr.name;
10659 				else static if(is(typeof(attr) : typeof(element)))
10660 					element = attr;
10661 			form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName, element));
10662 
10663 			form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
10664 		}}}
10665 
10666 		form.addChild("div", Html(`<input type="submit" value="Submit" />`), "submit-button-holder");
10667 
10668 		return form;
10669 	}
10670 
10671 	///
10672 	Element formatReturnValueAsHtml(T)(T t) {
10673 		import std.traits;
10674 
10675 		static if(is(T == typeof(null))) {
10676 			return Element.make("span");
10677 		} else static if(is(T : Element)) {
10678 			return t;
10679 		} else static if(is(T == MultipleResponses!Types, Types...)) {
10680 			foreach(index, type; Types) {
10681 				if(t.contains == index)
10682 					return formatReturnValueAsHtml(t.payload[index]);
10683 			}
10684 			assert(0);
10685 		} else static if(is(T == Paginated!E, E)) {
10686 			auto e = Element.make("div").addClass("paginated-result");
10687 			e.appendChild(formatReturnValueAsHtml(t.items));
10688 			if(t.nextPageUrl.length)
10689 				e.appendChild(Element.make("a", "Next Page", t.nextPageUrl));
10690 			return e;
10691 		} else static if(isIntegral!T || isSomeString!T || isFloatingPoint!T) {
10692 			return Element.make("span", to!string(t), "automatic-data-display");
10693 		} else static if(is(T == V[K], K, V)) {
10694 			auto dl = Element.make("dl");
10695 			dl.addClass("automatic-data-display associative-array");
10696 			foreach(k, v; t) {
10697 				dl.addChild("dt", to!string(k));
10698 				dl.addChild("dd", formatReturnValueAsHtml(v));
10699 			}
10700 			return dl;
10701 		} else static if(is(T == struct)) {
10702 			auto dl = Element.make("dl");
10703 			dl.addClass("automatic-data-display struct");
10704 
10705 			foreach(idx, memberName; __traits(allMembers, T))
10706 			static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
10707 				dl.addChild("dt", beautify(memberName));
10708 				dl.addChild("dd", formatReturnValueAsHtml(__traits(getMember, t, memberName)));
10709 			}
10710 
10711 			return dl;
10712 		} else static if(is(T == bool)) {
10713 			return Element.make("span", t ? "true" : "false", "automatic-data-display");
10714 		} else static if(is(T == E[], E)) {
10715 			static if(is(E : RestObject!Proxy, Proxy)) {
10716 				// treat RestObject similar to struct
10717 				auto table = cast(Table) Element.make("table");
10718 				table.addClass("automatic-data-display");
10719 				string[] names;
10720 				foreach(idx, memberName; __traits(derivedMembers, E))
10721 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10722 					names ~= beautify(memberName);
10723 				}
10724 				table.appendHeaderRow(names);
10725 
10726 				foreach(l; t) {
10727 					auto tr = table.appendRow();
10728 					foreach(idx, memberName; __traits(derivedMembers, E))
10729 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10730 						static if(memberName == "id") {
10731 							string val = to!string(__traits(getMember, l, memberName));
10732 							tr.addChild("td", Element.make("a", val, E.stringof.toLower ~ "s/" ~ val)); // FIXME
10733 						} else {
10734 							tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
10735 						}
10736 					}
10737 				}
10738 
10739 				return table;
10740 			} else static if(is(E == struct)) {
10741 				// an array of structs is kinda special in that I like
10742 				// having those formatted as tables.
10743 				auto table = cast(Table) Element.make("table");
10744 				table.addClass("automatic-data-display");
10745 				string[] names;
10746 				foreach(idx, memberName; __traits(allMembers, E))
10747 				static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10748 					names ~= beautify(memberName);
10749 				}
10750 				table.appendHeaderRow(names);
10751 
10752 				foreach(l; t) {
10753 					auto tr = table.appendRow();
10754 					foreach(idx, memberName; __traits(allMembers, E))
10755 					static if(__traits(compiles, __traits(getMember, E, memberName).offsetof)) {
10756 						tr.addChild("td", formatReturnValueAsHtml(__traits(getMember, l, memberName)));
10757 					}
10758 				}
10759 
10760 				return table;
10761 			} else {
10762 				// otherwise, I will just make a list.
10763 				auto ol = Element.make("ol");
10764 				ol.addClass("automatic-data-display");
10765 				foreach(e; t)
10766 					ol.addChild("li", formatReturnValueAsHtml(e));
10767 				return ol;
10768 			}
10769 		} else static if(is(T : Object)) {
10770 			static if(is(typeof(t.toHtml()))) // FIXME: maybe i will make this an interface
10771 				return Element.make("div", t.toHtml());
10772 			else
10773 				return Element.make("div", t.toString());
10774 		} else static assert(0, "bad return value for cgi call " ~ T.stringof);
10775 
10776 		assert(0);
10777 	}
10778 
10779 }
10780 
10781 /++
10782 	The base class for the [dispatcher] function and object support.
10783 +/
10784 class WebObject {
10785 	//protected Cgi cgi;
10786 
10787 	protected void initialize(Cgi cgi) {
10788 		//this.cgi = cgi;
10789 	}
10790 }
10791 
10792 /++
10793 	Can return one of the given types, decided at runtime. The syntax
10794 	is to declare all the possible types in the return value, then you
10795 	can `return typeof(return)(...value...)` to construct it.
10796 
10797 	It has an auto-generated constructor for each value it can hold.
10798 
10799 	---
10800 	MultipleResponses!(Redirection, string) getData(int how) {
10801 		if(how & 1)
10802 			return typeof(return)(Redirection("http://dpldocs.info/"));
10803 		else
10804 			return typeof(return)("hi there!");
10805 	}
10806 	---
10807 
10808 	If you have lots of returns, you could, inside the function, `alias r = typeof(return);` to shorten it a little.
10809 +/
10810 struct MultipleResponses(T...) {
10811 	private size_t contains;
10812 	private union {
10813 		private T payload;
10814 	}
10815 
10816 	static foreach(index, type; T)
10817 	public this(type t) {
10818 		contains = index;
10819 		payload[index] = t;
10820 	}
10821 
10822 	/++
10823 		This is primarily for testing. It is your way of getting to the response.
10824 
10825 		Let's say you wanted to test that one holding a Redirection and a string actually
10826 		holds a string, by name of "test":
10827 
10828 		---
10829 			auto valueToTest = your_test_function();
10830 
10831 			valueToTest.visit(
10832 				(Redirection r) { assert(0); }, // got a redirection instead of a string, fail the test
10833 				(string s) { assert(s == "test"); } // right value, go ahead and test it.
10834 			);
10835 		---
10836 
10837 		History:
10838 			Was horribly broken until June 16, 2022. Ironically, I wrote it for tests but never actually tested it.
10839 			It tried to use alias lambdas before, but runtime delegates work much better so I changed it.
10840 	+/
10841 	void visit(Handlers...)(Handlers handlers) {
10842 		template findHandler(type, int count, HandlersToCheck...) {
10843 			static if(HandlersToCheck.length == 0)
10844 				enum findHandler = -1;
10845 			else {
10846 				static if(is(typeof(HandlersToCheck[0].init(type.init))))
10847 					enum findHandler = count;
10848 				else
10849 					enum findHandler = findHandler!(type, count + 1, HandlersToCheck[1 .. $]);
10850 			}
10851 		}
10852 		foreach(index, type; T) {
10853 			enum handlerIndex = findHandler!(type, 0, Handlers);
10854 			static if(handlerIndex == -1)
10855 				static assert(0, "Type " ~ type.stringof ~ " was not handled by visitor");
10856 			else {
10857 				if(index == this.contains)
10858 					handlers[handlerIndex](this.payload[index]);
10859 			}
10860 		}
10861 	}
10862 
10863 	/+
10864 	auto toArsdJsvar()() {
10865 		import arsd.jsvar;
10866 		return var(null);
10867 	}
10868 	+/
10869 }
10870 
10871 // FIXME: implement this somewhere maybe
10872 struct RawResponse {
10873 	int code;
10874 	string[] headers;
10875 	const(ubyte)[] responseBody;
10876 }
10877 
10878 /++
10879 	You can return this from [WebObject] subclasses for redirections.
10880 
10881 	(though note the static types means that class must ALWAYS redirect if
10882 	you return this directly. You might want to return [MultipleResponses] if it
10883 	can be conditional)
10884 +/
10885 struct Redirection {
10886 	string to; /// The URL to redirect to.
10887 	int code = 303; /// The HTTP code to return.
10888 }
10889 
10890 /++
10891 	Serves a class' methods, as a kind of low-state RPC over the web. To be used with [dispatcher].
10892 
10893 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar] unless you have overriden
10894 	the presenter in the dispatcher.
10895 
10896 	FIXME: explain this better
10897 
10898 	You can overload functions to a limited extent: you can provide a zero-arg and non-zero-arg function,
10899 	and non-zero-arg functions can filter via UDAs for various http methods. Do not attempt other overloads,
10900 	the runtime result of that is undefined.
10901 
10902 	A method is assumed to allow any http method unless it lists some in UDAs, in which case it is limited to only those.
10903 	(this might change, like maybe i will use pure as an indicator GET is ok. idk.)
10904 
10905 	$(WARNING
10906 		---
10907 		// legal in D, undefined runtime behavior with cgi.d, it may call either method
10908 		// even if you put different URL udas on it, the current code ignores them.
10909 		void foo(int a) {}
10910 		void foo(string a) {}
10911 		---
10912 	)
10913 
10914 	See_Also: [serveRestObject], [serveStaticFile]
10915 +/
10916 auto serveApi(T)(string urlPrefix) {
10917 	assert(urlPrefix[$ - 1] == '/');
10918 	return serveApiInternal!T(urlPrefix);
10919 }
10920 
10921 private string nextPieceFromSlash(ref string remainingUrl) {
10922 	if(remainingUrl.length == 0)
10923 		return remainingUrl;
10924 	int slash = 0;
10925 	while(slash < remainingUrl.length && remainingUrl[slash] != '/') // && remainingUrl[slash] != '.')
10926 		slash++;
10927 
10928 	// I am specifically passing `null` to differentiate it vs empty string
10929 	// so in your ctor, `items` means new T(null) and `items/` means new T("")
10930 	auto ident = remainingUrl.length == 0 ? null : remainingUrl[0 .. slash];
10931 	// so if it is the last item, the dot can be used to load an alternative view
10932 	// otherwise tho the dot is considered part of the identifier
10933 	// FIXME
10934 
10935 	// again notice "" vs null here!
10936 	if(slash == remainingUrl.length)
10937 		remainingUrl = null;
10938 	else
10939 		remainingUrl = remainingUrl[slash + 1 .. $];
10940 
10941 	return ident;
10942 }
10943 
10944 /++
10945 	UDA used to indicate to the [dispatcher] that a trailing slash should always be added to or removed from the url. It will do it as a redirect header as-needed.
10946 +/
10947 enum AddTrailingSlash;
10948 /// ditto
10949 enum RemoveTrailingSlash;
10950 
10951 private auto serveApiInternal(T)(string urlPrefix) {
10952 
10953 	import arsd.dom;
10954 	import arsd.jsvar;
10955 
10956 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
10957 		string remainingUrl = cgi.pathInfo[urlPrefix.length .. $];
10958 
10959 		try {
10960 			// see duplicated code below by searching subresource_ctor
10961 			// also see mustNotBeSetFromWebParams
10962 
10963 			static if(is(typeof(T.__ctor) P == __parameters)) {
10964 				P params;
10965 
10966 				foreach(pidx, param; P) {
10967 					static if(is(param : Cgi)) {
10968 						static assert(!is(param == immutable));
10969 						cast() params[pidx] = cgi;
10970 					} else static if(is(param == Session!D, D)) {
10971 						static assert(!is(param == immutable));
10972 						cast() params[pidx] = cgi.getSessionObject!D();
10973 
10974 					} else {
10975 						static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
10976 							foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
10977 								static if(is(uda == ifCalledFromWeb!func, alias func)) {
10978 									static if(is(typeof(func(cgi))))
10979 										params[pidx] = func(cgi);
10980 									else
10981 										params[pidx] = func();
10982 								}
10983 							}
10984 						} else {
10985 
10986 							static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
10987 								params[pidx] = param.getAutomaticallyForCgi(cgi);
10988 							} else static if(is(param == string)) {
10989 								auto ident = nextPieceFromSlash(remainingUrl);
10990 								params[pidx] = ident;
10991 							} else static assert(0, "illegal type for subresource " ~ param.stringof);
10992 						}
10993 					}
10994 				}
10995 
10996 				auto obj = new T(params);
10997 			} else {
10998 				auto obj = new T();
10999 			}
11000 
11001 			return internalHandlerWithObject(obj, remainingUrl, cgi, presenter);
11002 		} catch(Throwable t) {
11003 			switch(cgi.request("format", "html")) {
11004 				case "html":
11005 					static void dummy() {}
11006 					presenter.presentExceptionAsHtml(cgi, t, null);
11007 				return true;
11008 				case "json":
11009 					var envelope = var.emptyObject;
11010 					envelope.success = false;
11011 					envelope.result = null;
11012 					envelope.error = t.toString();
11013 					cgi.setResponseContentType("application/json");
11014 					cgi.write(envelope.toJson(), true);
11015 				return true;
11016 				default:
11017 					throw t;
11018 				// return true;
11019 			}
11020 			// return true;
11021 		}
11022 
11023 		assert(0);
11024 	}
11025 
11026 	static bool internalHandlerWithObject(T, Presenter)(T obj, string remainingUrl, Cgi cgi, Presenter presenter) {
11027 
11028 		obj.initialize(cgi);
11029 
11030 		/+
11031 			Overload rules:
11032 				Any unique combination of HTTP verb and url path can be dispatched to function overloads
11033 				statically.
11034 
11035 				Moreover, some args vs no args can be overloaded dynamically.
11036 		+/
11037 
11038 		auto methodNameFromUrl = nextPieceFromSlash(remainingUrl);
11039 		/+
11040 		auto orig = remainingUrl;
11041 		assert(0,
11042 			(orig is null ? "__null" : orig)
11043 			~ " .. " ~
11044 			(methodNameFromUrl is null ? "__null" : methodNameFromUrl));
11045 		+/
11046 
11047 		if(methodNameFromUrl is null)
11048 			methodNameFromUrl = "__null";
11049 
11050 		string hack = to!string(cgi.requestMethod) ~ " " ~ methodNameFromUrl;
11051 
11052 		if(remainingUrl.length)
11053 			hack ~= "/";
11054 
11055 		switch(hack) {
11056 			foreach(methodName; __traits(derivedMembers, T))
11057 			static if(methodName != "__ctor")
11058 			foreach(idx, overload; __traits(getOverloads, T, methodName)) {
11059 			static if(is(typeof(overload) P == __parameters))
11060 			static if(is(typeof(overload) R == return))
11061 			static if(__traits(getProtection, overload) == "public" || __traits(getProtection, overload) == "export")
11062 			{
11063 			static foreach(urlNameForMethod; urlNamesForMethod!(overload, urlify(methodName)))
11064 			case urlNameForMethod:
11065 
11066 				static if(is(R : WebObject)) {
11067 					// if it returns a WebObject, it is considered a subresource. That means the url is dispatched like the ctor above.
11068 
11069 					// the only argument it is allowed to take, outside of cgi, session, and set up thingies, is a single string
11070 
11071 					// subresource_ctor
11072 					// also see mustNotBeSetFromWebParams
11073 
11074 					P params;
11075 
11076 					string ident;
11077 
11078 					foreach(pidx, param; P) {
11079 						static if(is(param : Cgi)) {
11080 							static assert(!is(param == immutable));
11081 							cast() params[pidx] = cgi;
11082 						} else static if(is(param == typeof(presenter))) {
11083 							cast() param[pidx] = presenter;
11084 						} else static if(is(param == Session!D, D)) {
11085 							static assert(!is(param == immutable));
11086 							cast() params[pidx] = cgi.getSessionObject!D();
11087 						} else {
11088 							static if(hasIfCalledFromWeb!(__traits(getAttributes, P[pidx .. pidx + 1]))) {
11089 								foreach(uda; __traits(getAttributes, P[pidx .. pidx + 1])) {
11090 									static if(is(uda == ifCalledFromWeb!func, alias func)) {
11091 										static if(is(typeof(func(cgi))))
11092 											params[pidx] = func(cgi);
11093 										else
11094 											params[pidx] = func();
11095 									}
11096 								}
11097 							} else {
11098 
11099 								static if(__traits(compiles, { params[pidx] = param.getAutomaticallyForCgi(cgi); } )) {
11100 									params[pidx] = param.getAutomaticallyForCgi(cgi);
11101 								} else static if(is(param == string)) {
11102 									ident = nextPieceFromSlash(remainingUrl);
11103 									if(ident is null) {
11104 										// trailing slash mandated on subresources
11105 										cgi.setResponseLocation(cgi.pathInfo ~ "/");
11106 										return true;
11107 									} else {
11108 										params[pidx] = ident;
11109 									}
11110 								} else static assert(0, "illegal type for subresource " ~ param.stringof);
11111 							}
11112 						}
11113 					}
11114 
11115 					auto nobj = (__traits(getOverloads, obj, methodName)[idx])(ident);
11116 					return internalHandlerWithObject!(typeof(nobj), Presenter)(nobj, remainingUrl, cgi, presenter);
11117 				} else {
11118 					// 404 it if any url left - not a subresource means we don't get to play with that!
11119 					if(remainingUrl.length)
11120 						return false;
11121 
11122 					bool automaticForm;
11123 
11124 					foreach(attr; __traits(getAttributes, overload))
11125 						static if(is(attr == AddTrailingSlash)) {
11126 							if(remainingUrl is null) {
11127 								cgi.setResponseLocation(cgi.pathInfo ~ "/");
11128 								return true;
11129 							}
11130 						} else static if(is(attr == RemoveTrailingSlash)) {
11131 							if(remainingUrl !is null) {
11132 								cgi.setResponseLocation(cgi.pathInfo[0 .. lastIndexOf(cgi.pathInfo, "/")]);
11133 								return true;
11134 							}
11135 
11136 						} else static if(__traits(isSame, AutomaticForm, attr)) {
11137 							automaticForm = true;
11138 						}
11139 
11140 				/+
11141 				int zeroArgOverload = -1;
11142 				int overloadCount = cast(int) __traits(getOverloads, T, methodName).length;
11143 				bool calledWithZeroArgs = true;
11144 				foreach(k, v; cgi.get)
11145 					if(k != "format") {
11146 						calledWithZeroArgs = false;
11147 						break;
11148 					}
11149 				foreach(k, v; cgi.post)
11150 					if(k != "format") {
11151 						calledWithZeroArgs = false;
11152 						break;
11153 					}
11154 
11155 				// first, we need to go through and see if there is an empty one, since that
11156 				// changes inside. But otherwise, all the stuff I care about can be done via
11157 				// simple looping (other improper overloads might be flagged for runtime semantic check)
11158 				//
11159 				// an argument of type Cgi is ignored for these purposes
11160 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
11161 					static if(is(typeof(overload) P == __parameters))
11162 						static if(P.length == 0)
11163 							zeroArgOverload = cast(int) idx;
11164 						else static if(P.length == 1 && is(P[0] : Cgi))
11165 							zeroArgOverload = cast(int) idx;
11166 				}}
11167 				// FIXME: static assert if there are multiple non-zero-arg overloads usable with a single http method.
11168 				bool overloadHasBeenCalled = false;
11169 				static foreach(idx, overload; __traits(getOverloads, T, methodName)) {{
11170 					bool callFunction = true;
11171 					// there is a zero arg overload and this is NOT it, and we have zero args - don't call this
11172 					if(overloadCount > 1 && zeroArgOverload != -1 && idx != zeroArgOverload && calledWithZeroArgs)
11173 						callFunction = false;
11174 					// if this is the zero-arg overload, obviously it cannot be called if we got any args.
11175 					if(overloadCount > 1 && idx == zeroArgOverload && !calledWithZeroArgs)
11176 						callFunction = false;
11177 
11178 					// FIXME: so if you just add ?foo it will give the error below even when. this might not be a great idea.
11179 
11180 					bool hadAnyMethodRestrictions = false;
11181 					bool foundAcceptableMethod = false;
11182 					foreach(attr; __traits(getAttributes, overload)) {
11183 						static if(is(typeof(attr) == Cgi.RequestMethod)) {
11184 							hadAnyMethodRestrictions = true;
11185 							if(attr == cgi.requestMethod)
11186 								foundAcceptableMethod = true;
11187 						}
11188 					}
11189 
11190 					if(hadAnyMethodRestrictions && !foundAcceptableMethod)
11191 						callFunction = false;
11192 
11193 					/+
11194 						The overloads we really want to allow are the sane ones
11195 						from the web perspective. Which is likely on HTTP verbs,
11196 						for the most part, but might also be potentially based on
11197 						some args vs zero args, or on argument names. Can't really
11198 						do argument types very reliable through the web though; those
11199 						should probably be different URLs.
11200 
11201 						Even names I feel is better done inside the function, so I'm not
11202 						going to support that here. But the HTTP verbs and zero vs some
11203 						args makes sense - it lets you define custom forms pretty easily.
11204 
11205 						Moreover, I'm of the opinion that empty overload really only makes
11206 						sense on GET for this case. On a POST, it is just a missing argument
11207 						exception and that should be handled by the presenter. But meh, I'll
11208 						let the user define that, D only allows one empty arg thing anyway
11209 						so the method UDAs are irrelevant.
11210 					+/
11211 					if(callFunction)
11212 				+/
11213 
11214 					auto format = cgi.request("format", defaultFormat!overload());
11215 					auto wantsFormFormat = format.startsWith("form-");
11216 
11217 					if(wantsFormFormat || (automaticForm && cgi.requestMethod == Cgi.RequestMethod.GET)) {
11218 						// Should I still show the form on a json thing? idk...
11219 						auto ret = presenter.createAutomaticFormForFunction!((__traits(getOverloads, obj, methodName)[idx]))(&(__traits(getOverloads, obj, methodName)[idx]));
11220 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), wantsFormFormat ? format["form_".length .. $] : "html");
11221 						return true;
11222 					}
11223 
11224 					try {
11225 						// a void return (or typeof(null) lol) means you, the user, is doing it yourself. Gives full control.
11226 						auto ret = callFromCgi!(__traits(getOverloads, obj, methodName)[idx])(&(__traits(getOverloads, obj, methodName)[idx]), cgi);
11227 						presenter.presentSuccessfulReturn(cgi, ret, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), format);
11228 					} catch(Throwable t) {
11229 						// presenter.presentExceptionAsHtml!(__traits(getOverloads, obj, methodName)[idx])(cgi, t, &(__traits(getOverloads, obj, methodName)[idx]));
11230 						presenter.presentExceptionalReturn(cgi, t, presenter.methodMeta!(__traits(getOverloads, obj, methodName)[idx]), format);
11231 					}
11232 					return true;
11233 				//}}
11234 
11235 				//cgi.header("Accept: POST"); // FIXME list the real thing
11236 				//cgi.setResponseStatus("405 Method Not Allowed"); // again, not exactly, but sort of. no overload matched our args, almost certainly due to http verb filtering.
11237 				//return true;
11238 				}
11239 			}
11240 			}
11241 			case "GET script.js":
11242 				cgi.setResponseContentType("text/javascript");
11243 				cgi.gzipResponse = true;
11244 				cgi.write(presenter.script(), true);
11245 				return true;
11246 			case "GET style.css":
11247 				cgi.setResponseContentType("text/css");
11248 				cgi.gzipResponse = true;
11249 				cgi.write(presenter.style(), true);
11250 				return true;
11251 			default:
11252 				return false;
11253 		}
11254 
11255 		assert(0);
11256 	}
11257 	return DispatcherDefinition!internalHandler(urlPrefix, false);
11258 }
11259 
11260 string defaultFormat(alias method)() {
11261 	bool nonConstConditionForWorkingAroundASpuriousDmdWarning = true;
11262 	foreach(attr; __traits(getAttributes, method)) {
11263 		static if(is(typeof(attr) == DefaultFormat)) {
11264 			if(nonConstConditionForWorkingAroundASpuriousDmdWarning)
11265 				return attr.value;
11266 		}
11267 	}
11268 	return "html";
11269 }
11270 
11271 struct Paginated(T) {
11272 	T[] items;
11273 	string nextPageUrl;
11274 }
11275 
11276 template urlNamesForMethod(alias method, string default_) {
11277 	string[] helper() {
11278 		auto verb = Cgi.RequestMethod.GET;
11279 		bool foundVerb = false;
11280 		bool foundNoun = false;
11281 
11282 		string def = default_;
11283 
11284 		bool hasAutomaticForm = false;
11285 
11286 		foreach(attr; __traits(getAttributes, method)) {
11287 			static if(is(typeof(attr) == Cgi.RequestMethod)) {
11288 				verb = attr;
11289 				if(foundVerb)
11290 					assert(0, "Multiple http verbs on one function is not currently supported");
11291 				foundVerb = true;
11292 			}
11293 			static if(is(typeof(attr) == UrlName)) {
11294 				if(foundNoun)
11295 					assert(0, "Multiple url names on one function is not currently supported");
11296 				foundNoun = true;
11297 				def = attr.name;
11298 			}
11299 			static if(__traits(isSame, attr, AutomaticForm)) {
11300 				hasAutomaticForm = true;
11301 			}
11302 		}
11303 
11304 		if(def is null)
11305 			def = "__null";
11306 
11307 		string[] ret;
11308 
11309 		static if(is(typeof(method) R == return)) {
11310 			static if(is(R : WebObject)) {
11311 				def ~= "/";
11312 				foreach(v; __traits(allMembers, Cgi.RequestMethod))
11313 					ret ~= v ~ " " ~ def;
11314 			} else {
11315 				if(hasAutomaticForm) {
11316 					ret ~= "GET " ~ def;
11317 					ret ~= "POST " ~ def;
11318 				} else {
11319 					ret ~= to!string(verb) ~ " " ~ def;
11320 				}
11321 			}
11322 		} else static assert(0);
11323 
11324 		return ret;
11325 	}
11326 	enum urlNamesForMethod = helper();
11327 }
11328 
11329 
11330 	enum AccessCheck {
11331 		allowed,
11332 		denied,
11333 		nonExistant,
11334 	}
11335 
11336 	enum Operation {
11337 		show,
11338 		create,
11339 		replace,
11340 		remove,
11341 		update
11342 	}
11343 
11344 	enum UpdateResult {
11345 		accessDenied,
11346 		noSuchResource,
11347 		success,
11348 		failure,
11349 		unnecessary
11350 	}
11351 
11352 	enum ValidationResult {
11353 		valid,
11354 		invalid
11355 	}
11356 
11357 
11358 /++
11359 	The base of all REST objects, to be used with [serveRestObject] and [serveRestCollectionOf].
11360 
11361 	WARNING: this is not stable.
11362 +/
11363 class RestObject(CRTP) : WebObject {
11364 
11365 	import arsd.dom;
11366 	import arsd.jsvar;
11367 
11368 	/// Prepare the object to be shown.
11369 	void show() {}
11370 	/// ditto
11371 	void show(string urlId) {
11372 		load(urlId);
11373 		show();
11374 	}
11375 
11376 	/// Override this to provide access control to this object.
11377 	AccessCheck accessCheck(string urlId, Operation operation) {
11378 		return AccessCheck.allowed;
11379 	}
11380 
11381 	ValidationResult validate() {
11382 		// FIXME
11383 		return ValidationResult.valid;
11384 	}
11385 
11386 	string getUrlSlug() {
11387 		import std.conv;
11388 		static if(is(typeof(CRTP.id)))
11389 			return to!string((cast(CRTP) this).id);
11390 		else
11391 			return null;
11392 	}
11393 
11394 	// The functions with more arguments are the low-level ones,
11395 	// they forward to the ones with fewer arguments by default.
11396 
11397 	// POST on a parent collection - this is called from a collection class after the members are updated
11398 	/++
11399 		Given a populated object, this creates a new entry. Returns the url identifier
11400 		of the new object.
11401 	+/
11402 	string create(scope void delegate() applyChanges) {
11403 		applyChanges();
11404 		save();
11405 		return getUrlSlug();
11406 	}
11407 
11408 	void replace() {
11409 		save();
11410 	}
11411 	void replace(string urlId, scope void delegate() applyChanges) {
11412 		load(urlId);
11413 		applyChanges();
11414 		replace();
11415 	}
11416 
11417 	void update(string[] fieldList) {
11418 		save();
11419 	}
11420 	void update(string urlId, scope void delegate() applyChanges, string[] fieldList) {
11421 		load(urlId);
11422 		applyChanges();
11423 		update(fieldList);
11424 	}
11425 
11426 	void remove() {}
11427 
11428 	void remove(string urlId) {
11429 		load(urlId);
11430 		remove();
11431 	}
11432 
11433 	abstract void load(string urlId);
11434 	abstract void save();
11435 
11436 	Element toHtml(Presenter)(Presenter presenter) {
11437 		import arsd.dom;
11438 		import std.conv;
11439 		auto obj = cast(CRTP) this;
11440 		auto div = Element.make("div");
11441 		div.addClass("Dclass_" ~ CRTP.stringof);
11442 		div.dataset.url = getUrlSlug();
11443 		bool first = true;
11444 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
11445 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11446 			if(!first) div.addChild("br"); else first = false;
11447 			div.appendChild(presenter.formatReturnValueAsHtml(__traits(getMember, obj, memberName)));
11448 		}
11449 		return div;
11450 	}
11451 
11452 	var toJson() {
11453 		import arsd.jsvar;
11454 		var v = var.emptyObject();
11455 		auto obj = cast(CRTP) this;
11456 		foreach(idx, memberName; __traits(derivedMembers, CRTP))
11457 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11458 			v[memberName] = __traits(getMember, obj, memberName);
11459 		}
11460 		return v;
11461 	}
11462 
11463 	/+
11464 	auto structOf(this This) {
11465 
11466 	}
11467 	+/
11468 }
11469 
11470 // FIXME XSRF token, prolly can just put in a cookie and then it needs to be copied to header or form hidden value
11471 // https://use-the-index-luke.com/sql/partial-results/fetch-next-page
11472 
11473 /++
11474 	Base class for REST collections.
11475 +/
11476 class CollectionOf(Obj) : RestObject!(CollectionOf) {
11477 	/// You might subclass this and use the cgi object's query params
11478 	/// to implement a search filter, for example.
11479 	///
11480 	/// FIXME: design a way to auto-generate that form
11481 	/// (other than using the WebObject thing above lol
11482 	// it'll prolly just be some searchParams UDA or maybe an enum.
11483 	//
11484 	// pagination too perhaps.
11485 	//
11486 	// and sorting too
11487 	IndexResult index() { return IndexResult.init; }
11488 
11489 	string[] sortableFields() { return null; }
11490 	string[] searchableFields() { return null; }
11491 
11492 	struct IndexResult {
11493 		Obj[] results;
11494 
11495 		string[] sortableFields;
11496 
11497 		string previousPageIdentifier;
11498 		string nextPageIdentifier;
11499 		string firstPageIdentifier;
11500 		string lastPageIdentifier;
11501 
11502 		int numberOfPages;
11503 	}
11504 
11505 	override string create(scope void delegate() applyChanges) { assert(0); }
11506 	override void load(string urlId) { assert(0); }
11507 	override void save() { assert(0); }
11508 	override void show() {
11509 		index();
11510 	}
11511 	override void show(string urlId) {
11512 		show();
11513 	}
11514 
11515 	/// Proxy POST requests (create calls) to the child collection
11516 	alias PostProxy = Obj;
11517 }
11518 
11519 /++
11520 	Serves a REST object, similar to a Ruby on Rails resource.
11521 
11522 	You put data members in your class. cgi.d will automatically make something out of those.
11523 
11524 	It will call your constructor with the ID from the URL. This may be null.
11525 	It will then populate the data members from the request.
11526 	It will then call a method, if present, telling what happened. You don't need to write these!
11527 	It finally returns a reply.
11528 
11529 	Your methods are passed a list of fields it actually set.
11530 
11531 	The URL mapping - despite my general skepticism of the wisdom - matches up with what most REST
11532 	APIs I have used seem to follow. (I REALLY want to put trailing slashes on it though. Works better
11533 	with relative linking. But meh.)
11534 
11535 	GET /items -> index. all values not set.
11536 	GET /items/id -> get. only ID will be set, other params ignored.
11537 	POST /items -> create. values set as given
11538 	PUT /items/id -> replace. values set as given
11539 		or POST /items/id with cgi.post["_method"] (thus urlencoded or multipart content-type) set to "PUT" to work around browser/html limitation
11540 		a GET with cgi.get["_method"] (in the url) set to "PUT" will render a form.
11541 	PATCH /items/id -> update. values set as given, list of changed fields passed
11542 		or POST /items/id with cgi.post["_method"] == "PATCH"
11543 	DELETE /items/id -> destroy. only ID guaranteed to be set
11544 		or POST /items/id with cgi.post["_method"] == "DELETE"
11545 
11546 	Following the stupid convention, there will never be a trailing slash here, and if it is there, it will
11547 	redirect you away from it.
11548 
11549 	API clients should set the `Accept` HTTP header to application/json or the cgi.get["_format"] = "json" var.
11550 
11551 	I will also let you change the default, if you must.
11552 
11553 	// One add-on is validation. You can issue a HTTP GET to a resource with _method = VALIDATE to check potential changes.
11554 
11555 	You can define sub-resources on your object inside the object. These sub-resources are also REST objects
11556 	that follow the same thing. They may be individual resources or collections themselves.
11557 
11558 	Your class is expected to have at least the following methods:
11559 
11560 	FIXME: i kinda wanna add a routes object to the initialize call
11561 
11562 	create
11563 		Create returns the new address on success, some code on failure.
11564 	show
11565 	index
11566 	update
11567 	remove
11568 
11569 	You will want to be able to customize the HTTP, HTML, and JSON returns but generally shouldn't have to - the defaults
11570 	should usually work. The returned JSON will include a field "href" on all returned objects along with "id". Or omething like that.
11571 
11572 	Usage of this function will add a dependency on [arsd.dom] and [arsd.jsvar].
11573 
11574 	NOT IMPLEMENTED
11575 
11576 
11577 	Really, a collection is a resource with a bunch of subresources.
11578 
11579 		GET /items
11580 			index because it is GET on the top resource
11581 
11582 		GET /items/foo
11583 			item but different than items?
11584 
11585 		class Items {
11586 
11587 		}
11588 
11589 	... but meh, a collection can be automated. not worth making it
11590 	a separate thing, let's look at a real example. Users has many
11591 	items and a virtual one, /users/current.
11592 
11593 	the individual users have properties and two sub-resources:
11594 	session, which is just one, and comments, a collection.
11595 
11596 	class User : RestObject!() { // no parent
11597 		int id;
11598 		string name;
11599 
11600 		// the default implementations of the urlId ones is to call load(that_id) then call the arg-less one.
11601 		// but you can override them to do it differently.
11602 
11603 		// any member which is of type RestObject can be linked automatically via href btw.
11604 
11605 		void show() {}
11606 		void show(string urlId) {} // automated! GET of this specific thing
11607 		void create() {} // POST on a parent collection - this is called from a collection class after the members are updated
11608 		void replace(string urlId) {} // this is the PUT; really, it just updates all fields.
11609 		void update(string urlId, string[] fieldList) {} // PATCH, it updates some fields.
11610 		void remove(string urlId) {} // DELETE
11611 
11612 		void load(string urlId) {} // the default implementation of show() populates the id, then
11613 
11614 		this() {}
11615 
11616 		mixin Subresource!Session;
11617 		mixin Subresource!Comment;
11618 	}
11619 
11620 	class Session : RestObject!() {
11621 		// the parent object may not be fully constructed/loaded
11622 		this(User parent) {}
11623 
11624 	}
11625 
11626 	class Comment : CollectionOf!Comment {
11627 		this(User parent) {}
11628 	}
11629 
11630 	class Users : CollectionOf!User {
11631 		// but you don't strictly need ANYTHING on a collection; it will just... collect. Implement the subobjects.
11632 		void index() {} // GET on this specific thing; just like show really, just different name for the different semantics.
11633 		User create() {} // You MAY implement this, but the default is to create a new object, populate it from args, and then call create() on the child
11634 	}
11635 
11636 +/
11637 auto serveRestObject(T)(string urlPrefix) {
11638 	assert(urlPrefix[0] == '/');
11639 	assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects.");
11640 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, immutable void* details) {
11641 		string url = cgi.pathInfo[urlPrefix.length .. $];
11642 
11643 		if(url.length && url[$ - 1] == '/') {
11644 			// remove the final slash...
11645 			cgi.setResponseLocation(cgi.scriptName ~ cgi.pathInfo[0 .. $ - 1]);
11646 			return true;
11647 		}
11648 
11649 		return restObjectServeHandler!T(cgi, presenter, url);
11650 	}
11651 	return DispatcherDefinition!internalHandler(urlPrefix, false);
11652 }
11653 
11654 /+
11655 /// Convenience method for serving a collection. It will be named the same
11656 /// as type T, just with an s at the end. If you need any further, just
11657 /// write the class yourself.
11658 auto serveRestCollectionOf(T)(string urlPrefix) {
11659 	assert(urlPrefix[0] == '/');
11660 	mixin(`static class `~T.stringof~`s : CollectionOf!(T) {}`);
11661 	return serveRestObject!(mixin(T.stringof ~ "s"))(urlPrefix);
11662 }
11663 +/
11664 
11665 bool restObjectServeHandler(T, Presenter)(Cgi cgi, Presenter presenter, string url) {
11666 	string urlId = null;
11667 	if(url.length && url[0] == '/') {
11668 		// asking for a subobject
11669 		urlId = url[1 .. $];
11670 		foreach(idx, ch; urlId) {
11671 			if(ch == '/') {
11672 				urlId = urlId[0 .. idx];
11673 				break;
11674 			}
11675 		}
11676 	}
11677 
11678 	// FIXME handle other subresources
11679 
11680 	static if(is(T : CollectionOf!(C), C)) {
11681 		if(urlId !is null) {
11682 			return restObjectServeHandler!(C, Presenter)(cgi, presenter, url); // FIXME?  urlId);
11683 		}
11684 	}
11685 
11686 	// FIXME: support precondition failed, if-modified-since, expectation failed, etc.
11687 
11688 	auto obj = new T();
11689 	obj.initialize(cgi);
11690 	// FIXME: populate reflection info delegates
11691 
11692 
11693 	// FIXME: I am not happy with this.
11694 	switch(urlId) {
11695 		case "script.js":
11696 			cgi.setResponseContentType("text/javascript");
11697 			cgi.gzipResponse = true;
11698 			cgi.write(presenter.script(), true);
11699 			return true;
11700 		case "style.css":
11701 			cgi.setResponseContentType("text/css");
11702 			cgi.gzipResponse = true;
11703 			cgi.write(presenter.style(), true);
11704 			return true;
11705 		default:
11706 			// intentionally blank
11707 	}
11708 
11709 
11710 
11711 
11712 	static void applyChangesTemplate(Obj)(Cgi cgi, Obj obj) {
11713 		foreach(idx, memberName; __traits(derivedMembers, Obj))
11714 		static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
11715 			__traits(getMember, obj, memberName) = cgi.request(memberName, __traits(getMember, obj, memberName));
11716 		}
11717 	}
11718 	void applyChanges() {
11719 		applyChangesTemplate(cgi, obj);
11720 	}
11721 
11722 	string[] modifiedList;
11723 
11724 	void writeObject(bool addFormLinks) {
11725 		if(cgi.request("format") == "json") {
11726 			cgi.setResponseContentType("application/json");
11727 			cgi.write(obj.toJson().toString, true);
11728 		} else {
11729 			auto container = presenter.htmlContainer();
11730 			if(addFormLinks) {
11731 				static if(is(T : CollectionOf!(C), C))
11732 				container.appendHtml(`
11733 					<form>
11734 						<button type="submit" name="_method" value="POST">Create New</button>
11735 					</form>
11736 				`);
11737 				else
11738 				container.appendHtml(`
11739 					<a href="..">Back</a>
11740 					<form>
11741 						<button type="submit" name="_method" value="PATCH">Edit</button>
11742 						<button type="submit" name="_method" value="DELETE">Delete</button>
11743 					</form>
11744 				`);
11745 			}
11746 			container.appendChild(obj.toHtml(presenter));
11747 			cgi.write(container.parentDocument.toString, true);
11748 		}
11749 	}
11750 
11751 	// FIXME: I think I need a set type in here....
11752 	// it will be nice to pass sets of members.
11753 
11754 	try
11755 	switch(cgi.requestMethod) {
11756 		case Cgi.RequestMethod.GET:
11757 			// I could prolly use template this parameters in the implementation above for some reflection stuff.
11758 			// sure, it doesn't automatically work in subclasses... but I instantiate here anyway...
11759 
11760 			// automatic forms here for usable basic auto site from browser.
11761 			// even if the format is json, it could actually send out the links and formats, but really there i'ma be meh.
11762 			switch(cgi.request("_method", "GET")) {
11763 				case "GET":
11764 					static if(is(T : CollectionOf!(C), C)) {
11765 						auto results = obj.index();
11766 						if(cgi.request("format", "html") == "html") {
11767 							auto container = presenter.htmlContainer();
11768 							auto html = presenter.formatReturnValueAsHtml(results.results);
11769 							container.appendHtml(`
11770 								<form>
11771 									<button type="submit" name="_method" value="POST">Create New</button>
11772 								</form>
11773 							`);
11774 
11775 							container.appendChild(html);
11776 							cgi.write(container.parentDocument.toString, true);
11777 						} else {
11778 							cgi.setResponseContentType("application/json");
11779 							import arsd.jsvar;
11780 							var json = var.emptyArray;
11781 							foreach(r; results.results) {
11782 								var o = var.emptyObject;
11783 								foreach(idx, memberName; __traits(derivedMembers, typeof(r)))
11784 								static if(__traits(compiles, __traits(getMember, r, memberName).offsetof)) {
11785 									o[memberName] = __traits(getMember, r, memberName);
11786 								}
11787 
11788 								json ~= o;
11789 							}
11790 							cgi.write(json.toJson(), true);
11791 						}
11792 					} else {
11793 						obj.show(urlId);
11794 						writeObject(true);
11795 					}
11796 				break;
11797 				case "PATCH":
11798 					obj.load(urlId);
11799 				goto case;
11800 				case "PUT":
11801 				case "POST":
11802 					// an editing form for the object
11803 					auto container = presenter.htmlContainer();
11804 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
11805 						auto form = (cgi.request("_method") == "POST") ? presenter.createAutomaticFormForObject(new obj.PostProxy()) : presenter.createAutomaticFormForObject(obj);
11806 					} else {
11807 						auto form = presenter.createAutomaticFormForObject(obj);
11808 					}
11809 					form.attrs.method = "POST";
11810 					form.setValue("_method", cgi.request("_method", "GET"));
11811 					container.appendChild(form);
11812 					cgi.write(container.parentDocument.toString(), true);
11813 				break;
11814 				case "DELETE":
11815 					// FIXME: a delete form for the object (can be phrased "are you sure?")
11816 					auto container = presenter.htmlContainer();
11817 					container.appendHtml(`
11818 						<form method="POST">
11819 							Are you sure you want to delete this item?
11820 							<input type="hidden" name="_method" value="DELETE" />
11821 							<input type="submit" value="Yes, Delete It" />
11822 						</form>
11823 
11824 					`);
11825 					cgi.write(container.parentDocument.toString(), true);
11826 				break;
11827 				default:
11828 					cgi.write("bad method\n", true);
11829 			}
11830 		break;
11831 		case Cgi.RequestMethod.POST:
11832 			// this is to allow compatibility with HTML forms
11833 			switch(cgi.request("_method", "POST")) {
11834 				case "PUT":
11835 					goto PUT;
11836 				case "PATCH":
11837 					goto PATCH;
11838 				case "DELETE":
11839 					goto DELETE;
11840 				case "POST":
11841 					static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
11842 						auto p = new obj.PostProxy();
11843 						void specialApplyChanges() {
11844 							applyChangesTemplate(cgi, p);
11845 						}
11846 						string n = p.create(&specialApplyChanges);
11847 					} else {
11848 						string n = obj.create(&applyChanges);
11849 					}
11850 
11851 					auto newUrl = cgi.scriptName ~ cgi.pathInfo ~ "/" ~ n;
11852 					cgi.setResponseLocation(newUrl);
11853 					cgi.setResponseStatus("201 Created");
11854 					cgi.write(`The object has been created.`);
11855 				break;
11856 				default:
11857 					cgi.write("bad method\n", true);
11858 			}
11859 			// FIXME this should be valid on the collection, but not the child....
11860 			// 303 See Other
11861 		break;
11862 		case Cgi.RequestMethod.PUT:
11863 		PUT:
11864 			obj.replace(urlId, &applyChanges);
11865 			writeObject(false);
11866 		break;
11867 		case Cgi.RequestMethod.PATCH:
11868 		PATCH:
11869 			obj.update(urlId, &applyChanges, modifiedList);
11870 			writeObject(false);
11871 		break;
11872 		case Cgi.RequestMethod.DELETE:
11873 		DELETE:
11874 			obj.remove(urlId);
11875 			cgi.setResponseStatus("204 No Content");
11876 		break;
11877 		default:
11878 			// FIXME: OPTIONS, HEAD
11879 	}
11880 	catch(Throwable t) {
11881 		presenter.presentExceptionAsHtml(cgi, t);
11882 	}
11883 
11884 	return true;
11885 }
11886 
11887 /+
11888 struct SetOfFields(T) {
11889 	private void[0][string] storage;
11890 	void set(string what) {
11891 		//storage[what] =
11892 	}
11893 	void unset(string what) {}
11894 	void setAll() {}
11895 	void unsetAll() {}
11896 	bool isPresent(string what) { return false; }
11897 }
11898 +/
11899 
11900 /+
11901 enum readonly;
11902 enum hideonindex;
11903 +/
11904 
11905 /++
11906 	Returns true if I recommend gzipping content of this type. You might
11907 	want to call it from your Presenter classes before calling cgi.write.
11908 
11909 	---
11910 	cgi.setResponseContentType(yourContentType);
11911 	cgi.gzipResponse = gzipRecommendedForContentType(yourContentType);
11912 	cgi.write(yourData, true);
11913 	---
11914 
11915 	This is used internally by [serveStaticFile], [serveStaticFileDirectory], [serveStaticData], and maybe others I forgot to update this doc about.
11916 
11917 
11918 	The implementation considers text content to be recommended to gzip. This may change, but it seems reasonable enough for now.
11919 
11920 	History:
11921 		Added January 28, 2023 (dub v11.0)
11922 +/
11923 bool gzipRecommendedForContentType(string contentType) {
11924 	if(contentType.startsWith("text/"))
11925 		return true;
11926 	if(contentType.startsWith("application/javascript"))
11927 		return true;
11928 
11929 	return false;
11930 }
11931 
11932 /++
11933 	Serves a static file. To be used with [dispatcher].
11934 
11935 	See_Also: [serveApi], [serveRestObject], [dispatcher], [serveRedirect]
11936 +/
11937 auto serveStaticFile(string urlPrefix, string filename = null, string contentType = null) {
11938 // https://baus.net/on-tcp_cork/
11939 // man 2 sendfile
11940 	assert(urlPrefix[0] == '/');
11941 	if(filename is null)
11942 		filename = decodeUriComponent(urlPrefix[1 .. $]); // FIXME is this actually correct?
11943 	if(contentType is null) {
11944 		contentType = contentTypeFromFileExtension(filename);
11945 	}
11946 
11947 	static struct DispatcherDetails {
11948 		string filename;
11949 		string contentType;
11950 	}
11951 
11952 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
11953 		if(details.contentType.indexOf("image/") == 0 || details.contentType.indexOf("audio/") == 0)
11954 			cgi.setCache(true);
11955 		cgi.setResponseContentType(details.contentType);
11956 		cgi.gzipResponse = gzipRecommendedForContentType(details.contentType);
11957 		cgi.write(std.file.read(details.filename), true);
11958 		return true;
11959 	}
11960 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(filename, contentType));
11961 }
11962 
11963 /++
11964 	Serves static data. To be used with [dispatcher].
11965 
11966 	History:
11967 		Added October 31, 2021
11968 +/
11969 auto serveStaticData(string urlPrefix, immutable(void)[] data, string contentType = null) {
11970 	assert(urlPrefix[0] == '/');
11971 	if(contentType is null) {
11972 		contentType = contentTypeFromFileExtension(urlPrefix);
11973 	}
11974 
11975 	static struct DispatcherDetails {
11976 		immutable(void)[] data;
11977 		string contentType;
11978 	}
11979 
11980 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
11981 		cgi.setCache(true);
11982 		cgi.setResponseContentType(details.contentType);
11983 		cgi.write(details.data, true);
11984 		return true;
11985 	}
11986 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType));
11987 }
11988 
11989 string contentTypeFromFileExtension(string filename) {
11990 		if(filename.endsWith(".png"))
11991 			return "image/png";
11992 		if(filename.endsWith(".apng"))
11993 			return "image/apng";
11994 		if(filename.endsWith(".svg"))
11995 			return "image/svg+xml";
11996 		if(filename.endsWith(".jpg"))
11997 			return "image/jpeg";
11998 		if(filename.endsWith(".html"))
11999 			return "text/html";
12000 		if(filename.endsWith(".css"))
12001 			return "text/css";
12002 		if(filename.endsWith(".js"))
12003 			return "application/javascript";
12004 		if(filename.endsWith(".wasm"))
12005 			return "application/wasm";
12006 		if(filename.endsWith(".mp3"))
12007 			return "audio/mpeg";
12008 		if(filename.endsWith(".pdf"))
12009 			return "application/pdf";
12010 		return null;
12011 }
12012 
12013 /// This serves a directory full of static files, figuring out the content-types from file extensions.
12014 /// It does not let you to descend into subdirectories (or ascend out of it, of course)
12015 auto serveStaticFileDirectory(string urlPrefix, string directory = null, bool recursive = false) {
12016 	assert(urlPrefix[0] == '/');
12017 	assert(urlPrefix[$-1] == '/');
12018 
12019 	static struct DispatcherDetails {
12020 		string directory;
12021 		bool recursive;
12022 	}
12023 
12024 	if(directory is null)
12025 		directory = urlPrefix[1 .. $];
12026 
12027 	if(directory.length == 0)
12028 		directory = "./";
12029 
12030 	assert(directory[$-1] == '/');
12031 
12032 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12033 		auto file = decodeUriComponent(cgi.pathInfo[urlPrefix.length .. $]); // FIXME: is this actually correct
12034 
12035 		if(details.recursive) {
12036 			// never allow a backslash since it isn't in a typical url anyway and makes the following checks easier
12037 			if(file.indexOf("\\") != -1)
12038 				return false;
12039 
12040 			import std.path;
12041 
12042 			file = std.path.buildNormalizedPath(file);
12043 			enum upOneDir = ".." ~ std.path.dirSeparator;
12044 
12045 			// also no point doing any kind of up directory things since that makes it more likely to break out of the parent
12046 			if(file == ".." || file.startsWith(upOneDir))
12047 				return false;
12048 			if(std.path.isAbsolute(file))
12049 				return false;
12050 
12051 			// FIXME: if it has slashes and stuff, should we redirect to the canonical resource? or what?
12052 
12053 			// once it passes these filters it is probably ok.
12054 		} else {
12055 			if(file.indexOf("/") != -1 || file.indexOf("\\") != -1)
12056 				return false;
12057 		}
12058 
12059 		if(file.length == 0)
12060 			return false;
12061 
12062 		auto contentType = contentTypeFromFileExtension(file);
12063 
12064 		auto fn = details.directory ~ file;
12065 		if(std.file.exists(fn)) {
12066 			//if(contentType.indexOf("image/") == 0)
12067 				//cgi.setCache(true);
12068 			//else if(contentType.indexOf("audio/") == 0)
12069 				cgi.setCache(true);
12070 			cgi.setResponseContentType(contentType);
12071 			cgi.gzipResponse = gzipRecommendedForContentType(contentType);
12072 			cgi.write(std.file.read(fn), true);
12073 			return true;
12074 		} else {
12075 			return false;
12076 		}
12077 	}
12078 
12079 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, false, DispatcherDetails(directory, recursive));
12080 }
12081 
12082 /++
12083 	Redirects one url to another
12084 
12085 	See_Also: [dispatcher], [serveStaticFile]
12086 +/
12087 auto serveRedirect(string urlPrefix, string redirectTo, int code = 303) {
12088 	assert(urlPrefix[0] == '/');
12089 	static struct DispatcherDetails {
12090 		string redirectTo;
12091 		string code;
12092 	}
12093 
12094 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12095 		cgi.setResponseLocation(details.redirectTo, true, details.code);
12096 		return true;
12097 	}
12098 
12099 
12100 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(redirectTo, getHttpCodeText(code)));
12101 }
12102 
12103 /// Used exclusively with `dispatchTo`
12104 struct DispatcherData(Presenter) {
12105 	Cgi cgi; /// You can use this cgi object.
12106 	Presenter presenter; /// This is the presenter from top level, and will be forwarded to the sub-dispatcher.
12107 	size_t pathInfoStart; /// This is forwarded to the sub-dispatcher. It may be marked private later, or at least read-only.
12108 }
12109 
12110 /++
12111 	Dispatches the URL to a specific function.
12112 +/
12113 auto handleWith(alias handler)(string urlPrefix) {
12114 	// cuz I'm too lazy to do it better right now
12115 	static class Hack : WebObject {
12116 		static import std.traits;
12117 		@UrlName("")
12118 		auto handle(std.traits.Parameters!handler args) {
12119 			return handler(args);
12120 		}
12121 	}
12122 
12123 	return urlPrefix.serveApiInternal!Hack;
12124 }
12125 
12126 /++
12127 	Dispatches the URL (and anything under it) to another dispatcher function. The function should look something like this:
12128 
12129 	---
12130 	bool other(DD)(DD dd) {
12131 		return dd.dispatcher!(
12132 			"/whatever".serveRedirect("/success"),
12133 			"/api/".serveApi!MyClass
12134 		);
12135 	}
12136 	---
12137 
12138 	The `DD` in there will be an instance of [DispatcherData] which you can inspect, or forward to another dispatcher
12139 	here. It is a template to account for any Presenter type, so you can do compile-time analysis in your presenters.
12140 	Or, of course, you could just use the exact type in your own code.
12141 
12142 	You return true if you handle the given url, or false if not. Just returning the result of [dispatcher] will do a
12143 	good job.
12144 
12145 
12146 +/
12147 auto dispatchTo(alias handler)(string urlPrefix) {
12148 	assert(urlPrefix[0] == '/');
12149 	assert(urlPrefix[$-1] != '/');
12150 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
12151 		return handler(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
12152 	}
12153 
12154 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
12155 }
12156 
12157 /++
12158 	See [serveStaticFile] if you want to serve a file off disk.
12159 
12160 	History:
12161 		Added January 28, 2023 (dub v11.0)
12162 +/
12163 auto serveStaticData(string urlPrefix, immutable(ubyte)[] data, string contentType, string filenameToSuggestAsDownload = null) {
12164 	assert(urlPrefix[0] == '/');
12165 
12166 	static struct DispatcherDetails {
12167 		immutable(ubyte)[] data;
12168 		string contentType;
12169 		string filenameToSuggestAsDownload;
12170 	}
12171 
12172 	static bool internalHandler(string urlPrefix, Cgi cgi, Object presenter, DispatcherDetails details) {
12173 		cgi.setCache(true);
12174 		cgi.setResponseContentType(details.contentType);
12175 		if(details.filenameToSuggestAsDownload.length)
12176     			cgi.header("Content-Disposition: attachment; filename=\""~details.filenameToSuggestAsDownload~"\"");
12177 		cgi.gzipResponse = gzipRecommendedForContentType(details.contentType);
12178 		cgi.write(details.data, true);
12179 		return true;
12180 	}
12181 	return DispatcherDefinition!(internalHandler, DispatcherDetails)(urlPrefix, true, DispatcherDetails(data, contentType, filenameToSuggestAsDownload));
12182 }
12183 
12184 /++
12185 	Placeholder for use with [dispatchSubsection]'s `NewPresenter` argument to indicate you want to keep the parent's presenter.
12186 
12187 	History:
12188 		Added January 28, 2023 (dub v11.0)
12189 +/
12190 alias KeepExistingPresenter = typeof(null);
12191 
12192 /++
12193 	For use with [dispatchSubsection]. Calls your filter with the request and if your filter returns false,
12194 	this issues the given errorCode and stops processing.
12195 
12196 	---
12197 		bool hasAdminPermissions(Cgi cgi) {
12198 			return true;
12199 		}
12200 
12201 		mixin DispatcherMain!(
12202 			"/admin".dispatchSubsection!(
12203 				passFilterOrIssueError!(hasAdminPermissions, 403),
12204 				KeepExistingPresenter,
12205 				"/".serveApi!AdminFunctions
12206 			)
12207 		);
12208 	---
12209 
12210 	History:
12211 		Added January 28, 2023 (dub v11.0)
12212 +/
12213 template passFilterOrIssueError(alias filter, int errorCode) {
12214 	bool passFilterOrIssueError(DispatcherDetails)(DispatcherDetails dd) {
12215 		if(filter(dd.cgi))
12216 			return true;
12217 		dd.presenter.renderBasicError(dd.cgi, errorCode);
12218 		return false;
12219 	}
12220 }
12221 
12222 /++
12223 	Allows for a subsection of your dispatched urls to be passed through other a pre-request filter, optionally pick up an new presenter class,
12224 	and then be dispatched to their own handlers.
12225 
12226 	---
12227 	/+
12228 	// a long-form filter function
12229 	bool permissionCheck(DispatcherData)(DispatcherData dd) {
12230 		// you are permitted to call mutable methods on the Cgi object
12231 		// Note if you use a Cgi subclass, you can try dynamic casting it back to your custom type to attach per-request data
12232 		// though much of the request is immutable so there's only so much you're allowed to do to modify it.
12233 
12234 		if(checkPermissionOnRequest(dd.cgi)) {
12235 			return true; // OK, allow processing to continue
12236 		} else {
12237 			dd.presenter.renderBasicError(dd.cgi, 403); // reply forbidden to the requester
12238 			return false; // and stop further processing into this subsection
12239 		}
12240 	}
12241 	+/
12242 
12243 	// but you can also do short-form filters:
12244 
12245 	bool permissionCheck(Cgi cgi) {
12246 		return ("ok" in cgi.get) !is null;
12247 	}
12248 
12249 	// handler for the subsection
12250 	class AdminClass : WebObject {
12251 		int foo() { return 5; }
12252 	}
12253 
12254 	// handler for the main site
12255 	class TheMainSite : WebObject {}
12256 
12257 	mixin DispatcherMain!(
12258 		"/admin".dispatchSubsection!(
12259 			// converts our short-form filter into a long-form filter
12260 			passFilterOrIssueError!(permissionCheck, 403),
12261 			// can use a new presenter if wanted for the subsection
12262 			KeepExistingPresenter,
12263 			// and then provide child route dispatchers
12264 			"/".serveApi!AdminClass
12265 		),
12266 		// and back to the top level
12267 		"/".serveApi!TheMainSite
12268 	);
12269 	---
12270 
12271 	Note you can encapsulate sections in files like this:
12272 
12273 	---
12274 	auto adminDispatcher(string urlPrefix) {
12275 		return urlPrefix.dispatchSubsection!(
12276 			....
12277 		);
12278 	}
12279 
12280 	mixin DispatcherMain!(
12281 		"/admin".adminDispatcher,
12282 		// and so on
12283 	)
12284 	---
12285 
12286 	If you want no filter, you can pass `(cgi) => true` as the filter to approve all requests.
12287 
12288 	If you want to keep the same presenter as the parent, use [KeepExistingPresenter] as the presenter argument.
12289 
12290 
12291 	History:
12292 		Added January 28, 2023 (dub v11.0)
12293 +/
12294 auto dispatchSubsection(alias PreRequestFilter, NewPresenter, definitions...)(string urlPrefix) {
12295 	assert(urlPrefix[0] == '/');
12296 	assert(urlPrefix[$-1] != '/');
12297 	static bool internalHandler(Presenter)(string urlPrefix, Cgi cgi, Presenter presenter, const void* details) {
12298 		static if(!is(PreRequestFilter == typeof(null))) {
12299 			if(!PreRequestFilter(DispatcherData!Presenter(cgi, presenter, urlPrefix.length)))
12300 				return true; // we handled it by rejecting it
12301 		}
12302 
12303 		static if(is(NewPresenter == Presenter) || is(NewPresenter == typeof(null))) {
12304 			return dispatcher!definitions(DispatcherData!Presenter(cgi, presenter, urlPrefix.length));
12305 		} else {
12306 			auto newPresenter = new NewPresenter();
12307 			return dispatcher!(definitions(DispatcherData!NewPresenter(cgi, presenter, urlPrefix.length)));
12308 		}
12309 	}
12310 
12311 	return DispatcherDefinition!(internalHandler)(urlPrefix, false);
12312 }
12313 
12314 /++
12315 	A URL dispatcher.
12316 
12317 	---
12318 	if(cgi.dispatcher!(
12319 		"/api/".serveApi!MyApiClass,
12320 		"/objects/lol".serveRestObject!MyRestObject,
12321 		"/file.js".serveStaticFile,
12322 		"/admin/".dispatchTo!adminHandler
12323 	)) return;
12324 	---
12325 
12326 
12327 	You define a series of url prefixes followed by handlers.
12328 
12329 	You may want to do different pre- and post- processing there, for example,
12330 	an authorization check and different page layout. You can use different
12331 	presenters and different function chains. See [dispatchSubsection] for details.
12332 
12333 	[dispatchTo] will send the request to another function for handling.
12334 +/
12335 template dispatcher(definitions...) {
12336 	bool dispatcher(Presenter)(Cgi cgi, Presenter presenterArg = null) {
12337 		static if(is(Presenter == typeof(null))) {
12338 			static class GenericWebPresenter : WebPresenter!(GenericWebPresenter) {}
12339 			auto presenter = new GenericWebPresenter();
12340 		} else
12341 			alias presenter = presenterArg;
12342 
12343 		return dispatcher(DispatcherData!(typeof(presenter))(cgi, presenter, 0));
12344 	}
12345 
12346 	bool dispatcher(DispatcherData)(DispatcherData dispatcherData) if(!is(DispatcherData : Cgi)) {
12347 		// I can prolly make this more efficient later but meh.
12348 		foreach(definition; definitions) {
12349 			if(definition.rejectFurther) {
12350 				if(dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $] == definition.urlPrefix) {
12351 					auto ret = definition.handler(
12352 						dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
12353 						dispatcherData.cgi, dispatcherData.presenter, definition.details);
12354 					if(ret)
12355 						return true;
12356 				}
12357 			} else if(
12358 				dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart .. $].startsWith(definition.urlPrefix) &&
12359 				// cgi.d dispatcher urls must be complete or have a /;
12360 				// "foo" -> thing should NOT match "foobar", just "foo" or "foo/thing"
12361 				(definition.urlPrefix[$-1] == '/' || (dispatcherData.pathInfoStart + definition.urlPrefix.length) == dispatcherData.cgi.pathInfo.length
12362 				|| dispatcherData.cgi.pathInfo[dispatcherData.pathInfoStart + definition.urlPrefix.length] == '/')
12363 				) {
12364 				auto ret = definition.handler(
12365 					dispatcherData.cgi.pathInfo[0 .. dispatcherData.pathInfoStart + definition.urlPrefix.length],
12366 					dispatcherData.cgi, dispatcherData.presenter, definition.details);
12367 				if(ret)
12368 					return true;
12369 			}
12370 		}
12371 		return false;
12372 	}
12373 }
12374 
12375 });
12376 
12377 private struct StackBuffer {
12378 	char[1024] initial = void;
12379 	char[] buffer;
12380 	size_t position;
12381 
12382 	this(int a) {
12383 		buffer = initial[];
12384 		position = 0;
12385 	}
12386 
12387 	void add(in char[] what) {
12388 		if(position + what.length > buffer.length)
12389 			buffer.length = position + what.length + 1024; // reallocate with GC to handle special cases
12390 		buffer[position .. position + what.length] = what[];
12391 		position += what.length;
12392 	}
12393 
12394 	void add(in char[] w1, in char[] w2, in char[] w3 = null) {
12395 		add(w1);
12396 		add(w2);
12397 		add(w3);
12398 	}
12399 
12400 	void add(long v) {
12401 		char[16] buffer = void;
12402 		auto pos = buffer.length;
12403 		bool negative;
12404 		if(v < 0) {
12405 			negative = true;
12406 			v = -v;
12407 		}
12408 		do {
12409 			buffer[--pos] = cast(char) (v % 10 + '0');
12410 			v /= 10;
12411 		} while(v);
12412 
12413 		if(negative)
12414 			buffer[--pos] = '-';
12415 
12416 		auto res = buffer[pos .. $];
12417 
12418 		add(res[]);
12419 	}
12420 
12421 	char[] get() @nogc {
12422 		return buffer[0 .. position];
12423 	}
12424 }
12425 
12426 // duplicated in http2.d
12427 private static string getHttpCodeText(int code) pure nothrow @nogc {
12428 	switch(code) {
12429 		case 200: return "200 OK";
12430 		case 201: return "201 Created";
12431 		case 202: return "202 Accepted";
12432 		case 203: return "203 Non-Authoritative Information";
12433 		case 204: return "204 No Content";
12434 		case 205: return "205 Reset Content";
12435 		case 206: return "206 Partial Content";
12436 		//
12437 		case 300: return "300 Multiple Choices";
12438 		case 301: return "301 Moved Permanently";
12439 		case 302: return "302 Found";
12440 		case 303: return "303 See Other";
12441 		case 304: return "304 Not Modified";
12442 		case 305: return "305 Use Proxy";
12443 		case 307: return "307 Temporary Redirect";
12444 		case 308: return "308 Permanent Redirect";
12445 
12446 		//
12447 		case 400: return "400 Bad Request";
12448 		case 401: return "401 Unauthorized";
12449 		case 402: return "402 Payment Required";
12450 		case 403: return "403 Forbidden";
12451 		case 404: return "404 Not Found";
12452 		case 405: return "405 Method Not Allowed";
12453 		case 406: return "406 Not Acceptable";
12454 		case 407: return "407 Proxy Authentication Required";
12455 		case 408: return "408 Request Timeout";
12456 		case 409: return "409 Conflict";
12457 		case 410: return "410 Gone";
12458 		case 411: return "411 Length Required";
12459 		case 412: return "412 Precondition Failed";
12460 		case 413: return "413 Payload Too Large";
12461 		case 414: return "414 URI Too Long";
12462 		case 415: return "415 Unsupported Media Type";
12463 		case 416: return "416 Range Not Satisfiable";
12464 		case 417: return "417 Expectation Failed";
12465 		case 418: return "418 I'm a teapot";
12466 		case 421: return "421 Misdirected Request";
12467 		case 422: return "422 Unprocessable Entity (WebDAV)";
12468 		case 423: return "423 Locked (WebDAV)";
12469 		case 424: return "424 Failed Dependency (WebDAV)";
12470 		case 425: return "425 Too Early";
12471 		case 426: return "426 Upgrade Required";
12472 		case 428: return "428 Precondition Required";
12473 		case 431: return "431 Request Header Fields Too Large";
12474 		case 451: return "451 Unavailable For Legal Reasons";
12475 
12476 		case 500: return "500 Internal Server Error";
12477 		case 501: return "501 Not Implemented";
12478 		case 502: return "502 Bad Gateway";
12479 		case 503: return "503 Service Unavailable";
12480 		case 504: return "504 Gateway Timeout";
12481 		case 505: return "505 HTTP Version Not Supported";
12482 		case 506: return "506 Variant Also Negotiates";
12483 		case 507: return "507 Insufficient Storage (WebDAV)";
12484 		case 508: return "508 Loop Detected (WebDAV)";
12485 		case 510: return "510 Not Extended";
12486 		case 511: return "511 Network Authentication Required";
12487 		//
12488 		default: assert(0, "Unsupported http code");
12489 	}
12490 }
12491 
12492 
12493 /+
12494 /++
12495 	This is the beginnings of my web.d 2.0 - it dispatches web requests to a class object.
12496 
12497 	It relies on jsvar.d and dom.d.
12498 
12499 
12500 	You can get javascript out of it to call. The generated functions need to look
12501 	like
12502 
12503 	function name(a,b,c,d,e) {
12504 		return _call("name", {"realName":a,"sds":b});
12505 	}
12506 
12507 	And _call returns an object you can call or set up or whatever.
12508 +/
12509 bool apiDispatcher()(Cgi cgi) {
12510 	import arsd.jsvar;
12511 	import arsd.dom;
12512 }
12513 +/
12514 version(linux)
12515 private extern(C) int eventfd (uint initval, int flags) nothrow @trusted @nogc;
12516 /*
12517 Copyright: Adam D. Ruppe, 2008 - 2023
12518 License:   [http://www.boost.org/LICENSE_1_0.txt|Boost License 1.0].
12519 Authors: Adam D. Ruppe
12520 
12521 	Copyright Adam D. Ruppe 2008 - 2023.
12522 Distributed under the Boost Software License, Version 1.0.
12523    (See accompanying file LICENSE_1_0.txt or copy at
12524 	http://www.boost.org/LICENSE_1_0.txt)
12525 */