라우팅과 미들웨어
미들웨어는 요청과 응답 사이에서 공통 작업을 처리합니다. 로깅, CORS, 인증 토큰 검증이 대표적인 예입니다. dart_frog의 미들웨어는 _middleware.dart 파일로 관리합니다.
미들웨어 개념
미들웨어는 요청을 받아 다음 핸들러로 전달하거나, 직접 응답을 반환하는 함수입니다.
요청 → 미들웨어 1 → 미들웨어 2 → 핸들러
↓
응답 ← 미들웨어 1 ← 미들웨어 2 ← 응답
dart_frog에서 미들웨어는 Middleware 타입입니다. 핸들러를 감싸는 함수입니다.
Middleware myMiddleware() {
return (Handler handler) {
return (RequestContext context) async {
// 요청 전 처리
print('요청 시작: ${context.request.uri}');
// 다음 핸들러 호출
final response = await handler(context);
// 응답 후 처리
print('응답 완료: ${response.statusCode}');
return response;
};
};
}
_middleware.dart 파일
routes/_middleware.dart는 전체 라우트에 적용되는 글로벌 미들웨어를 정의합니다.
// 새 파일: routes/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
Handler middleware(Handler handler) {
return handler
.use(requestLogger())
.use(corsHeaders());
}
requestLogger()는 dart_frog가 기본 제공하는 로깅 미들웨어입니다. .use()로 미들웨어를 체이닝합니다.
CORS 미들웨어 직접 구현
dart_frog는 corsHeaders()를 기본 제공하지만, 세밀하게 제어하려면 직접 구현합니다.
// 새 파일: lib/src/middleware/cors_middleware.dart
import 'package:dart_frog/dart_frog.dart';
/// CORS 헤더를 추가하는 미들웨어.
Middleware cors({
List<String> allowedOrigins = const ['*'],
List<String> allowedMethods = const [
'GET', 'POST', 'PUT', 'DELETE', 'OPTIONS',
],
List<String> allowedHeaders = const [
'Content-Type', 'Authorization',
],
}) {
return (Handler handler) {
return (RequestContext context) async {
final origin = context.request.headers['Origin'] ?? '*';
final isAllowed =
allowedOrigins.contains('*') || allowedOrigins.contains(origin);
final corsResponseHeaders = {
'Access-Control-Allow-Origin': isAllowed ? origin : '',
'Access-Control-Allow-Methods': allowedMethods.join(', '),
'Access-Control-Allow-Headers': allowedHeaders.join(', '),
'Access-Control-Max-Age': '86400',
};
// OPTIONS(preflight) 요청 처리
if (context.request.method == HttpMethod.options) {
return Response(
statusCode: 204,
headers: corsResponseHeaders,
);
}
final response = await handler(context);
// 기존 헤더에 CORS 헤더 추가
return response.copyWith(
headers: {...response.headers, ...corsResponseHeaders},
);
};
};
}
로깅 미들웨어 직접 구현
요청과 응답을 구조화된 형태로 로깅합니다.
// 새 파일: lib/src/middleware/logging_middleware.dart
import 'dart:io';
import 'package:dart_frog/dart_frog.dart';
/// 요청/응답을 로깅하는 미들웨어.
Middleware requestLogging() {
return (Handler handler) {
return (RequestContext context) async {
final start = DateTime.now();
final request = context.request;
final response = await handler(context);
final elapsed = DateTime.now().difference(start).inMilliseconds;
final method = request.method.value.padRight(7);
final status = response.statusCode;
final path = request.uri.path;
final statusColor = _statusColor(status);
stderr.writeln(
'$statusColor[$status]\x1B[0m $method $path ${elapsed}ms',
);
return response;
};
};
}
String _statusColor(int status) {
if (status < 300) return '\x1B[32m'; // 초록
if (status < 400) return '\x1B[33m'; // 노랑
return '\x1B[31m'; // 빨강
}
글로벌 미들웨어 업데이트
// 수정: routes/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import '../lib/src/middleware/cors_middleware.dart';
import '../lib/src/middleware/logging_middleware.dart';
Handler middleware(Handler handler) {
return handler
.use(requestLogging())
.use(cors(
allowedOrigins: ['http://localhost:3000', 'https://yourapp.com'],
));
}
에러 처리 미들웨어
예외가 발생했을 때 일관된 에러 응답을 반환하는 미들웨어입니다.
// 새 파일: lib/src/middleware/error_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import '../exceptions.dart';
/// 예외를 잡아 표준화된 에러 응답으로 변환합니다.
Middleware errorHandler() {
return (Handler handler) {
return (RequestContext context) async {
try {
return await handler(context);
} on AppException catch (e) {
return Response.json(
statusCode: e.statusCode,
body: {
'error': e.message,
'code': e.code,
},
);
} on FormatException catch (e) {
return Response.json(
statusCode: 400,
body: {'error': '잘못된 요청 형식: ${e.message}'},
);
} catch (e, st) {
// 예상치 못한 에러
print('내부 서버 오류: $e\n$st');
return Response.json(
statusCode: 500,
body: {'error': '내부 서버 오류가 발생했습니다.'},
);
}
};
};
}
// 새 파일: lib/src/exceptions.dart
/// 앱 전용 기본 예외 클래스.
class AppException implements Exception {
const AppException({
required this.message,
required this.statusCode,
this.code,
});
final String message;
final int statusCode;
final String? code;
}
class NotFoundException extends AppException {
const NotFoundException(String message)
: super(message: message, statusCode: 404, code: 'NOT_FOUND');
}
class ValidationException extends AppException {
const ValidationException(String message)
: super(message: message, statusCode: 422, code: 'VALIDATION_ERROR');
}
class UnauthorizedException extends AppException {
const UnauthorizedException([String message = '인증이 필요합니다.'])
: super(message: message, statusCode: 401, code: 'UNAUTHORIZED');
}
class ForbiddenException extends AppException {
const ForbiddenException([String message = '권한이 없습니다.'])
: super(message: message, statusCode: 403, code: 'FORBIDDEN');
}
글로벌 미들웨어 최종 구성
// 수정: routes/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import '../lib/src/middleware/cors_middleware.dart';
import '../lib/src/middleware/error_middleware.dart';
import '../lib/src/middleware/logging_middleware.dart';
Handler middleware(Handler handler) {
return handler
.use(errorHandler()) // 가장 바깥: 에러 처리
.use(requestLogging()) // 로깅
.use(cors()); // CORS
}
미들웨어 순서가 중요합니다. .use()는 안쪽부터 적용됩니다. 위 코드에서 실행 순서는 cors → requestLogging → errorHandler → handler입니다.
/todos 전용 인증 미들웨어 뼈대
routes/todos/_middleware.dart는 /todos 경로에만 적용됩니다.
// 새 파일: routes/todos/_middleware.dart
import 'package:dart_frog/dart_frog.dart';
import '../../lib/src/exceptions.dart';
/// /todos 경로에 인증을 적용하는 미들웨어.
/// JWT 검증은 Ch 06에서 구현합니다.
Handler middleware(Handler handler) {
return handler.use(_authMiddleware());
}
Middleware _authMiddleware() {
return (Handler handler) {
return (RequestContext context) async {
final authHeader = context.request.headers['Authorization'];
if (authHeader == null || !authHeader.startsWith('Bearer ')) {
throw const UnauthorizedException();
}
// Ch 06에서 실제 JWT 검증으로 교체
final token = authHeader.substring(7);
if (token.isEmpty) {
throw const UnauthorizedException('유효하지 않은 토큰입니다.');
}
return handler(context);
};
};
}
실행 확인
dart_frog dev
# CORS preflight 요청curl -s -X OPTIONS http://localhost:8080/todos \ -H "Origin: http://localhost:3000" \ -H "Access-Control-Request-Method: POST" \ -v 2>&1 | grep -i "access-control"# 인증 없이 /todos 접근 (401 예상)curl -s http://localhost:8080/todos# 인증 헤더와 함께 접근 (토큰 값은 아직 더미)curl -s http://localhost:8080/todos \ -H "Authorization: Bearer dummy-token"
정리
이번 챕터에서는 미들웨어로 공통 기능을 구현했습니다.
_middleware.dart파일이 해당 디렉토리 라우트 전체에 적용됩니다.- CORS, 로깅, 에러 처리 미들웨어를 직접 구현했습니다.
AppException계층으로 에러 코드를 표준화했습니다.routes/todos/_middleware.dart로 특정 경로에만 인증을 적용합니다.
다음 챕터에서는 요청 처리와 응답 설계를 다듬습니다. DTO 패턴과 유효성 검사를 구현합니다.