Line data Source code
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 : import 'dart:async';
6 : import 'dart:collection';
7 :
8 : import 'byte_stream.dart';
9 : import 'client.dart';
10 : import 'streamed_response.dart';
11 : import 'utils.dart';
12 :
13 : /// The base class for HTTP requests.
14 : ///
15 : /// Subclasses of [BaseRequest] can be constructed manually and passed to
16 : /// [BaseClient.send], which allows the user to provide fine-grained control
17 : /// over the request properties. However, usually it's easier to use convenience
18 : /// methods like [get] or [BaseClient.get].
19 : abstract class BaseRequest {
20 : /// The HTTP method of the request. Most commonly "GET" or "POST", less
21 : /// commonly "HEAD", "PUT", or "DELETE". Non-standard method names are also
22 : /// supported.
23 : final String method;
24 :
25 : /// The URL to which the request will be sent.
26 : final Uri url;
27 :
28 : /// The size of the request body, in bytes.
29 : ///
30 : /// This defaults to `null`, which indicates that the size of the request is
31 : /// not known in advance.
32 0 : int get contentLength => _contentLength;
33 : int _contentLength;
34 :
35 : set contentLength(int value) {
36 0 : if (value != null && value < 0) {
37 0 : throw new ArgumentError("Invalid content length $value.");
38 : }
39 0 : _checkFinalized();
40 0 : _contentLength = value;
41 : }
42 :
43 : /// Whether a persistent connection should be maintained with the server.
44 : /// Defaults to true.
45 0 : bool get persistentConnection => _persistentConnection;
46 : bool _persistentConnection = true;
47 :
48 : set persistentConnection(bool value) {
49 0 : _checkFinalized();
50 0 : _persistentConnection = value;
51 : }
52 :
53 : /// Whether the client should follow redirects while resolving this request.
54 : /// Defaults to true.
55 0 : bool get followRedirects => _followRedirects;
56 : bool _followRedirects = true;
57 :
58 : set followRedirects(bool value) {
59 0 : _checkFinalized();
60 0 : _followRedirects = value;
61 : }
62 :
63 : /// The maximum number of redirects to follow when [followRedirects] is true.
64 : /// If this number is exceeded the [BaseResponse] future will signal a
65 : /// [RedirectException]. Defaults to 5.
66 0 : int get maxRedirects => _maxRedirects;
67 : int _maxRedirects = 5;
68 :
69 : set maxRedirects(int value) {
70 0 : _checkFinalized();
71 0 : _maxRedirects = value;
72 : }
73 :
74 : // TODO(nweiz): automatically parse cookies from headers
75 :
76 : // TODO(nweiz): make this a HttpHeaders object
77 : /// The headers for this request.
78 : final Map<String, String> headers;
79 :
80 : /// Whether the request has been finalized.
81 0 : bool get finalized => _finalized;
82 : bool _finalized = false;
83 :
84 : /// Creates a new HTTP request.
85 : BaseRequest(this.method, this.url)
86 0 : : headers = new LinkedHashMap(
87 0 : equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
88 0 : hashCode: (key) => key.toLowerCase().hashCode);
89 :
90 : /// Finalizes the HTTP request in preparation for it being sent. This freezes
91 : /// all mutable fields and returns a single-subscription [ByteStream] that
92 : /// emits the body of the request.
93 : ///
94 : /// The base implementation of this returns null rather than a [ByteStream];
95 : /// subclasses are responsible for creating the return value, which should be
96 : /// single-subscription to ensure that no data is dropped. They should also
97 : /// freeze any additional mutable fields they add that don't make sense to
98 : /// change after the request headers are sent.
99 : ByteStream finalize() {
100 : // TODO(nweiz): freeze headers
101 0 : if (finalized) throw new StateError("Can't finalize a finalized Request.");
102 0 : _finalized = true;
103 : return null;
104 : }
105 :
106 : /// Sends this request.
107 : ///
108 : /// This automatically initializes a new [Client] and closes that client once
109 : /// the request is complete. If you're planning on making multiple requests to
110 : /// the same server, you should use a single [Client] for all of those
111 : /// requests.
112 : Future<StreamedResponse> send() async {
113 0 : var client = new Client();
114 :
115 : try {
116 0 : var response = await client.send(this);
117 0 : var stream = onDone(response.stream, client.close);
118 0 : return new StreamedResponse(
119 0 : new ByteStream(stream),
120 0 : response.statusCode,
121 0 : contentLength: response.contentLength,
122 0 : request: response.request,
123 0 : headers: response.headers,
124 0 : isRedirect: response.isRedirect,
125 0 : persistentConnection: response.persistentConnection,
126 0 : reasonPhrase: response.reasonPhrase);
127 : } catch (_) {
128 0 : client.close();
129 : rethrow;
130 : }
131 0 : }
132 :
133 : // Throws an error if this request has been finalized.
134 : void _checkFinalized() {
135 0 : if (!finalized) return;
136 0 : throw new StateError("Can't modify a finalized Request.");
137 : }
138 :
139 0 : String toString() => "$method $url";
140 : }
|