utils.dart 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. // Copyright (c) 2014, 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. import 'dart:async';
  5. import 'dart:convert';
  6. import 'dart:io';
  7. import 'dart:typed_data';
  8. import 'package:dio/dio.dart';
  9. import 'package:pedantic/pedantic.dart';
  10. import 'package:test/test.dart';
  11. /// The current server instance.
  12. HttpServer _server;
  13. Encoding requiredEncodingForCharset(String charset) =>
  14. Encoding.getByName(charset) ??
  15. (throw FormatException('Unsupported encoding "$charset".'));
  16. /// The URL for the current server instance.
  17. Uri get serverUrl => Uri.parse('http://localhost:${_server.port}');
  18. /// Starts a new HTTP server.
  19. Future<void> startServer() async {
  20. _server = (await HttpServer.bind('localhost', 0))
  21. ..listen((request) async {
  22. var path = request.uri.path;
  23. var response = request.response;
  24. if (path == '/error') {
  25. const content = 'error';
  26. response
  27. ..statusCode = 400
  28. ..contentLength = content.length
  29. ..write(content);
  30. unawaited(response.close());
  31. return;
  32. }
  33. if (path == '/loop') {
  34. var n = int.parse(request.uri.query);
  35. response
  36. ..statusCode = 302
  37. ..headers
  38. .set('location', serverUrl.resolve('/loop?${n + 1}').toString())
  39. ..contentLength = 0;
  40. unawaited(response.close());
  41. return;
  42. }
  43. if (path == '/redirect') {
  44. response
  45. ..statusCode = 302
  46. ..headers.set('location', serverUrl.resolve('/').toString())
  47. ..contentLength = 0;
  48. unawaited(response.close());
  49. return;
  50. }
  51. if (path == '/no-content-length') {
  52. response
  53. ..statusCode = 200
  54. ..contentLength = -1
  55. ..write('body');
  56. unawaited(response.close());
  57. return;
  58. }
  59. if (path == '/list') {
  60. response.headers.contentType = ContentType('application', 'json');
  61. response
  62. ..statusCode = 200
  63. ..contentLength = -1
  64. ..write('[1,2,3]');
  65. unawaited(response.close());
  66. return;
  67. }
  68. if (path == "/download") {
  69. const content = 'I am a text file';
  70. response.headers.set("content-encoding", 'plain');
  71. response
  72. ..statusCode = 200
  73. ..contentLength = content.length
  74. ..write(content);
  75. Future.delayed(Duration(milliseconds: 300), () {
  76. response.close();
  77. });
  78. return;
  79. }
  80. var requestBodyBytes = await ByteStream(request).toBytes();
  81. var encodingName = request.uri.queryParameters['response-encoding'];
  82. var outputEncoding = encodingName == null
  83. ? ascii
  84. : requiredEncodingForCharset(encodingName);
  85. response.headers.contentType =
  86. ContentType('application', 'json', charset: outputEncoding.name);
  87. response.headers.set('single', 'value');
  88. dynamic requestBody;
  89. if (requestBodyBytes.isEmpty) {
  90. requestBody = null;
  91. } else if (request.headers.contentType?.charset != null) {
  92. var encoding =
  93. requiredEncodingForCharset(request.headers.contentType.charset);
  94. requestBody = encoding.decode(requestBodyBytes);
  95. } else {
  96. requestBody = requestBodyBytes;
  97. }
  98. var content = <String, dynamic>{
  99. 'method': request.method,
  100. 'path': request.uri.path,
  101. 'query': request.uri.query,
  102. 'headers': {}
  103. };
  104. if (requestBody != null) content['body'] = requestBody;
  105. request.headers.forEach((name, values) {
  106. // These headers are automatically generated by dart:io, so we don't
  107. // want to test them here.
  108. if (name == 'cookie' || name == 'host') return;
  109. content['headers'][name] = values;
  110. });
  111. var body = json.encode(content);
  112. response
  113. ..contentLength = body.length
  114. ..write(body);
  115. unawaited(response.close());
  116. });
  117. }
  118. /// Stops the current HTTP server.
  119. void stopServer() {
  120. if (_server != null) {
  121. _server.close();
  122. _server = null;
  123. }
  124. }
  125. /// A matcher for functions that throw SocketException.
  126. final Matcher throwsSocketException =
  127. throwsA(const TypeMatcher<SocketException>());
  128. /// A stream of chunks of bytes representing a single piece of data.
  129. class ByteStream extends StreamView<List<int>> {
  130. ByteStream(Stream<List<int>> stream) : super(stream);
  131. /// Returns a single-subscription byte stream that will emit the given bytes
  132. /// in a single chunk.
  133. factory ByteStream.fromBytes(List<int> bytes) =>
  134. ByteStream(Stream.fromIterable([bytes]));
  135. /// Collects the data of this stream in a [Uint8List].
  136. Future<Uint8List> toBytes() {
  137. var completer = Completer<Uint8List>();
  138. var sink = ByteConversionSink.withCallback(
  139. (bytes) => completer.complete(Uint8List.fromList(bytes)));
  140. listen(sink.add,
  141. onError: completer.completeError,
  142. onDone: sink.close,
  143. cancelOnError: true);
  144. return completer.future;
  145. }
  146. /// Collect the data of this stream in a [String], decoded according to
  147. /// [encoding], which defaults to `UTF8`.
  148. Future<String> bytesToString([Encoding encoding = utf8]) =>
  149. encoding.decodeStream(this);
  150. Stream<String> toStringStream([Encoding encoding = utf8]) =>
  151. encoding.decoder.bind(this);
  152. }