Browse Source

Upgrade to Hunt 1.2.x (#4846)

* Upgrade to Hunt 1.2.x

* Rename docker file

* Adjust the upper bound for random id
Xueping 6 years ago
parent
commit
faf4ebaed0

+ 6 - 0
frameworks/D/hunt/build.sh

@@ -5,3 +5,9 @@ git clone https://github.com/h2o/picohttpparser.git
 cp patches/Makefile picohttpparser
 cd picohttpparser
 make package
+
+
+rm -rf http-parser
+git clone https://github.com/nodejs/http-parser.git
+cd http-parser
+make package

+ 17 - 2
frameworks/D/hunt/dub.json

@@ -6,7 +6,7 @@
 	"homepage": "https://www.huntlabs.net",
 	"license": "Apache-2.0",
 	"dependencies": {
-		"hunt": "~>1.2.0",
+		"hunt": "~>1.2.2",
 		"hunt-database": "~>1.2.0",
 		"std_data_json": "~>0.18.2"
 	},
@@ -16,6 +16,7 @@
 	"configurations": [
 		{
 			"name": "default",
+			"sourcePaths": ["http"],
 			"libs-posix": [
 				"http_parser"
 			],
@@ -28,6 +29,7 @@
 		},
 		{
 			"name": "minihttp",
+			"sourcePaths": ["pico"],
 			"libs-posix": [
 				"picohttpparser"
 			],
@@ -37,9 +39,22 @@
 			"versions": [
 				"MINIHTTP"
 			]
+		},
+		{
+			"name": "mmap",
+			"sourcePaths": ["mmap"],
+			"libs-posix": [
+				"picohttpparser"
+			],
+			"lflags-posix": [
+				"-Lpicohttpparser/"
+			],
+			"versions": [
+				"MMAP"
+			]
 		}
 	],
 	"subConfigurations": {
 		"hunt-database": "postgresql"
 	}
-}
+}

+ 47 - 0
frameworks/D/hunt/http/app.d

@@ -0,0 +1,47 @@
+/*
+ * Collie - An asynchronous event-driven network framework using Dlang development
+ *
+ * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd 
+ *
+ * Developer: Putao's Dlang team
+ *
+ * Licensed under the Apache-2.0 License.
+ *
+ */
+import std.getopt;
+import std.stdio;
+
+import hunt.database;
+import hunt.io;
+import hunt.system.Memory : totalCPUs;
+import http.Processor;
+import http.Server;
+import http.DemoProcessor;
+
+void main(string[] args) {
+	ushort port = 8080;
+	GetoptResult o = getopt(args, "port|p", "Port (default 8080)", &port);
+	if (o.helpWanted) {
+		defaultGetoptPrinter("A simple http server powered by Hunt!", o.options);
+		return;
+	}
+
+	version (POSTGRESQL) {
+		DatabaseOption options;
+		debug {
+			options = new DatabaseOption(
+					"postgresql://benchmarkdbuser:[email protected]:5432/hello_world?charset=utf-8");
+		} else {
+			options = new DatabaseOption(
+					"postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?charset=utf-8");
+		}
+						
+		options.setMinimumConnection(totalCPUs*3);
+		options.setMaximumConnection(totalCPUs*3);
+		dbConnection = new Database(options);
+	}
+
+	AbstractTcpServer httpServer = new HttpServer!(DemoProcessor)("0.0.0.0", port, totalCPUs);
+	writefln("listening on http://%s", httpServer.bindingAddress.toString());
+	httpServer.start();
+}

+ 5 - 5
frameworks/D/hunt/source/http/DemoProcessor.d → frameworks/D/hunt/http/http/DemoProcessor.d

@@ -1,6 +1,6 @@
 module http.DemoProcessor;
 
-version(HTTP) :
+
 
 // import stdx.data.json;
 import std.json;
