| // Copyright 2019 The Flutter Authors. 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:convert'; |
| import 'dart:io'; |
| import 'dart:typed_data'; |
| |
| import 'package:meta/meta.dart'; |
| |
| import 'body.dart'; |
| import 'exceptions.dart'; |
| import 'request_handler.dart'; |
| |
| /// A [RequestHandler] that handles requests with no authentication. |
| /// |
| /// No Auth requests enables [requestData] |
| /// |
| /// `T` is the type of object that is returned as the body of the HTTP response |
| /// (before serialization). Subclasses whose HTTP responses don't include a |
| /// body should extend `RequestHandler<Body>` and return null in their service |
| /// handlers ([get] and [post]). |
| @immutable |
| abstract class NoAuthRequestHandler<T extends Body> extends RequestHandler<T> { |
| /// Creates a new [NoAuthRequestHandler]. |
| const NoAuthRequestHandler({ |
| required super.config, |
| }); |
| |
| /// Throws a [BadRequestException] if any of [requiredParameters] is missing |
| /// from [requestData]. |
| @protected |
| void checkRequiredParameters(List<String> requiredParameters) { |
| final Iterable<String> missingParams = requiredParameters..removeWhere(requestData!.containsKey); |
| if (missingParams.isNotEmpty) { |
| throw BadRequestException('Missing required parameter: ${missingParams.join(', ')}'); |
| } |
| } |
| |
| /// The raw byte contents of the HTTP request body. |
| /// |
| /// If the request did not specify any content in the body, this will be an |
| /// empty list. It will never be null. |
| /// |
| /// See also: |
| /// |
| /// * [requestData], which contains the JSON-decoded [Map] of the request |
| /// body content (if applicable). |
| @protected |
| Uint8List? get requestBody => getValue<Uint8List>(NoAuthKey.requestBody); |
| |
| /// The JSON data specified in the HTTP request body. |
| /// |
| /// This is guaranteed to be non-null. If the request body was empty, or if |
| /// it contained non-JSON or binary (non-UTF-8) data, this will be an empty |
| /// map. |
| /// |
| /// See also: |
| /// |
| /// * [requestBody], which specifies the raw bytes of the HTTP request body. |
| @protected |
| Map<String, dynamic>? get requestData => getValue<Map<String, dynamic>>(NoAuthKey.requestData); |
| |
| @override |
| Future<void> service( |
| HttpRequest request, { |
| Future<void> Function(HttpStatusException)? onError, |
| }) async { |
| List<int> body; |
| try { |
| body = await request.expand<int>((List<int> chunk) => chunk).toList(); |
| } catch (error) { |
| final HttpResponse response = request.response; |
| response |
| ..statusCode = HttpStatus.internalServerError |
| ..write('$error'); |
| await response.flush(); |
| await response.close(); |
| return; |
| } |
| |
| Map<String, dynamic>? requestData = const <String, dynamic>{}; |
| if (body.isNotEmpty) { |
| try { |
| requestData = json.decode(utf8.decode(body)) as Map<String, dynamic>?; |
| } on FormatException { |
| // The HTTP request body is not valid UTF-8 encoded JSON. This is |
| // allowed; just let [requestData] be null. |
| } catch (error) { |
| final HttpResponse response = request.response; |
| response |
| ..statusCode = HttpStatus.internalServerError |
| ..write('$error'); |
| await response.flush(); |
| await response.close(); |
| return; |
| } |
| } |
| |
| await runZoned<Future<void>>( |
| () async { |
| await super.service(request); |
| }, |
| zoneValues: <NoAuthKey<dynamic>, Object?>{ |
| NoAuthKey.requestBody: Uint8List.fromList(body), |
| NoAuthKey.requestData: requestData, |
| }, |
| ); |
| } |
| } |
| |
| @visibleForTesting |
| class NoAuthKey<T> extends RequestKey<T> { |
| const NoAuthKey._(super.name); |
| |
| static const NoAuthKey<Uint8List> requestBody = NoAuthKey<Uint8List>._('requestBody'); |
| static const NoAuthKey<Map<String, dynamic>> requestData = NoAuthKey<Map<String, dynamic>>._('requestData'); |
| } |