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