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