1 module drocks.request; 2 3 import std.stdio : stderr, writeln; 4 import std.range : join, isInputRange, ElementType; 5 import std.traits : isIntegral; 6 import std.algorithm : map; 7 import std.socket : InternetAddress, SocketException; 8 import std.typecons : Tuple, tuple; 9 import std.conv : to; 10 11 public import drocks.exception : ClientException; 12 import drocks.sockhandler : SockHandler; 13 import drocks.response : Response; 14 import drocks.pair : Pair; 15 16 // Creates sockets and generates requests 17 struct Request 18 { 19 private: 20 InternetAddress _addr; 21 string _host; 22 23 public: 24 this(string host, ushort port) 25 { 26 _host = host; 27 _addr = new InternetAddress(host, port); 28 } 29 @disable this (); 30 31 // Generates a request and returns response 32 Response request(string req) 33 { 34 auto sock = new SockHandler(); 35 36 try { 37 sock.connect(_addr); 38 sock.send(req); 39 } catch (SocketException e) { 40 throw new ClientException(e); 41 } 42 43 // Check response status 44 auto status = sock.receiveHeader(); 45 enum string expectedStatus = "200 OK"; 46 if( status.length < expectedStatus.length ){ 47 throw new ClientException("Empty response"); 48 } 49 50 // Expected: status == "HTTP/1.1 200 OK" 51 if( !sock.isValid() || expectedStatus != status[$-expectedStatus.length..$] ){ 52 throw new ClientException("Status error: " ~ status.idup); 53 } 54 55 // skip headers 56 while (sock.receiveHeader().length) {} 57 58 return Response(sock); 59 } 60 61 // 62 // GET requests 63 // 64 Response httpGet(const string path) 65 { 66 string buf; 67 buf = "GET /" ~ path ~ " HTTP/1.1\r\n" ~ 68 "Host:" ~ _host ~ "\r\n" ~ 69 headsEnd; 70 return this.request(buf); 71 } 72 73 Response httpGet(const string path, string data) 74 { 75 string buf; 76 buf = "GET /" ~ path ~ "?" ~ data ~ " HTTP/1.1\r\n" ~ 77 "Host:" ~ _host ~ "\r\n" ~ 78 headsEnd; 79 return this.request(buf); 80 } 81 82 Response httpGet(T)(const string path, auto ref T data) 83 if(isIntegral!T) 84 { 85 return this.httpGet(path, data.to!string); 86 } 87 88 Response httpGet(Range)(const string path, auto ref Range range) 89 if(isInputRange!Range && is(ElementType!Range == string)) 90 { 91 return this.httpGet(path, range.join("&")); 92 } 93 94 Response httpGet(Args...)(const string path, auto ref Args args) 95 if(Args.length > 1) 96 { 97 return this.httpGet(path, join(tuple(args), '&')); 98 } 99 100 // 101 // POST requests 102 // 103 Response httpPost(const string path, string data) 104 { 105 string buf; 106 buf = headsStartPost(path) ~ 107 headsEndPost(data.length) ~ 108 data; 109 110 return this.request(buf); 111 } 112 113 Response httpPost(T)(const string path, auto ref T data) 114 if(isIntegral!T) 115 { 116 return this.httpPost(path, data.to!string); 117 } 118 119 Response httpPost(const string path) 120 { 121 return this.request( headsStartPost(path) ~ headsEnd ); 122 } 123 124 Response httpPost(Range)(const string path, auto ref Range range) 125 if(isInputRange!Range && is(ElementType!Range == string)) 126 { 127 return this.httpPost(path, range.join("\n")); 128 } 129 130 Response httpPost(Range)(const string path, auto ref Range range) 131 if( isInputRange!Range && is(ElementType!Range == Pair) ) 132 { 133 string data = range 134 .map!( (const Pair x) {return x.serialize;}) 135 .join("\n"); 136 137 return this.httpPost(path, data); 138 } 139 140 Response httpPost(Range)(const string path, auto ref Range range) 141 if( isInputRange!Range && is(ElementType!Range: Tuple!(string, string)) ) 142 { 143 return this.httpPost(path, range 144 .map!( (const Tuple!(string, string) x) {return Pair(x);}) 145 ); 146 } 147 148 Response httpPost(Range)(const string path, auto ref Range range) 149 if(isInputRange!Range && isIntegral!(ElementType!Range)) 150 { 151 return this.httpPost(path, range.map!"a.to!string"); 152 } 153 154 Response httpPost(Args...)(const string path, auto ref Args args) 155 if(Args.length > 1) 156 { 157 return this.httpPost(path, join(tuple(args), '\n')); 158 } 159 160 161 private: 162 163 static string join(Args...)(auto ref Tuple!Args args, char c) 164 if(Args.length > 0) 165 { 166 static if(is(Args[0] == string)) { 167 string data = args[0]; 168 } else { 169 string data = args[0].to!string; 170 } 171 172 static foreach(enum ind; 1..Args.length) { 173 static if(is(Args[ind] == string)) { 174 data ~= c ~ args[ind]; 175 } else { 176 data ~= c ~ args[ind].to!string; 177 } 178 } 179 return data; 180 } 181 182 enum string headsEnd = 183 "Content-Type: charset=UTF-8\r\n" ~ 184 "Connection: Close\r\n\r\n"; 185 186 string headsStartPost(const string path) 187 { 188 return 189 "POST /" ~ path ~ " HTTP/1.1\r\n" ~ 190 "Host:" ~ _host ~ "\r\n"; 191 } 192 string headsEndPost(size_t len) 193 { 194 return 195 "Content-Type:application/x-www-form-urlencoded; charset=UTF-8\r\n" ~ 196 "Content-Length: " ~ len.to!string ~ "\r\n" ~ 197 "Connection: Close\r\n\r\n"; 198 } 199 200 }