-
Notifications
You must be signed in to change notification settings - Fork 378
/
Copy pathbase_request.dart
155 lines (133 loc) · 5.26 KB
/
base_request.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'package:meta/meta.dart';
import '../http.dart' show get;
import 'base_client.dart';
import 'base_response.dart';
import 'byte_stream.dart';
import 'client.dart';
import 'streamed_response.dart';
import 'utils.dart';
/// The base class for HTTP requests.
///
/// Subclasses of [BaseRequest] can be constructed manually and passed to
/// [BaseClient.send], which allows the user to provide fine-grained control
/// over the request properties. However, usually it's easier to use convenience
/// methods like [get] or [BaseClient.get].
abstract class BaseRequest {
/// The HTTP method of the request.
///
/// Most commonly "GET" or "POST", less commonly "HEAD", "PUT", or "DELETE".
/// Non-standard method names are also supported.
final String method;
/// The URL to which the request will be sent.
final Uri url;
/// The size of the request body, in bytes.
///
/// This defaults to `null`, which indicates that the size of the request is
/// not known in advance. May not be assigned a negative value.
int? get contentLength => _contentLength;
int? _contentLength;
set contentLength(int? value) {
if (value != null && value < 0) {
throw ArgumentError('Invalid content length $value.');
}
_checkFinalized();
_contentLength = value;
}
/// Whether a persistent connection should be maintained with the server.
///
/// Defaults to true.
bool get persistentConnection => _persistentConnection;
bool _persistentConnection = true;
set persistentConnection(bool value) {
_checkFinalized();
_persistentConnection = value;
}
/// Whether the client should follow redirects while resolving this request.
///
/// Defaults to true.
bool get followRedirects => _followRedirects;
bool _followRedirects = true;
set followRedirects(bool value) {
_checkFinalized();
_followRedirects = value;
}
/// The maximum number of redirects to follow when [followRedirects] is true.
///
/// If this number is exceeded the [BaseResponse] future will signal a
/// `RedirectException`. Defaults to 5.
int get maxRedirects => _maxRedirects;
int _maxRedirects = 5;
set maxRedirects(int value) {
_checkFinalized();
_maxRedirects = value;
}
// TODO(nweiz): automatically parse cookies from headers
// TODO(nweiz): make this a HttpHeaders object
final Map<String, String> headers;
/// Whether [finalize] has been called.
bool get finalized => _finalized;
bool _finalized = false;
BaseRequest(this.method, this.url)
: headers = LinkedHashMap(
equals: (key1, key2) => key1.toLowerCase() == key2.toLowerCase(),
hashCode: (key) => key.toLowerCase().hashCode);
/// Finalizes the HTTP request in preparation for it being sent.
///
/// Freezes all mutable fields and returns a single-subscription [ByteStream]
/// that emits the body of the request.
///
/// The base implementation of this returns an empty [ByteStream];
/// subclasses are responsible for creating the return value, which should be
/// single-subscription to ensure that no data is dropped. They should also
/// freeze any additional mutable fields they add that don't make sense to
/// change after the request headers are sent.
@mustCallSuper
ByteStream finalize() {
// TODO(nweiz): freeze headers
if (finalized) throw StateError("Can't finalize a finalized Request.");
_finalized = true;
return const ByteStream(Stream.empty());
}
/// Sends this request.
///
/// This automatically initializes a new [Client] and closes that client once
/// the request is complete. If you're planning on making multiple requests to
/// the same server, you should use a single [Client] for all of those
/// requests.
///
/// If [contentTimeout] is not null the request will be aborted if it takes
/// longer than the given duration to receive the entire response. If the
/// timeout occurs before any reply is received from the server the returned
/// future will as an error with a [TimeoutException]. If the timout occurs
/// after the reply has been started but before the entire body has been read
/// the response stream will emit a [TimeoutException] and close.
Future<StreamedResponse> send({Duration? contentTimeout}) async {
var client = Client();
try {
var response = await client.send(this, contentTimeout: contentTimeout);
var stream = onDone(response.stream, client.close);
return StreamedResponse(ByteStream(stream), response.statusCode,
contentLength: response.contentLength,
request: response.request,
headers: response.headers,
isRedirect: response.isRedirect,
persistentConnection: response.persistentConnection,
reasonPhrase: response.reasonPhrase);
} catch (_) {
client.close();
rethrow;
}
}
/// Throws an error if this request has been finalized.
void _checkFinalized() {
if (!finalized) return;
throw StateError("Can't modify a finalized Request.");
}
@override
String toString() => '$method $url';
}