1 /** 2 * ae.sys.net implementation using std.net.curl 3 * Note: std.net.curl requires libcurl. 4 * 5 * License: 6 * This Source Code Form is subject to the terms of 7 * the Mozilla Public License, v. 2.0. If a copy of 8 * the MPL was not distributed with this file, You 9 * can obtain one at http://mozilla.org/MPL/2.0/. 10 * 11 * Authors: 12 * Vladimir Panteleev <vladimir@thecybershadow.net> 13 */ 14 15 module ae.sys.net.curl; 16 17 import etc.c.curl : CurlSeekPos, CurlSeek; 18 19 import std.algorithm.comparison; 20 import std.file; 21 import std.net.curl; 22 import std.string; 23 24 import ae.net.http.common; 25 import ae.net.ietf.url; 26 import ae.sys.data; 27 import ae.sys.net; 28 29 class CurlNetwork : Network 30 { 31 override void downloadFile(string url, string target) 32 { 33 std.file.write(target, getFile(url)); 34 } 35 36 override void[] getFile(string url) 37 { 38 return get!(AutoProtocol, ubyte)(url); 39 } 40 41 override void[] post(string url, in void[] data) 42 { 43 return .post!ubyte(url, data); 44 } 45 46 override bool urlOK(string url) 47 { 48 try 49 { 50 auto http = HTTP(url); 51 http.method = HTTP.Method.head; 52 http.perform(); 53 return http.statusLine.code == 200; // OK 54 } 55 catch (Exception e) 56 return false; 57 } 58 59 override string resolveRedirect(string url) 60 { 61 string result = null; 62 63 auto http = HTTP(url); 64 http.method = HTTP.Method.head; 65 http.onReceiveHeader = 66 (in char[] key, in char[] value) 67 { 68 if (icmp(key, "Location")==0) 69 { 70 result = value.idup; 71 if (result) 72 result = url.applyRelativeURL(result); 73 } 74 }; 75 http.perform(); 76 77 return result; 78 } 79 80 override HttpResponse httpRequest(HttpRequest request) 81 { 82 auto http = HTTP(); 83 http.url = request.url; 84 switch (request.method.toUpper) 85 { 86 case "HEAD" : http.method = HTTP.Method.head; break; 87 case "GET" : http.method = HTTP.Method.get; break; 88 case "POST" : http.method = HTTP.Method.post; break; 89 case "PUT" : http.method = HTTP.Method.put; break; 90 case "DEL" : http.method = HTTP.Method.del; break; 91 case "OPTIONS": http.method = HTTP.Method.options; break; 92 case "TRACE" : http.method = HTTP.Method.trace; break; 93 case "CONNECT": http.method = HTTP.Method.connect; break; 94 case "PATCH" : http.method = HTTP.Method.patch; break; 95 default: throw new Exception("Unknown HTTP method: " ~ request.method); 96 } 97 foreach (name, value; request.headers) 98 http.addRequestHeader(name, value); 99 100 if (request.data) 101 { 102 auto requestData = request.data.bytes; 103 http.contentLength = requestData.length; 104 auto remainingData = requestData; 105 http.onSend = 106 (void[] buf) 107 { 108 size_t bytesToSend = min(buf.length, remainingData.length); 109 if (!bytesToSend) 110 return 0; 111 auto dataToSend = remainingData[0 .. bytesToSend]; 112 { 113 size_t p = 0; 114 foreach (datum; dataToSend) 115 { 116 buf[p .. p + datum.length] = datum.contents; 117 p += datum.length; 118 } 119 } 120 remainingData = remainingData[bytesToSend .. $].bytes; 121 return bytesToSend; 122 }; 123 http.handle.onSeek = 124 (long offset, CurlSeekPos mode) 125 { 126 switch (mode) 127 { 128 case CurlSeekPos.set: 129 remainingData = requestData[cast(size_t) offset .. $].bytes; 130 return CurlSeek.ok; 131 default: 132 return CurlSeek.cantseek; 133 } 134 }; 135 } 136 137 auto response = new HttpResponse; 138 http.onReceiveStatusLine = 139 (HTTP.StatusLine statusLine) 140 { 141 response.status = cast(HttpStatusCode)statusLine.code; 142 response.statusMessage = statusLine.reason; 143 }; 144 http.onReceiveHeader = 145 (in char[] key, in char[] value) 146 { 147 response.headers.add(key.idup, value.idup); 148 }; 149 http.onReceive = 150 (ubyte[] data) 151 { 152 response.data ~= Data(data); 153 return data.length; 154 }; 155 http.perform(); 156 return response; 157 } 158 } 159 160 static this() 161 { 162 net = new CurlNetwork(); 163 }