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:async'; | 7 import 'dart:async'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 | 9 |
10 import '../../pkg/http/lib/http.dart' as http; | 10 import '../../pkg/http/lib/http.dart' as http; |
(...skipping 23 matching lines...) Expand all Loading... |
34 Future<http.StreamedResponse> send(http.BaseRequest request) { | 34 Future<http.StreamedResponse> send(http.BaseRequest request) { |
35 log.fine("Sending Curl request $request"); | 35 log.fine("Sending Curl request $request"); |
36 | 36 |
37 var requestStream = request.finalize(); | 37 var requestStream = request.finalize(); |
38 return withTempDir((tempDir) { | 38 return withTempDir((tempDir) { |
39 var headerFile = join(tempDir, "curl-headers"); | 39 var headerFile = join(tempDir, "curl-headers"); |
40 var arguments = _argumentsForRequest(request, headerFile); | 40 var arguments = _argumentsForRequest(request, headerFile); |
41 var process; | 41 var process; |
42 return startProcess(executable, arguments).then((process_) { | 42 return startProcess(executable, arguments).then((process_) { |
43 process = process_; | 43 process = process_; |
44 return requestStream.pipe(wrapOutputStream(process.stdin)); | 44 return Future.wait([ |
45 }).then((_) { | 45 store(requestStream, process.stdin), |
46 return _waitForHeaders(process, expectBody: request.method != "HEAD"); | 46 _waitForHeaders(process, expectBody: request.method != "HEAD") |
| 47 ]); |
47 }).then((_) => new File(headerFile).readAsLines()) | 48 }).then((_) => new File(headerFile).readAsLines()) |
48 .then((lines) => _buildResponse(request, process, lines)); | 49 .then((lines) => _buildResponse(request, process, lines)); |
49 }); | 50 }); |
50 } | 51 } |
51 | 52 |
52 /// Returns the list of arguments to `curl` necessary for performing | 53 /// Returns the list of arguments to `curl` necessary for performing |
53 /// [request]. [headerFile] is the path to the file where the response headers | 54 /// [request]. [headerFile] is the path to the file where the response headers |
54 /// should be stored. | 55 /// should be stored. |
55 List<String> _argumentsForRequest( | 56 List<String> _argumentsForRequest( |
56 http.BaseRequest request, String headerFile) { | 57 http.BaseRequest request, String headerFile) { |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
104 /// Returns a [Future] that completes once the `curl` [process] has finished | 105 /// Returns a [Future] that completes once the `curl` [process] has finished |
105 /// receiving the response headers. [expectBody] indicates that the server is | 106 /// receiving the response headers. [expectBody] indicates that the server is |
106 /// expected to send a response body (which is not the case for HEAD | 107 /// expected to send a response body (which is not the case for HEAD |
107 /// requests). | 108 /// requests). |
108 /// | 109 /// |
109 /// Curl prints the headers to a file and then prints the body to stdout. So, | 110 /// Curl prints the headers to a file and then prints the body to stdout. So, |
110 /// in theory, we could read the headers as soon as we see anything appear | 111 /// in theory, we could read the headers as soon as we see anything appear |
111 /// in stdout. However, that seems to be too early to successfully read the | 112 /// in stdout. However, that seems to be too early to successfully read the |
112 /// file (at least on Mac). Instead, this just waits until the entire process | 113 /// file (at least on Mac). Instead, this just waits until the entire process |
113 /// has completed. | 114 /// has completed. |
114 Future _waitForHeaders(Process process, {bool expectBody}) { | 115 Future _waitForHeaders(PubProcess process, {bool expectBody}) { |
115 var completer = new Completer(); | 116 var future = process.exitCode.then((exitCode) { |
116 process.onExit = (exitCode) { | |
117 log.io("Curl process exited with code $exitCode."); | 117 log.io("Curl process exited with code $exitCode."); |
| 118 if (exitCode == 0) return; |
118 | 119 |
119 if (exitCode == 0) { | 120 process.stderr.bytesToString().then((message) { |
120 completer.complete(null); | |
121 return; | |
122 } | |
123 | |
124 chainToCompleter(consumeInputStream(process.stderr).then((stderrBytes) { | |
125 var message = new String.fromCharCodes(stderrBytes); | |
126 log.fine('Got error reading headers from curl: $message'); | 121 log.fine('Got error reading headers from curl: $message'); |
127 if (exitCode == 47) { | 122 if (exitCode == 47) { |
128 throw new RedirectLimitExceededException([]); | 123 throw new RedirectLimitExceededException([]); |
129 } else { | 124 } else { |
130 throw new HttpException(message); | 125 throw new HttpException(message); |
131 } | 126 } |
132 }), completer); | 127 }); |
133 }; | 128 }); |
| 129 |
| 130 if (expectBody) return future; |
134 | 131 |
135 // If there's not going to be a response body (e.g. for HEAD requests), curl | 132 // If there's not going to be a response body (e.g. for HEAD requests), curl |
136 // prints the headers to stdout instead of the body. We want to wait until | 133 // prints the headers to stdout instead of the body. We want to wait until |
137 // all the headers are received to read them from the header file. | 134 // all the headers are received to read them from the header file. |
138 if (!expectBody) { | 135 return Future.wait([process.stdout.toBytes(), future]); |
139 return Future.wait([ | |
140 consumeInputStream(process.stdout), | |
141 completer.future | |
142 ]); | |
143 } | |
144 | |
145 return completer.future; | |
146 } | 136 } |
147 | 137 |
148 /// Returns a [http.StreamedResponse] from the response data printed by the | 138 /// Returns a [http.StreamedResponse] from the response data printed by the |
149 /// `curl` [process]. [lines] are the headers that `curl` wrote to a file. | 139 /// `curl` [process]. [lines] are the headers that `curl` wrote to a file. |
150 http.StreamedResponse _buildResponse( | 140 http.StreamedResponse _buildResponse( |
151 http.BaseRequest request, Process process, List<String> lines) { | 141 http.BaseRequest request, PubProcess process, List<String> lines) { |
152 // When curl follows redirects, it prints the redirect headers as well as | 142 // When curl follows redirects, it prints the redirect headers as well as |
153 // the headers of the final request. Each block is separated by a blank | 143 // the headers of the final request. Each block is separated by a blank |
154 // line. We just care about the last block. There is one trailing empty | 144 // line. We just care about the last block. There is one trailing empty |
155 // line, though, which we don't want to consider a separator. | 145 // line, though, which we don't want to consider a separator. |
156 var lastBlank = lines.lastIndexOf("", lines.length - 2); | 146 var lastBlank = lines.lastIndexOf("", lines.length - 2); |
157 if (lastBlank != -1) lines.removeRange(0, lastBlank + 1); | 147 if (lastBlank != -1) lines.removeRange(0, lastBlank + 1); |
158 | 148 |
159 var statusParts = lines.removeAt(0).split(" "); | 149 var statusParts = lines.removeAt(0).split(" "); |
160 var status = int.parse(statusParts[1]); | 150 var status = int.parse(statusParts[1]); |
161 var isRedirect = status >= 300 && status < 400; | 151 var isRedirect = status >= 300 && status < 400; |
162 var reasonPhrase = | 152 var reasonPhrase = |
163 Strings.join(statusParts.getRange(2, statusParts.length - 2), " "); | 153 Strings.join(statusParts.getRange(2, statusParts.length - 2), " "); |
164 var headers = {}; | 154 var headers = {}; |
165 for (var line in lines) { | 155 for (var line in lines) { |
166 if (line.isEmpty) continue; | 156 if (line.isEmpty) continue; |
167 var split = split1(line, ":"); | 157 var split = split1(line, ":"); |
168 headers[split[0].toLowerCase()] = split[1].trim(); | 158 headers[split[0].toLowerCase()] = split[1].trim(); |
169 } | 159 } |
170 var responseStream = process.stdout; | |
171 if (responseStream.closed) { | |
172 responseStream = new ListInputStream(); | |
173 responseStream.markEndOfStream(); | |
174 } | |
175 var contentLength = -1; | 160 var contentLength = -1; |
176 if (headers.containsKey('content-length')) { | 161 if (headers.containsKey('content-length')) { |
177 contentLength = int.parse(headers['content-length']); | 162 contentLength = int.parse(headers['content-length']); |
178 } | 163 } |
179 | 164 |
180 return new http.StreamedResponse( | 165 return new http.StreamedResponse( |
181 wrapInputStream(responseStream), status, contentLength, | 166 process.stdout, status, contentLength, |
182 request: request, | 167 request: request, |
183 headers: headers, | 168 headers: headers, |
184 isRedirect: isRedirect, | 169 isRedirect: isRedirect, |
185 reasonPhrase: reasonPhrase); | 170 reasonPhrase: reasonPhrase); |
186 } | 171 } |
187 | 172 |
188 /// The default executable to use for running curl. On Windows, this is the | 173 /// The default executable to use for running curl. On Windows, this is the |
189 /// path to the bundled `curl.exe`; elsewhere, this is just "curl", and we | 174 /// path to the bundled `curl.exe`; elsewhere, this is just "curl", and we |
190 /// assume it to be installed and on the user's PATH. | 175 /// assume it to be installed and on the user's PATH. |
191 static String get _defaultExecutable { | 176 static String get _defaultExecutable { |
192 if (Platform.operatingSystem != 'windows') return 'curl'; | 177 if (Platform.operatingSystem != 'windows') return 'curl'; |
193 // Note: This line of code gets munged by create_sdk.py to be the correct | 178 // Note: This line of code gets munged by create_sdk.py to be the correct |
194 // relative path to curl in the SDK. | 179 // relative path to curl in the SDK. |
195 var pathToCurl = "../../third_party/curl/curl.exe"; | 180 var pathToCurl = "../../third_party/curl/curl.exe"; |
196 return relativeToPub(pathToCurl); | 181 return relativeToPub(pathToCurl); |
197 } | 182 } |
198 } | 183 } |
OLD | NEW |