blob: 92a52442d8a73ec529be75b1230b99db4cd3a3aa [file] [log] [blame]
// 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');
}