BastliBridge

git clone git://xatko.vsos.ethz.ch/BastliBridge.git
Log | Files | Refs | Submodules | README

http.d (7073B)


      1 module telegram.http;
      2 
      3 import std.stdio;
      4 import std.array;
      5 import std.typecons;
      6 import std.socket;
      7 import std.format;
      8 import std.range;
      9 import std.algorithm;
     10 import std.meta;
     11 import std.conv;
     12 import std.traits;
     13 import std.exception;
     14 import std.experimental.logger;
     15 
     16 @safe:
     17 
     18 class OutBuffer{
     19 	ubyte[] buffer;
     20 	size_t offset;
     21 	
     22 	invariant(){
     23 		assert(offset <= buffer.length);
     24 	}
     25 	
     26 	void reserve(size_t len)
     27 	in{
     28 		assert(offset + len >= offset);
     29 	}
     30 	out{
     31 		assert(offset + len <= buffer.length);
     32 	}
     33 	body{
     34 		if(offset+len>buffer.length){
     35 			buffer.length=(offset + len)*2;
     36 		}
     37 	}
     38 	alias put = write;
     39 	
     40 	ubyte[] write(const(ubyte)[] bytes){
     41 		reserve(bytes.length);
     42 		auto slice=buffer[offset..offset+bytes.length];
     43 		slice[]=bytes[];
     44 		offset+=bytes.length;
     45 		return slice;
     46 	}
     47 	
     48 	unittest{
     49 		ubyte[] test=iota(0,255).map!(a=>cast(ubyte)a).array;
     50 		OutBuffer ob;
     51 		ob.write(test);
     52 		assert(ob.toBytes == test);
     53 	}
     54 	
     55 	void write(const(char)[] bytes){
     56 		write(cast(const(ubyte)[])bytes);
     57 	}
     58 	void write(in ubyte b){
     59 		reserve(ubyte.sizeof);
     60 		buffer[offset]=b;
     61 		offset++;
     62 	}
     63 	
     64 	ubyte[] toBytes(){
     65 		return buffer[0..offset];
     66 	}
     67 	
     68 	override string toString(){
     69 		return (cast(const(char)[])toBytes).idup;
     70 	}
     71 	
     72 }
     73 
     74 private static immutable string http_version="HTTP/1.1";
     75 private static immutable string nl="\r\n";
     76 
     77 ubyte[] receiveData(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){
     78 	ubyte[] buf=buffer;
     79 	size_t pos=0,res=0;
     80 	while(true){
     81 		res=sock.receive(buf);
     82 		if(res==0){
     83 			return [];
     84 		}
     85 		else if(res==Socket.ERROR){
     86 			throw new ErrnoException("Error reading from socket");
     87 		}
     88 		pos+=res;
     89 		if(pos<buffer.length)
     90 			break;
     91 		if(pos>=buffer.length){
     92 			buffer.length+=chunk_size;
     93 		}
     94 		buf=buffer[pos..$];
     95 	}
     96 	return buffer[0..pos];
     97 }
     98 
     99 Nullable!(HttpResponse!T) receiveHttpResponse(T=void)(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){
    100 	auto data=receiveData(sock,buffer,chunk_size);
    101 	if(data.length==0){
    102 		return Nullable!(HttpResponse!T).init;
    103 	}
    104 	return nullable(HttpResponse!T.parse(data));
    105 }
    106 
    107 Nullable!(HttpRequest!T) receiveHttpRequest(T=void)(Socket sock, ubyte[] buffer, in size_t chunk_size=1024){
    108 	auto data=receiveData(sock,buffer,chunk_size);
    109 	if(data.length==0){
    110 		return Nullable!(HttpRequest!T).init;
    111 	}
    112 	return nullable(HttpRequest!T.parse(data));
    113 }
    114 
    115 
    116 struct HttpRequest(T=void){
    117 	private auto ob=appender!(ubyte[])();
    118 	private uint state=0;
    119 	const(char)[] method;
    120 	const(char)[] url;
    121 	const(char)[] ver;
    122 	const(ubyte)[] content;
    123 	
    124 	static if(!is(T == void)){
    125 		static assert(__traits(compiles, (){const(char)[] c=""; (T.init)[c]=c;}));
    126 		T headers;
    127 	}
    128 	
    129 	private void clear(){
    130 		ob.clear();
    131 		state=0;
    132 	}
    133 	
    134 	void write(T)(in T[] buf) if(is(T:ubyte)){
    135 		ob.put(cast(const(ubyte)[])buf);
    136 	}
    137 	
    138 	void request(TM, TU)(TM method, TU url){
    139 		clear();
    140 		ob.formattedWrite!("%s /%s "~http_version~nl)(method,url);
    141 		state=1;
    142 	}
    143 	
    144 	void header(TN,TV)(TN name, TV value)
    145 	in{
    146 		assert(state==1);
    147 	}
    148 	body{
    149 		ob.formattedWrite!("%s: %s"~nl)(name, value);
    150 	}
    151 	
    152 	void header(A...)(A args)
    153 	in{
    154 		assert(args.length%2==0);
    155 	}
    156 	body{
    157 		
    158 		string generateMixin(){
    159 			string str;
    160 			for(size_t i=0; i<args.length; i+=2){
    161 				str~=format!"header(args[%d],args[%d]);"(i,i+1);
    162 			}
    163 			return str;
    164 		}
    165 		mixin(generateMixin());
    166 	}
    167 	
    168 	void data(TD)(TD data) if(hasLength!TD)
    169 	in{
    170 		assert(state<2);
    171 	}
    172 	body{
    173 		header("Content-Length", data.length);
    174 		write(nl);
    175 		state=2;
    176 		write(data);
    177 	}
    178 	
    179 	void data (TD)(TD data) if(isSomeString!TD){
    180 		import std.string:representation;
    181 		this.data(data.representation);
    182 	}
    183 	
    184 	void finalize(){
    185 		if(state<2){
    186 			write(nl);
    187 			state=2;
    188 		}
    189 	}
    190 	
    191 	void perform(Socket sock){
    192 		finalize();
    193 		writeln(cast(char[])ob.data);
    194 		sock.send(ob.data);
    195 	}
    196 	
    197 	static HttpRequest parse(const(ubyte)[] buf){
    198 		HttpRequest r;
    199 		const(char)[] buffer=cast(const(char)[])buf;
    200 		auto lines=buffer.splitter(nl);
    201 		
    202 		auto s=lines.front.findSplit(" ");
    203 		r.method=s[0];
    204 		s=s[2].findSplit(" ");
    205 		r.url=s[0];
    206 		r.ver=s[2];
    207 		
    208 		lines.popFront();
    209 		static if(!is(T==void)){
    210 			foreach(line; lines){
    211 				s=line.findSplit(": ");
    212 				res._headers[s[0]]=s[2];
    213 			}
    214 		}
    215 		if(buffer.findSkip(nl~nl)){
    216 			r.content=cast(const(ubyte)[])buffer;
    217 		}
    218 		return r;
    219 	}
    220 	
    221 	const(char)[] bufferString() const{
    222 		return cast(const(char)[])ob.data;
    223 	}
    224 	
    225 	string toString(){
    226 		static if(is(T==void)){
    227 			return format!"HttpRequest %s %s\n%s"(method, url, cast(const char[])content);
    228 		}
    229 		else{
    230 			return format!"HttpRequest %s %s\n%s\n%s"(method, url, headers.to!string, cast(const char[])content);
    231 		}
    232 	}
    233 }
    234 
    235 struct CopyAssoc{
    236 	const(char)[][string] assoc;
    237 	mixin Proxy!assoc;
    238 	
    239 	auto opIndexAssign(in char[] value, in char[] key){
    240 		assoc[key.idup]=value;
    241 		return value;
    242 	}
    243 }
    244 
    245 struct HttpResponse(T=void){
    246 	private auto ob=appender!(ubyte[])();
    247 	string ver;
    248 	ushort code;
    249 	string codename;
    250 	private uint state;
    251 	
    252 	static if(!is(T == void)){
    253 		static assert(__traits(compiles, (){const(char)[] c=""; (T.init)[c]=c;}));
    254 		T headers;
    255 	}
    256 	const(ubyte)[] content;
    257 	
    258 	static HttpResponse parse(const(ubyte)[] data){
    259 		const(char)[] buf=cast(const char[]) data;
    260 		HttpResponse res;
    261 		auto lines=buf.splitter(nl);
    262 		auto status=lines.front;
    263 		auto split=status.splitter(" ");
    264 		
    265 		res.ver=split.front.idup;
    266 		split.popFront();
    267 		res.code=split.front.to!ushort;
    268 		split.popFront();
    269 		res.codename=split.joiner.to!string;
    270 		static if(!is(T==void)){
    271 			lines.popFront();
    272 			foreach(line;lines.until!(a=>a.length==0)){
    273 				auto headsplit=line.findSplit(": ");
    274 				res.headers[headsplit[0]]=headsplit[2];
    275 			}
    276 		}
    277 		
    278 		res.content=cast(const(ubyte)[])buf.findSplit("\r\n\r\n")[2];
    279 		return res;
    280 	}
    281 	
    282 	void response(TN)(ushort code, TN codename){
    283 		ob.clear();
    284 		ob.formattedWrite!(http_version~" %d %s"~nl)(code,codename);
    285 		state=1;
    286 	}
    287 	
    288 	void header(TN,TV)(TN name, TV value)
    289 	in{
    290 		assert(state==1);
    291 	}
    292 	body{
    293 		ob.formattedWrite!("%s: %s"~nl)(name, value);
    294 	}
    295 	
    296 	void header(A...)(A args)
    297 	in{
    298 		assert(args.length%2==0);
    299 	}
    300 	body{
    301 		
    302 		string generateMixin(){
    303 			string str;
    304 			for(size_t i=0; i<args.length; i+=2){
    305 				str~=format!"header(args[%d],args[%d]);"(i,i+1);
    306 			}
    307 			return str;
    308 		}
    309 		mixin(generateMixin());
    310 	}
    311 	
    312 	void write(T)(in T[] buf) if(is(T:ubyte)){
    313 		ob.put(cast(const(ubyte)[])buf);
    314 	}
    315 	
    316 	void data(TD)(TD data) if(hasLength!TD)
    317 	in{
    318 		assert(state<2);
    319 	}
    320 	body{
    321 		header("Content-Length", data.length);
    322 		write(nl);
    323 		state=2;
    324 		write(data);
    325 	}
    326 	
    327 	void data (TD)(TD data) if(isSomeString!TD){
    328 		import std.string:representation;
    329 		this.data(data.representation);
    330 	}
    331 	
    332 	void finalize(){
    333 		if(state<2){
    334 			write(nl);
    335 			state=2;
    336 		}
    337 	}
    338 	
    339 	void perform(Socket sock){
    340 		finalize();
    341 		writeln(bufferString);
    342 		sock.send(ob.data);
    343 	}
    344 	
    345 	const(char)[] str() const{
    346 		return cast(const(char)[]) content;
    347 	}
    348 	
    349 	const(char)[] bufferString() const{
    350 		return cast(const(char)[])ob.data;
    351 	}
    352 	
    353 	string toString(){
    354 		static if(is(T==void)){
    355 			return format!"HttpResponse %d %s\n%s"(code, codename, cast(const char[])content);
    356 		}
    357 		else{
    358 			return format!"HttpResponse %d %s\n%s\n%s"(code, codename, headers.to!string, cast(const char[])content);
    359 		}
    360 	}
    361 }
    362