BastliBridge

A bot framework bridgin multiple IM protocols, and mail
git clone git://xatko.vsos.ethz.ch/BastliBridge.git
Log | Files | Refs | Submodules

commit 754f913f30ce81b3f934dd5a1168b9bdf238abae
parent 8b5b2a035e3d1c67a363da971c8e2667d07e3c4f
Author: Dominik Schmidt <das1993@hotmail.com>
Date:   Mon, 25 Sep 2017 22:41:23 +0200

Improve the http-module

we now support formatting a http-response, not only requests.

Diffstat:
src/bastlibridge/http.d | 190++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
src/bastlibridge/telegram.d | 10++++++----
2 files changed, 157 insertions(+), 43 deletions(-)

diff --git a/src/bastlibridge/http.d b/src/bastlibridge/http.d @@ -1,6 +1,7 @@ module bastlibridge.http; import std.stdio; +import std.array; import std.typecons; import std.socket; import std.format; @@ -36,10 +37,12 @@ class OutBuffer{ } alias put = write; - void write(const(ubyte)[] bytes){ + ubyte[] write(const(ubyte)[] bytes){ reserve(bytes.length); - buffer[offset..offset+bytes.length]=bytes[]; + auto slice=buffer[offset..offset+bytes.length]; + slice[]=bytes[]; offset+=bytes.length; + return slice; } unittest{ @@ -71,13 +74,6 @@ class OutBuffer{ private static immutable string http_version="HTTP/1.1"; private static immutable string nl="\r\n"; - -ubyte[] receiveData(Socket sock){ - static immutable size_t chunk_size=1024; - static ubyte[] buffer=new ubyte[chunk_size]; - return receiveData(sock,buffer,chunk_size); -} - ubyte[] receiveData(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){ ubyte[] buf=buffer; size_t pos=0,res=0; @@ -100,7 +96,7 @@ ubyte[] receiveData(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){ return buffer[0..pos]; } -Nullable!(HttpResponse!T) receiveHttp(T=void)(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){ +Nullable!(HttpResponse!T) receiveHttpResponse(T=void)(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){ auto data=receiveData(sock,buffer,chunk_size); if(data.length==0){ return Nullable!(HttpResponse!T).init; @@ -108,31 +104,37 @@ Nullable!(HttpResponse!T) receiveHttp(T=void)(Socket sock, ubyte[] buffer, in si return nullable(HttpResponse!T.parse(data)); } -Nullable!(HttpResponse!T) receiveHttp(T=void)(Socket sock){ - auto data=receiveData(sock); +Nullable!(HttpRequest!T) receiveHttpRequest(T=void)(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){ + auto data=receiveData(sock,buffer,chunk_size); if(data.length==0){ - return Nullable!(HttpResponse!T).init; + return Nullable!(HttpRequest!T).init; } - return nullable(HttpResponse!T.parse(data)); + return nullable(HttpRequest!T.parse(data)); } -struct HttpRequest{ - private OutBuffer ob; + +struct HttpRequest(T=void){ + private auto ob=appender!(ubyte[])(); private uint state=0; + const(char)[] method; + const(char)[] url; + const(char)[] ver; + const(ubyte)[] content; - this(OutBuffer ob){ - setBuffer(ob); - } - - void setBuffer(OutBuffer ob){ - this.ob=ob; + static if(!is(T == void)){ + static assert(__traits(compiles, (){const(char)[] c=""; (T.init)[c]=c;})); + T headers; } private void clear(){ - ob.offset=0; + ob.clear(); state=0; } + void write(T)(in T[] buf) if(is(T:ubyte)){ + ob.put(cast(const(ubyte)[])buf); + } + void request(TM, TU)(TM method, TU url){ clear(); ob.formattedWrite!("%s /%s "~http_version~nl)(method,url); @@ -147,7 +149,7 @@ struct HttpRequest{ ob.formattedWrite!("%s: %s"~nl)(name, value); } - void headers(A...)(A args) + void header(A...)(A args) in{ assert(args.length%2==0); } @@ -163,39 +165,71 @@ struct HttpRequest{ mixin(generateMixin()); } - void data(T)(T data) if(hasLength!T) + void data(TD)(TD data) if(hasLength!TD) in{ - assert(state==1); + assert(state<2); } body{ header("Content-Length", data.length); - ob.write(nl); + write(nl); state=2; - ob.write(data); + write(data); } - void data (T)(T data) if(isSomeString!T){ + void data (TD)(TD data) if(isSomeString!TD){ import std.string:representation; this.data(data.representation); } void finalize(){ if(state<2){ - ob.write(nl); + write(nl); state=2; } } void perform(Socket sock){ finalize(); - writeln(cast(char[])ob.toBytes); - sock.send(ob.toBytes); + writeln(cast(char[])ob.data); + sock.send(ob.data); } - string toString(){ - return ob.toString(); + static HttpRequest parse(const(ubyte)[] buf){ + HttpRequest r; + const(char)[] buffer=cast(const(char)[])buf; + auto lines=buffer.splitter(nl); + + auto s=lines.front.findSplit(" "); + r.method=s[0]; + s=s[2].findSplit(" "); + r.url=s[0]; + r.ver=s[2]; + + lines.popFront(); + static if(!is(T==void)){ + foreach(line; lines){ + s=line.findSplit(": "); + res._headers[s[0]]=s[2]; + } + } + if(buffer.findSkip(nl~nl)){ + r.content=cast(const(ubyte)[])buffer; + } + return r; + } + + const(char)[] bufferString() const{ + return cast(const(char)[])ob.data; } + string toString(){ + static if(is(T==void)){ + return format!"HttpRequest %s %s\n%s"(method, url, cast(const char[])content); + } + else{ + return format!"HttpRequest %s %s\n%s\n%s"(method, url, headers.to!string, cast(const char[])content); + } + } } struct CopyAssoc{ @@ -209,20 +243,22 @@ struct CopyAssoc{ } struct HttpResponse(T=void){ + private auto ob=appender!(ubyte[])(); string ver; ushort code; string codename; + private uint state; static if(!is(T == void)){ static assert(__traits(compiles, (){const(char)[] c=""; (T.init)[c]=c;})); T headers; } - const(ubyte)[] data; + const(ubyte)[] content; static HttpResponse parse(const(ubyte)[] data){ const(char)[] buf=cast(const char[]) data; HttpResponse res; - auto lines=buf.splitter("\r\n"); + auto lines=buf.splitter(nl); auto status=lines.front; auto split=status.splitter(" "); @@ -234,17 +270,93 @@ struct HttpResponse(T=void){ static if(!is(T==void)){ lines.popFront(); foreach(line;lines.until!(a=>a.length==0)){ - auto headsplit=line.findSplit(":"); + auto headsplit=line.findSplit(": "); res.headers[headsplit[0]]=headsplit[2]; } } - res.data=cast(const(ubyte)[])buf.findSplit("\r\n\r\n")[2]; + res.content=cast(const(ubyte)[])buf.findSplit("\r\n\r\n")[2]; return res; } + void response(TN)(ushort code, TN codename){ + ob.clear(); + ob.formattedWrite!(http_version~" %d %s"~nl)(code,codename); + state=1; + } + + void header(TN,TV)(TN name, TV value) + in{ + assert(state==1); + } + body{ + ob.formattedWrite!("%s: %s"~nl)(name, value); + } + + void header(A...)(A args) + in{ + assert(args.length%2==0); + } + body{ + + string generateMixin(){ + string str; + for(size_t i=0; i<args.length; i+=2){ + str~=format!"header(args[%d],args[%d]);"(i,i+1); + } + return str; + } + mixin(generateMixin()); + } + + void write(T)(in T[] buf) if(is(T:ubyte)){ + ob.put(cast(const(ubyte)[])buf); + } + + void data(TD)(TD data) if(hasLength!TD) + in{ + assert(state<2); + } + body{ + header("Content-Length", data.length); + write(nl); + state=2; + write(data); + } + + void data (TD)(TD data) if(isSomeString!TD){ + import std.string:representation; + this.data(data.representation); + } + + void finalize(){ + if(state<2){ + write(nl); + state=2; + } + } + + void perform(Socket sock){ + finalize(); + writeln(bufferString); + sock.send(ob.data); + } + const(char)[] str() const{ - return cast(const(char)[]) data; + return cast(const(char)[]) content; + } + + const(char)[] bufferString() const{ + return cast(const(char)[])ob.data; + } + + string toString(){ + static if(is(T==void)){ + return format!"HttpResponse %d %s\n%s"(code, codename, cast(const char[])content); + } + else{ + return format!"HttpResponse %d %s\n%s\n%s"(code, codename, headers.to!string, cast(const char[])content); + } } } diff --git a/src/bastlibridge/telegram.d b/src/bastlibridge/telegram.d @@ -69,12 +69,13 @@ struct Telegram{ private Address ApiAddrObj; - private HttpRequest httpRequest; + private HttpRequest!void httpRequest; + private static immutable httpBufferChunk=1024; + private ubyte[] httpBuffer=new ubyte[httpBufferChunk]; @disable this(); this(string token){ this.token=token; - httpRequest.setBuffer(new OutBuffer); } void connect(bool test=true){ @@ -110,7 +111,7 @@ struct Telegram{ return chain("bot",token,"/",method); } - void httpRequestHeaders(ref HttpRequest httpRequest){ + void httpRequestHeaders(ref HttpRequest!void httpRequest){ httpRequest.header("Host", ApiAddr); httpRequest.header("Connection", "keep-alive"); } @@ -170,7 +171,8 @@ struct Telegram{ } Nullable!(JSONValue) response(){ - auto h=receiveHttp(sock); + auto h=receiveHttpResponse(sock, httpBuffer, httpBufferChunk); + trace("Got response from Telegram: ", h.to!string); if(h.isNull){ //throw new TelegramError("Socket closed"); warning("Telegram closed _another_ socket :/ ",sock.handle);