Skip to content

Commit 2d3ed9c

Browse files
authored
Merge pull request #52 from donny-dont/master
Copy components from shelf
2 parents efc13de + 0dac77f commit 2d3ed9c

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

lib/src/body.dart

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) 2017, 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:convert';
7+
8+
import 'package:async/async.dart';
9+
import 'package:collection/collection.dart';
10+
11+
/// The body of a request or response.
12+
///
13+
/// This tracks whether the body has been read. It's separate from [Message]
14+
/// because the message may be changed with [Message.change], but each instance
15+
/// should share a notion of whether the body was read.
16+
class Body {
17+
/// The contents of the message body.
18+
///
19+
/// This will be `null` after [read] is called.
20+
Stream<List<int>> _stream;
21+
22+
/// The encoding used to encode the stream returned by [read], or `null` if no
23+
/// encoding was used.
24+
final Encoding encoding;
25+
26+
/// The length of the stream returned by [read], or `null` if that can't be
27+
/// determined efficiently.
28+
final int contentLength;
29+
30+
Body._(this._stream, this.encoding, this.contentLength);
31+
32+
/// Converts [body] to a byte stream and wraps it in a [Body].
33+
///
34+
/// [body] may be either a [Body], a [String], a [List<int>], a
35+
/// [Stream<List<int>>], or `null`. If it's a [String], [encoding] will be
36+
/// used to convert it to a [Stream<List<int>>].
37+
factory Body(body, [Encoding encoding]) {
38+
if (body is Body) return body;
39+
40+
Stream<List<int>> stream;
41+
int contentLength;
42+
if (body == null) {
43+
contentLength = 0;
44+
stream = new Stream.fromIterable([]);
45+
} else if (body is String) {
46+
if (encoding == null) {
47+
var encoded = UTF8.encode(body);
48+
// If the text is plain ASCII, don't modify the encoding. This means
49+
// that an encoding of "text/plain" will stay put.
50+
if (!_isPlainAscii(encoded, body.length)) encoding = UTF8;
51+
contentLength = encoded.length;
52+
stream = new Stream.fromIterable([encoded]);
53+
} else {
54+
var encoded = encoding.encode(body);
55+
contentLength = encoded.length;
56+
stream = new Stream.fromIterable([encoded]);
57+
}
58+
} else if (body is List) {
59+
contentLength = body.length;
60+
stream = new Stream.fromIterable([DelegatingList.typed(body)]);
61+
} else if (body is Stream) {
62+
stream = DelegatingStream.typed(body);
63+
} else {
64+
throw new ArgumentError('Response body "$body" must be a String or a '
65+
'Stream.');
66+
}
67+
68+
return new Body._(stream, encoding, contentLength);
69+
}
70+
71+
/// Returns whether [bytes] is plain ASCII.
72+
///
73+
/// [codeUnits] is the number of code units in the original string.
74+
static bool _isPlainAscii(List<int> bytes, int codeUnits) {
75+
// Most non-ASCII code units will produce multiple bytes and make the text
76+
// longer.
77+
if (bytes.length != codeUnits) return false;
78+
79+
// Non-ASCII code units between U+0080 and U+009F produce 8-bit characters
80+
// with the high bit set.
81+
return bytes.every((byte) => byte & 0x80 == 0);
82+
}
83+
84+
/// Returns a [Stream] representing the body.
85+
///
86+
/// Can only be called once.
87+
Stream<List<int>> read() {
88+
if (_stream == null) {
89+
throw new StateError("The 'read' method can only be called once on a "
90+
"http.Request/http.Response object.");
91+
}
92+
var stream = _stream;
93+
_stream = null;
94+
return stream;
95+
}
96+
}

lib/src/http_unmodifiable_map.dart

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (c) 2017, 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:collection';
6+
7+
import 'package:collection/collection.dart';
8+
import 'package:http_parser/http_parser.dart';
9+
10+
/// A simple wrapper over [UnmodifiableMapView] which avoids re-wrapping itself.
11+
class HttpUnmodifiableMap<V> extends UnmodifiableMapView<String, V> {
12+
/// `true` if the key values are already lowercase.
13+
final bool _ignoreKeyCase;
14+
15+
/// If [source] is a [HttpUnmodifiableMap] with matching [ignoreKeyCase],
16+
/// then [source] is returned.
17+
///
18+
/// If [source] is `null` it is treated like an empty map.
19+
///
20+
/// If [ignoreKeyCase] is `true`, the keys will have case-insensitive access.
21+
///
22+
/// [source] is copied to a new [Map] to ensure changes to the parameter value
23+
/// after constructions are not reflected.
24+
factory HttpUnmodifiableMap(Map<String, V> source,
25+
{bool ignoreKeyCase: false}) {
26+
if (source is HttpUnmodifiableMap<V> &&
27+
// !ignoreKeyCase: no transformation of the input is required
28+
// source._ignoreKeyCase: the input cannot be transformed any more
29+
(!ignoreKeyCase || source._ignoreKeyCase)) {
30+
return source;
31+
}
32+
33+
if (source == null || source.isEmpty) {
34+
return const _EmptyHttpUnmodifiableMap();
35+
}
36+
37+
if (ignoreKeyCase) {
38+
source = new CaseInsensitiveMap<V>.from(source);
39+
} else {
40+
source = new Map<String, V>.from(source);
41+
}
42+
43+
return new HttpUnmodifiableMap<V>._(source, ignoreKeyCase);
44+
}
45+
46+
/// Returns an empty [HttpUnmodifiableMap].
47+
const factory HttpUnmodifiableMap.empty() = _EmptyHttpUnmodifiableMap<V>;
48+
49+
HttpUnmodifiableMap._(Map<String, V> source, this._ignoreKeyCase)
50+
: super(source);
51+
}
52+
53+
/// A const implementation of an empty [HttpUnmodifiableMap].
54+
class _EmptyHttpUnmodifiableMap<V> extends MapView<String, V>
55+
implements HttpUnmodifiableMap<V> {
56+
bool get _ignoreKeyCase => true;
57+
const _EmptyHttpUnmodifiableMap() : super(const {});
58+
}

0 commit comments

Comments
 (0)