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

Side by Side Diff: pkg/http/lib/src/curl_client.dart

Issue 11394003: Add an HTTP client based on Curl. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 1 month 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
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:io';
8
9 import 'base_client.dart';
10 import 'base_request.dart';
11 import 'streamed_response.dart';
12 import 'utils.dart';
13
14 /// A drop-in replacement for [Client] that uses the `curl` command-line utility
15 /// rather than [dart:io] to make requests. This class will only exist
16 /// temporarily until [dart:io] natively supports requests over HTTPS.
Bob Nystrom 2012/11/07 23:15:15 Given this, maybe it should be put in a sublibrary
nweiz 2012/11/08 00:33:37 I don't think that adds any utility. It just makes
17 class CurlClient extends BaseClient {
18 /// The path to the `curl` executable to run. By default, this will look up
19 /// `curl` on the system path.
20 final String executable;
21
22 CurlClient([String executable])
Bob Nystrom 2012/11/07 23:15:15 It's seems a bit redundant, but document the const
nweiz 2012/11/08 00:33:37 Done.
23 : executable = executable == null ? "curl" : executable;
24
25 /// Sends a request via `curl` and returns the response.
26 Future<StreamedResponse> send(BaseRequest request) {
27 var requestStream = request.finalize();
28 return withTempDir((tempDir) {
29 var headerFile = new Path(tempDir).append("curl-headers").toNativePath();
30 var arguments = _argumentsForRequest(request, headerFile);
31 var process;
32 return Process.start("curl", arguments).chain((process_) {
33 process = process_;
34 if (requestStream.closed) {
35 process.stdin.close();
36 } else {
37 requestStream.pipe(process.stdin);
38 }
39
40 return _waitForHeaders(process, request.method == "HEAD");
41 }).chain((_) => new File(headerFile).readAsLines())
42 .transform((lines) => _buildResponse(process, lines));
43 });
44 }
45
46 /// Returns the list of arguments to `curl` necessary for performing
47 /// [request]. [headerFile] is the path to the file where the response headers
48 /// should be stored.
49 List<String> _argumentsForRequest(BaseRequest request, String headerFile) {
50 var arguments = ["--dump-header", headerFile];
51 if (request.method == 'HEAD') {
52 arguments.add("--head");
53 } else {
54 arguments.add("--request");
55 arguments.add(request.method);
56 }
57 if (request.followRedirects) {
58 arguments.add("--location");
59 arguments.add("--max-redirs");
60 arguments.add(request.maxRedirects.toString());
61 }
62 if (request.contentLength != 0) {
63 arguments.add("--data-binary");
64 arguments.add("@-");
65 }
66
67 // Override the headers automatically added by curl. We want to make it
68 // behave as much like the dart:io client as possible.
69 var headers = {
70 'accept': '',
71 'user-agent': ''
72 };
73 mapAddAll(headers, request.headers);
74 if (request.contentLength < 0) {
75 headers['content-length'] = '';
76 headers['transfer-encoding'] = 'chunked';
77 } else if (request.contentLength > 0) {
78 headers['content-length'] = request.contentLength.toString();
79 }
80
81 headers.forEach((name, value) {
82 arguments.add("--header");
83 arguments.add("$name: $value");
84 });
85 arguments.add(request.url.toString());
86
87 return arguments;
88 }
89
90 /// Returns a [Future] that completes once the `curl` [process] has finished
91 /// receiving the response headers. [noResponseBody] indicates that the server
92 /// is only expected to send headers, not a body (e.g. for HEAD requests).
93 Future _waitForHeaders(Process process, bool noResponseBody) {
Bob Nystrom 2012/11/07 23:15:15 How about "expectBody" instead of "noResponseBody"
nweiz 2012/11/08 00:33:37 Done.
94 var exitCompleter = new Completer<int>();
95 var exitFuture = exitCompleter.future;
96 process.onExit = (exitCode) {
97 if (exitCode == 0) {
98 exitCompleter.complete(0);
99 return;
100 }
101
102 chainToCompleter(consumeInputStream(process.stderr)
103 .transform((stderrBytes) {
104 var message = new String.fromCharCodes(stderrBytes);
105 if (exitCode == 47) {
106 throw new RedirectLimitExceededException(message);
107 } else {
108 throw new HttpException(message);
109 }
110 }), exitCompleter);
111 };
112
113 // If there's not going to be a response body (e.g. for HEAD requests), curl
114 // prints the headers to stdout instead of the body. We want to wait until
115 // all the headers are received to read them from the header file.
116 if (noResponseBody) {
117 return Futures.wait([
118 consumeInputStream(process.stdout),
119 exitFuture
120 ]);
121 }
122
123 var completer = new Completer();
124 var resetCallbacks = () {
Bob Nystrom 2012/11/07 23:15:15 resetCallbacks() { ... } Dart has nested functi
nweiz 2012/11/08 00:33:37 Done.
125 process.stdout.onData = null;
126 process.stdout.onError = null;
127 process.stdout.onClosed = null;
128 };
129 process.stdout.onData = () {
130 // TODO(nweiz): If an error happens after the body data starts being
131 // received, it should be piped through Response.stream once issue
132 // 3657 is fixed.
133 exitFuture.handleException((e) => true);
134 resetCallbacks();
135 completer.complete(null);
136 };
137 process.stdout.onError = (e) {
138 resetCallbacks();
139 completer.completeException(e);
140 };
141 process.stdout.onClosed = () {
142 resetCallbacks();
143 chainToCompleter(exitFuture, completer);
144 };
145 return completer.future;
146 }
147
148 /// Returns a [StreamedResponse] from the response data printed by the `curl`
149 /// [process]. [lines] are the headers that `curl` wrote to a file.
150 StreamedResponse _buildResponse(Process process, List<String> lines) {
151 // When curl follows redirects, it prints the redirect headers as well as
152 // the headers of the final request. Each block is separated by a blank
153 // line. We just care about the last block. There is one trailing empty
154 // line, though, which we don't want to consider a separator.
155 var lastBlank = lines.lastIndexOf("", lines.length - 2);
156 if (lastBlank != -1) lines.removeRange(0, lastBlank + 1);
157
158 var statusParts = lines.removeAt(0).split(" ");
159 var status = int.parse(statusParts[1]);
160 var isRedirect = status >= 300 && status < 400;
161 var reasonPhrase =
162 Strings.join(" ", statusParts.getRange(2, statusParts.length - 2));
163 var headers = <String>{};
164 for (var line in lines) {
165 if (line.isEmpty) continue;
166 var split = split1(line, ":");
167 headers[split[0].toLowerCase()] = split[1].trim();
Bob Nystrom 2012/11/07 23:15:15 What's the toLowercase here for?
nweiz 2012/11/08 00:33:37 Matching dart:io's convention of lower-casing head
Bob Nystrom 2012/11/08 00:49:13 Works for me. A comment to that effect would help
168 }
169 var responseStream = process.stdout;
170 if (responseStream.closed) {
171 responseStream = new ListInputStream();
172 responseStream.markEndOfStream();
173 }
174 var contentLength = -1;
175 if (headers.containsKey('content-length')) {
176 contentLength = int.parse(headers['content-length']);
177 }
178
179 return new StreamedResponse(responseStream, status, contentLength,
180 headers: headers,
181 isRedirect: isRedirect,
182 reasonPhrase: reasonPhrase);
183 }
184 }
OLDNEW
« no previous file with comments | « pkg/http/lib/http.dart ('k') | pkg/http/lib/src/utils.dart » ('j') | pkg/http/lib/src/utils.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698