OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library curl_client; | 5 library curl_client; |
6 | 6 |
7 import 'dart:io'; | 7 import 'dart:io'; |
8 | 8 |
9 import '../../pkg/http/lib/http.dart' as http; | 9 import '../../pkg/http/lib/http.dart' as http; |
10 import 'io.dart'; | 10 import 'io.dart'; |
11 import 'log.dart' as log; | |
12 import 'utils.dart'; | 11 import 'utils.dart'; |
13 | 12 |
14 /// A drop-in replacement for [http.Client] that uses the `curl` command-line | 13 /// A drop-in replacement for [http.Client] that uses the `curl` command-line |
15 /// utility rather than [dart:io] to make requests. This class will only exist | 14 /// utility rather than [dart:io] to make requests. This class will only exist |
16 /// temporarily until [dart:io] natively supports requests over HTTPS. | 15 /// temporarily until [dart:io] natively supports requests over HTTPS. |
17 class CurlClient extends http.BaseClient { | 16 class CurlClient extends http.BaseClient { |
18 /// The path to the `curl` executable to run. | 17 /// The path to the `curl` executable to run. |
19 /// | 18 /// |
20 /// By default on Unix-like operating systems, this will look up `curl` on the | 19 /// By default on Unix-like operating systems, this will look up `curl` on the |
21 /// system path. On Windows, it will use the bundled `curl.exe`. | 20 /// system path. On Windows, it will use the bundled `curl.exe`. |
22 final String executable; | 21 final String executable; |
23 | 22 |
24 /// Creates a new [CurlClient] with [executable] as the path to the `curl` | 23 /// Creates a new [CurlClient] with [executable] as the path to the `curl` |
25 /// executable. | 24 /// executable. |
26 /// | 25 /// |
27 /// By default on Unix-like operating systems, this will look up `curl` on the | 26 /// By default on Unix-like operating systems, this will look up `curl` on the |
28 /// system path. On Windows, it will use the bundled `curl.exe`. | 27 /// system path. On Windows, it will use the bundled `curl.exe`. |
29 CurlClient([String executable]) | 28 CurlClient([String executable]) |
30 : executable = executable == null ? _defaultExecutable : executable; | 29 : executable = executable == null ? _defaultExecutable : executable; |
31 | 30 |
32 /// Sends a request via `curl` and returns the response. | 31 /// Sends a request via `curl` and returns the response. |
33 Future<http.StreamedResponse> send(http.BaseRequest request) { | 32 Future<http.StreamedResponse> send(http.BaseRequest request) { |
34 log.fine("Sending Curl request $request"); | |
35 | |
36 var requestStream = request.finalize(); | 33 var requestStream = request.finalize(); |
37 return withTempDir((tempDir) { | 34 return withTempDir((tempDir) { |
38 var headerFile = new Path(tempDir).append("curl-headers").toNativePath(); | 35 var headerFile = new Path(tempDir).append("curl-headers").toNativePath(); |
39 var arguments = _argumentsForRequest(request, headerFile); | 36 var arguments = _argumentsForRequest(request, headerFile); |
40 log.process(executable, arguments); | |
41 var process; | 37 var process; |
42 return Process.start(executable, arguments).chain((process_) { | 38 return Process.start(executable, arguments).chain((process_) { |
43 process = process_; | 39 process = process_; |
44 if (requestStream.closed) { | 40 if (requestStream.closed) { |
45 process.stdin.close(); | 41 process.stdin.close(); |
46 } else { | 42 } else { |
47 requestStream.pipe(process.stdin); | 43 requestStream.pipe(process.stdin); |
48 } | 44 } |
49 | 45 |
50 return _waitForHeaders(process, expectBody: request.method != "HEAD"); | 46 return _waitForHeaders(process, expectBody: request.method != "HEAD"); |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
111 /// requests). | 107 /// requests). |
112 /// | 108 /// |
113 /// Curl prints the headers to a file and then prints the body to stdout. So, | 109 /// Curl prints the headers to a file and then prints the body to stdout. So, |
114 /// in theory, we could read the headers as soon as we see anything appear | 110 /// in theory, we could read the headers as soon as we see anything appear |
115 /// in stdout. However, that seems to be too early to successfully read the | 111 /// in stdout. However, that seems to be too early to successfully read the |
116 /// file (at least on Mac). Instead, this just waits until the entire process | 112 /// file (at least on Mac). Instead, this just waits until the entire process |
117 /// has completed. | 113 /// has completed. |
118 Future _waitForHeaders(Process process, {bool expectBody}) { | 114 Future _waitForHeaders(Process process, {bool expectBody}) { |
119 var completer = new Completer(); | 115 var completer = new Completer(); |
120 process.onExit = (exitCode) { | 116 process.onExit = (exitCode) { |
121 log.io("Curl process exited with code $exitCode."); | |
122 | |
123 if (exitCode == 0) { | 117 if (exitCode == 0) { |
124 completer.complete(null); | 118 completer.complete(null); |
125 return; | 119 return; |
126 } | 120 } |
127 | 121 |
128 chainToCompleter(consumeInputStream(process.stderr) | 122 chainToCompleter(consumeInputStream(process.stderr) |
129 .transform((stderrBytes) { | 123 .transform((stderrBytes) { |
130 var message = new String.fromCharCodes(stderrBytes); | 124 var message = new String.fromCharCodes(stderrBytes); |
131 log.fine('Got error reading headers from curl: $message'); | |
132 if (exitCode == 47) { | 125 if (exitCode == 47) { |
133 throw new RedirectLimitExceededException([]); | 126 throw new RedirectLimitExceededException([]); |
134 } else { | 127 } else { |
135 throw new HttpException(message); | 128 throw new HttpException(message); |
136 } | 129 } |
137 }), completer); | 130 }), completer); |
138 }; | 131 }; |
139 | 132 |
140 // If there's not going to be a response body (e.g. for HEAD requests), curl | 133 // If there's not going to be a response body (e.g. for HEAD requests), curl |
141 // prints the headers to stdout instead of the body. We want to wait until | 134 // prints the headers to stdout instead of the body. We want to wait until |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
193 /// path to the bundled `curl.exe`; elsewhere, this is just "curl", and we | 186 /// path to the bundled `curl.exe`; elsewhere, this is just "curl", and we |
194 /// assume it to be installed and on the user's PATH. | 187 /// assume it to be installed and on the user's PATH. |
195 static String get _defaultExecutable { | 188 static String get _defaultExecutable { |
196 if (Platform.operatingSystem != 'windows') return 'curl'; | 189 if (Platform.operatingSystem != 'windows') return 'curl'; |
197 // Note: This line of code gets munged by create_sdk.py to be the correct | 190 // Note: This line of code gets munged by create_sdk.py to be the correct |
198 // relative path to curl in the SDK. | 191 // relative path to curl in the SDK. |
199 var pathToCurl = "../../third_party/curl/curl.exe"; | 192 var pathToCurl = "../../third_party/curl/curl.exe"; |
200 return relativeToPub(pathToCurl); | 193 return relativeToPub(pathToCurl); |
201 } | 194 } |
202 } | 195 } |
OLD | NEW |