Since a mid
project is part a server project and another part a client project, there are interceptors for each side. In additon, given that mid
runs both an http server for handling regular requests and a websocket server for handling streams, there are in total there four interceptors:
- Server HTTP interceptor (from
mid_server
) - Server Websocket Messages Interceptor (from
mid_server
) - Client HTTP interceptor (from
mid_client
) - Client Websocket Messages Interceptor (from
mid_client
)
It's important to understand that if a server exposes both regular endpoints and stream endpoints, then handling authentication will require creating an HttpInterceptorServer
and a MessageInterceptorServer
to handle the authentication for http and websocket respectively. For more details, read the headers documentation.
To intercept http requests and responses on the server side, the mid_server
package provides an HttpInterceptorServer type. The type is exported by the generated client library to facilitate its usage. The interceptor types can be imported from your frontend project as follow:
import 'package:<project_name>_server/interceptors.dart';
Here are two simple examples for http interceptor on the server:
class VerifyTokenHttpInterceptor extends HttpInterceptorServer {
@override
Future<Request> onRequest(Request request) async {
final token = request.headers['your-token-id'];
final isVerified = auth.verifyToken(token);
if (isVerified) {
return request;
} else {
// `mid` will return this response to the client
// the response will also pass through the response interceptors
throw Response(401, body: 'Invalid Token');
}
}
}
class HttpLogInterceptor extends HttpInterceptorServer {
@override
Future<Request> onRequest(Request request) async {
print('--> request received from client for url: ${request.url}');
return request;
}
@override
Future<Response> onResponse(Response response) async {
print('<-- resposne is being sent to the client with status: ${response.statusCode}');
return response;
}
}
Note: the
Response
andRequest
types of theHttpInterceptorServer
are from the shelf package. They are not the same as the ones used in the client http interceptors.
You can add a list of HttpInterceptorServer
in the ServerConfig
located at:
|- <project_name>_server
|- bin
|- server.dart
Using Quick Start Tutorial as an example), the interceptors can be added as follows:
Future<void> main(List<String> args) async {
final handlers = await getHandlers();
final serverConfig = ServerConfig(
handlers: handlers,
httpInterceptors: [VerifyTokenHttpInterceptor(), HttpLogInterceptor()],
address: InternetAddress.anyIPv4,
port: int.parse(Platform.environment['PORT'] ?? '8000'),
);
midServer(serverConfig);
}
For streaming endpoints, the websocket messages between the server and client can be intercepted using MessageInterceptorServer from the mid_server
package which is automatically added to every generated server project.
For client messages, the server interceptor has an extra headers
argument. These headers are passed by the server to the interceptor to facilitate any authorization and validation of the connection. The headers are either from [ConnectionInitMessage] or [ConnectionUpdateMessage], the former would be the initial headers when the connection was established, whearas the latter will be any subsequent headers that were sent to the server by the client (i.e. Client.updateHeaders
). Read the headers documentation for more details.
Here are simple examples of messages interceptors:
class VerifyMessagesToken extends MessageInterceptorServer {
@override // intercept messages from client to server
Message clientMessage(Message message, Map<String, String> headers) {
final token = headers['your-auth-token-id'];
final isVerified = auth.verifyToken(token);
if (isVerified) {
return message;
} else {
return ErrorMessage(
id: defaultErrorID,
payload: ErrorPayload(errorCode: 401, errorMessage: 'Invalid Token'),
);
}
return message;
}
}
class LogInterceptor extends MessageInterceptorServer {
@override // intercept messages from client to server
Message clientMessage(Message message, Map<String, String> headers) {
log(message);
return message;
}
@override // intercept messages from server to client
Message serverMessage(Message message) {
if (message is ErrorMessage) {
log.error(message.payload.errorMessage);
} else {
log.trace(message.toJson());
}
return message;
}
}
You can add a list of MessageInterceptorServer
in the ServerConfig
located at:
|- <project_name>_server
|- bin
|- server.dart
The interceptor(s) can be added to the ServerConfig
(using Quick Start Tutorial as an example) such as:
Future<void> main(List<String> args) async {
final handlers = await getHandlers();
final serverConfig = ServerConfig(
handlers: handlers,
httpInterceptors: [VerifyTokenHttpInterceptor(), HttpLogInterceptor()],
messagesInterceptors: [LogInterceptor(), VerifyMessagesToken()], // <~~~ here
address: InternetAddress.anyIPv4,
port: int.parse(Platform.environment['PORT'] ?? '8000'),
);
midServer(serverConfig);
}
To intercept http requests and responses on the client side, the mid_client
package provides an HttpInterceptorClient type. The type is exported by the generated client library to facilitate its usage. The interceptor types can be imported from your frontend project as follow:
import 'package:<project_name>_client/interceptors.dart';
Here is an example http interceptor:
class AddAuthTokenInterceptor extends HttpInterceptorClient {
@override
Future<Request> onRequest(Request request) async {
request.headers['your-auth-token-id'] = 'the-user-auth-token';
return request;
}
}
Note: the
Response
andRequest
types ofHttpInterceptorClient
are from the http package. They are not the same as the one used in the server http interceptors.
The interceptor(s) can be added to the client upon instantiation (using Quick Start Tutorial as an example):
final client = QuickStartClient(
url: 'localhost:8000',
httpInterceptors: [AddAuthTokenInterceptor()] // <~~~ here
);
To intercept Messages on the client side, the MessageInterceptorClient type can be used in the client side.
For example, to keep track of active subscription, one can create the following interceptor:
class TrackSubscriptions extends MessageInterceptorClient {
int currentSubs = 0;
@override
Message clientMessage(Message message) {
if (message is SubscribeMessage) {
currentSubs++;
}
if (message is StopMessage) {
currentSubs--;
}
return message;
}
@override
Message serverMessage(Message message) {
// assuming this is an error that'll terminate the connection
if (message is ErrorMessage) {
currentSubs = 0;
}
return message;
}
}
Taking the Quick Start tutorial as an example, the interceptors can be added as follows:
final client = QuickStartClient(
url: 'localhost:8000',
httpInterceptors: [AddAuthTokenInterceptor()], // i.e. http interceptor
messageInterceptors: [TrackSubscriptions()] // <~~~ here
);