Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(501)

Side by Side Diff: utils/pub/curl_client.dart

Issue 12090104: Stop using cURL in Pub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/curl/openssl.LICENSE ('k') | utils/pub/hosted_source.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library curl_client;
6
7 import 'dart:async';
8 import 'dart:io';
9
10 import '../../pkg/http/lib/http.dart' as http;
11 import 'io.dart';
12 import 'log.dart' as log;
13 import 'utils.dart';
14
15 /// A drop-in replacement for [http.Client] that uses the `curl` command-line
16 /// utility rather than [dart:io] to make requests. This class will only exist
17 /// temporarily until [dart:io] natively supports requests over HTTPS.
18 class CurlClient extends http.BaseClient {
19 /// The path to the `curl` executable to run.
20 ///
21 /// By default on Unix-like operating systems, this will look up `curl` on the
22 /// system path. On Windows, it will use the bundled `curl.exe`.
23 final String executable;
24
25 /// Creates a new [CurlClient] with [executable] as the path to the `curl`
26 /// executable.
27 ///
28 /// By default on Unix-like operating systems, this will look up `curl` on the
29 /// system path. On Windows, it will use the bundled `curl.exe`.
30 CurlClient([String executable])
31 : executable = executable == null ? _defaultExecutable : executable;
32
33 /// Sends a request via `curl` and returns the response.
34 Future<http.StreamedResponse> send(http.BaseRequest request) {
35 log.fine("Sending Curl request $request");
36
37 var requestStream = request.finalize();
38 return withTempDir((tempDir) {
39 var headerFile = join(tempDir, "curl-headers");
40 var arguments = _argumentsForRequest(request, headerFile);
41 var process;
42 return startProcess(executable, arguments).then((process_) {
43 process = process_;
44 return requestStream.pipe(wrapOutputStream(process.stdin));
45 }).then((_) {
46 return _waitForHeaders(process, expectBody: request.method != "HEAD");
47 }).then((_) => new File(headerFile).readAsLines())
48 .then((lines) => _buildResponse(request, process, lines));
49 });
50 }
51
52 /// Returns the list of arguments to `curl` necessary for performing
53 /// [request]. [headerFile] is the path to the file where the response headers
54 /// should be stored.
55 List<String> _argumentsForRequest(
56 http.BaseRequest request, String headerFile) {
57 // Note: This line of code gets munged by create_sdk.py to be the correct
58 // relative path to the certificate file in the SDK.
59 var pathToCertificates = "../../third_party/curl/ca-certificates.crt";
60
61 var arguments = [
62 "--dump-header", headerFile,
63 "--cacert", relativeToPub(pathToCertificates)
64 ];
65 if (request.method == 'HEAD') {
66 arguments.add("--head");
67 } else {
68 arguments.add("--request");
69 arguments.add(request.method);
70 }
71 if (request.followRedirects) {
72 arguments.add("--location");
73 arguments.add("--max-redirs");
74 arguments.add(request.maxRedirects.toString());
75 }
76 if (request.contentLength != 0) {
77 arguments.add("--data-binary");
78 arguments.add("@-");
79 }
80
81 // Override the headers automatically added by curl. We want to make it
82 // behave as much like the dart:io client as possible.
83 var headers = {
84 'accept': '',
85 'user-agent': ''
86 };
87 request.headers.forEach((name, value) => headers[name] = value);
88 if (request.contentLength < 0) {
89 headers['content-length'] = '';
90 headers['transfer-encoding'] = 'chunked';
91 } else if (request.contentLength > 0) {
92 headers['content-length'] = request.contentLength.toString();
93 }
94
95 headers.forEach((name, value) {
96 arguments.add("--header");
97 arguments.add("$name: $value");
98 });
99 arguments.add(request.url.toString());
100
101 return arguments;
102 }
103
104 /// Returns a [Future] that completes once the `curl` [process] has finished
105 /// 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 /// requests).
108 ///
109 /// 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 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 /// has completed.
114 Future _waitForHeaders(Process process, {bool expectBody}) {
115 var completer = new Completer();
116 process.onExit = (exitCode) {
117 log.io("Curl process exited with code $exitCode.");
118
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);
126 log.fine('Got error reading headers from curl: $message');
127 if (exitCode == 47) {
128 throw new RedirectLimitExceededException([]);
129 } else {
130 throw new HttpException(message);
131 }
132 }), completer);
133 };
134
135 // 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
137 // all the headers are received to read them from the header file.
138 if (!expectBody) {
139 return Future.wait([
140 consumeInputStream(process.stdout),
141 completer.future
142 ]);
143 }
144
145 return completer.future;
146 }
147
148 /// Returns a [http.StreamedResponse] from the response data printed by the
149 /// `curl` [process]. [lines] are the headers that `curl` wrote to a file.
150 http.StreamedResponse _buildResponse(
151 http.BaseRequest request, Process process, List<String> lines) {
152 // 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
154 // 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.
156 var lastBlank = lines.lastIndexOf("", lines.length - 2);
157 if (lastBlank != -1) lines.removeRange(0, lastBlank + 1);
158
159 var statusParts = lines.removeAt(0).split(" ");
160 var status = int.parse(statusParts[1]);
161 var isRedirect = status >= 300 && status < 400;
162 var reasonPhrase =
163 Strings.join(statusParts.getRange(2, statusParts.length - 2), " ");
164 var headers = {};
165 for (var line in lines) {
166 if (line.isEmpty) continue;
167 var split = split1(line, ":");
168 headers[split[0].toLowerCase()] = split[1].trim();
169 }
170 var responseStream = process.stdout;
171 if (responseStream.closed) {
172 responseStream = new ListInputStream();
173 responseStream.markEndOfStream();
174 }
175 var contentLength = -1;
176 if (headers.containsKey('content-length')) {
177 contentLength = int.parse(headers['content-length']);
178 }
179
180 return new http.StreamedResponse(
181 wrapInputStream(responseStream), status, contentLength,
182 request: request,
183 headers: headers,
184 isRedirect: isRedirect,
185 reasonPhrase: reasonPhrase);
186 }
187
188 /// 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
190 /// assume it to be installed and on the user's PATH.
191 static String get _defaultExecutable {
192 if (Platform.operatingSystem != 'windows') return 'curl';
193 // Note: This line of code gets munged by create_sdk.py to be the correct
194 // relative path to curl in the SDK.
195 var pathToCurl = "../../third_party/curl/curl.exe";
196 return relativeToPub(pathToCurl);
197 }
198 }
OLDNEW
« no previous file with comments | « third_party/curl/openssl.LICENSE ('k') | utils/pub/hosted_source.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698