1 module httpbin;
2 
3 version(vibeD) {
4 } else
5 version (httpbin)
6 {
7 	import requests.server.httpd;
8 	import requests.utils;
9 	import std.datetime;
10 	import std.json;
11 	import std.conv;
12 	import std.range;
13 	import std.string;
14 	import std.stdio;
15 	import core.thread;
16 	import std.experimental.logger;
17 
18 	auto buildReply(ref HTTPD_Request rq) {
19 		auto method  = JSONValue(rq.method);
20 		auto args    = JSONValue(rq.query);
21 		auto headers = JSONValue(rq.requestHeaders);
22 		auto url     = JSONValue(rq.uri.uri);
23 		auto json    = JSONValue(parseJSON(rq.json));
24 		auto data    = JSONValue(rq.data);
25 		auto form    = JSONValue(rq.form);
26 		auto files   = JSONValue(rq.files);
27 		auto reply   = JSONValue([
28 				"method": method,
29 				"headers": headers,
30 				"args":args,
31 				"json": json,
32 				"url": url,
33 				"data": data,
34 				"form": form,
35 				"files": files
36 			]);
37 		return reply.toString();
38 	}
39 
40 	HTTPD httpbinApp() {
41 	    pragma(msg, "Compiling httpbin server");
42 		debug(requests) trace("start httpbin app");
43 		HTTPD server = new HTTPD();
44 		App httpbin = App("httpbin");
45 
46 		httpbin.port = 8081;
47 		httpbin.host = "127.0.0.1";
48 		httpbin.timeout = 10.seconds;
49 		httpbin.rqLimit = 5;
50 		server.app(httpbin);
51 
52 		auto root(in App app, ref HTTPD_Request rq, RequestArgs args) {
53 			debug (httpd) trace("handler / called");
54 			auto rs = response(rq, buildReply(rq));
55 			rs.headers["Content-Type"] = "application/json";
56 			return rs;
57 		}
58 		auto anything(in App app, ref HTTPD_Request rq, RequestArgs args) {
59 				auto rs = response(rq, buildReply(rq));
60 				return rs;
61 		}
62 		auto get(in App app, ref HTTPD_Request rq, RequestArgs args) {
63 			debug (httpd) trace("handler /get called");
64 			auto rs = response(rq, buildReply(rq));
65 			rs.headers["Content-Type"] = "application/json";
66 			return rs;
67 		}
68 		auto incomplete(in App app, ref HTTPD_Request rq, RequestArgs args) {
69 			debug (httpd) trace("handler /incomplete called");
70 			auto rs = response(rq, buildReply(rq), 600);
71 			rs._status_reason = "";
72 			return rs;
73 		}
74 		auto del(in App app, ref HTTPD_Request rq, RequestArgs args) {
75 			if ( rq.method != "DELETE") {
76 				auto rs = response(rq, "Illegal method %s".format(rq.method), 405);
77 				return rs;
78 			}
79 			else {
80 				auto rs = response(rq, buildReply(rq));
81 				return rs;
82 			}
83 		}
84 		auto put(in App app, ref HTTPD_Request rq, RequestArgs args) {
85 			if ( rq.method != "PUT") {
86 				auto rs = response(rq, "Illegal method %s".format(rq.method), 405);
87 				return rs;
88 			}
89 			else {
90 				auto rs = response(rq, buildReply(rq));
91 				return rs;
92 			}
93 		}
94 		auto patch(in App app, ref HTTPD_Request rq, RequestArgs args) {
95 			if ( rq.method != "PATCH") {
96 				auto rs = response(rq, "Illegal method %s".format(rq.method), 405);
97 				return rs;
98 			}
99 			else {
100 				auto rs = response(rq, buildReply(rq));
101 				return rs;
102 			}
103 		}
104 		auto post(in App app, ref HTTPD_Request rq, RequestArgs args) {
105 			auto rs = response(rq, buildReply(rq));
106 			return rs;
107 		}
108 		auto gzip(in App app, ref HTTPD_Request rq, RequestArgs args) {
109 			auto content = ["gzipped":true];
110 			auto rs = response(rq, JSONValue(content).toPrettyString);
111 			rs.compress(Compression.gzip);
112 			rs.headers["Content-Type"] = "application/json";
113 			return rs;
114 		}
115 		auto deflate(in App app, ref HTTPD_Request rq, RequestArgs args) {
116 			auto content = ["deflated":true];
117 			auto rs = response(rq, JSONValue(content).toPrettyString);
118 			rs.compress(Compression.deflate);
119 			return rs;
120 		}
121 		auto rel_redir(in App app, ref HTTPD_Request rq, RequestArgs args) {
122 			auto rs = response(rq, buildReply(rq));
123 			auto redirects = to!long(args["redirects"]);
124 			if ( redirects > 1 ) {
125 				rs.headers["Location"] = "/relative-redirect/%d".format(redirects-1);
126 			} else {
127 				rs.headers["Location"] = "/get";
128 			}
129 			rs.status    = 302;
130 			return rs;
131 		}
132 		auto abs_redir(in App app, ref HTTPD_Request rq, RequestArgs args) {
133 			auto rs = response(rq, buildReply(rq));
134 			auto redirects = to!long(args["redirects"]);
135 			if ( redirects > 1 ) {
136 				rs.headers["Location"] = "http://127.0.0.1:8081/absolute-redirect/%d".format(redirects-1);
137 			} else {
138 				rs.headers["Location"] = "http://127.0.0.1:8081/get";
139 			}
140 			rs.status    = 302;
141 			return rs;
142 		}
143 		auto cookiesSet(in App app, ref HTTPD_Request rq, RequestArgs args) {
144 			Cookie[] cookies;
145 			foreach(p; rq.query.byKeyValue) {
146 				cookies ~= Cookie("/cookies", rq.requestHeaders["host"], p.key, p.value);
147 			}
148 			auto rs = response(rq, buildReply(rq), 302);
149 			rs.headers["Location"] = "/cookies";
150 			rs.cookies = cookies;
151 			return rs;
152 		}
153 		auto cookies(in App app, ref HTTPD_Request rq, RequestArgs args) {
154 			auto cookies = ["cookies": JSONValue(rq.cookies)];
155 			auto rs = response(rq, JSONValue(cookies).toString);
156 			return rs;
157 		}
158 		auto range(in App app, ref HTTPD_Request rq, RequestArgs args) {
159 			auto size = to!size_t(args["size"]);
160 			auto rs = response(rq, new ubyte[size].chunks(16));
161 			rs.compress(Compression.yes);
162 			return rs;
163 		}
164 		auto basicAuth(in App app, ref HTTPD_Request rq, RequestArgs args) {
165 			import std.base64;
166 			auto user    = args["user"];
167 			auto password= args["password"];
168 			auto auth    = cast(string)Base64.decode(rq.requestHeaders["authorization"].split()[1]);
169 			auto up      = auth.split(":");
170 			short status;
171 			if ( up[0]==user && up[1]==password) {
172 				status = 200;
173 			} else {
174 				status = 401;
175 			}
176 			auto rs = response(rq, buildReply(rq), status);
177 			rs.headers["Content-Type"] = "application/json";
178 			return rs;
179 		}
180 		auto delay(in App app, ref HTTPD_Request rq, RequestArgs args) {
181 			auto delay = dur!"seconds"(to!long(args["delay"]));
182 			Thread.sleep(delay);
183 			auto rs = response(rq, buildReply(rq));
184 			rs.headers["Content-Type"] = "application/json";
185 			return rs;
186 		}
187 		auto stream(in App app, ref HTTPD_Request rq, RequestArgs args) {
188 			auto lines = to!size_t(args["lines"]);
189 			import std.stdio;
190 			auto rs = response(rq, (buildReply(rq) ~ "\n").repeat(lines));
191 			rs.headers["Content-Type"] = "application/json";
192 			return rs;
193 		}
194 		server.addRoute(exactRoute(r"/",             &root)).
195 				addRoute(regexRoute(r"/anything",   &anything)).
196 				addRoute(exactRoute(r"/get",         &get)).
197 				addRoute(exactRoute(r"/post",        &post)).
198 				addRoute(exactRoute(r"/delete",      &del)).
199 				addRoute(exactRoute(r"/put",         &put)).
200 				addRoute(exactRoute(r"/patch",       &patch)).
201 				addRoute(exactRoute(r"/cookies",     &cookies)).
202 				addRoute(exactRoute(r"/cookies/set", &cookiesSet)).
203 				addRoute(exactRoute(r"/gzip",        &gzip)).
204 				addRoute(exactRoute(r"/deflate",     &deflate)).
205 				addRoute(exactRoute(r"/incomplete",  &incomplete)).
206 				addRoute(regexRoute(r"/delay/(?P<delay>\d+)",  &delay)).
207 				addRoute(regexRoute(r"/stream/(?P<lines>\d+)", &stream)).
208 				addRoute(regexRoute(r"/range/(?P<size>\d+)",   &range)).
209 				addRoute(regexRoute(r"/relative-redirect/(?P<redirects>\d+)", &rel_redir)).
210 				addRoute(regexRoute(r"/absolute-redirect/(?P<redirects>\d+)", &abs_redir)).
211 				addRoute(regexRoute(r"/basic-auth/(?P<user>[^/]+)/(?P<password>[^/]+)", &basicAuth));
212 
213 		return server;
214 	}
215 }