1 module requests.base; 2 3 import requests.streams; 4 import requests.utils; 5 import requests.uri; 6 import requests.connmanager; 7 8 import std.format; 9 import std.datetime; 10 import core.time; 11 import std.stdio; 12 import std.algorithm; 13 import std.string; 14 import std.exception; 15 import std.bitmanip; 16 import std.conv; 17 import std.typecons; 18 19 /++ 20 Interface to provide user info and http headers requred for server auth. 21 +/ 22 public interface Auth { 23 string[string] authHeaders(string domain); /// Create headers for authentication 24 string userName(); /// Returns user name 25 string password(); /// Returns user password 26 } 27 /** 28 * Basic authentication. 29 * Adds $(B Authorization: Basic) header to request 30 * Example: 31 * --- 32 * import requests; 33 * void main() { 34 * rq = Request(); 35 * rq.authenticator = new BasicAuthentication("user", "passwd"); 36 * rs = rq.get("http://httpbin.org/basic-auth/user/passwd"); 37 * } 38 * --- 39 */ 40 public class BasicAuthentication: Auth { 41 private { 42 string _username, _password; 43 string[] _domains; 44 } 45 /// Constructor. 46 /// Params: 47 /// username = username 48 /// password = password 49 /// domains = not used now 50 /// 51 this(string username, string password, string[] domains = []) { 52 _username = username; 53 _password = password; 54 _domains = domains; 55 } 56 /// create Basic Auth header 57 override string[string] authHeaders(string domain) { 58 import std.base64; 59 string[string] auth; 60 auth["Authorization"] = "Basic " ~ to!string(Base64.encode(cast(ubyte[])"%s:%s".format(_username, _password))); 61 return auth; 62 } 63 /// returns username 64 override string userName() { 65 return _username; 66 } 67 /// return user password 68 override string password() { 69 return _password; 70 } 71 } 72 73 /** 74 * Struct to send multiple files in POST request. 75 */ 76 public struct PostFile { 77 /// Path to the file to send. 78 string fileName; 79 /// Name of the field (if empty - send file base name) 80 string fieldName; 81 /// contentType of the file if not empty 82 string contentType; 83 } 84 /// 85 /// This is File-like interface for sending data to multipart forms 86 /// 87 public interface FiniteReadable { 88 /// size of the content 89 abstract ulong getSize(); 90 /// file-like read() 91 abstract ubyte[] read(); 92 } 93 /// 94 /// Helper to create form elements from File. 95 /// Params: 96 /// name = name of the field in form 97 /// f = opened std.stio.File to send to server 98 /// parameters = optional parameters (most important are "filename" and "Content-Type") 99 /// 100 public auto formData(string name, File f, string[string] parameters = null) { 101 return MultipartForm.FormData(name, new FormDataFile(f), parameters); 102 } 103 /// 104 /// Helper to create form elements from ubyte[]. 105 /// Params: 106 /// name = name of the field in form 107 /// b = data to send to server 108 /// parameters = optional parameters (can be "filename" and "Content-Type") 109 /// 110 public auto formData(string name, ubyte[] b, string[string] parameters = null) { 111 return MultipartForm.FormData(name, new FormDataBytes(b), parameters); 112 } 113 public auto formData(string name, string b, string[string] parameters = null) { 114 return MultipartForm.FormData(name, new FormDataBytes(b.dup.representation), parameters); 115 } 116 117 private immutable uint defaultBufferSize = 12*1024; 118 /// Class to provide FiniteReadable from user-provided ubyte[] 119 public class FormDataBytes : FiniteReadable { 120 private { 121 ulong _size; 122 ubyte[] _data; 123 size_t _offset; 124 bool _exhausted; 125 } 126 /// constructor from ubyte[] 127 this(ubyte[] data) { 128 _data = data; 129 _size = data.length; 130 } 131 final override ulong getSize() { 132 return _size; 133 } 134 final override ubyte[] read() { 135 enforce( !_exhausted, "You can't read froum exhausted source" ); 136 size_t toRead = min(defaultBufferSize, _size - _offset); 137 auto result = _data[_offset.._offset+toRead]; 138 _offset += toRead; 139 if ( toRead == 0 ) { 140 _exhausted = true; 141 } 142 return result; 143 } 144 } 145 /// Class to provide FiniteReadable from File 146 public class FormDataFile : FiniteReadable { 147 import std.file; 148 private { 149 File _fileHandle; 150 ulong _fileSize; 151 size_t _processed; 152 bool _exhausted; 153 } 154 /// constructor from File object 155 this(File file) { 156 import std.file; 157 _fileHandle = file; 158 _fileSize = std.file.getSize(file.name); 159 } 160 final override ulong getSize() pure nothrow @safe { 161 return _fileSize; 162 } 163 final override ubyte[] read() { 164 enforce( !_exhausted, "You can't read froum exhausted source" ); 165 auto b = new ubyte[defaultBufferSize]; 166 auto r = _fileHandle.rawRead(b); 167 auto toRead = min(r.length, _fileSize - _processed); 168 if ( toRead == 0 ) { 169 _exhausted = true; 170 } 171 _processed += toRead; 172 return r[0..toRead]; 173 } 174 } 175 /// 176 /// This struct used to bulld POST's to forms. 177 /// Each part have name and data. data is something that can be read-ed and have size. 178 /// For example this can be string-like object (wrapped for reading) or opened File. 179 /// 180 public struct MultipartForm { 181 package struct FormData { 182 FiniteReadable input; 183 string name; 184 string[string] parameters; 185 this(string name, FiniteReadable i, string[string] parameters = null) { 186 this.input = i; 187 this.name = name; 188 this.parameters = parameters; 189 } 190 } 191 192 package FormData[] _sources; 193 auto add(FormData d) { 194 _sources ~= d; 195 return this; 196 } 197 auto add(string name, FiniteReadable i, string[string]parameters = null) { 198 _sources ~= FormData(name, i, parameters); 199 return this; 200 } 201 bool empty() const 202 { 203 return _sources.length == 0; 204 } 205 } 206 /// 207 208 /// General type exception from Request 209 public class RequestException: Exception { 210 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow { 211 super(msg, file, line, next); 212 } 213 } 214 215 /** 216 ReceiveAsRange is InputRange used to supply client with data from response. 217 Each popFront fetch next data portion. 218 */ 219 public struct ReceiveAsRange { 220 bool empty() { 221 return data.length == 0; 222 } 223 ubyte[] front() { 224 return data; 225 } 226 void popFront() { 227 if ( read ) { 228 // get new portion 229 data = read(); 230 } else { 231 // we can't read any new data 232 data.length = 0; 233 } 234 } 235 package { 236 bool activated; 237 ubyte[] data; 238 /// HTTP or FTP module set up delegate read() for reading next portion of data. 239 ubyte[] delegate() read; 240 RefCounted!ConnManager cm; 241 } 242 } 243 244 /** 245 Response 246 */ 247 public class Response { 248 package { 249 /// Server status code 250 ushort _code; 251 /// Response body 252 Buffer!ubyte _responseBody; 253 /// Response headers 254 string[string] _responseHeaders; 255 /// Initial URI 256 URI _uri; 257 /// Final URI. Can differ from uri() if request go through redirections. 258 URI _finalURI; 259 /// stream range stored here 260 ReceiveAsRange _receiveAsRange; 261 SysTime _startedAt, 262 _connectedAt, 263 _requestSentAt, 264 _finishedAt; 265 /// Length of received content 266 long _contentReceived; 267 /// Server-supplied content length (can be -1 when unknown) 268 long _contentLength = -1; 269 mixin(Setter!ushort("code")); 270 mixin(Setter!URI("uri")); 271 mixin(Setter!URI("finalURI")); 272 } 273 mixin(Getter("code")); 274 mixin(Getter("contentReceived")); 275 mixin(Getter("contentLength")); 276 mixin(Getter("uri")); 277 mixin(Getter("finalURI")); 278 279 @property auto getStats() const pure @safe { 280 import std.typecons: Tuple; 281 alias statTuple = Tuple!(Duration, "connectTime", 282 Duration, "sendTime", 283 Duration, "recvTime"); 284 statTuple stat; 285 stat.connectTime = _connectedAt - _startedAt; 286 stat.sendTime = _requestSentAt - _connectedAt; 287 stat.recvTime = _finishedAt - _requestSentAt; 288 return stat; 289 } 290 @property auto ref responseBody() @safe nothrow { 291 return _responseBody; 292 } 293 @property auto ref responseHeaders() pure @safe nothrow { 294 return _responseHeaders; 295 } 296 @property auto ref receiveAsRange() pure @safe nothrow { 297 return _receiveAsRange; 298 } 299 /// string representation of response 300 override string toString() const { 301 return "Response(%d, %s)".format(_code, _finalURI.uri()); 302 } 303 /// format response to string (hpPqsBTUS). 304 /** 305 306 %h - remote hostname 307 308 %p - remote port 309 310 %P - remote path 311 312 %q - query parameters 313 314 %s - string representation 315 316 %B - received bytes 317 318 %T - resquest total time 319 320 %U - request uri() 321 322 %S - status code 323 */ 324 string format(string fmt) const { 325 import std.array; 326 auto a = appender!string(); 327 auto f = FormatSpec!char(fmt); 328 while (f.writeUpToNextSpec(a)) { 329 switch (f.spec) { 330 case 'h': 331 // Remote hostname. 332 a.put(_uri.host); 333 break; 334 case 'p': 335 // Remote port. 336 a.put("%d".format(_uri.port)); 337 break; 338 case 'P': 339 // Path. 340 a.put(_uri.path); 341 break; 342 case 'q': 343 // query parameters supplied with url. 344 a.put(_uri.query); 345 break; 346 case 's': 347 a.put("Response(%d, %s)".format(_code, _finalURI.uri())); 348 break; 349 case 'B': // received bytes 350 a.put("%d".format(_responseBody.length)); 351 break; 352 case 'T': // request total time, ms 353 a.put("%d".format((_finishedAt - _startedAt).total!"msecs")); 354 break; 355 case 'U': 356 a.put(_uri.uri()); 357 break; 358 case 'S': 359 a.put("%d".format(_code)); 360 break; 361 default: 362 throw new FormatException("Unknown Response format specifier: %" ~ f.spec); 363 } 364 } 365 return a.data(); 366 } 367 } 368 369 struct _UH { 370 // flags for each important header, added by user using addHeaders 371 mixin(bitfields!( 372 bool, "Host", 1, 373 bool, "UserAgent", 1, 374 bool, "ContentLength", 1, 375 bool, "Connection", 1, 376 bool, "AcceptEncoding", 1, 377 bool, "ContentType", 1, 378 bool, "Cookie", 1, 379 uint, "", 1 380 )); 381 } 382