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