| 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 |