Commit 8ab29186 authored by Matthias Klumpp's avatar Matthias Klumpp

New upstream version 0.8.2

parent 5042c82d
......@@ -9,21 +9,25 @@ services:
addons:
apt:
packages:
- libevent-dev
- libssl-dev
- pkg-config
- zlib1g-dev
- pkg-config
- zlib1g-dev
- libevent-dev
- libssl-dev
d:
# order: latest DMD, oldest DMD, LDC/GDC, remaining DMD versions
# this way the overall test time gets cut down (GDC/LDC are a lot
# slower tham DMD, so they should be started early), while still
# catching most DMD version related build failures early
- dmd-2.075.0
- dmd-2.077.0
- dmd-2.071.2
- ldc-1.5.0
- ldc-1.4.0
- ldc-1.3.0
- ldc-1.2.0
- ldc-1.1.1
- dmd-2.076.1
- dmd-2.075.1
- dmd-2.074.1
- dmd-2.073.2
- dmd-2.072.2
......@@ -50,11 +54,12 @@ matrix:
env: VIBED_DRIVER=libasync BUILD_EXAMPLE=0 RUN_TEST=0
before_install:
- pip3 install meson>=0.40
- pyenv global system 3.5
- pip3 install 'meson>=0.42,!=0.43'
install:
- mkdir .ntmp
- curl -L https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-linux.zip -o .ntmp/ninja-linux.zip
- curl -L https://github.com/ninja-build/ninja/releases/download/v1.8.2/ninja-linux.zip -o .ntmp/ninja-linux.zip
- unzip .ntmp/ninja-linux.zip -d .ntmp
before_script:
......
This diff is collapsed.
......@@ -10,7 +10,7 @@ available.
Visit the website at <http://vibed.org/> for more information.
[![DUB Package](https://img.shields.io/dub/v/vibe-d.svg)](https://code.dlang.org/packages/vibe-d)
[![Posix Build Status](https://travis-ci.org/rejectedsoftware/vibe.d.svg?branch=master)](https://travis-ci.org/rejectedsoftware/vibe.d)
[![Posix Build Status](https://travis-ci.org/vibe-d/vibe.d.svg?branch=master)](https://travis-ci.org/vibe-d/vibe.d)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/cp2kxg70h54pga9d/branch/master?svg=true)](https://ci.appveyor.com/project/s-ludwig/vibe-d/branch/master)
Hello Vibe.d
......@@ -48,11 +48,15 @@ Support
Vibe.d aims to support at least the 3 latest minor releases of D.
At the moment, the following compilers are supported and tested:
- DMD 2.075.0
- DMD 2.077.0
- DMD 2.076.1
- DMD 2.075.1
- DMD 2.074.1
- DMD 2.073.2
- DMD 2.072.2 **Warning: 2.072.0 and 2.072.1 are affected by a serious DMD regression: [16980](https://issues.dlang.org/show_bug.cgi?id=16980)**
- DMD 2.071.2
- LDC 1.5.0 (FE: 2.075.1)
- LDC 1.4.0 (FE: 2.074.1)
- LDC 1.3.0 (FE: 2.073.2)
- LDC 1.2.0 (FE: 2.072.2)
- LDC 1.1.1 (FE: 2.071.2)
......
......@@ -2,19 +2,27 @@ platform: x64
environment:
matrix:
- DC: dmd
DVersion: 2.075.0
DVersion: 2.077.0
arch: x86_mscoff
config: vibe-core
- DC: dmd
DVersion: 2.075.0
DVersion: 2.077.0
arch: x86
config: win32
- DC: dmd
DVersion: 2.075.0
DVersion: 2.077.0
arch: x64
config: win32
- DC: dmd
DVersion: 2.075.0
DVersion: 2.077.0
arch: x86_mscoff
config: win32
- DC: dmd
DVersion: 2.076.1
arch: x86_mscoff
config: win32
- DC: dmd
DVersion: 2.075.1
arch: x86_mscoff
config: win32
- DC: dmd
......
......@@ -55,5 +55,5 @@ configuration "winrt" {
configuration "vibe-core" {
targetType "none"
dependency "vibe-core" version="~>1.0"
dependency "vibe-core" version=">=1.2.1-alpha.2 <2.0.0-0"
}
......@@ -16,7 +16,7 @@ module vibe.core.args;
import vibe.core.log;
import vibe.data.json;
import std.algorithm : any, map, sort;
import std.algorithm : any, filter, map, sort, startsWith;
import std.array : array, join, replicate, split;
import std.exception;
import std.file;
......@@ -165,6 +165,31 @@ bool finalizeCommandLineOptions(string[]* args_out = null)
return true;
}
/**
This functions allows the usage of a custom command line argument parser
with vibe.d.
$(OL
$(LI build executable with version(VibeDisableCommandLineParsing))
$(LI parse main function arguments with a custom command line parser)
$(LI pass vibe.d arguments to `setCommandLineArgs`)
$(LI use vibe.d command line parsing utilities)
)
Params:
args = The arguments that should be handled by vibe.d
*/
void setCommandLineArgs(string[] args)
{
g_args = args;
}
///
unittest {
import std.format : format;
string[] args = ["--foo", "10"];
setCommandLineArgs(args);
}
private struct OptionInfo {
string[] names;
......@@ -197,7 +222,7 @@ private void init()
import vibe.utils.string : stripUTF8Bom;
version (VibeDisableCommandLineParsing) {}
else g_args = Runtime.args;
else g_args = Runtime.args.filter!(arg => !arg.startsWith("--DRT-")).array;
if (!g_args.length) g_args = ["dummy"];
......
......@@ -987,7 +987,7 @@ void setTaskEventCallback(TaskEventCb func)
/**
A version string representing the current vibe.d version
*/
enum vibeVersionString = "0.8.1";
enum vibeVersionString = "0.8.2";
/**
......
......@@ -60,43 +60,33 @@ version (Windows)
else
version(VibePragmaLib) pragma(lib, "event");
version(Windows)
{
version(Windows) {
import core.sys.windows.winsock2;
alias EWOULDBLOCK = WSAEWOULDBLOCK;
}
version(OSX)
{
static if (__VERSION__ < 2077)
{
} else version(OSX) {
static if (__VERSION__ < 2077) {
enum IP_ADD_MEMBERSHIP = 12;
enum IP_MULTICAST_LOOP = 11;
}
else
import core.sys.darwin.netinet.in_ : IP_ADD_MEMBERSHIP, IP_MULTICAST_LOOP;
} else version(FreeBSD)
{
static if (__VERSION__ < 2077)
{
} else version (FreeBSD) {
static if (__VERSION__ < 2077) {
enum IP_ADD_MEMBERSHIP = 12;
enum IP_MULTICAST_LOOP = 11;
}
else
import core.sys.freebsd.netinet.in_ : IP_ADD_MEMBERSHIP, IP_MULTICAST_LOOP;
} else version(linux)
{
static if (__VERSION__ < 2077)
{
} else version (linux) {
static if (__VERSION__ < 2077) {
enum IP_ADD_MEMBERSHIP = 35;
enum IP_MULTICAST_LOOP = 34;
}
else
import core.sys.linux.netinet.in_ : IP_ADD_MEMBERSHIP, IP_MULTICAST_LOOP;
} else version(Windows)
{
// IP_ADD_MEMBERSHIP and IP_MULTICAST_LOOP are included in winsock(2) import above
} else version (Solaris) {
enum IP_ADD_MEMBERSHIP = 0x13;
enum IP_MULTICAST_LOOP = 0x12;
}
final class Libevent2Driver : EventDriver {
......@@ -396,7 +386,7 @@ final class Libevent2Driver : EventDriver {
int tmp_reuse = 1;
socketEnforce(() @trusted { return setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &tmp_reuse, tmp_reuse.sizeof); } () == 0,
"Error enabling socket address reuse on listening socket");
version (linux) {
static if (is(typeof(SO_REUSEPORT))) {
if (options & TCPListenOptions.reusePort) {
if (() @trusted { return setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &tmp_reuse, tmp_reuse.sizeof); } ()) {
if (errno != EINVAL && errno != ENOPROTOOPT) {
......
......@@ -43,11 +43,10 @@ version (Windows) {
alias SystemSocketException = WSAErrorException;
} else alias SystemSocketException = ErrnoException;
version (linux) {
import core.sys.posix.sys.socket;
static if (!is(typeof(SO_REUSEPORT))) {
enum { SO_REUSEPORT = 15 }
}
import core.sys.posix.sys.socket;
static if (!is(typeof(SO_REUSEPORT))) {
version (linux) enum SO_REUSEPORT = 15;
else enum SO_REUSEPORT = 0x200;
}
T socketEnforce(T)(T value, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
......
......@@ -151,7 +151,7 @@ struct Path {
@property bool absolute() const { return m_absolute; }
/// Forward compatibility property for vibe-code
@property immutable(PathEntry)[] bySegment() { return nodes; }
@property immutable(PathEntry)[] bySegment() const { return nodes; }
/// Resolves all '.' and '..' path entries as far as possible.
void normalize()
......
......@@ -67,7 +67,9 @@ struct Task {
// FIXME: this is not thread safe!
@property ref ThreadInfo tidInfo() { return m_fiber ? fiber.tidInfo : s_tidInfo; }
@property ref const(ThreadInfo) tidInfo() const { return m_fiber ? fiber.tidInfo : s_tidInfo; }
@property Tid tid() { return tidInfo.ident; }
@property const(Tid) tid() const { return tidInfo.ident; }
}
/// Reserved for internal use!
......
......@@ -67,6 +67,7 @@ import std.base64;
import std.bitmanip;
import std.conv;
import std.datetime;
import std.uuid: UUID;
import std.exception;
import std.range;
import std.traits;
......@@ -216,6 +217,8 @@ struct Bson {
this(long value) { opAssign(value); }
/// ditto
this(in Json value) { opAssign(value); }
/// ditto
this(in UUID value) { opAssign(value); }
/**
Assigns a D type to a BSON value.
......@@ -346,6 +349,11 @@ struct Bson {
m_type = writeBson(app, value);
m_data = app.data;
}
/// ditto
void opAssign(in UUID value)
{
opAssign(BsonBinData(BsonBinData.Type.uuid, value.data.idup));
}
/**
Returns the BSON type of this value.
......@@ -429,6 +437,13 @@ struct Bson {
pragma(msg, "Bson.get!Json() and Bson.opCast!Json() will soon be removed. Please use Bson.toJson() instead.");
return this.toJson();
}
else static if( is(T == UUID) ){
checkType(Type.binData);
auto bbd = this.get!BsonBinData();
enforce(bbd.type == BsonBinData.Type.uuid, "BsonBinData value is type '"~to!string(bbd.type)~"', expected to be uuid");
const ubyte[16] b = bbd.rawData;
return UUID(b);
}
else static assert(false, "Cannot cast "~typeof(this).stringof~" to '"~T.stringof~"'.");
}
......@@ -589,6 +604,18 @@ struct Bson {
assert(value["c"] == Bson("foo"));
}
///
unittest {
auto srcUuid = UUID("00010203-0405-0607-0809-0a0b0c0d0e0f");
Bson b = srcUuid;
auto u = b.get!UUID();
assert(b.type == Bson.Type.binData);
assert(b.get!BsonBinData().type == BsonBinData.Type.uuid);
assert(u == srcUuid);
}
/** Allows index based access of a BSON array value.
Returns a null value if the index is out of bounds.
......
......@@ -1192,7 +1192,7 @@ Json parseJson(R)(ref R range, int* line = null, string filename = null)
if( is_float ) {
ret = to!double(num);
} else if (is_long_overflow) {
ret = () @trusted { return BigInt(num); } ();
ret = () @trusted { return BigInt(num.to!string); } ();
} else {
ret = to!long(num);
}
......@@ -1267,32 +1267,50 @@ Json parseJsonString(string str, string filename = null)
}
@safe unittest {
assert(parseJsonString("null") == Json(null));
assert(parseJsonString("true") == Json(true));
assert(parseJsonString("false") == Json(false));
assert(parseJsonString("1") == Json(1));
// These currently don't work at compile time
assert(parseJsonString("17559991181826658461") == Json(BigInt(17559991181826658461UL)));
assert(parseJsonString("99999999999999999999999999") == () @trusted { return Json(BigInt("99999999999999999999999999")); } ());
assert(parseJsonString("2.0") == Json(2.0));
assert(parseJsonString("\"test\"") == Json("test"));
assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)]));
assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)]));
assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234");
auto json = parseJsonString(`{"hey": "This is @à test éhééhhéhéé !%/??*&?\ud83d\udcec"}`);
assert(json.toPrettyString() == parseJsonString(json.toPrettyString()).toPrettyString());
bool test() {
assert(parseJsonString("null") == Json(null));
assert(parseJsonString("true") == Json(true));
assert(parseJsonString("false") == Json(false));
assert(parseJsonString("1") == Json(1));
assert(parseJsonString("2.0") == Json(2.0));
assert(parseJsonString("\"test\"") == Json("test"));
assert(parseJsonString("[1, 2, 3]") == Json([Json(1), Json(2), Json(3)]));
assert(parseJsonString("{\"a\": 1}") == Json(["a": Json(1)]));
assert(parseJsonString(`"\\\/\b\f\n\r\t\u1234"`).get!string == "\\/\b\f\n\r\t\u1234");
return true;
}
// Run at compile time and runtime
assert(test());
static assert(test());
}
@safe unittest {
try parseJsonString(" \t\n ");
catch (Exception e) assert(e.msg.endsWith("JSON string contains only whitespaces."));
try parseJsonString(`{"a": 1`);
catch (Exception e) assert(e.msg.endsWith("Missing '}' before EOF."));
try parseJsonString(`{"a": 1 x`);
catch (Exception e) assert(e.msg.endsWith("Expected '}' or ',' - got 'x'."));
try parseJsonString(`[1`);
catch (Exception e) assert(e.msg.endsWith("Missing ']' before EOF."));
try parseJsonString(`[1 x`);
catch (Exception e) assert(e.msg.endsWith("Expected ']' or ',' - got 'x'."));
bool test() {
try parseJsonString(" \t\n ");
catch (Exception e) assert(e.msg.endsWith("JSON string contains only whitespaces."));
try parseJsonString(`{"a": 1`);
catch (Exception e) assert(e.msg.endsWith("Missing '}' before EOF."));
try parseJsonString(`{"a": 1 x`);
catch (Exception e) assert(e.msg.endsWith("Expected '}' or ',' - got 'x'."));
try parseJsonString(`[1`);
catch (Exception e) assert(e.msg.endsWith("Missing ']' before EOF."));
try parseJsonString(`[1 x`);
catch (Exception e) assert(e.msg.endsWith("Expected ']' or ',' - got 'x'."));
return true;
}
// Run at compile time and runtime
assert(test());
static assert(test());
}
/**
......@@ -1914,7 +1932,7 @@ struct JsonStringSerializer(R, bool pretty = false)
bool is_long_overflow;
auto num = m_range.skipNumber(is_float, is_long_overflow);
enforceJson(!is_float, "Expecting integer number.");
enforceJson(!is_long_overflow, num~" is too big for long.");
enforceJson(!is_long_overflow, num.to!string~" is too big for long.");
return to!T(num);
} else static if (is(T : BigInt)) {
bool is_float;
......@@ -2268,71 +2286,93 @@ private string jsonUnescape(R)(ref R range)
return ret.data;
}
private auto skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow) @safe
if (isNarrowString!R)
{
auto r = s.representation;
version (assert) auto rEnd = (() @trusted => r.ptr + r.length - 1)();
auto res = skipNumber(r, is_float, is_long_overflow);
version (assert) assert(rEnd == (() @trusted => r.ptr + r.length - 1)()); // check nothing taken off the end
s = s[$ - r.length .. $];
return res.assumeUTF();
}
/// private
private string skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow)
private auto skipNumber(R)(ref R s, out bool is_float, out bool is_long_overflow)
if (!isNarrowString!R && isForwardRange!R)
{
// TODO: make this work with input ranges
pragma(msg, R);
auto sOrig = s.save;
size_t idx = 0;
is_float = false;
is_long_overflow = false;
ulong int_part = 0;
if (s[idx] == '-') idx++;
if (s[idx] == '0') idx++;
if (s.front == '-') {
s.popFront(); ++idx;
}
if (s.front == '0') {
s.popFront(); ++idx;
}
else {
enforceJson(isDigit(s[idx]), "Digit expected at beginning of number.");
int_part = s[idx++] - '0';
while( idx < s.length && isDigit(s[idx]) )
{
if (!is_long_overflow)
{
auto dig = s[idx] - '0';
if ((long.max / 10) > int_part || ((long.max / 10) == int_part && (long.max % 10) >= dig))
{
enforceJson(isDigit(s.front), "Digit expected at beginning of number.");
int_part = s.front - '0';
s.popFront(); ++idx;
while( !s.empty && isDigit(s.front) ) {
if (!is_long_overflow) {
auto dig = s.front - '0';
if ((long.max / 10) > int_part || ((long.max / 10) == int_part && (long.max % 10) >= dig)) {
int_part *= 10;
int_part += dig;
}
else
{
else {
is_long_overflow = true;
}
}
idx++;
s.popFront(); ++idx;
}
}
if( idx < s.length && s[idx] == '.' ){
idx++;
if( !s.empty && s.front == '.' ) {
s.popFront(); ++idx;
is_float = true;
while( idx < s.length && isDigit(s[idx]) ) idx++;
while( !s.empty && isDigit(s.front) ) {
s.popFront(); ++idx;
}
}
if( idx < s.length && (s[idx] == 'e' || s[idx] == 'E') ){
idx++;
if( !s.empty && (s.front == 'e' || s.front == 'E') ) {
s.popFront(); ++idx;
is_float = true;
if( idx < s.length && (s[idx] == '+' || s[idx] == '-') ) idx++;
enforceJson( idx < s.length && isDigit(s[idx]), "Expected exponent." ~ s[0 .. idx]);
idx++;
while( idx < s.length && isDigit(s[idx]) ) idx++;
if( !s.empty && (s.front == '+' || s.front == '-') ) {
s.popFront(); ++idx;
}
enforceJson( !s.empty && isDigit(s.front), "Expected exponent." ~ sOrig.takeExactly(idx).to!string);
s.popFront(); ++idx;
while( !s.empty && isDigit(s.front) ) {
s.popFront(); ++idx;
}
}
string ret = s[0 .. idx];
s = s[idx .. $];
return ret;
return sOrig.takeExactly(idx);
}
unittest
{
string test_1 = "9223372036854775806"; // lower then long.max
string test_2 = "9223372036854775807"; // long.max
string test_3 = "9223372036854775808"; // greater then long.max
bool is_float;
bool is_long_overflow;
test_1.skipNumber(is_float, is_long_overflow);
assert(!is_long_overflow);
test_2.skipNumber(is_float, is_long_overflow);
assert(!is_long_overflow);
test_3.skipNumber(is_float, is_long_overflow);
assert(is_long_overflow);
import std.meta : AliasSeq;
// test for string and for a simple range
foreach (foo; AliasSeq!(to!string, map!"a")) {
auto test_1 = foo("9223372036854775806"); // lower then long.max
auto test_2 = foo("9223372036854775807"); // long.max
auto test_3 = foo("9223372036854775808"); // greater then long.max
bool is_float;
bool is_long_overflow;
test_1.skipNumber(is_float, is_long_overflow);
assert(!is_long_overflow);
test_2.skipNumber(is_float, is_long_overflow);
assert(!is_long_overflow);
test_3.skipNumber(is_float, is_long_overflow);
assert(is_long_overflow);
}
}
/// private
......
{
"name": "http-forward-proxy-example",
"description": "Sets up a simple forward proxy.",
"dependencies": {
"vibe-d:http": {"path": "../../"}
},
"versions": ["VibeDefaultMain"]
}
\ No newline at end of file
import vibe.appmain;
import vibe.http.proxy;
import vibe.http.server;
shared static this()
{
auto settings = new HTTPServerSettings;
settings.port = 8080;
settings.bindAddresses = ["::1", "127.0.0.1"];
listenHTTPForwardProxy(settings);
}
......@@ -628,7 +628,7 @@ final class HTTPClient {
}
return () @trusted { // scoped
auto req = scoped!HTTPClientRequest(m_stream, m_conn.localAddress);
auto req = scoped!HTTPClientRequest(m_stream, m_conn);
if (m_useTLS)
req.m_peerCertificate = m_tlsStream.peerCertificate;
......@@ -668,19 +668,20 @@ final class HTTPClientRequest : HTTPRequest {
FreeListRef!ChunkedOutputStream m_chunkedStream;
bool m_headerWritten = false;
FixedAppender!(string, 22) m_contentLengthBuffer;
NetworkAddress m_localAddress;
TCPConnection m_rawConn;
TLSCertificateInformation m_peerCertificate;
}
/// private
this(InterfaceProxy!Stream conn, NetworkAddress local_addr)
this(InterfaceProxy!Stream conn, TCPConnection raw_conn)
{
super(conn);
m_localAddress = local_addr;
m_rawConn = raw_conn;
}
@property NetworkAddress localAddress() const { return m_localAddress; }
@property NetworkAddress localAddress() const { return m_rawConn.localAddress; }
@property NetworkAddress remoteAddress() const { return m_rawConn.remoteAddress; }
@property ref inout(TLSCertificateInformation) peerCertificate() inout { return m_peerCertificate; }
......
......@@ -638,30 +638,72 @@ final class Cookie {
none = raw
}
/// Cookie payload
@property void value(string value) { m_value = urlEncode(value); }
/// ditto
@property string value() const { return urlDecode(m_value); }
/// Undecoded cookie payload
@property void rawValue(string value) { m_value = value; }
/// ditto
@property string rawValue() const { return m_value; }
/// The domain for which the cookie is valid
@property void domain(string value) { m_domain = value; }
/// ditto
@property string domain() const { return m_domain; }
/// The path/local URI for which the cookie is valid
@property void path(string value) { m_path = value; }
/// ditto
@property string path() const { return m_path; }
/// Expiration date of the cookie
@property void expires(string value) { m_expires = value; }
/// ditto
@property void expires(SysTime value) { m_expires = value.toRFC822DateTimeString(); }
/// ditto
@property string expires() const { return m_expires; }
/** Maximum life time of the cookie
This is the modern variant of `expires`. For backwards compatibility it
is recommended to set both properties, or to use the `expire` method.
*/
@property void maxAge(long value) { m_maxAge = value; }
/// ditto
@property void maxAge(Duration value) { m_maxAge = value.total!"seconds"; }
/// ditto
@property long maxAge() const { return m_maxAge; }
/** Require a secure connection for transmission of this cookie
*/
@property void secure(bool value) { m_secure = value; }
/// ditto
@property bool secure() const { return m_secure; }
/** Prevents access to the cookie from scripts.
*/
@property void httpOnly(bool value) { m_httpOnly = value; }
/// ditto
@property bool httpOnly() const { return m_httpOnly; }
/** Sets the "expires" and "max-age" attributes to limit the life time of
the cookie.
*/
void expire(Duration max_age)
{
this.expires = Clock.currTime(UTC()) + max_age;
this.maxAge = max_age;
}
/// ditto
void expire(SysTime expire_time)
{
this.expires = expire_time;
this.maxAge = expire_time - Clock.currTime(UTC());
}
/// Sets the cookie value encoded with the specified encoding.
void setValue(string value, Encoding encoding)
{
final switch (encoding) {
......@@ -670,6 +712,7 @@ final class Cookie {
}
}
/// Writes out the full cookie in HTTP compatible format.
void writeString(R)(R dst, string name)
if (isOutputRange!(R, char))
{
......
......@@ -23,7 +23,7 @@ import std.process;
This function is usable as direct replacement of listenHTTP
*/
void listenHTTPDist(HTTPServerSettings settings, HTTPServerRequestDelegate handler, string balancer_address, ushort balancer_port = 11000)
HTTPListener listenHTTPDist(HTTPServerSettings settings, HTTPServerRequestDelegate handler, string balancer_address, ushort balancer_port = 11000)
@safe {
Json regmsg = Json.emptyObject;
regmsg["host_name"] = settings.hostName;
......@@ -36,7 +36,7 @@ void listenHTTPDist(HTTPServerSettings settings, HTTPServerRequestDelegate handl
local_settings.bindAddresses = ["127.0.0.1"];
local_settings.port = 0;
local_settings.disableDistHost = true;
listenHTTP(local_settings, handler);
auto ret = listenHTTP(local_settings, handler);