DEV Community

Silfalion
Silfalion

Posted on

Tip: add default headers to Serverpod endpoint calls today with a custom client

Serverpod does not currently expose global default headers for generated endpoint HTTP calls, but you can work around that today by overriding the generated client and reissuing the HTTP request yourself. This is useful for values like Accept-Language that you want to send on most requests without adding an argument to every endpoint.

Client workaround

import 'package:http/http.dart' as http;
import 'package:serverpod_client/serverpod_client.dart';
import 'src/protocol/client.dart';

class AppClient extends Client {
  AppClient(super.host, [super.securityContext]);

  final _http = http.Client();

  final Map<String, String> defaultHeaders = {};

  @override
  Future<T> callServerEndpoint<T>(
    String endpoint,
    String method,
    Map<String, dynamic> args, {
    bool authenticated = true,
  }) async {
    final auth = authenticated ? await authKeyProvider?.authHeaderValue : null;

    final response = await _http.post(
      Uri.parse('$host$endpoint'),
      headers: {
        'content-type': 'application/json; charset=utf-8',
        if (auth != null) 'authorization': auth,
        ...defaultHeaders,
      },
      body: SerializationManager.encode({
        ...args,
        'method': method,
      }),
    ).timeout(connectionTimeout);

    if (response.statusCode != 200) {
      throw ServerpodClientException(response.body, response.statusCode);
    }

    if (T == getType<void>()) return null as T;
    return serializationManager.decode<T>(response.body, T);
  }

  @override
  void close() {
    _http.close();
    super.close();
  }
}
Enter fullscreen mode Exit fullscreen mode

Global usage

final client = AppClient('http://localhost:8080/')
  ..defaultHeaders['accept-language'] = 'fr-FR';
Enter fullscreen mode Exit fullscreen mode

Grouped usage

If only one feature area should get the extra headers, create a second preconfigured client for that feature area instead of mutating one shared client back and forth:

class LocalizedGreetingApi {
  LocalizedGreetingApi(Client baseClient)
    : client = AppClient(baseClient.host)
        ..authKeyProvider = baseClient.authKeyProvider
        ..defaultHeaders['accept-language'] = 'fr-FR';

  final AppClient client;

  Future<String> hello(String name) async {
    return client.greeting.hello(name);
  }
}
Enter fullscreen mode Exit fullscreen mode

Server-side read

Future<String> hello(Session session, String name) async {
  final language = session.request?.headers['accept-language']?.firstOrNull;
  return 'hello $name ($language)';
}
Enter fullscreen mode Exit fullscreen mode

Caveats

  • This is for normal generated endpoint HTTP calls only.
  • This is not for method streams.
  • Do not use this to override authorization.
  • Do not use this to override content-type.
  • If you need arbitrary custom headers from browser clients, remember CORS can matter.
  • This is a workaround until the framework supports default client headers directly.

Top comments (0)