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 <ae@cy.md> 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 : Data; 27 import ae.sys.dataset : DataVec, bytes; 28 import ae.sys.net; 29 30 /// Implementation of `Network` using libcurl. 31 class CurlNetwork : Network 32 { 33 override void downloadFile(string url, string target) 34 { 35 std.file.write(target, getFile(url)); 36 } /// 37 38 override ubyte[] getFile(string url) 39 { 40 return get!(AutoProtocol, ubyte)(url); 41 } /// 42 43 override ubyte[] post(string url, const(ubyte)[] data) 44 { 45 return .post!ubyte(url, data); 46 } /// 47 48 override bool urlOK(string url) 49 { 50 try 51 { 52 auto http = HTTP(url); 53 http.method = HTTP.Method.head; 54 http.perform(); 55 return http.statusLine.code == 200; // OK 56 } 57 catch (Exception e) 58 return false; 59 } /// 60 61 override string resolveRedirect(string url) 62 { 63 string result = null; 64 65 auto http = HTTP(url); 66 http.method = HTTP.Method.head; 67 http.onReceiveHeader = 68 (in char[] key, in char[] value) 69 { 70 if (icmp(key, "Location")==0) 71 { 72 result = value.idup; 73 if (result) 74 result = url.applyRelativeURL(result); 75 } 76 }; 77 http.perform(); 78 79 return result; 80 } /// 81 82 override HttpResponse httpRequest(HttpRequest request) 83 { 84 auto http = HTTP(); 85 http.url = request.url; 86 switch (request.method.toUpper) 87 { 88 case "HEAD" : http.method = HTTP.Method.head; break; 89 case "GET" : http.method = HTTP.Method.get; break; 90 case "POST" : http.method = HTTP.Method.post; break; 91 case "PUT" : http.method = HTTP.Method.put; break; 92 case "DEL" : http.method = HTTP.Method.del; break; 93 case "OPTIONS": http.method = HTTP.Method.options; break; 94 case "TRACE" : http.method = HTTP.Method.trace; break; 95 case "CONNECT": http.method = HTTP.Method.connect; break; 96 case "PATCH" : http.method = HTTP.Method.patch; break; 97 default: throw new Exception("Unknown HTTP method: " ~ request.method); 98 } 99 foreach (name, value; request.headers) 100 http.addRequestHeader(name, value); 101 102 DataVec remainingData; // https://issues.dlang.org/show_bug.cgi?id=14261 103 if (request.data) 104 { 105 remainingData = request.data.dup; 106 http.contentLength = request.data.bytes.length; 107 http.onSend = 108 (void[] buf) 109 { 110 size_t bytesToSend = min(buf.length, remainingData.bytes.length); 111 if (!bytesToSend) 112 return 0; 113 auto dataToSend = remainingData.bytes[0 .. bytesToSend]; 114 { 115 size_t p = 0; 116 foreach (ref datum; dataToSend) 117 { 118 datum.enter((contents) { 119 buf[p .. p + datum.length] = contents; 120 }); 121 p += datum.length; 122 } 123 } 124 remainingData = remainingData.bytes[bytesToSend .. $]; 125 return bytesToSend; 126 }; 127 http.handle.onSeek = 128 (long offset, CurlSeekPos mode) 129 { 130 switch (mode) 131 { 132 case CurlSeekPos.set: 133 remainingData = request.data.bytes[cast(size_t) offset .. $]; 134 return CurlSeek.ok; 135 default: 136 return CurlSeek.cantseek; 137 } 138 }; 139 } 140 141 auto response = new HttpResponse; 142 http.onReceiveStatusLine = 143 (HTTP.StatusLine statusLine) 144 { 145 response.status = cast(HttpStatusCode)statusLine.code; 146 response.statusMessage = statusLine.reason; 147 }; 148 http.onReceiveHeader = 149 (in char[] key, in char[] value) 150 { 151 response.headers.add(key.idup, value.idup); 152 }; 153 http.onReceive = 154 (ubyte[] data) 155 { 156 response.data ~= Data(data); 157 return data.length; 158 }; 159 http.perform(); 160 return response; 161 } /// 162 } 163 164 static this() 165 { 166 net = new CurlNetwork(); 167 }