1 /** 2 Downloading and uploading of data from/to URLs. 3 4 Note that this module is scheduled for deprecation and will be replaced by 5 another module in the future. All functions are defined as templates to 6 avoid this dependency issue when building the library. 7 8 Copyright: © 2012-2015 Sönke Ludwig 9 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 10 Authors: Sönke Ludwig 11 */ 12 module vibe.inet.urltransfer; 13 14 import vibe.core.log; 15 import vibe.core.file; 16 import vibe.inet.url; 17 import vibe.core.stream; 18 import vibe.internal.interfaceproxy : asInterface; 19 20 import std.exception; 21 import std.string; 22 23 24 /** 25 Downloads a file from the specified URL. 26 27 Any redirects will be followed until the actual file resource is reached or if the redirection 28 limit of 10 is reached. Note that only HTTP(S) is currently supported. 29 */ 30 void download(HTTPClient_ = void*)(URL url, scope void delegate(scope InputStream) callback, HTTPClient_ client_ = null) 31 { 32 import vibe.http.client; 33 34 assert(url.username.length == 0 && url.password.length == 0, "Auth not supported yet."); 35 assert(url.schema == "http" || url.schema == "https", "Only http(s):// supported for now."); 36 37 HTTPClient client; 38 static if (is(HTTPClient_ == HTTPClient)) client = client_; 39 if (!client) client = new HTTPClient(); 40 scope (exit) { 41 if (client_ is null) // disconnect default client 42 client.disconnect(); 43 } 44 45 if (!url.port) 46 url.port = url.defaultPort; 47 48 foreach( i; 0 .. 10 ){ 49 client.connect(url.host, url.port, url.schema == "https"); 50 logTrace("connect to %s", url.host); 51 bool done = false; 52 client.request( 53 (scope HTTPClientRequest req) { 54 req.requestURL = url.localURI; 55 logTrace("REQUESTING %s!", req.requestURL); 56 }, 57 (scope HTTPClientResponse res) { 58 logTrace("GOT ANSWER!"); 59 60 switch( res.statusCode ){ 61 default: 62 throw new HTTPStatusException(res.statusCode, "Server responded with "~httpStatusText(res.statusCode)~" for "~url.toString()); 63 case HTTPStatus.ok: 64 done = true; 65 callback(res.bodyReader.asInterface!InputStream); 66 break; 67 case HTTPStatus.movedPermanently: 68 case HTTPStatus.found: 69 case HTTPStatus.seeOther: 70 case HTTPStatus.temporaryRedirect: 71 logTrace("Status code: %s", res.statusCode); 72 auto pv = "Location" in res.headers; 73 enforce(pv !is null, "Server responded with redirect but did not specify the redirect location for "~url.toString()); 74 logDebug("Redirect to '%s'", *pv); 75 if( startsWith((*pv), "http:") || startsWith((*pv), "https:") ){ 76 logTrace("parsing %s", *pv); 77 auto nurl = URL(*pv); 78 if (!nurl.port) 79 nurl.port = nurl.defaultPort; 80 if (url.host != nurl.host || url.schema != nurl.schema || 81 url.port != nurl.port) 82 client.disconnect(); 83 url = nurl; 84 } else 85 url.localURI = *pv; 86 break; 87 } 88 } 89 ); 90 if (done) return; 91 } 92 enforce(false, "Too many redirects!"); 93 assert(false); 94 } 95 96 /// ditto 97 void download(HTTPClient_ = void*)(string url, scope void delegate(scope InputStream) callback, HTTPClient_ client_ = null) 98 { 99 download(URL(url), callback, client_); 100 } 101 102 /// ditto 103 void download()(string url, string filename) 104 { 105 download(url, (scope input){ 106 auto fil = openFile(filename, FileMode.createTrunc); 107 scope(exit) fil.close(); 108 fil.write(input); 109 }); 110 } 111 112 /// ditto 113 void download()(URL url, NativePath filename) 114 { 115 download(url.toString(), filename.toNativeString()); 116 }