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