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:
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);