serve method

Future<void> serve(
  1. HttpRequest request,
  2. Uri baseUri
)

Serves request; baseUri is the address of the fake.

Implementation

Future<void> serve(HttpRequest request, Uri baseUri) async {
  final method = request.method.toUpperCase();
  final path = request.uri.path;
  final headers = <String, String>{};
  request.headers.forEach(
    (name, values) => headers[name.toLowerCase()] = values.join(','),
  );
  final received = BytesBuilder(copy: false);
  await request.forEach(received.add);
  final rawBytes = received.takeBytes();
  // 12 3.2: a part is the raw bytes of `application/octet-stream`, never JSON.
  final binary =
      request.headers.contentType?.mimeType == 'application/octet-stream';
  final raw = binary ? '' : utf8.decode(rawBytes, allowMalformed: true);
  Object? body;
  if (raw.isNotEmpty) {
    try {
      body = jsonDecode(raw);
    } on FormatException {
      await _send(
        request,
        'unknown',
        method,
        path,
        null,
        headers,
        _problem(400, 'malformed_request'),
      );
      return;
    }
  }
  _Route? route;
  final params = <String, String>{};
  for (final r in _routes) {
    if (r.op.method != method) continue;
    final m = r.pattern.firstMatch(path);
    if (m == null) continue;
    route = r;
    for (var i = 0; i < r.names.length; i++) {
      params[r.names[i]] = Uri.decodeComponent(m[i + 1]!);
    }
    break;
  }
  if (route == null) {
    await _send(
      request,
      'unknown',
      method,
      path,
      body,
      headers,
      _problem(404, 'not_found'),
    );
    return;
  }
  final op = route.op.operationId;
  final handler = _handlers[op];
  if (handler == null) {
    await _send(
      request,
      op,
      method,
      path,
      body,
      headers,
      _problem(501, 'internal_error', detail: 'not implemented by the fake'),
    );
    return;
  }
  final htu = '${baseUri.origin}$path';
  FakeResponse response;
  FakeSession? session;
  String? jkt;
  try {
    (session, jkt) =
        _ticketSession(op, headers, request.uri) ??
        _authenticate(route.op, method, htu, headers);
    final requestViolations = contract.request(op, body);
    if (requestViolations.isNotEmpty) {
      violations.add('request $op: ${requestViolations.join('; ')}');
      throw FakeProblem(
        400,
        'validation_failed',
        errors: [
          for (final v in requestViolations)
            {'pointer': v.pointer, 'code': v.code},
        ],
      );
    }
    final fake = FakeRequest(
      operationId: op,
      method: method,
      uri: request.uri,
      pathParams: params,
      headers: headers,
      body: body,
      world: world,
      baseUri: baseUri,
      session: session,
      proofJkt: jkt,
      bytes: binary ? rawBytes : null,
    );
    final fault = faults.take(op);
    if (fault is AnswerFault) {
      response = _problem(
        fault.status,
        fault.code,
        reason: fault.reason,
        retryAfter: fault.retryAfter,
      );
    } else {
      response = await _idempotent(fake, handler);
      if (fault is DelayFault) await Future<void>.delayed(fault.duration);
      if (fault is DropResponseFault) {
        requests.add(
          FakeRequestRecord(
            operationId: op,
            method: method,
            path: path,
            status: 0,
            body: body,
            headers: headers,
          ),
        );
        final socket = await request.response.detachSocket(
          writeHeaders: false,
        );
        socket.destroy();
        return;
      }
    }
  } on FakeProblem catch (p) {
    response = _problem(
      p.status,
      p.code,
      current: p.current,
      reason: p.reason,
      retryAfter: p.retryAfter,
      errors: p.errors,
      extra: p.extra,
    );
  } on _Unauthorized catch (u) {
    response = u.response;
  }
  await _send(request, op, method, path, body, headers, response);
}