1 module httpbin;
2 
3 version(vibeD) {
4 } else
5 version (httpbin) 
6 {
7 	import requests.server;
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 del(in App app, ref HTTPD_Request rq, RequestArgs args) {
64 			if ( rq.method != "DELETE") {
65 				auto rs = response(rq, "Illegal method %s".format(rq.method), 405);
66 				return rs;
67 			}
68 			else {
69 				auto rs = response(rq, buildReply(rq));
70 				return rs;
71 			}
72 		}
73 		auto put(in App app, ref HTTPD_Request rq, RequestArgs args) {
74 			if ( rq.method != "PUT") {
75 				auto rs = response(rq, "Illegal method %s".format(rq.method), 405);
76 				return rs;
77 			}
78 			else {
79 				auto rs = response(rq, buildReply(rq));
80 				return rs;
81 			}
82 		}
83 		auto patch(in App app, ref HTTPD_Request rq, RequestArgs args) {
84 			if ( rq.method != "PATCH") {
85 				auto rs = response(rq, "Illegal method %s".format(rq.method), 405);
86 				return rs;
87 			}
88 			else {
89 				auto rs = response(rq, buildReply(rq));
90 				return rs;
91 			}
92 		}
93 		auto post(in App app, ref HTTPD_Request rq, RequestArgs args) {
94 			auto rs = response(rq, buildReply(rq));
95 			return rs;
96 		}
97 		auto gzip(in App app, ref HTTPD_Request rq, RequestArgs args) {
98 			auto content = ["gzipped":true];
99 			auto rs = response(rq, JSONValue(content).toPrettyString);
100 			rs.compress(Compression.gzip);
101 			rs.headers["Content-Type"] = "application/json";
102 			return rs;
103 		}
104 		auto deflate(in App app, ref HTTPD_Request rq, RequestArgs args) {
105 			auto content = ["deflated":true];
106 			auto rs = response(rq, JSONValue(content).toPrettyString);
107 			rs.compress(Compression.deflate);
108 			return rs;
109 		}
110 		auto rel_redir(in App app, ref HTTPD_Request rq, RequestArgs args) {
111 			auto rs = response(rq, buildReply(rq));
112 			auto redirects = to!long(args["redirects"]);
113 			if ( redirects > 1 ) {
114 				rs.headers["Location"] = "/relative-redirect/%d".format(redirects-1);
115 			} else {
116 				rs.headers["Location"] = "/get";
117 			}
118 			rs.status    = 302;
119 			return rs;
120 		}
121 		auto abs_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"] = "http://127.0.0.1:8081/absolute-redirect/%d".format(redirects-1);
126 			} else {
127 				rs.headers["Location"] = "http://127.0.0.1:8081/get";
128 			}
129 			rs.status    = 302;
130 			return rs;
131 		}
132 		auto cookiesSet(in App app, ref HTTPD_Request rq, RequestArgs args) {
133 			Cookie[] cookies;
134 			foreach(p; rq.query.byKeyValue) {
135 				cookies ~= Cookie("/cookies", rq.requestHeaders["host"], p.key, p.value);
136 			}
137 			auto rs = response(rq, buildReply(rq), 302);
138 			rs.headers["Location"] = "/cookies";
139 			rs.cookies = cookies;
140 			return rs;
141 		}
142 		auto cookies(in App app, ref HTTPD_Request rq, RequestArgs args) {
143 			auto cookies = ["cookies": JSONValue(rq.cookies)];
144 			auto rs = response(rq, JSONValue(cookies).toString);
145 			return rs;
146 		}
147 		auto range(in App app, ref HTTPD_Request rq, RequestArgs args) {
148 			auto size = to!size_t(args["size"]);
149 			auto rs = response(rq, new ubyte[size].chunks(16));
150 			rs.compress(Compression.yes);
151 			return rs;
152 		}
153 		auto basicAuth(in App app, ref HTTPD_Request rq, RequestArgs args) {
154 			import std.base64;
155 			auto user    = args["user"];
156 			auto password= args["password"];
157 			auto auth    = cast(string)Base64.decode(rq.requestHeaders["authorization"].split()[1]);
158 			auto up      = auth.split(":");
159 			short status;
160 			if ( up[0]==user && up[1]==password) {
161 				status = 200;
162 			} else {
163 				status = 401;
164 			}
165 			auto rs = response(rq, buildReply(rq), status);
166 			rs.headers["Content-Type"] = "application/json";
167 			return rs;
168 		}
169 		auto delay(in App app, ref HTTPD_Request rq, RequestArgs args) {
170 			auto delay = dur!"seconds"(to!long(args["delay"]));
171 			Thread.sleep(delay);
172 			auto rs = response(rq, buildReply(rq));
173 			rs.headers["Content-Type"] = "application/json";
174 			return rs;
175 		}
176 		auto stream(in App app, ref HTTPD_Request rq, RequestArgs args) {
177 			auto lines = to!size_t(args["lines"]);
178 			import std.stdio;
179 			auto rs = response(rq, (buildReply(rq) ~ "\n").repeat(lines));
180 			rs.headers["Content-Type"] = "application/json";
181 			return rs;
182 		}
183 		server.addRoute(exactRoute(r"/",             &root)).
184 				addRoute(exactRoute(r"/get",         &get)).
185 				addRoute(exactRoute(r"/post",        &post)).
186 				addRoute(exactRoute(r"/delete",      &del)).
187 				addRoute(exactRoute(r"/put",         &put)).
188 				addRoute(exactRoute(r"/patch",       &patch)).
189 				addRoute(exactRoute(r"/cookies",     &cookies)).
190 				addRoute(exactRoute(r"/cookies/set", &cookiesSet)).
191 				addRoute(exactRoute(r"/gzip",        &gzip)).
192 				addRoute(exactRoute(r"/deflate",     &deflate)).
193 				addRoute(regexRoute(r"/delay/(?P<delay>\d+)",  &delay)).
194 				addRoute(regexRoute(r"/stream/(?P<lines>\d+)", &stream)).
195 				addRoute(regexRoute(r"/range/(?P<size>\d+)",   &range)).
196 				addRoute(regexRoute(r"/relative-redirect/(?P<redirects>\d+)", &rel_redir)).
197 				addRoute(regexRoute(r"/absolute-redirect/(?P<redirects>\d+)", &abs_redir)).
198 				addRoute(regexRoute(r"/basic-auth/(?P<user>[^/]+)/(?P<password>[^/]+)", &basicAuth));
199 
200 		return server;
201 	}
202 }