@@ -150,7 +150,7 @@ class DemoProcessor : HttpProcessor {
 
 
         private void respondSingleQuery() {
-            int id = uniform(1, 10000);
+            int id = uniform(1, 10001);
             string query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
             ResultSet rs = dbConnection.query(query);
 
@@ -168,7 +168,7 @@ class DemoProcessor : HttpProcessor {
 
             JSONValue[] arr = new JSONValue[queries];
             for (int i = 0; i < queries; i++) {
-                immutable id = uniform(1, 10000);
+                immutable id = uniform(1, 10001);
                 immutable query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
                 ResultSet rs = dbConnection.query(query);
 
@@ -224,14 +224,14 @@ class DemoProcessor : HttpProcessor {
 
             JSONValue[] arr = new JSONValue[queries];
             for (int i = 0; i < queries; i++) {
-                immutable id = uniform(1, 10000);
+                immutable id = uniform(1, 10001);
                 immutable idString = id.to!string;
                 immutable query = "SELECT randomNumber FROM world WHERE id = " ~ idString;
                 ResultSet rs = dbConnection.query(query);
                 int randomNumber = to!int(rs.front()[0]);
                 debug tracef("id=%d, randomNumber=%d", id, randomNumber);
 
-                randomNumber = uniform(1, 10000);
+                randomNumber = uniform(1, 10001);
                 string updateSql = "UPDATE world SET randomNumber = "
                     ~ randomNumber.to!string ~ "  WHERE id = " ~ idString;
                 int r = dbConnection.execute(updateSql);

+ 1 - 1
frameworks/D/hunt/source/http/HttpURI.d → frameworks/D/hunt/http/http/HttpURI.d

@@ -1,6 +1,6 @@
 module http.HttpURI;
 
-version(HTTP) :
+
 
 import hunt.collection.MultiMap;
 

+ 1 - 1
frameworks/D/hunt/source/http/Parser.d → frameworks/D/hunt/http/http/Parser.d

@@ -2,7 +2,7 @@
 /// Used for benchmarks with simple server
 module http.Parser;
 
-version(HTTP) :
+
 
 private:
 

+ 1 - 1
frameworks/D/hunt/source/http/Processor.d → frameworks/D/hunt/http/http/Processor.d

@@ -2,7 +2,7 @@
 ///
 module http.Processor;
 
-version(HTTP) :
+
 
 import std.array, std.exception, std.format, std.algorithm.mutation, std.socket;
 import core.stdc.stdlib;

+ 1 - 1
frameworks/D/hunt/source/http/Server.d → frameworks/D/hunt/http/http/Server.d

@@ -1,6 +1,6 @@
 module http.Server;
 
-version(HTTP) :
+
 
 import hunt.event;
 import hunt.io;

+ 1 - 1
frameworks/D/hunt/source/http/UrlEncoded.d → frameworks/D/hunt/http/http/UrlEncoded.d

@@ -1,6 +1,6 @@
 module http.UrlEncoded;
 
-version(HTTP) :
+
 
 import hunt.collection.List;
 import hunt.collection.MultiMap;

+ 1 - 1
frameworks/D/hunt/hunt-dmd.dockerfile

@@ -12,6 +12,6 @@ RUN git clone https://github.com/h2o/picohttpparser.git && \
     cd ..
 
 RUN dub upgrade --verbose
-RUN dub build --build=release --arch=x86_64 --compiler=dmd  -c=minihttp
+RUN dub build --build=release --arch=x86_64 --compiler=dmd  -c=mmap -f
 
 CMD ["./hunt-minihttp"]

+ 2 - 2
frameworks/D/hunt/hunt-http.dockerfile

@@ -11,6 +11,6 @@ RUN git clone https://github.com/nodejs/http-parser.git && \
     cd ..
 
 RUN dub upgrade --verbose
-RUN dub build --build=release --arch=x86_64 --compiler=ldc2
+RUN dub build --build=release --arch=x86_64 --compiler=ldc2 -f
 
-CMD ["./hunt-minihttp"]
+CMD ["./hunt-minihttp"]

+ 1 - 1
frameworks/D/hunt/hunt.dockerfile

@@ -12,6 +12,6 @@ RUN git clone https://github.com/h2o/picohttpparser.git && \
     cd ..
 
 RUN dub upgrade --verbose
-RUN dub build --build=release --arch=x86_64 --compiler=ldc2 -c=minihttp 
+RUN dub build --build=release --arch=x86_64 --compiler=ldc2 -c=minihttp -f
 
 CMD ["./hunt-minihttp"]

+ 267 - 0
frameworks/D/hunt/mmap/DemoProcessor.d

@@ -0,0 +1,267 @@
+module DemoProcessor;
+
+// import stdx.data.json;
+import std.json;
+
+import hunt.database;
+import hunt.io;
+import http.Common;
+import http.Processor;
+import http.HttpURI;
+import http.UrlEncoded;
+import hunt.logging.ConsoleLogger : trace, warning, tracef;
+
+import std.algorithm;
+import std.array;
+import std.exception;
+import std.random;
+import std.string;
+
+version (POSTGRESQL) {
+    __gshared Database dbConnection;
+}
+
+enum HttpHeader textHeader = HttpHeader("Content-Type", "text/plain; charset=UTF-8");
+enum HttpHeader htmlHeader = HttpHeader("Content-Type", "text/html; charset=UTF-8");
+enum HttpHeader jsonHeader = HttpHeader("Content-Type", "application/json; charset=UTF-8");
+
+
+enum plaintextLength = "/plaintext".length;
+enum jsonLength = "/json".length;
+enum dbLength = "/db".length;
+enum fortunesLength = "/fortunes".length;
+
+class DemoProcessor : HttpProcessor {
+    version (POSTGRESQL) HttpURI uri;
+
+    this(TcpStream client) {
+        version (POSTGRESQL) uri = new HttpURI();
+        super(client);
+    }
+
+    override void onComplete(ref HttpRequest req) {
+        debug {
+            trace(req.uri());
+            trace(req.method());
+            trace(req.headers());
+        }
+
+        string path = req.uri;
+        // auto uri = new HttpURI(req.uri);
+        // uri.parse(req.uri);
+        // if(cmp(path, "/plaintext") == 0) {
+        //     respondWith("Hello, World!", 200, textHeader);
+        // } else if(cmp(path, "/json") == 0) {
+        //     JSONValue js = JSONValue(["message" : JSONValue("Hello, World!")]);
+        //     respondWith(js.toJSON(), 200, jsonHeader);
+        // } else {
+        //     respondWith404();
+        // }
+        if(path.length == plaintextLength) { // plaintext
+            respondWith("Hello, World!", 200, textHeader);
+        } else if(path.length == jsonLength) { // json
+            JSONValue js = JSONValue(["message" : JSONValue("Hello, World!")]);
+            respondWith(js.toJSON(), 200, jsonHeader);
+        } else {
+
+        version (POSTGRESQL) {
+            if(path.length == dbLength) {
+                respondSingleQuery();
+            } else if(path.length == fortunesLength) {
+                respondFortunes();
+            } else {
+                handleDbUpdate(path);
+            }
+
+        } else {
+            respondWith404();
+        }
+        }    
+    }
+
+
+    private void respondWith404() {
+        version (POSTGRESQL) {
+            respondWith("The available paths are: /plaintext, /json, /db, /fortunes," ~
+             " /queries?queries=number, /updates?queries=number", 404);
+        } else {
+            respondWith("The available paths are: /plaintext, /json", 404);
+        }
+    }
+
+    version (POSTGRESQL) {
+        private void handleDbUpdate(string url) {
+            uri.parse(url);
+            
+            switch(uri.getPath()) {
+            case "/queries":
+                UrlEncoded queriesMap = new UrlEncoded();
+                uri.decodeQueryTo(queriesMap);
+                int number = 1;
+                debug {
+                    trace(queriesMap.toString());
+                    if (!queriesMap.containsKey("queries")) {
+                        respondWith404();
+                        return;
+                    }
+
+                    string v = queriesMap.getValue("queries", 0);
+                    if (!v.empty) {
+                        try {
+                            number = to!int(v);
+                        } catch (Exception ex) {
+                            warning(ex.msg);
+                        }
+                    }
+                } else {
+                    string v = queriesMap.getValue("queries", 0);
+                    if (!v.empty) {
+                        try {
+                            number = to!int(v);
+                        } catch (Exception ex) {
+                        }
+                    }
+                }
+
+                respondMultipleQuery(number);
+                break;
+
+
+            case "/updates":
+                UrlEncoded queriesMap = new UrlEncoded();
+                uri.decodeQueryTo(queriesMap);
+                int number = 1;
+                debug {
+                    if (!queriesMap.containsKey("queries")) {
+                        respondWith404();
+                        return;
+                    }
+
+                    string v = queriesMap.getValue("queries", 0);
+                    if (!v.empty) {
+                        try {
+                            number = to!int(v);
+                        } catch (Exception ex) {
+                            warning(ex.msg);
+                        }
+                    }
+                } else {
+                    string v = queriesMap.getValue("queries", 0);
+                    if (!v.empty) {
+                        try {
+                            number = to!int(v);
+                        } catch (Exception ex) {
+                        }
+                    }
+                }
+                respondUpdates(number);
+                break;
+
+            default:
+                respondWith404();
+                break;
+            }
+        }
+
+
+        private void respondSingleQuery() {
+            int id = uniform(1, 10001);
+            string query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
+            ResultSet rs = dbConnection.query(query);
+
+            JSONValue js = JSONValue(["id" : JSONValue(id), "randomNumber"
+                    : JSONValue(to!int(rs.front()[0]))]);
+
+            respondWith(js.toJSON(), 200, jsonHeader);
+        }
+
+        private void respondMultipleQuery(int queries) {
+            if (queries < 1)
+                queries = 1;
+            else if (queries > 500)
+                queries = 500;
+
+            JSONValue[] arr = new JSONValue[queries];
+            for (int i = 0; i < queries; i++) {
+                immutable id = uniform(1, 10001);
+                immutable query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
+                ResultSet rs = dbConnection.query(query);
+
+                arr[i] = JSONValue(["id" : JSONValue(id), "randomNumber"
+                        : JSONValue(to!int(rs.front()[0]))]);
+            }
+            JSONValue js = JSONValue(arr);
+            respondWith(js.toJSON(), 200, jsonHeader);
+        }
+
+        private void respondFortunes() {
+            immutable query = "SELECT id, message::text FROM Fortune";
+            ResultSet rs = dbConnection.query(query);
+            FortuneModel[] data = rs.map!(f => FortuneModel(f["id"].to!int, f["message"])).array;
+            data ~= FortuneModel(0, "Additional fortune added at request time.");
+            data.sort!((a, b) => a.message < b.message);
+            // trace(data);
+
+            respondWith(randerFortunes(data), 200, htmlHeader);
+        }
+
+        static string randerFortunes(FortuneModel[] data) {
+            Appender!string sb;
+            sb.put(`<!DOCTYPE html>
+<html>
+	<head>
+		<title>Fortunes</title>
+	</head>
+	<body>
+		<table>
+			<tr>
+				<th>id</th><th>message</th>
+			</tr>
+`);
+
+            foreach (FortuneModel f; data) {
+                string message = replace(f.message, ">", "&gt;");
+                message = replace(message, "<", "&lt;");
+                message = replace(message, "\"", "&quot;");
+                sb.put(format("			<tr>\n				<td>%d</td><td>%s</td>\n			</tr>\n", f.id, message));
+            }
+
+            sb.put("		</table>\n	</body>\n</html>");
+
+            return sb.data;
+        }
+
+        private void respondUpdates(int queries) {
+            if (queries < 1)
+                queries = 1;
+            else if (queries > 500)
+                queries = 500;
+
+            JSONValue[] arr = new JSONValue[queries];
+            for (int i = 0; i < queries; i++) {
+                immutable id = uniform(1, 10001);
+                immutable idString = id.to!string;
+                immutable query = "SELECT randomNumber FROM world WHERE id = " ~ idString;
+                ResultSet rs = dbConnection.query(query);
+                int randomNumber = to!int(rs.front()[0]);
+                debug tracef("id=%d, randomNumber=%d", id, randomNumber);
+
+                randomNumber = uniform(1, 10001);
+                string updateSql = "UPDATE world SET randomNumber = "
+                    ~ randomNumber.to!string ~ "  WHERE id = " ~ idString;
+                int r = dbConnection.execute(updateSql);
+                // debug tracef("r=%d", r);
+
+                arr[i] = JSONValue(["id" : JSONValue(id), "randomNumber" : JSONValue(randomNumber)]);
+            }
+
+            JSONValue js = JSONValue(arr);
+            respondWith(js.toJSON(), 200, jsonHeader);
+        }
+    }
+}
+
+struct FortuneModel {
+    int id;
+    string message;
+}

+ 47 - 0
frameworks/D/hunt/mmap/app.d

@@ -0,0 +1,47 @@
+/*
+ * Collie - An asynchronous event-driven network framework using Dlang development
+ *
+ * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd 
+ *
+ * Developer: Putao's Dlang team
+ *
+ * Licensed under the Apache-2.0 License.
+ *
+ */
+import std.getopt;
+import std.stdio;
+
+import hunt.database;
+import hunt.io;
+import hunt.system.Memory : totalCPUs;
+import http.Processor;
+import http.Server;
+import DemoProcessor;
+
+void main(string[] args) {
+	ushort port = 8080;
+	GetoptResult o = getopt(args, "port|p", "Port (default 8080)", &port);
+	if (o.helpWanted) {
+		defaultGetoptPrinter("A simple http server powered by Hunt!", o.options);
+		return;
+	}
+
+	version (POSTGRESQL) {
+		DatabaseOption options;
+		debug {
+			options = new DatabaseOption(
+					"postgresql://benchmarkdbuser:[email protected]:5432/hello_world?charset=utf-8");
+		} else {
+			options = new DatabaseOption(
+					"postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?charset=utf-8");
+		}
+		
+		options.setMinimumConnection(totalCPUs*3);
+		options.setMaximumConnection(totalCPUs*3);
+		dbConnection = new Database(options);
+	}
+
+	AbstractTcpServer httpServer = new HttpServer!(DemoProcessor)("0.0.0.0", port, totalCPUs);
+	writefln("listening on http://%s", httpServer.bindingAddress.toString());
+	httpServer.start();
+}

+ 1 - 2
frameworks/D/hunt/source/minihttp/Common.d → frameworks/D/hunt/mmap/http/Common.d

@@ -1,6 +1,5 @@
-module minihttp.Common;
+module http.Common;
 
-version(MINIHTTP):
 
 public enum HttpParserType : uint {
 	request = 0,

+ 1166 - 0
frameworks/D/hunt/mmap/http/HttpURI.d

@@ -0,0 +1,1166 @@
+module http.HttpURI;
+
+import hunt.collection.MultiMap;
+
+import hunt.Exceptions;
+import hunt.text.Charset;
+import hunt.text.Common;
+import hunt.text.StringBuilder;
+import hunt.util.TypeUtils;
+import http.UrlEncoded;
+
+import std.array;
+import std.conv;
+import std.string;
+
+import hunt.logging;
+
+
+/**
+ * Http URI. Parse a HTTP URI from a string or byte array. Given a URI
+ * <code>http://user@host:port/path/info;param?query#fragment</code> this class
+ * will split it into the following undecoded optional elements:
+ * <ul>
+ * <li>{@link #getScheme()} - http:</li>
+ * <li>{@link #getAuthority()} - //name@host:port</li>
+ * <li>{@link #getHost()} - host</li>
+ * <li>{@link #getPort()} - port</li>
+ * <li>{@link #getPath()} - /path/info</li>
+ * <li>{@link #getParam()} - param</li>
+ * <li>{@link #getQuery()} - query</li>
+ * <li>{@link #getFragment()} - fragment</li>
+ * </ul>
+ * 
+	https://bob:[email protected]:8080/file;p=1?q=2#third
+	\___/   \_/ \___/ \______________/ \__/\_______/ \_/ \___/
+	|      |    |          |          |      | \_/  |    |
+	Scheme User Password    Host       Port  Path |   | Fragment
+			\_____________________________/       | Query
+						|               Path parameter
+					Authority 
+ * <p>
+ * Any parameters will be returned from {@link #getPath()}, but are excluded
+ * from the return value of {@link #getDecodedPath()}. If there are multiple
+ * parameters, the {@link #getParam()} method returns only the last one.
+ * 
+ * See_Also:
+ *	 https://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20
+ *   https://web.archive.org/web/20151218094722/http://blog.lunatech.com/2009/02/03/what-every-web-developer-must-know-about-url-encoding
+ */
+class HttpURI {
+	private enum State {
+		START, HOST_OR_PATH, SCHEME_OR_PATH, HOST, IPV6, PORT, PATH, PARAM, QUERY, FRAGMENT, ASTERISK
+	}
+
+	private string _scheme;
+	private string _user;
+	private string _host;
+	private int _port;
+	private string _path;
+	private string _param;
+	private string _query;
+	private string _fragment;
+
+	string _uri;
+	string _decodedPath;
+
+	/**
+	 * Construct a normalized URI. Port is not set if it is the default port.
+	 * 
+	 * @param scheme
+	 *            the URI scheme
+	 * @param host
+	 *            the URI hose
+	 * @param port
+	 *            the URI port
+	 * @param path
+	 *            the URI path
+	 * @param param
+	 *            the URI param
+	 * @param query
+	 *            the URI query
+	 * @param fragment
+	 *            the URI fragment
+	 * @return the normalized URI
+	 */
+	static HttpURI createHttpURI(string scheme, string host, int port, string path, string param, string query,
+			string fragment) {
+		if (port == 80 && (scheme == "http"))
+			port = 0;
+		if (port == 443 && (scheme == "https"))
+			port = 0;
+		return new HttpURI(scheme, host, port, path, param, query, fragment);
+	}
+
+	this() {
+	}
+
+	this(string scheme, string host, int port, string path, string param, string query, string fragment) {
+		_scheme = scheme;
+		_host = host;
+		_port = port;
+		_path = path;
+		_param = param;
+		_query = query;
+		_fragment = fragment;
+	}
+
+	this(HttpURI uri) {
+		this(uri._scheme, uri._host, uri._port, uri._path, uri._param, uri._query, uri._fragment);
+		_uri = uri._uri;
+	}
+
+	this(string uri) {
+		_port = -1;
+		parse(State.START, uri);
+	}
+
+	// this(URI uri) {
+	// 	_uri = null;
+
+	// 	_scheme = uri.getScheme();
+	// 	_host = uri.getHost();
+	// 	if (_host == null && uri.getRawSchemeSpecificPart().startsWith("//"))
+	// 		_host = "";
+	// 	_port = uri.getPort();
+	// 	_user = uri.getUserInfo();
+	// 	_path = uri.getRawPath();
+
+	// 	_decodedPath = uri.getPath();
+	// 	if (_decodedPath != null) {
+	// 		int p = _decodedPath.lastIndexOf(';');
+	// 		if (p >= 0)
+	// 			_param = _decodedPath.substring(p + 1);
+	// 	}
+	// 	_query = uri.getRawQuery();
+	// 	_fragment = uri.getFragment();
+
+	// 	_decodedPath = null;
+	// }
+
+	this(string scheme, string host, int port, string pathQuery) {
+		_uri = null;
+
+		_scheme = scheme;
+		_host = host;
+		_port = port;
+
+		parse(State.PATH, pathQuery);
+
+	}
+
+	void parse(string uri) {
+		clear();
+		_uri = uri;
+		parse(State.START, uri);
+	}
+
+	/**
+	 * Parse according to https://tools.ietf.org/html/rfc7230#section-5.3
+	 * 
+	 * @param method
+	 *            the request method
+	 * @param uri
+	 *            the request uri
+	 */
+	void parseRequestTarget(string method, string uri) {
+		clear();
+		_uri = uri;
+
+		if (method == "CONNECT")
+			_path = uri;
+		else
+			parse(uri.startsWith("/") ? State.PATH : State.START, uri);
+	}
+
+	// deprecated("")
+	// void parseConnect(string uri) {
+	// 	clear();
+	// 	_uri = uri;
+	// 	_path = uri;
+	// }
+
+	void parse(string uri, int offset, int length) {
+		clear();
+		int end = offset + length;
+		_uri = uri.substring(offset, end);
+		parse(State.START, uri);
+	}
+
+	private void parse(State state, string uri) {
+		bool encoded = false;
+		int end = cast(int)uri.length;
+		int mark = 0;
+		int path_mark = 0;
+		char last = '/';
+		for (int i = 0; i < end; i++) {
+			char c = uri[i];
+
+			final switch (state) {
+			case State.START: {
+				switch (c) {
+				case '/':
+					mark = i;
+					state = State.HOST_OR_PATH;
+					break;
+				case ';':
+					mark = i + 1;
+					state = State.PARAM;
+					break;
+				case '?':
+					// assume empty path (if seen at start)
+					_path = "";
+					mark = i + 1;
+					state = State.QUERY;
+					break;
+				case '#':
+					mark = i + 1;
+					state = State.FRAGMENT;
+					break;
+				case '*':
+					_path = "*";
+					state = State.ASTERISK;
+					break;
+
+				case '.':
+					path_mark = i;
+					state = State.PATH;
+					encoded = true;
+					break;
+
+				default:
+					mark = i;
+					if (_scheme == null)
+						state = State.SCHEME_OR_PATH;
+					else {
+						path_mark = i;
+						state = State.PATH;
+					}
+					break;
+				}
+
+				continue;
+			}
+
+			case State.SCHEME_OR_PATH: {
+				switch (c) {
+				case ':':
+					// must have been a scheme
+					_scheme = uri.substring(mark, i);
+					// Start again with scheme set
+					state = State.START;
+					break;
+
+				case '/':
+					// must have been in a path and still are
+					state = State.PATH;
+					break;
+
+				case ';':
+					// must have been in a path
+					mark = i + 1;
+					state = State.PARAM;
+					break;
+
+				case '?':
+					// must have been in a path
+					_path = uri.substring(mark, i);
+					mark = i + 1;
+					state = State.QUERY;
+					break;
+
+				case '%':
+					// must have be in an encoded path
+					encoded = true;
+					state = State.PATH;
+					break;
+
+				case '#':
+					// must have been in a path
+					_path = uri.substring(mark, i);
+					state = State.FRAGMENT;
+					break;
+
+				default:
+					break;
+				}
+				continue;
+			}
+
+			case State.HOST_OR_PATH: {
+				switch (c) {
+				case '/':
+					_host = "";
+					mark = i + 1;
+					state = State.HOST;
+					break;
+
+				case '@':
+				case ';':
+				case '?':
+				case '#':
+					// was a path, look again
+					i--;
+					path_mark = mark;
+					state = State.PATH;
+					break;
+
+				case '.':
+					// it is a path
+					encoded = true;
+					path_mark = mark;
+					state = State.PATH;
+					break;
+
+				default:
+					// it is a path
+					path_mark = mark;
+					state = State.PATH;
+				}
+				continue;
+			}
+
+			case State.HOST: {
+				switch (c) {
+				case '/':
+					_host = uri.substring(mark, i);
+					path_mark = mark = i;
+					state = State.PATH;
+					break;
+				case ':':
+					if (i > mark)
+						_host = uri.substring(mark, i);
+					mark = i + 1;
+					state = State.PORT;
+					break;
+				case '@':
+					if (_user != null)
+						throw new IllegalArgumentException("Bad authority");
+					_user = uri.substring(mark, i);
+					mark = i + 1;
+					break;
+
+				case '[':
+					state = State.IPV6;
+					break;
+					
+				default:
+					break;
+				}
+				break;
+			}
+
+			case State.IPV6: {
+				switch (c) {
+				case '/':
+					throw new IllegalArgumentException("No closing ']' for ipv6 in " ~ uri);
+				case ']':
+					c = uri.charAt(++i);
+					_host = uri.substring(mark, i);
+					if (c == ':') {
+						mark = i + 1;
+						state = State.PORT;
+					} else {
+						path_mark = mark = i;
+						state = State.PATH;
+					}
+					break;
+					
+				default:
+					break;
+				}
+
+				break;
+			}
+
+			case State.PORT: {
+				if (c == '@') {
+					if (_user != null)
+						throw new IllegalArgumentException("Bad authority");
+					// It wasn't a port, but a password!
+					_user = _host ~ ":" ~ uri.substring(mark, i);
+					mark = i + 1;
+					state = State.HOST;
+				} else if (c == '/') {
+					// _port = TypeUtils.parseInt(uri, mark, i - mark, 10);
+					_port = to!int(uri[mark .. i], 10);
+					path_mark = mark = i;
+					state = State.PATH;
+				}
+				break;
+			}
+
+			case State.PATH: {
+				switch (c) {
+				case ';':
+					mark = i + 1;
+					state = State.PARAM;
+					break;
+				case '?':
+					_path = uri.substring(path_mark, i);
+					mark = i + 1;
+					state = State.QUERY;
+					break;
+				case '#':
+					_path = uri.substring(path_mark, i);
+					mark = i + 1;
+					state = State.FRAGMENT;
+					break;
+				case '%':
+					encoded = true;
+					break;
+				case '.':
+					if ('/' == last)
+						encoded = true;
+					break;
+					
+				default:
+					break;
+				}
+				break;
+			}
+
+			case State.PARAM: {
+				switch (c) {
+				case '?':
+					_path = uri.substring(path_mark, i);
+					_param = uri.substring(mark, i);
+					mark = i + 1;
+					state = State.QUERY;
+					break;
+				case '#':
+					_path = uri.substring(path_mark, i);
+					_param = uri.substring(mark, i);
+					mark = i + 1;
+					state = State.FRAGMENT;
+					break;
+				case '/':
+					encoded = true;
+					// ignore internal params
+					state = State.PATH;
+					break;
+				case ';':
+					// multiple parameters
+					mark = i + 1;
+					break;
+					
+				default:
+					break;
+				}
+				break;
+			}
+
+			case State.QUERY: {
+				if (c == '#') {
+					_query = uri.substring(mark, i);
+					mark = i + 1;
+					state = State.FRAGMENT;
+				}
+				break;
+			}
+
+			case State.ASTERISK: {
+				throw new IllegalArgumentException("Bad character '*'");
+			}
+
+			case State.FRAGMENT: {
+				_fragment = uri.substring(mark, end);
+				i = end;
+				break;
+			}
+			}
+			last = c;
+		}
+
+		final switch (state) {
+		case State.START:
+			break;
+		case State.SCHEME_OR_PATH:
+			_path = uri.substring(mark, end);
+			break;
+
+		case State.HOST_OR_PATH:
+			_path = uri.substring(mark, end);
+			break;
+
+		case State.HOST:
+			if (end > mark)
+				_host = uri.substring(mark, end);
+			break;
+
+		case State.IPV6:
+			throw new IllegalArgumentException("No closing ']' for ipv6 in " ~ uri);
+
+		case State.PORT:
+			// _port = TypeUtils.parseInt(uri, mark, end - mark, 10);
+			_port = to!int(uri[mark .. end], 10);
+			break;
+
+		case State.ASTERISK:
+			break;
+
+		case State.FRAGMENT:
+			_fragment = uri.substring(mark, end);
+			break;
+
+		case State.PARAM:
+			_path = uri.substring(path_mark, end);
+			_param = uri.substring(mark, end);
+			break;
+
+		case State.PATH:
+			_path = uri.substring(path_mark, end);
+			break;
+
+		case State.QUERY:
+			_query = uri.substring(mark, end);
+			break;
+		}
+
+		if (!encoded) {
+			if (_param == null)
+				_decodedPath = _path;
+			else
+				_decodedPath = _path[0 .. _path.length - _param.length - 1];
+		}
+	}
+
+	string getScheme() {
+		return _scheme;
+	}
+
+	string getHost() {
+		// Return null for empty host to retain compatibility with java.net.URI
+		if (_host != null && _host.length == 0)
+			return null;
+		return _host;
+	}
+
+	int getPort() {
+		return _port;
+	}
+
+	/**
+	 * The parsed Path.
+	 * 
+	 * @return the path as parsed on valid URI. null for invalid URI.
+	 */
+	string getPath() {
+		return _path;
+	}
+
+	string getDecodedPath() {
+		if (_decodedPath.empty && !_path.empty)
+			_decodedPath = URIUtils.canonicalPath(URIUtils.decodePath(_path));
+		return _decodedPath;
+	}
+
+	string getParam() {
+		return _param;
+	}
+
+	string getQuery() {
+		return _query;
+	}
+
+	bool hasQuery() {
+		return _query != null && _query.length > 0;
+	}
+
+	string getFragment() {
+		return _fragment;
+	}
+
+	void decodeQueryTo(MultiMap!string parameters, string encoding = StandardCharsets.UTF_8) {
+		if (_query == _fragment)
+			return;
+
+		UrlEncoded.decodeTo(_query, parameters, encoding);
+	}
+
+	void clear() {
+		_uri = null;
+
+		_scheme = null;
+		_host = null;
+		_port = -1;
+		_path = null;
+		_param = null;
+		_query = null;
+		_fragment = null;
+
+		_decodedPath = null;
+	}
+
+	bool isAbsolute() {
+		return _scheme != null && _scheme.length > 0;
+	}
+
+	override
+	string toString() {
+		if (_uri is null) {
+			StringBuilder ot = new StringBuilder();
+
+			if (_scheme != null)
+				ot.append(_scheme).append(':');
+
+			if (_host != null) {
+				ot.append("//");
+				if (_user != null)
+					ot.append(_user).append('@');
+				ot.append(_host);
+			}
+
+			if (_port > 0)
+				ot.append(':').append(_port);
+
+			if (_path != null)
+				ot.append(_path);
+
+			if (_query != null)
+				ot.append('?').append(_query);
+
+			if (_fragment != null)
+				ot.append('#').append(_fragment);
+
+			if (ot.length > 0)
+				_uri = ot.toString();
+			else
+				_uri = "";
+		}
+		return _uri;
+	}
+
+	bool equals(Object o) {
+		if (o is this)
+			return true;
+		if (!(typeid(o) == typeid(HttpURI)))
+			return false;
+		return toString().equals(o.toString());
+	}
+
+	void setScheme(string scheme) {
+		_scheme = scheme;
+		_uri = null;
+	}
+
+	/**
+	 * @param host
+	 *            the host
+	 * @param port
+	 *            the port
+	 */
+	void setAuthority(string host, int port) {
+		_host = host;
+		_port = port;
+		_uri = null;
+	}
+
+	/**
+	 * @param path
+	 *            the path
+	 */
+	void setPath(string path) {
+		_uri = null;
+		_path = path;
+		_decodedPath = null;
+	}
+
+	/**
+	 * @param path
+	 *            the decoded path
+	 */
+	// void setDecodedPath(string path) {
+	// 	_uri = null;
+	// 	_path = URIUtils.encodePath(path);
+	// 	_decodedPath = path;
+	// }
+
+	void setPathQuery(string path) {
+		_uri = null;
+		_path = null;
+		_decodedPath = null;
+		_param = null;
+		_fragment = null;
+		if (path != null)
+			parse(State.PATH, path);
+	}
+
+	void setQuery(string query) {
+		_query = query;
+		_uri = null;
+	}
+
+	// URI toURI() {
+	// 	return new URI(_scheme, null, _host, _port, _path, _query == null ? null : UrlEncoded.decodestring(_query),
+	// 			_fragment);
+	// }
+
+	string getPathQuery() {
+		if (_query == null)
+			return _path;
+		return _path ~ "?" ~ _query;
+	}
+
+	bool hasAuthority() {
+		return _host != null;
+	}
+
+	string getAuthority() {
+		if (_port > 0)
+			return _host ~ ":" ~ to!string(_port);
+		return _host;
+	}
+
+	string getUser() {
+		return _user;
+	}
+
+}
+
+
+/**
+ * Parse an authority string into Host and Port
+ * <p>Parse a string in the form "host:port", handling IPv4 an IPv6 hosts</p>
+ *
+ */
+class URIUtils
+{
+	/* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters
+     */
+    static string decodePath(string path) {
+        return decodePath(path, 0, cast(int)path.length);
+    }
+
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters of UTF-8 path
+     */
+    static string decodePath(string path, int offset, int length) {
+        try {
+            StringBuilder builder = null;
+
+            int end = offset + length;
+            for (int i = offset; i < end; i++) {
+                char c = path[i];
+                switch (c) {
+                    case '%':
+                        if (builder is null) {
+                            builder = new StringBuilder(path.length);
+                            builder.append(path, offset, i - offset);
+                        }
+                        if ((i + 2) < end) {
+                            char u = path.charAt(i + 1);
+                            if (u == 'u') {
+                                // TODO this is wrong. This is a codepoint not a char
+                                builder.append(cast(char) (0xffff & TypeUtils.parseInt(path, i + 2, 4, 16)));
+                                i += 5;
+                            } else {
+                                builder.append(cast(byte) (0xff & (TypeUtils.convertHexDigit(u) * 16 + TypeUtils.convertHexDigit(path.charAt(i + 2)))));
+                                i += 2;
+                            }
+                        } else {
+                            throw new IllegalArgumentException("Bad URI % encoding");
+                        }
+
+                        break;
+
+                    case ';':
+                        if (builder is null) {
+                            builder = new StringBuilder(path.length);
+                            builder.append(path, offset, i - offset);
+                        }
+
+                        while (++i < end) {
+                            if (path[i] == '/') {
+                                builder.append('/');
+                                break;
+                            }
+                        }
+
+                        break;
+
+                    default:
+                        if (builder !is null)
+                            builder.append(c);
+                        break;
+                }
+            }
+
+            if (builder !is null)
+                return builder.toString();
+            if (offset == 0 && length == path.length)
+                return path;
+            return path.substring(offset, end);
+        } catch (Exception e) {
+            // System.err.println(path.substring(offset, offset + length) + " " + e);
+			error(e.toString);
+            return decodeISO88591Path(path, offset, length);
+        }
+    }
+
+
+    /* ------------------------------------------------------------ */
+    /* Decode a URI path and strip parameters of ISO-8859-1 path
+     */
+    private static string decodeISO88591Path(string path, int offset, int length) {
+        StringBuilder builder = null;
+        int end = offset + length;
+        for (int i = offset; i < end; i++) {
+            char c = path[i];
+            switch (c) {
+                case '%':
+                    if (builder is null) {
+                        builder = new StringBuilder(path.length);
+                        builder.append(path, offset, i - offset);
+                    }
+                    if ((i + 2) < end) {
+                        char u = path.charAt(i + 1);
+                        if (u == 'u') {
+                            // TODO this is wrong. This is a codepoint not a char
+                            builder.append(cast(char) (0xffff & TypeUtils.parseInt(path, i + 2, 4, 16)));
+                            i += 5;
+                        } else {
+                            builder.append(cast(byte) (0xff & (TypeUtils.convertHexDigit(u) * 16 + TypeUtils.convertHexDigit(path.charAt(i + 2)))));
+                            i += 2;
+                        }
+                    } else {
+                        throw new IllegalArgumentException("");
+                    }
+
+                    break;
+
+                case ';':
+                    if (builder is null) {
+                        builder = new StringBuilder(path.length);
+                        builder.append(path, offset, i - offset);
+                    }
+                    while (++i < end) {
+                        if (path[i] == '/') {
+                            builder.append('/');
+                            break;
+                        }
+                    }
+                    break;
+
+
+                default:
+                    if (builder !is null)
+                        builder.append(c);
+                    break;
+            }
+        }
+
+        if (builder !is null)
+            return builder.toString();
+        if (offset == 0 && length == path.length)
+            return path;
+        return path.substring(offset, end);
+    }
+
+	/* ------------------------------------------------------------ */
+
+    /**
+     * Convert a decoded path to a canonical form.
+     * <p>
+     * All instances of "." and ".." are factored out.
+     * </p>
+     * <p>
+     * Null is returned if the path tries to .. above its root.
+     * </p>
+     *
+     * @param path the path to convert, decoded, with path separators '/' and no queries.
+     * @return the canonical path, or null if path traversal above root.
+     */
+    static string canonicalPath(string path) {
+        if (path.empty)
+            return path;
+
+        bool slash = true;
+        int end = cast(int)path.length;
+        int i = 0;
+
+        loop:
+        while (i < end) {
+            char c = path[i];
+            switch (c) {
+                case '/':
+                    slash = true;
+                    break;
+
+                case '.':
+                    if (slash)
+                        break loop;
+                    slash = false;
+                    break;
+
+                default:
+                    slash = false;
+            }
+
+            i++;
+        }
+
+        if (i == end)
+            return path;
+
+        StringBuilder canonical = new StringBuilder(path.length);
+        canonical.append(path, 0, i);
+
+        int dots = 1;
+        i++;
+        while (i <= end) {
+            char c = i < end ? path[i] : '\0';
+            switch (c) {
+                case '\0':
+                case '/':
+                    switch (dots) {
+                        case 0:
+                            if (c != '\0')
+                                canonical.append(c);
+                            break;
+
+                        case 1:
+                            break;
+
+                        case 2:
+                            if (canonical.length < 2)
+                                return null;
+                            canonical.setLength(canonical.length - 1);
+                            canonical.setLength(canonical.lastIndexOf("/") + 1);
+                            break;
+
+                        default:
+                            while (dots-- > 0)
+                                canonical.append('.');
+                            if (c != '\0')
+                                canonical.append(c);
+                    }
+
+                    slash = true;
+                    dots = 0;
+                    break;
+
+                case '.':
+                    if (dots > 0)
+                        dots++;
+                    else if (slash)
+                        dots = 1;
+                    else
+                        canonical.append('.');
+                    slash = false;
+                    break;
+
+                default:
+                    while (dots-- > 0)
+                        canonical.append('.');
+                    canonical.append(c);
+                    dots = 0;
+                    slash = false;
+            }
+
+            i++;
+        }
+        return canonical.toString();
+    }
+
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Convert a path to a cananonical form.
+     * <p>
+     * All instances of "." and ".." are factored out.
+     * </p>
+     * <p>
+     * Null is returned if the path tries to .. above its root.
+     * </p>
+     *
+     * @param path the path to convert (expects URI/URL form, encoded, and with path separators '/')
+     * @return the canonical path, or null if path traversal above root.
+     */
+    static string canonicalEncodedPath(string path) {
+        if (path.empty)
+            return path;
+
+        bool slash = true;
+        int end = cast(int)path.length;
+        int i = 0;
+
+        loop:
+        while (i < end) {
+            char c = path[i];
+            switch (c) {
+                case '/':
+                    slash = true;
+                    break;
+
+                case '.':
+                    if (slash)
+                        break loop;
+                    slash = false;
+                    break;
+
+                case '?':
+                    return path;
+
+                default:
+                    slash = false;
+            }
+
+            i++;
+        }
+
+        if (i == end)
+            return path;
+
+        StringBuilder canonical = new StringBuilder(path.length);
+        canonical.append(path, 0, i);
+
+        int dots = 1;
+        i++;
+        while (i <= end) {
+            char c = i < end ? path[i] : '\0';
+            switch (c) {
+                case '\0':
+                case '/':
+                case '?':
+                    switch (dots) {
+                        case 0:
+                            if (c != '\0')
+                                canonical.append(c);
+                            break;
+
+                        case 1:
+                            if (c == '?')
+                                canonical.append(c);
+                            break;
+
+                        case 2:
+                            if (canonical.length < 2)
+                                return null;
+                            canonical.setLength(canonical.length - 1);
+                            canonical.setLength(canonical.lastIndexOf("/") + 1);
+                            if (c == '?')
+                                canonical.append(c);
+                            break;
+                        default:
+                            while (dots-- > 0)
+                                canonical.append('.');
+                            if (c != '\0')
+                                canonical.append(c);
+                    }
+
+                    slash = true;
+                    dots = 0;
+                    break;
+
+                case '.':
+                    if (dots > 0)
+                        dots++;
+                    else if (slash)
+                        dots = 1;
+                    else
+                        canonical.append('.');
+                    slash = false;
+                    break;
+
+                default:
+                    while (dots-- > 0)
+                        canonical.append('.');
+                    canonical.append(c);
+                    dots = 0;
+                    slash = false;
+            }
+
+            i++;
+        }
+        return canonical.toString();
+    }
+
+
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * Convert a path to a compact form.
+     * All instances of "//" and "///" etc. are factored out to single "/"
+     *
+     * @param path the path to compact
+     * @return the compacted path
+     */
+    static string compactPath(string path) {
+        if (path == null || path.length == 0)
+            return path;
+
+        int state = 0;
+        int end = cast(int)path.length;
+        int i = 0;
+
+        loop:
+        while (i < end) {
+            char c = path[i];
+            switch (c) {
+                case '?':
+                    return path;
+                case '/':
+                    state++;
+                    if (state == 2)
+                        break loop;
+                    break;
+                default:
+                    state = 0;
+            }
+            i++;
+        }
+
+        if (state < 2)
+            return path;
+
+        StringBuilder buf = new StringBuilder(path.length);
+        buf.append(path, 0, i);
+
+        loop2:
+        while (i < end) {
+            char c = path[i];
+            switch (c) {
+                case '?':
+                    buf.append(path, i, end);
+                    break loop2;
+                case '/':
+                    if (state++ == 0)
+                        buf.append(c);
+                    break;
+                default:
+                    state = 0;
+                    buf.append(c);
+            }
+            i++;
+        }
+
+        return buf.toString();
+    }
+
+    /* ------------------------------------------------------------ */
+
+    /**
+     * @param uri URI
+     * @return True if the uri has a scheme
+     */
+    static bool hasScheme(string uri) {
+        for (int i = 0; i < uri.length; i++) {
+            char c = uri[i];
+            if (c == ':')
+                return true;
+            if (!(c >= 'a' && c <= 'z' ||
+                    c >= 'A' && c <= 'Z' ||
+                    (i > 0 && (c >= '0' && c <= '9' ||
+                            c == '.' ||
+                            c == '+' ||
+                            c == '-'))
+            ))
+                break;
+        }
+        return false;
+    }
+}

+ 202 - 0
frameworks/D/hunt/mmap/http/Parser.d

@@ -0,0 +1,202 @@
+/// Minimalistic low-overhead wrapper for nodejs/http-parser
+/// Used for benchmarks with simple server
+module http.Parser;
+
+import http.Common;
+
+import hunt.logging.ConsoleLogger;
+import std.conv;
+import std.range.primitives;
+import core.stdc.string;
+
+
+
+/* contains name and value of a header (name == NULL if is a continuing line
+ * of a multiline header */
+struct phr_header {
+    const char *name;
+    size_t name_len;
+    const char *value;
+    size_t value_len;
+}
+
+/* returns number of bytes consumed if successful, -2 if request is partial,
+ * -1 if failed */
+extern (C) pure @nogc nothrow int phr_parse_request(const char *buf, size_t len, const char **method, 
+	size_t *method_len, const char **path, size_t *path_len,
+    int *minor_version, phr_header *headers, size_t *num_headers, size_t last_len);
+
+/* ditto */
+extern (C) pure @nogc nothrow int phr_parse_response(const char *_buf, size_t len, int *minor_version, 
+	int *status, const char **msg, size_t *msg_len,
+    phr_header *headers, size_t *num_headers, size_t last_len);
+
+/* ditto */
+extern (C) pure @nogc nothrow int phr_parse_headers(const char *buf, size_t len, 
+	phr_header *headers, size_t *num_headers, size_t last_len);
+
+/* should be zero-filled before start */
+struct phr_chunked_decoder {
+    size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
+    char consume_trailer;       /* if trailing headers should be consumed */
+    char _hex_count;
+    char _state;
+}
+
+/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
+ * encoding headers.  When the function returns without an error, bufsz is
+ * updated to the length of the decoded data available.  Applications should
+ * repeatedly call the function while it returns -2 (incomplete) every time
+ * supplying newly arrived data.  If the end of the chunked-encoded data is
+ * found, the function returns a non-negative number indicating the number of
+ * octets left undecoded at the tail of the supplied buffer.  Returns -1 on
+ * error.
+ */
+extern (C) pure @nogc nothrow ptrdiff_t phr_decode_chunked(phr_chunked_decoder *decoder, char *buf, size_t *bufsz);
+
+/* returns if the chunked decoder is in middle of chunked data */
+extern (C) pure @nogc nothrow int phr_decode_chunked_is_in_data(phr_chunked_decoder *decoder);
+
+
+// =========== Public interface starts here =============
+
+public:
+
+class HttpException : Exception {
+	HttpError error;
+
+	pure @nogc nothrow this(HttpError error, string file = __FILE__,
+			size_t line = __LINE__, Throwable nextInChain = null) {
+		this.error = error;
+		super("Http exception", file, line, nextInChain);
+	}
+}
+
+struct HttpParser(Interceptor) {
+	
+private {
+	Interceptor interceptor;
+	Throwable failure;
+	phr_header[50] _headers;
+	char *_method;
+	char *path;
+
+	int minor_version;
+	size_t buflen = 0, prevbuflen = 0, method_len, path_len, num_headers;
+}
+
+
+	alias interceptor this;
+
+	this(Interceptor interceptor) {
+		this.interceptor = interceptor;
+	}
+
+	@property bool status() pure @safe nothrow {
+		return failure is null;
+	}
+
+	string uri(bool canCopy=false)() {
+		static if(canCopy) {
+			return cast(string)path[0..path_len].dup;
+		} else {
+			return cast(string)path[0..path_len];
+		}
+	}
+
+	@property HttpMethod method() {
+		string s = cast(string)_method[0..method_len];
+		return to!HttpMethod(s);
+	}
+
+
+	HttpHeader[] headers(bool canCopy=false)() {
+		HttpHeader[] hs = new HttpHeader[num_headers];
+		
+		for(int i; i<num_headers; i++) {
+			phr_header* h = &_headers[i];
+			static if(canCopy) {
+				hs[i].name = cast(string)h.name[0..h.name_len].idup;
+				hs[i].value = cast(string)h.value[0..h.value_len].idup;
+			} else {
+				hs[i].name = cast(string)h.name[0..h.name_len];
+				hs[i].value = cast(string)h.value[0..h.value_len];
+			}
+		}
+
+		return hs;
+	}
+
+	@property bool shouldKeepAlive() pure nothrow {
+		return true;
+	}
+
+	@property ushort httpMajor() @safe pure nothrow {
+		return 1;
+	}
+
+	@property ushort httpMinor() @safe pure nothrow {
+		return cast(ushort)minor_version;
+	}
+
+	int execute(const(ubyte)[] str) {
+		return doexecute( str);
+	}
+
+	private int doexecute(const(ubyte)[] chunk) {
+		failure = null;
+		num_headers = cast(int)_headers.length;
+		int pret = phr_parse_request(cast(const char*)chunk.ptr, cast(int)chunk.length, 
+					&_method, &method_len, 
+					&path, &path_len,
+					&minor_version, 
+					_headers.ptr, &num_headers,
+					0);
+		debug {
+			infof("buffer: %d bytes, request: %d bytes", chunk.length, pret);
+		} 
+
+		if(pret > 0) {
+			/* successfully parsed the request */
+			onMessageComplete();
+
+			if(pret < chunk.length) {
+				debug infof("try to parse next request");
+				pret += doexecute(chunk[pret .. $]); // try to parse next http request data
+			}
+
+			debug infof("pret=%d", pret);
+			return pret;
+		} else if(pret == -2) {
+			debug warning("parsing incomplete");
+			num_headers = 0;
+			// failure = new HttpException(HttpError.UNKNOWN);
+			// throw failure;
+
+			debug infof("pret=%d, chunk=%d", pret, chunk.length);
+			return 0;			
+		}
+
+		warning("wrong data format");
+		num_headers = 0;
+		failure = new HttpException(HttpError.UNKNOWN);
+		throw failure;
+	}
+
+	void onMessageComplete() {
+		// interceptor.onHeadersComplete();
+		debug {
+			tracef("method is %s", _method[0..method_len]);
+			tracef("path is %s", path[0..path_len]);
+			tracef("HTTP version is 1.%d", minor_version);
+			foreach(ref phr_header h; _headers[0..num_headers]) {
+				tracef("Header: %s = %s", h.name[0..h.name_len], h.value[0..h.value_len]);
+			}
+		}
+		interceptor.onMessageComplete();
+	}
+}
+
+auto httpParser(Interceptor)(Interceptor interceptor) {
+	return HttpParser!Interceptor(interceptor);
+}

+ 9 - 7
frameworks/D/hunt/source/minihttp/Processor.d → frameworks/D/hunt/mmap/http/Processor.d

@@ -1,17 +1,15 @@
 /// An example "HTTP server" with poor usability but sensible performance
 ///
-module minihttp.Processor;
-
-version(MINIHTTP):
+module http.Processor;
 
 import std.conv;
 import std.array, std.exception, std.format, std.algorithm.mutation, std.socket;
 import core.stdc.stdlib;
 import core.thread, core.atomic;
-import minihttp.Parser;
+import http.Parser;
 
 import hunt.collection.ByteBuffer;
-import minihttp.Common;
+import http.Common;
 import hunt.logging;
 import hunt.io;
 import hunt.util.DateTime;
@@ -61,15 +59,19 @@ public:
 	}
 
 	void run() {
-		client.onReceived((ByteBuffer buffer) { 
+		client.onReceived(delegate int (ubyte[] buffer) { 
 			version(NO_HTTPPARSER) {
 				client.write(cast(ubyte[])ResponseData);
 			} else {
+				int len = 0;
 				try {
-					parser.execute(cast(ubyte[]) buffer.getRemaining());
+					len = parser.execute(buffer);
 				} catch(Exception ex) {
 					respondWith(ex.msg, 500);
+					len = cast(int)buffer.length;
 				}
+
+				return len;
 			}
 		})
 		.onClosed(() {

+ 122 - 0
frameworks/D/hunt/mmap/http/Server.d

@@ -0,0 +1,122 @@
+module http.Server;
+
+import hunt.event;
+import hunt.io;
+import hunt.logging.ConsoleLogger;
+import hunt.system.Memory : totalCPUs;
+import hunt.util.DateTime;
+
+import std.array;
+import std.conv;
+import std.json;
+import std.socket;
+import std.string;
+import std.stdio;
+
+import http.Parser;
+import http.Processor;
+
+shared static this() {
+	DateTimeHelper.startClock();
+}
+
+import hunt.io.channel;
+
+/**
+*/
+abstract class AbstractTcpServer {
+	protected EventLoopGroup _group = null;
+	protected bool _isStarted = false;
+	protected Address _address;
+	protected int _workersCount;
+	TcpStreamOption _tcpStreamoption;
+
+	this(Address address, int thread = (totalCPUs - 1), int workersCount = 0) {
+		this._address = address;
+		_tcpStreamoption = TcpStreamOption.createOption();
+		_tcpStreamoption.bufferSize = 1024 * 2;
+		_tcpStreamoption.isKeepalive = false;
+		_group = new EventLoopGroup(cast(uint) thread);
+		this._workersCount = workersCount;
+	}
+
+	@property Address bindingAddress() {
+		return _address;
+	}
+
+	void start() {
+		if (_isStarted)
+			return;
+		_isStarted = true;
+
+		Socket server = new TcpSocket();
+		server.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, true);
+		server.bind(new InternetAddress("0.0.0.0", 8080));
+		server.listen(8192);
+
+		trace("Launching server");
+		debug {
+			_group.start();
+		} else {
+			_group.start(100);
+		}
+
+		if (_workersCount) {
+			defaultPoolThreads = _workersCount;
+			workerPool(); // Initilize worker poll
+		}
+		writefln("worker count: %d", _workersCount);
+		writefln("IO thread: %d", _group.size);
+
+		while (true) {
+			try {
+				version (HUNT_DEBUG)
+					trace("Waiting for server.accept()");
+
+				Socket socket = server.accept();
+				version (HUNT_DEBUG) {
+					infof("new connection from %s, fd=%d",
+							socket.remoteAddress.toString(), socket.handle());
+				}
+				// EventLoop loop = _group.nextLoop();
+				EventLoop loop = _group.nextLoop(socket.handle);
+				TcpStream stream = new TcpStream(loop, socket, _tcpStreamoption);
+				onConnectionAccepted(stream);
+			} catch (Exception e) {
+				warningf("Failure on accepting %s", e);
+				break;
+			}
+		}
+		_isStarted = false;
+	}
+
+	protected void onConnectionAccepted(TcpStream client);
+
+	void stop() {
+		if (!_isStarted)
+			return;
+		_isStarted = false;
+		_group.stop();
+	}
+}
+
+alias ProcessorCreater = HttpProcessor delegate(TcpStream client);
+
+/**
+*/
+class HttpServer(T) : AbstractTcpServer if (is(T : HttpProcessor)) {
+
+	this(string ip, ushort port, int thread = (totalCPUs - 1)) {
+		super(new InternetAddress(ip, port), thread);
+	}
+
+	this(Address address, int thread = (totalCPUs - 1)) {
+		super(address, thread);
+	}
+
+	override protected void onConnectionAccepted(TcpStream client) {
+		HttpProcessor httpProcessor = new T(client);
+		httpProcessor.run();
+	}
+
+}

+ 361 - 0
frameworks/D/hunt/mmap/http/UrlEncoded.d

@@ -0,0 +1,361 @@
+module http.UrlEncoded;
+
+import hunt.collection.List;
+import hunt.collection.MultiMap;
+import hunt.collection.StringBuffer;
+import hunt.Exceptions;
+import hunt.logging;
+import hunt.text.Charset;
+import hunt.text.Common;
+import hunt.text.StringBuilder;
+import hunt.util.TypeUtils;
+
+import std.conv;
+import std.array;
+
+
+/**
+ * Handles coding of MIME "x-www-form-urlencoded".
+ * <p>
+ * This class handles the encoding and decoding for either the query string of a
+ * URL or the _content of a POST HTTP request.
+ * </p>
+ * <b>Notes</b>
+ * <p>
+ * The UTF-8 charset is assumed, unless otherwise defined by either passing a
+ * parameter or setting the "org.hunt.utils.UrlEncoding.charset" System
+ * property.
+ * </p>
+ * <p>
+ * The hashtable either contains string single values, vectors of string or
+ * arrays of Strings.
+ * </p>
+ * <p>
+ * This class is only partially synchronised. In particular, simple get
+ * operations are not protected from concurrent updates.
+ * </p>
+ *
+ * @see java.net.URLEncoder
+ */
+class UrlEncoded  : MultiMap!string { 
+    
+    enum string ENCODING = StandardCharsets.UTF_8;
+
+
+    this() {
+    }
+
+    this(string query) {
+        decodeTo(query, this, ENCODING);
+    }
+
+    void decode(string query) {
+        decodeTo(query, this, ENCODING);
+    }
+
+    void decode(string query, string charset) {
+        decodeTo(query, this, charset);
+    }
+
+    /**
+     * Encode MultiMap with % encoding for UTF8 sequences.
+     *
+     * @return the MultiMap as a string with % encoding
+     */
+    string encode() {
+        return encode(ENCODING, false);
+    }
+
+    /**
+     * Encode MultiMap with % encoding for arbitrary string sequences.
+     *
+     * @param charset the charset to use for encoding
+     * @return the MultiMap as a string encoded with % encodings
+     */
+    string encode(string charset) {
+        return encode(charset, false);
+    }
+
+    /**
+     * Encode MultiMap with % encoding.
+     *
+     * @param charset            the charset to encode with
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     *                           for parameters without a value. e.g. <code>"blah?a=&amp;b=&amp;c="</code>.
+     * @return the MultiMap as a string encoded with % encodings
+     */
+    string encode(string charset, bool equalsForNullValue) {
+        return encode(this, charset, equalsForNullValue);
+    }
+
+    /**
+     * Encode MultiMap with % encoding.
+     *
+     * @param map                the map to encode
+     * @param charset            the charset to use for encoding (uses default encoding if null)
+     * @param equalsForNullValue if True, then an '=' is always used, even
+     *                           for parameters without a value. e.g. <code>"blah?a=&amp;b=&amp;c="</code>.
+     * @return the MultiMap as a string encoded with % encodings.
+     */
+    static string encode(MultiMap!string map, string charset, bool equalsForNullValue) {
+        if (charset is null)
+            charset = ENCODING;
+
+        StringBuilder result = new StringBuilder(128);
+
+        bool delim = false;
+        foreach(string key, List!string list; map)
+        {
+            int s = list.size();
+
+            if (delim) {
+                result.append('&');
+            }
+
+            if (s == 0) {
+                result.append(encodeString(key, charset));
+                if (equalsForNullValue)
+                    result.append('=');
+            } else {
+                for (int i = 0; i < s; i++) {
+                    if (i > 0)
+                        result.append('&');
+                    string val = list.get(i);
+                    result.append(encodeString(key, charset));
+
+                    if (val != null) {
+                        if (val.length > 0) {
+                            result.append('=');
+                            result.append(encodeString(val, charset));
+                        } else if (equalsForNullValue)
+                            result.append('=');
+                    } else if (equalsForNullValue)
+                        result.append('=');
+                }
+            }
+            delim = true;
+        }
+        return result.toString();
+    }
+
+    /**
+     * Decoded parameters to Map.
+     *
+     * @param content the string containing the encoded parameters
+     * @param map     the MultiMap to put parsed query parameters into
+     * @param charset the charset to use for decoding
+     */
+    static void decodeTo(string content, MultiMap!string map, string charset = ENCODING) {
+        if (charset.empty)
+            charset = ENCODING;
+
+        synchronized (map) {
+            string key = null;
+            string value = null;
+            int mark = -1;
+            bool encoded = false;
+            for (int i = 0; i < content.length; i++) {
+                char c = content[i];
+                switch (c) {
+                    case '&':
+                        int l = i - mark - 1;
+                        value = l == 0 ? "" :
+                                (encoded ? decodeString(content, mark + 1, l) : content.substring(mark + 1, i));
+                        mark = i;
+                        encoded = false;
+                        if (key != null) {
+                            map.add(key, value);
+                        } else if (value != null && value.length > 0) {
+                            map.add(value, "");
+                        }
+                        key = null;
+                        value = null;
+                        break;
+                    case '=':
+                        if (key != null)
+                            break;
+                        key = encoded ? decodeString(content, mark + 1, i - mark - 1) : content.substring(mark + 1, i);
+                        mark = i;
+                        encoded = false;
+                        break;
+                    case '+':
+                        encoded = true;
+                        break;
+                    case '%':
+                        encoded = true;
+                        break;
+                    default: break;
+                }
+            }
+
+            int contentLen = cast(int)content.length;
+
+            if (key != null) {
+                int l =  contentLen - mark - 1;
+                value = l == 0 ? "" : (encoded ? decodeString(content, mark + 1, l) : content.substring(mark + 1));
+                version(HUNT_DEBUG) tracef("key=%s, value=%s", key, value);
+                map.add(key, value);
+            } else if (mark < contentLen) {
+                version(HUNT_DEBUG) tracef("empty value: content=%s, key=%s", content, key);
+                key = encoded
+                        ? decodeString(content, mark + 1, contentLen - mark - 1, charset)
+                        : content.substring(mark + 1);
+                if (!key.empty) {
+                    map.add(key, "");
+                }
+            } else {
+                warningf("No key found.");
+            }
+        }
+    }
+
+    /**
+     * Decode string with % encoding.
+     * This method makes the assumption that the majority of calls
+     * will need no decoding.
+     *
+     * @param encoded the encoded string to decode
+     * @return the decoded string
+     */
+    static string decodeString(string encoded) {
+        return decodeString(encoded, 0, cast(int)encoded.length);
+    }
+
+    /**
+     * Decode string with % encoding.
+     * This method makes the assumption that the majority of calls
+     * will need no decoding.
+     *
+     * @param encoded the encoded string to decode
+     * @param offset  the offset in the encoded string to decode from
+     * @param length  the length of characters in the encoded string to decode
+     * @param charset the charset to use for decoding
+     * @return the decoded string
+     */
+    static string decodeString(string encoded, int offset, int length, string charset = ENCODING) {
+        StringBuffer buffer = null;
+
+        for (int i = 0; i < length; i++) {
+            char c = encoded.charAt(offset + i);
+            if (c < 0 || c > 0xff) {
+                if (buffer is null) {
+                    buffer = new StringBuffer(length);
+                    buffer.append(encoded, offset, offset + i + 1);
+                } else
+                    buffer.append(c);
+            } else if (c == '+') {
+                if (buffer is null) {
+                    buffer = new StringBuffer(length);
+                    buffer.append(encoded, offset, offset + i);
+                }
+
+                buffer.append(' ');
+            } else if (c == '%') {
+                if (buffer is null) {
+                    buffer = new StringBuffer(length);
+                    buffer.append(encoded, offset, offset + i);
+                }
+
+                byte[] ba = new byte[length];
+                int n = 0;
+                while (c >= 0 && c <= 0xff) {
+                    if (c == '%') {
+                        if (i + 2 < length) {
+                            int o = offset + i + 1;
+                            i += 3;
+                            ba[n] = cast(byte) TypeUtils.parseInt(encoded, o, 2, 16);
+                            n++;
+                        } else {
+                            ba[n++] = cast(byte) '?';
+                            i = length;
+                        }
+                    } else if (c == '+') {
+                        ba[n++] = cast(byte) ' ';
+                        i++;
+                    } else {
+                        ba[n++] = cast(byte) c;
+                        i++;
+                    }
+
+                    if (i >= length)
+                        break;
+                    c = encoded.charAt(offset + i);
+                }
+
+                i--;
+                buffer.append(cast(string)(ba[0 .. n]));
+
+            } else if (buffer !is null)
+                buffer.append(c);
+        }
+
+        if (buffer is null) {
+            if (offset == 0 && encoded.length == length)
+                return encoded;
+            return encoded.substring(offset, offset + length);
+        }
+
+        return buffer.toString();
+    }
+
+
+    /**
+     * Perform URL encoding.
+     *
+     * @param string the string to encode
+     * @return encoded string.
+     */
+    static string encodeString(string string) {
+        return encodeString(string, ENCODING);
+    }
+
+    /**
+     * Perform URL encoding.
+     *
+     * @param string  the string to encode
+     * @param charset the charset to use for encoding
+     * @return encoded string.
+     */
+    static string encodeString(string str, string charset) {
+        if (charset is null)
+            charset = ENCODING;
+        byte[] bytes = cast(byte[])str;
+        // bytes = string.getBytes(charset);
+
+        int len = cast(int)bytes.length;
+        byte[] encoded = new byte[bytes.length * 3];
+        int n = 0;
+        bool noEncode = true;
+
+        for (int i = 0; i < len; i++) {
+            byte b = bytes[i];
+
+            if (b == ' ') {
+                noEncode = false;
+                encoded[n++] = cast(byte) '+';
+            } else if (b >= 'a' && b <= 'z' ||
+                    b >= 'A' && b <= 'Z' ||
+                    b >= '0' && b <= '9') {
+                encoded[n++] = b;
+            } else {
+                noEncode = false;
+                encoded[n++] = cast(byte) '%';
+                byte nibble = cast(byte) ((b & 0xf0) >> 4);
+                if (nibble >= 10)
+                    encoded[n++] = cast(byte) ('A' + nibble - 10);
+                else
+                    encoded[n++] = cast(byte) ('0' + nibble);
+                nibble = cast(byte) (b & 0xf);
+                if (nibble >= 10)
+                    encoded[n++] = cast(byte) ('A' + nibble - 10);
+                else
+                    encoded[n++] = cast(byte) ('0' + nibble);
+            }
+        }
+
+        if (noEncode)
+            return str;
+
+        return cast(string)(encoded[0 .. n]);
+    }
+}

+ 47 - 0
frameworks/D/hunt/pico/app.d

@@ -0,0 +1,47 @@
+/*
+ * Collie - An asynchronous event-driven network framework using Dlang development
+ *
+ * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd 
+ *
+ * Developer: Putao's Dlang team
+ *
+ * Licensed under the Apache-2.0 License.
+ *
+ */
+import std.getopt;
+import std.stdio;
+
+import hunt.database;
+import hunt.io;
+import hunt.system.Memory : totalCPUs;
+import http.Processor;
+import http.Server;
+import http.DemoProcessor;
+
+void main(string[] args) {
+	ushort port = 8080;
+	GetoptResult o = getopt(args, "port|p", "Port (default 8080)", &port);
+	if (o.helpWanted) {
+		defaultGetoptPrinter("A mini-http server powered by Hunt!", o.options);
+		return;
+	}
+
+	version (POSTGRESQL) {
+		DatabaseOption options;
+		debug {
+			options = new DatabaseOption(
+					"postgresql://benchmarkdbuser:[email protected]:5432/hello_world?charset=utf-8");
+		} else {
+			options = new DatabaseOption(
+					"postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?charset=utf-8");
+		}
+		
+		options.setMinimumConnection(totalCPUs*3);
+		options.setMaximumConnection(totalCPUs*3);
+		dbConnection = new Database(options);
+	}
+
+	AbstractTcpServer httpServer = new HttpServer!(DemoProcessor)("0.0.0.0", port, totalCPUs);
+	writefln("listening on http://%s", httpServer.bindingAddress.toString());
+	httpServer.start();
+}

+ 85 - 0
frameworks/D/hunt/pico/http/Common.d

@@ -0,0 +1,85 @@
+module http.Common;
+
+
+
+public enum HttpParserType : uint {
+	request = 0,
+	response = 1,
+	both = 2
+}
+
+struct HttpHeader {
+	string name, value;
+}
+
+public enum HttpMethod : uint {
+	DELETE = 0,
+	GET = 1,
+	HEAD = 2,
+	POST = 3,
+	PUT = 4,
+	/* pathological */
+	CONNECT = 5,
+	OPTIONS = 6,
+	TRACE = 7,
+	/* WebDAV */
+	COPY = 8,
+	LOCK = 9,
+	MKCOL = 10,
+	MOVE = 11,
+	PROPFIND = 12,
+	PROPPATCH = 13,
+	SEARCH = 14,
+	UNLOCK = 15,
+	BIND = 16,
+	REBIND = 17,
+	UNBIND = 18,
+	ACL = 19,
+	/* subversion */
+	REPORT = 20,
+	MKACTIVITY = 21,
+	CHECKOUT = 22,
+	MERGE = 23,
+	/* upnp */
+	MSEARCH = 24,
+	NOTIFY = 25,
+	SUBSCRIBE = 26,
+	UNSUBSCRIBE = 27,
+	/* RFC-5789 */
+	PATCH = 28,
+	PURGE = 29,
+	/* CalDAV */
+	MKCALENDAR = 30,
+	/* RFC-2068, section 19.6.1.2 */
+	LINK = 31,
+	UNLINK = 32,
+	/* icecast */
+	SOURCE = 33,
+}
+
+enum HttpError : uint {
+	OK,
+	/* Parsing-related errors */
+	INVALID_EOF_STATE,
+	HEADER_OVERFLOW,
+	CLOSED_CONNECTION,
+	INVALID_VERSION,
+	INVALID_STATUS,
+	INVALID_METHOD,
+	INVALID_URL,
+	INVALID_HOST,
+	INVALID_PORT,
+	INVALID_PATH,
+	INVALID_QUERY_STRING,
+	INVALID_FRAGMENT,
+	LF_EXPECTED,
+	INVALID_HEADER_TOKEN,
+	INVALID_CONTENT_LENGTH,
+	UNEXPECTED_CONTENT_LENGTH,
+	INVALID_CHUNK_SIZE,
+	INVALID_CONSTANT,
+	INVALID_INTERNAL_STATE,
+	STRICT,
+	PAUSED,
+	UNKNOWN
+}

+ 10 - 10
frameworks/D/hunt/source/minihttp/DemoProcessor.d → frameworks/D/hunt/pico/http/DemoProcessor.d

@@ -1,16 +1,16 @@
-module minihttp.DemoProcessor;
+module http.DemoProcessor;
+
 
-version(MINIHTTP):
 
 // import stdx.data.json;
 import std.json;
 
 import hunt.database;
 import hunt.io;
-import minihttp.Common;
-import minihttp.Processor;
-import minihttp.HttpURI;
-import minihttp.UrlEncoded;
+import http.Common;
+import http.Processor;
+import http.HttpURI;
+import http.UrlEncoded;
 import hunt.logging.ConsoleLogger : trace, warning, tracef;
 
 import std.algorithm;
@@ -152,7 +152,7 @@ class DemoProcessor : HttpProcessor {
 
 
         private void respondSingleQuery() {
-            int id = uniform(1, 10000);
+            int id = uniform(1, 10001);
             string query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
             ResultSet rs = dbConnection.query(query);
 
@@ -170,7 +170,7 @@ class DemoProcessor : HttpProcessor {
 
             JSONValue[] arr = new JSONValue[queries];
             for (int i = 0; i < queries; i++) {
-                immutable id = uniform(1, 10000);
+                immutable id = uniform(1, 10001);
                 immutable query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
                 ResultSet rs = dbConnection.query(query);
 
@@ -226,14 +226,14 @@ class DemoProcessor : HttpProcessor {
 
             JSONValue[] arr = new JSONValue[queries];
             for (int i = 0; i < queries; i++) {
-                immutable id = uniform(1, 10000);
+                immutable id = uniform(1, 10001);
                 immutable idString = id.to!string;
                 immutable query = "SELECT randomNumber FROM world WHERE id = " ~ idString;
                 ResultSet rs = dbConnection.query(query);
                 int randomNumber = to!int(rs.front()[0]);
                 debug tracef("id=%d, randomNumber=%d", id, randomNumber);
 
-                randomNumber = uniform(1, 10000);
+                randomNumber = uniform(1, 10001);
                 string updateSql = "UPDATE world SET randomNumber = "
                     ~ randomNumber.to!string ~ "  WHERE id = " ~ idString;
                 int r = dbConnection.execute(updateSql);

+ 3 - 3
frameworks/D/hunt/source/minihttp/HttpURI.d → frameworks/D/hunt/pico/http/HttpURI.d

@@ -1,6 +1,6 @@
-module minihttp.HttpURI;
+module http.HttpURI;
+
 
-version(MINIHTTP):
 
 import hunt.collection.MultiMap;
 
@@ -9,7 +9,7 @@ import hunt.text.Charset;
 import hunt.text.Common;
 import hunt.text.StringBuilder;
 import hunt.util.TypeUtils;
-import minihttp.UrlEncoded;
+import http.UrlEncoded;
 
 import std.array;
 import std.conv;

+ 20 - 15
frameworks/D/hunt/source/minihttp/Parser.d → frameworks/D/hunt/pico/http/Parser.d

@@ -1,10 +1,10 @@
 /// Minimalistic low-overhead wrapper for nodejs/http-parser
 /// Used for benchmarks with simple server
-module minihttp.Parser;
+module http.Parser;
 
-version(MINIHTTP):
 
-import minihttp.Common;
+
+import http.Common;
 
 import hunt.logging.ConsoleLogger;
 import std.conv;
@@ -141,11 +141,13 @@ private {
 		return cast(ushort)minor_version;
 	}
 
-	void execute(const(char)[] str) {
-		execute(cast(const(ubyte)[]) str);
+	int execute(const(ubyte)[] str) {
+		return doexecute( str);
 	}
 
-	void execute(const(ubyte)[] chunk) {
+	private int doexecute(const(ubyte)[] chunk) {
+		debug trace(cast(string)chunk);
+
 		failure = null;
 		num_headers = cast(int)_headers.length;
 		int pret = phr_parse_request(cast(const char*)chunk.ptr, cast(int)chunk.length, 
@@ -164,19 +166,22 @@ private {
 
 			if(pret < chunk.length) {
 				debug infof("try to parse next request");
-				execute(chunk[pret .. $]); // try to parse next http request data
+				pret += doexecute(chunk[pret .. $]); // try to parse next http request data
 			}
-		} else if (pret == -1) {
-			warning("wrong data format");
-			num_headers = 0;
-			failure = new HttpException(HttpError.UNKNOWN);
-			throw failure;
+
+			debug infof("pret=%d", pret);
+			return pret;
 		} else if(pret == -2) {
-			warning("parsing incomplete");
+			debug warning("parsing incomplete");
 			num_headers = 0;
-			failure = new HttpException(HttpError.UNKNOWN);
-			throw failure;
+			debug infof("pret=%d, chunk=%d", pret, chunk.length);
+			return 0;			
 		}
+
+		warning("wrong data format");
+		num_headers = 0;
+		failure = new HttpException(HttpError.UNKNOWN);
+		throw failure;
 	}
 
 	void onMessageComplete() {

+ 137 - 0
frameworks/D/hunt/pico/http/Processor.d

@@ -0,0 +1,137 @@
+/// An example "HTTP server" with poor usability but sensible performance
+///
+module http.Processor;
+
+
+
+import std.conv;
+import std.array, std.exception, std.format, std.algorithm.mutation, std.socket;
+import core.stdc.stdlib;
+import core.thread, core.atomic;
+import http.Parser;
+
+import hunt.collection.ByteBuffer;
+import http.Common;
+import hunt.logging;
+import hunt.io;
+import hunt.util.DateTime;
+
+
+private	alias Parser = HttpParser!HttpProcessor;
+
+
+struct HttpRequest {
+	private Parser* parser;
+
+	HttpHeader[] headers(bool canCopy=false)() @property {
+		return parser.headers!canCopy();
+	}
+
+	HttpMethod method() @property {
+		return parser.method();
+	}
+
+	string uri(bool canCopy=false)() @property {
+		return parser.uri!(canCopy)();
+	}
+}
+
+version(NO_HTTPPARSER) {
+enum string ResponseData = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\nConnection: Keep-Alive\r\nContent-Type: text/plain\r\nServer: Hunt/1.0\r\nDate: Wed, 17 Apr 2013 12:00:00 GMT\r\n\r\nHello, World!";
+}
+
+abstract class HttpProcessor {
+	
+package:
+	Appender!(char[]) outBuf;
+	HttpHeader[] headers; // buffer for headers
+	Parser parser;
+	HttpRequest request;
+	bool serving;
+	
+public:
+	TcpStream client;
+
+	this(TcpStream sock) {
+		serving = true;
+		client = sock;
+		headers = new HttpHeader[1];
+		parser = httpParser(this);
+		request.parser = &parser;
+	}
+
+	void run() {
+		client.onReceived((ByteBuffer buffer) { 
+			version(NO_HTTPPARSER) {
+				client.write(cast(ubyte[])ResponseData);
+			} else {
+				try {
+					int len =  parser.execute(cast(ubyte[]) buffer.getRemaining());
+					buffer.position(buffer.position() + len);
+				} catch(Exception ex) {
+					buffer.clear(); // drop all the  wrong data
+					respondWith(ex.msg, 500);
+				}
+			}
+		})
+		.onClosed(() {
+			// notifyClientClosed();
+		})
+		.onError((string msg) { 
+			debug warning("Error: ", msg); 
+		})
+		.start();
+	}
+
+	protected void notifyClientClosed() {
+		debug tracef("The connection[%s] is closed", client.remoteAddress());
+	}
+
+	void respondWith(string _body, uint status, HttpHeader[] headers...) {
+		return respondWith(cast(const(ubyte)[]) _body, status, headers);
+	}
+
+	void respondWith(const(ubyte)[] _body, uint status, HttpHeader[] headers...) {
+		outBuf.clear();
+		formattedWrite(outBuf, "HTTP/1.1 %s OK\r\n", status);
+		outBuf.put("Server: Hunt/1.0\r\n");
+
+		formattedWrite(outBuf, "Date: %s\r\n", DateTimeHelper.getDateAsGMT());
+		if (!parser.shouldKeepAlive)
+			outBuf.put("Connection: close\r\n");
+		foreach (ref hdr; headers) {
+			outBuf.put(hdr.name);
+			outBuf.put(": ");
+			outBuf.put(hdr.value);
+			outBuf.put("\r\n");
+		}
+		formattedWrite(outBuf, "Content-Length: %d\r\n\r\n", _body.length);
+		outBuf.put(cast(string) _body);
+		client.write(cast(ubyte[]) outBuf.data); // TODO: short-writes are quite possible
+	}
+
+	void onChunk(ref HttpRequest req, const(ubyte)[] chunk) {
+		// TODO: Tasks pending completion - 5/16/2019, 5:40:18 PM
+		// 
+	}
+
+	void onComplete(ref HttpRequest req);
+
+
+	final int onBody(Parser* parser, const(ubyte)[] chunk) {
+		onChunk(request, chunk);
+		return 0;
+	}
+
+	final int onMessageComplete() {
+		try {
+			onComplete(request);
+		} catch(Exception ex) {
+			respondWith(ex.msg, 500);
+		}
+		if (!parser.shouldKeepAlive)
+			serving = false;
+		return 0;
+	}
+
+}

+ 4 - 4
frameworks/D/hunt/source/minihttp/Server.d → frameworks/D/hunt/pico/http/Server.d

@@ -1,6 +1,6 @@
-module minihttp.Server;
+module http.Server;
+
 
-version(MINIHTTP):
 
 import hunt.event;
 import hunt.io;
@@ -15,8 +15,8 @@ import std.socket;
 import std.string;
 import std.stdio;
 
-import minihttp.Parser;
-import minihttp.Processor;
+import http.Parser;
+import http.Processor;
 
 shared static this() {
 	DateTimeHelper.startClock();

+ 2 - 2
frameworks/D/hunt/source/minihttp/UrlEncoded.d → frameworks/D/hunt/pico/http/UrlEncoded.d

@@ -1,6 +1,6 @@
-module minihttp.UrlEncoded;
+module http.UrlEncoded;
+
 
-version(MINIHTTP):
 
 import hunt.collection.List;
 import hunt.collection.MultiMap;

+ 0 - 90
frameworks/D/hunt/source/app.d

@@ -1,90 +0,0 @@
-/*
- * Collie - An asynchronous event-driven network framework using Dlang development
- *
- * Copyright (C) 2015-2018  Shanghai Putao Technology Co., Ltd 
- *
- * Developer: Putao's Dlang team
- *
- * Licensed under the Apache-2.0 License.
- *
- */
- version(MINIHTTP)
- {
-	import std.getopt;
-	import std.stdio;
-
-	import hunt.database;
-	import hunt.io;
-	import hunt.system.Memory : totalCPUs;
-	import minihttp.Processor;
-	import minihttp.Server;
-	import minihttp.DemoProcessor;
-
-	void main(string[] args) {
-		ushort port = 8080;
-		GetoptResult o = getopt(args, "port|p", "Port (default 8080)", &port);
-		if (o.helpWanted) {
-			defaultGetoptPrinter("A mini-http server powered by Hunt!", o.options);
-			return;
-		}
-
-		version (POSTGRESQL) {
-			DatabaseOption options;
-			debug {
-				options = new DatabaseOption(
-						"postgresql://benchmarkdbuser:[email protected]:5432/hello_world?charset=utf-8");
-			} else {
-				options = new DatabaseOption(
-						"postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?charset=utf-8");
-			}
-			options.setMinimumConnection(totalCPUs*3);
-			options.setMaximumConnection(totalCPUs*3);
-			dbConnection = new Database(options);
-		}
-
-		AbstractTcpServer httpServer = new HttpServer!(DemoProcessor)("0.0.0.0", port, totalCPUs);
-		writefln("listening on http://%s", httpServer.bindingAddress.toString());
-		httpServer.start();
-	}
-
- }
-else
-{
-
-import std.getopt;
-import std.stdio;
-
-import hunt.database;
-import hunt.io;
-import hunt.system.Memory : totalCPUs;
-import http.Processor;
-import http.Server;
-import http.DemoProcessor;
-
-void main(string[] args) {
-	ushort port = 8080;
-	GetoptResult o = getopt(args, "port|p", "Port (default 8080)", &port);
-	if (o.helpWanted) {
-		defaultGetoptPrinter("A simple http server powered by Hunt!", o.options);
-		return;
-	}
-
-	version (POSTGRESQL) {
-		DatabaseOption options;
-		debug {
-			options = new DatabaseOption(
-					"postgresql://benchmarkdbuser:[email protected]:5432/hello_world?charset=utf-8");
-		} else {
-			options = new DatabaseOption(
-					"postgresql://benchmarkdbuser:benchmarkdbpass@tfb-database:5432/hello_world?charset=utf-8");
-		}
-		options.setMinimumConnection(totalCPUs*2);
-		options.setMaximumConnection(totalCPUs*2);
-		dbConnection = new Database(options);
-	}
-
-	AbstractTcpServer httpServer = new HttpServer!(DemoProcessor)("0.0.0.0", port, totalCPUs);
-	writefln("listening on http://%s", httpServer.bindingAddress.toString());
-	httpServer.start();
-}
-}