mcp-security-framework 0.1.0__py3-none-any.whl → 1.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_security_framework/__init__.py +26 -15
- mcp_security_framework/cli/__init__.py +1 -1
- mcp_security_framework/cli/cert_cli.py +233 -197
- mcp_security_framework/cli/security_cli.py +324 -234
- mcp_security_framework/constants.py +21 -27
- mcp_security_framework/core/auth_manager.py +49 -20
- mcp_security_framework/core/cert_manager.py +398 -104
- mcp_security_framework/core/permission_manager.py +13 -9
- mcp_security_framework/core/rate_limiter.py +10 -0
- mcp_security_framework/core/security_manager.py +286 -229
- mcp_security_framework/examples/__init__.py +6 -0
- mcp_security_framework/examples/comprehensive_example.py +954 -0
- mcp_security_framework/examples/django_example.py +276 -202
- mcp_security_framework/examples/fastapi_example.py +897 -393
- mcp_security_framework/examples/flask_example.py +311 -200
- mcp_security_framework/examples/gateway_example.py +373 -214
- mcp_security_framework/examples/microservice_example.py +337 -172
- mcp_security_framework/examples/standalone_example.py +719 -478
- mcp_security_framework/examples/test_all_examples.py +572 -0
- mcp_security_framework/middleware/__init__.py +46 -55
- mcp_security_framework/middleware/auth_middleware.py +62 -63
- mcp_security_framework/middleware/fastapi_auth_middleware.py +179 -110
- mcp_security_framework/middleware/fastapi_middleware.py +156 -148
- mcp_security_framework/middleware/flask_auth_middleware.py +267 -107
- mcp_security_framework/middleware/flask_middleware.py +183 -157
- mcp_security_framework/middleware/mtls_middleware.py +106 -117
- mcp_security_framework/middleware/rate_limit_middleware.py +105 -101
- mcp_security_framework/middleware/security_middleware.py +109 -124
- mcp_security_framework/schemas/config.py +2 -1
- mcp_security_framework/schemas/models.py +19 -6
- mcp_security_framework/utils/cert_utils.py +14 -8
- mcp_security_framework/utils/datetime_compat.py +116 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/METADATA +2 -1
- mcp_security_framework-1.1.1.dist-info/RECORD +84 -0
- tests/conftest.py +303 -0
- tests/test_cli/test_cert_cli.py +194 -174
- tests/test_cli/test_security_cli.py +274 -247
- tests/test_core/test_cert_manager.py +33 -19
- tests/test_core/test_security_manager.py +2 -2
- tests/test_examples/test_comprehensive_example.py +613 -0
- tests/test_examples/test_fastapi_example.py +290 -169
- tests/test_examples/test_flask_example.py +304 -162
- tests/test_examples/test_standalone_example.py +106 -168
- tests/test_integration/test_auth_flow.py +214 -198
- tests/test_integration/test_certificate_flow.py +181 -150
- tests/test_integration/test_fastapi_integration.py +140 -149
- tests/test_integration/test_flask_integration.py +144 -141
- tests/test_integration/test_standalone_integration.py +331 -300
- tests/test_middleware/test_fastapi_auth_middleware.py +745 -0
- tests/test_middleware/test_fastapi_middleware.py +147 -132
- tests/test_middleware/test_flask_auth_middleware.py +696 -0
- tests/test_middleware/test_flask_middleware.py +201 -179
- tests/test_middleware/test_security_middleware.py +151 -130
- tests/test_utils/test_datetime_compat.py +147 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +0 -76
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/WHEEL +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/entry_points.txt +0 -0
- {mcp_security_framework-0.1.0.dist-info → mcp_security_framework-1.1.1.dist-info}/top_level.txt +0 -0
@@ -17,31 +17,37 @@ Version: 1.0.0
|
|
17
17
|
License: MIT
|
18
18
|
"""
|
19
19
|
|
20
|
-
import
|
20
|
+
import asyncio
|
21
21
|
import json
|
22
22
|
import logging
|
23
|
-
import
|
23
|
+
import os
|
24
|
+
from datetime import datetime, timedelta, timezone
|
25
|
+
from typing import Any, Dict, List, Optional
|
26
|
+
|
24
27
|
import aiohttp
|
25
|
-
from typing import Dict, List, Any, Optional
|
26
|
-
from datetime import datetime, timedelta
|
27
28
|
|
28
|
-
from mcp_security_framework.
|
29
|
+
from mcp_security_framework.constants import (
|
30
|
+
AUTH_METHODS,
|
31
|
+
DEFAULT_CLIENT_IP,
|
32
|
+
DEFAULT_SECURITY_HEADERS,
|
33
|
+
HTTP_FORBIDDEN,
|
34
|
+
HTTP_TOO_MANY_REQUESTS,
|
35
|
+
HTTP_UNAUTHORIZED,
|
36
|
+
ErrorCodes,
|
37
|
+
)
|
29
38
|
from mcp_security_framework.core.auth_manager import AuthManager
|
30
|
-
from mcp_security_framework.core.ssl_manager import SSLManager
|
31
39
|
from mcp_security_framework.core.permission_manager import PermissionManager
|
32
40
|
from mcp_security_framework.core.rate_limiter import RateLimiter
|
33
|
-
from mcp_security_framework.
|
34
|
-
from mcp_security_framework.
|
35
|
-
from mcp_security_framework.
|
36
|
-
|
37
|
-
ErrorCodes, HTTP_UNAUTHORIZED, HTTP_FORBIDDEN, HTTP_TOO_MANY_REQUESTS
|
38
|
-
)
|
41
|
+
from mcp_security_framework.core.security_manager import SecurityManager
|
42
|
+
from mcp_security_framework.core.ssl_manager import SSLManager
|
43
|
+
from mcp_security_framework.schemas.config import AuthConfig, SecurityConfig, SSLConfig
|
44
|
+
from mcp_security_framework.schemas.models import AuthMethod, AuthResult, AuthStatus
|
39
45
|
|
40
46
|
|
41
47
|
class APIGatewayExample:
|
42
48
|
"""
|
43
49
|
Complete API Gateway Example with Security Framework Implementation
|
44
|
-
|
50
|
+
|
45
51
|
This class demonstrates a production-ready API Gateway
|
46
52
|
with comprehensive security features including:
|
47
53
|
- Request routing and load balancing
|
@@ -51,11 +57,11 @@ class APIGatewayExample:
|
|
51
57
|
- Request/response transformation
|
52
58
|
- Comprehensive logging and monitoring
|
53
59
|
"""
|
54
|
-
|
60
|
+
|
55
61
|
def __init__(self, config_path: Optional[str] = None):
|
56
62
|
"""
|
57
63
|
Initialize API Gateway example with security configuration.
|
58
|
-
|
64
|
+
|
59
65
|
Args:
|
60
66
|
config_path: Path to security configuration file
|
61
67
|
"""
|
@@ -65,45 +71,58 @@ class APIGatewayExample:
|
|
65
71
|
self._setup_logging()
|
66
72
|
self._setup_routing_rules()
|
67
73
|
self._setup_load_balancer()
|
68
|
-
|
74
|
+
|
69
75
|
def _load_config(self, config_path: Optional[str]) -> SecurityConfig:
|
70
76
|
"""
|
71
77
|
Load security configuration from file or create default.
|
72
|
-
|
78
|
+
|
73
79
|
Args:
|
74
80
|
config_path: Path to configuration file
|
75
|
-
|
81
|
+
|
76
82
|
Returns:
|
77
83
|
SecurityConfig: Loaded configuration
|
78
84
|
"""
|
79
85
|
if config_path and os.path.exists(config_path):
|
80
|
-
with open(config_path,
|
86
|
+
with open(config_path, "r") as f:
|
81
87
|
config_data = json.load(f)
|
82
88
|
return SecurityConfig(**config_data)
|
83
|
-
|
89
|
+
|
84
90
|
# Create production-ready API Gateway configuration
|
85
91
|
return SecurityConfig(
|
86
92
|
auth=AuthConfig(
|
87
93
|
enabled=True,
|
88
|
-
methods=[
|
94
|
+
methods=[
|
95
|
+
AUTH_METHODS["API_KEY"],
|
96
|
+
AUTH_METHODS["JWT"],
|
97
|
+
AUTH_METHODS["CERTIFICATE"],
|
98
|
+
],
|
89
99
|
api_keys={
|
90
|
-
"gateway_key_123": {
|
91
|
-
|
92
|
-
|
100
|
+
"gateway_key_123": {
|
101
|
+
"username": "gateway-admin",
|
102
|
+
"roles": ["gateway", "admin"],
|
103
|
+
},
|
104
|
+
"client_key_456": {
|
105
|
+
"username": "client-app",
|
106
|
+
"roles": ["client", "user"],
|
107
|
+
},
|
108
|
+
"service_key_789": {
|
109
|
+
"username": "internal-service",
|
110
|
+
"roles": ["service", "internal"],
|
111
|
+
},
|
93
112
|
},
|
94
113
|
jwt_secret="your-super-secret-jwt-key-change-in-production",
|
95
114
|
jwt_algorithm="HS256",
|
96
115
|
jwt_expiry_hours=24,
|
97
116
|
public_paths=["/health", "/metrics", "/status"],
|
98
|
-
security_headers=DEFAULT_SECURITY_HEADERS
|
117
|
+
security_headers=DEFAULT_SECURITY_HEADERS,
|
99
118
|
),
|
100
119
|
ssl=SSLConfig(
|
101
|
-
enabled=
|
102
|
-
cert_file=
|
103
|
-
key_file=
|
104
|
-
ca_cert_file=
|
120
|
+
enabled=False, # Disable SSL for example
|
121
|
+
cert_file=None,
|
122
|
+
key_file=None,
|
123
|
+
ca_cert_file=None,
|
105
124
|
verify_mode="CERT_REQUIRED",
|
106
|
-
min_version="TLSv1.2"
|
125
|
+
min_version="TLSv1.2",
|
107
126
|
),
|
108
127
|
rate_limit={
|
109
128
|
"enabled": True,
|
@@ -116,16 +135,16 @@ class APIGatewayExample:
|
|
116
135
|
"host": "redis-cluster",
|
117
136
|
"port": 6379,
|
118
137
|
"db": 0,
|
119
|
-
"password": None
|
138
|
+
"password": None,
|
120
139
|
},
|
121
140
|
"exempt_paths": ["/health", "/metrics", "/status"],
|
122
|
-
"exempt_roles": ["gateway", "admin"]
|
141
|
+
"exempt_roles": ["gateway", "admin"],
|
123
142
|
},
|
124
143
|
permissions={
|
125
144
|
"enabled": True,
|
126
145
|
"roles_file": "config/roles.json",
|
127
146
|
"default_role": "user",
|
128
|
-
"hierarchy_enabled": True
|
147
|
+
"hierarchy_enabled": True,
|
129
148
|
},
|
130
149
|
logging={
|
131
150
|
"enabled": True,
|
@@ -135,10 +154,10 @@ class APIGatewayExample:
|
|
135
154
|
"max_file_size": 10,
|
136
155
|
"backup_count": 5,
|
137
156
|
"console_output": True,
|
138
|
-
"json_format": True
|
139
|
-
}
|
157
|
+
"json_format": True,
|
158
|
+
},
|
140
159
|
)
|
141
|
-
|
160
|
+
|
142
161
|
def _setup_logging(self):
|
143
162
|
"""Setup logging configuration."""
|
144
163
|
if self.config.logging.enabled:
|
@@ -146,61 +165,78 @@ class APIGatewayExample:
|
|
146
165
|
level=getattr(logging, self.config.logging.level),
|
147
166
|
format=self.config.logging.format,
|
148
167
|
handlers=[
|
149
|
-
|
150
|
-
|
151
|
-
|
168
|
+
(
|
169
|
+
logging.FileHandler(self.config.logging.file_path)
|
170
|
+
if self.config.logging.file_path
|
171
|
+
else logging.NullHandler()
|
172
|
+
),
|
173
|
+
(
|
174
|
+
logging.StreamHandler()
|
175
|
+
if self.config.logging.console_output
|
176
|
+
else logging.NullHandler()
|
177
|
+
),
|
178
|
+
],
|
152
179
|
)
|
153
|
-
|
180
|
+
|
154
181
|
def _setup_routing_rules(self):
|
155
182
|
"""Setup routing rules for different services."""
|
156
183
|
self.routing_rules = {
|
157
184
|
"/api/v1/users": {
|
158
185
|
"service": "user-service",
|
159
|
-
"endpoints": [
|
186
|
+
"endpoints": [
|
187
|
+
"https://user-service-1:8080",
|
188
|
+
"https://user-service-2:8080",
|
189
|
+
],
|
160
190
|
"timeout": 30,
|
161
191
|
"retries": 3,
|
162
|
-
"required_permissions": ["read", "user"]
|
192
|
+
"required_permissions": ["read", "user"],
|
163
193
|
},
|
164
194
|
"/api/v1/orders": {
|
165
195
|
"service": "order-service",
|
166
|
-
"endpoints": [
|
196
|
+
"endpoints": [
|
197
|
+
"https://order-service-1:8081",
|
198
|
+
"https://order-service-2:8081",
|
199
|
+
],
|
167
200
|
"timeout": 60,
|
168
201
|
"retries": 3,
|
169
|
-
"required_permissions": ["read", "write", "order"]
|
202
|
+
"required_permissions": ["read", "write", "order"],
|
170
203
|
},
|
171
204
|
"/api/v1/payments": {
|
172
205
|
"service": "payment-service",
|
173
|
-
"endpoints": [
|
206
|
+
"endpoints": [
|
207
|
+
"https://payment-service-1:8082",
|
208
|
+
"https://payment-service-2:8082",
|
209
|
+
],
|
174
210
|
"timeout": 45,
|
175
211
|
"retries": 2,
|
176
|
-
"required_permissions": ["write", "payment"]
|
212
|
+
"required_permissions": ["write", "payment"],
|
177
213
|
},
|
178
214
|
"/api/v1/admin": {
|
179
215
|
"service": "admin-service",
|
180
216
|
"endpoints": ["https://admin-service:8083"],
|
181
217
|
"timeout": 30,
|
182
218
|
"retries": 1,
|
183
|
-
"required_permissions": ["admin"]
|
184
|
-
}
|
219
|
+
"required_permissions": ["admin"],
|
220
|
+
},
|
185
221
|
}
|
186
|
-
|
222
|
+
|
187
223
|
def _setup_load_balancer(self):
|
188
224
|
"""Setup load balancer for service endpoints."""
|
189
225
|
self.load_balancer = {
|
190
226
|
"algorithm": "round_robin", # round_robin, least_connections, weighted
|
191
227
|
"health_check_interval": 30,
|
192
228
|
"health_check_timeout": 5,
|
193
|
-
"max_failures": 3
|
229
|
+
"max_failures": 3,
|
194
230
|
}
|
195
231
|
self.current_endpoint_index = {} # Track current endpoint for round-robin
|
196
|
-
|
232
|
+
|
197
233
|
async def route_request(self, request_data: Dict[str, Any]) -> Dict[str, Any]:
|
198
234
|
"""
|
199
235
|
Route request through API Gateway with security validation.
|
200
|
-
|
236
|
+
|
201
237
|
Args:
|
202
238
|
request_data: Request data including path, method, headers, body
|
203
|
-
|
239
|
+
|
204
240
|
Returns:
|
205
241
|
Dict[str, Any]: Response data
|
206
242
|
"""
|
@@ -212,16 +248,16 @@ class APIGatewayExample:
|
|
212
248
|
body = request_data.get("body", {})
|
213
249
|
client_ip = request_data.get("client_ip", DEFAULT_CLIENT_IP)
|
214
250
|
request_id = request_data.get("request_id", self._generate_request_id())
|
215
|
-
|
251
|
+
|
216
252
|
# Step 1: Rate limiting check
|
217
253
|
if not self.check_rate_limit(client_ip):
|
218
254
|
return self._create_error_response(
|
219
255
|
"Rate limit exceeded",
|
220
256
|
ErrorCodes.RATE_LIMIT_EXCEEDED_ERROR,
|
221
257
|
HTTP_TOO_MANY_REQUESTS,
|
222
|
-
request_id
|
258
|
+
request_id,
|
223
259
|
)
|
224
|
-
|
260
|
+
|
225
261
|
# Step 2: Authentication
|
226
262
|
auth_result = self.authenticate_request(headers)
|
227
263
|
if not auth_result.is_valid:
|
@@ -230,82 +266,88 @@ class APIGatewayExample:
|
|
230
266
|
auth_result.error_code,
|
231
267
|
HTTP_UNAUTHORIZED,
|
232
268
|
request_id,
|
233
|
-
auth_result.error_message
|
269
|
+
auth_result.error_message,
|
234
270
|
)
|
235
|
-
|
271
|
+
|
236
272
|
# Step 3: Find routing rule
|
237
273
|
routing_rule = self._find_routing_rule(path)
|
238
274
|
if not routing_rule:
|
239
275
|
return self._create_error_response(
|
240
|
-
"Service not found",
|
241
|
-
ErrorCodes.GENERAL_ERROR,
|
242
|
-
404,
|
243
|
-
request_id
|
276
|
+
"Service not found", ErrorCodes.GENERAL_ERROR, 404, request_id
|
244
277
|
)
|
245
|
-
|
278
|
+
|
246
279
|
# Step 4: Authorization check
|
247
|
-
if not self.check_permissions(
|
280
|
+
if not self.check_permissions(
|
281
|
+
auth_result.roles, routing_rule["required_permissions"]
|
282
|
+
):
|
248
283
|
return self._create_error_response(
|
249
284
|
"Insufficient permissions",
|
250
285
|
ErrorCodes.PERMISSION_DENIED_ERROR,
|
251
286
|
HTTP_FORBIDDEN,
|
252
|
-
request_id
|
287
|
+
request_id,
|
253
288
|
)
|
254
|
-
|
289
|
+
|
255
290
|
# Step 5: Route request to backend service
|
256
291
|
response = await self._forward_request(
|
257
292
|
routing_rule, method, path, headers, body, request_id
|
258
293
|
)
|
259
|
-
|
294
|
+
|
260
295
|
# Step 6: Log security event
|
261
|
-
self._log_security_event(
|
262
|
-
"
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
296
|
+
self._log_security_event(
|
297
|
+
"request_routed",
|
298
|
+
{
|
299
|
+
"username": auth_result.username,
|
300
|
+
"path": path,
|
301
|
+
"method": method,
|
302
|
+
"service": routing_rule["service"],
|
303
|
+
"success": True,
|
304
|
+
"request_id": request_id,
|
305
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
306
|
+
},
|
307
|
+
)
|
308
|
+
|
271
309
|
return response
|
272
|
-
|
310
|
+
|
273
311
|
except Exception as e:
|
274
312
|
self.logger.error(f"Request routing failed: {str(e)}")
|
275
313
|
return self._create_error_response(
|
276
314
|
"Internal server error",
|
277
315
|
ErrorCodes.GENERAL_ERROR,
|
278
316
|
500,
|
279
|
-
request_id if
|
317
|
+
request_id if "request_id" in locals() else self._generate_request_id(),
|
280
318
|
)
|
281
|
-
|
319
|
+
|
282
320
|
def authenticate_request(self, headers: Dict[str, str]) -> AuthResult:
|
283
321
|
"""
|
284
322
|
Authenticate request using headers.
|
285
|
-
|
323
|
+
|
286
324
|
Args:
|
287
325
|
headers: Request headers
|
288
|
-
|
326
|
+
|
289
327
|
Returns:
|
290
328
|
AuthResult: Authentication result
|
291
329
|
"""
|
292
330
|
try:
|
293
331
|
# Try API key authentication
|
294
|
-
api_key = headers.get("X-API-Key") or headers.get(
|
332
|
+
api_key = headers.get("X-API-Key") or headers.get(
|
333
|
+
"Authorization", ""
|
334
|
+
).replace("Bearer ", "")
|
295
335
|
if api_key:
|
296
336
|
return self.security_manager.auth_manager.authenticate_api_key(api_key)
|
297
|
-
|
337
|
+
|
298
338
|
# Try JWT authentication
|
299
339
|
auth_header = headers.get("Authorization", "")
|
300
340
|
if auth_header.startswith("Bearer "):
|
301
341
|
token = auth_header[7:]
|
302
342
|
return self.security_manager.auth_manager.authenticate_jwt_token(token)
|
303
|
-
|
343
|
+
|
304
344
|
# Try certificate authentication (would be handled at TLS level)
|
305
345
|
client_cert = headers.get("X-Client-Cert")
|
306
346
|
if client_cert:
|
307
|
-
return self.security_manager.auth_manager.authenticate_certificate(
|
308
|
-
|
347
|
+
return self.security_manager.auth_manager.authenticate_certificate(
|
348
|
+
client_cert
|
349
|
+
)
|
350
|
+
|
309
351
|
return AuthResult(
|
310
352
|
is_valid=False,
|
311
353
|
status=AuthStatus.FAILED,
|
@@ -313,9 +355,9 @@ class APIGatewayExample:
|
|
313
355
|
roles=[],
|
314
356
|
auth_method=None,
|
315
357
|
error_code=ErrorCodes.AUTHENTICATION_ERROR,
|
316
|
-
error_message="No valid authentication credentials found"
|
358
|
+
error_message="No valid authentication credentials found",
|
317
359
|
)
|
318
|
-
|
360
|
+
|
319
361
|
except Exception as e:
|
320
362
|
self.logger.error(f"Authentication failed: {str(e)}")
|
321
363
|
return AuthResult(
|
@@ -325,17 +367,19 @@ class APIGatewayExample:
|
|
325
367
|
roles=[],
|
326
368
|
auth_method=None,
|
327
369
|
error_code=ErrorCodes.AUTHENTICATION_ERROR,
|
328
|
-
error_message=str(e)
|
370
|
+
error_message=str(e),
|
329
371
|
)
|
330
|
-
|
331
|
-
def check_permissions(
|
372
|
+
|
373
|
+
def check_permissions(
|
374
|
+
self, user_roles: List[str], required_permissions: List[str]
|
375
|
+
) -> bool:
|
332
376
|
"""
|
333
377
|
Check if user has required permissions.
|
334
|
-
|
378
|
+
|
335
379
|
Args:
|
336
380
|
user_roles: User roles
|
337
381
|
required_permissions: Required permissions
|
338
|
-
|
382
|
+
|
339
383
|
Returns:
|
340
384
|
bool: True if user has required permissions
|
341
385
|
"""
|
@@ -346,14 +390,14 @@ class APIGatewayExample:
|
|
346
390
|
except Exception as e:
|
347
391
|
self.logger.error(f"Permission check failed: {str(e)}")
|
348
392
|
return False
|
349
|
-
|
393
|
+
|
350
394
|
def check_rate_limit(self, identifier: str) -> bool:
|
351
395
|
"""
|
352
396
|
Check if request is within rate limits.
|
353
|
-
|
397
|
+
|
354
398
|
Args:
|
355
399
|
identifier: Request identifier (IP, user ID, etc.)
|
356
|
-
|
400
|
+
|
357
401
|
Returns:
|
358
402
|
bool: True if request is within rate limits
|
359
403
|
"""
|
@@ -362,14 +406,14 @@ class APIGatewayExample:
|
|
362
406
|
except Exception as e:
|
363
407
|
self.logger.error(f"Rate limit check failed: {str(e)}")
|
364
408
|
return True # Allow request if rate limiting fails
|
365
|
-
|
409
|
+
|
366
410
|
def _find_routing_rule(self, path: str) -> Optional[Dict[str, Any]]:
|
367
411
|
"""
|
368
412
|
Find routing rule for the given path.
|
369
|
-
|
413
|
+
|
370
414
|
Args:
|
371
415
|
path: Request path
|
372
|
-
|
416
|
+
|
373
417
|
Returns:
|
374
418
|
Optional[Dict[str, Any]]: Routing rule or None
|
375
419
|
"""
|
@@ -377,12 +421,19 @@ class APIGatewayExample:
|
|
377
421
|
if path.startswith(route_path):
|
378
422
|
return rule
|
379
423
|
return None
|
380
|
-
|
381
|
-
async def _forward_request(
|
382
|
-
|
424
|
+
|
425
|
+
async def _forward_request(
|
426
|
+
self,
|
427
|
+
routing_rule: Dict[str, Any],
|
428
|
+
method: str,
|
429
|
+
path: str,
|
430
|
+
headers: Dict[str, str],
|
431
|
+
body: Dict[str, Any],
|
432
|
+
request_id: str,
|
433
|
+
) -> Dict[str, Any]:
|
383
434
|
"""
|
384
435
|
Forward request to backend service.
|
385
|
-
|
436
|
+
|
386
437
|
Args:
|
387
438
|
routing_rule: Routing rule for the service
|
388
439
|
method: HTTP method
|
@@ -390,136 +441,150 @@ class APIGatewayExample:
|
|
390
441
|
headers: Request headers
|
391
442
|
body: Request body
|
392
443
|
request_id: Request ID
|
393
|
-
|
444
|
+
|
394
445
|
Returns:
|
395
446
|
Dict[str, Any]: Response from backend service
|
396
447
|
"""
|
397
448
|
try:
|
398
449
|
# Select endpoint using load balancer
|
399
|
-
endpoint = self._select_endpoint(
|
400
|
-
|
450
|
+
endpoint = self._select_endpoint(
|
451
|
+
routing_rule["service"], routing_rule["endpoints"]
|
452
|
+
)
|
453
|
+
|
401
454
|
# Prepare headers for backend service
|
402
455
|
backend_headers = {
|
403
456
|
**headers,
|
404
457
|
"X-Request-ID": request_id,
|
405
458
|
"X-Gateway": "true",
|
406
459
|
"X-Forwarded-For": headers.get("X-Forwarded-For", DEFAULT_CLIENT_IP),
|
407
|
-
"X-Original-Path": path
|
460
|
+
"X-Original-Path": path,
|
408
461
|
}
|
409
|
-
|
462
|
+
|
410
463
|
# Create SSL context if needed
|
411
464
|
ssl_context = None
|
412
465
|
if self.config.ssl.enabled:
|
413
466
|
ssl_context = self.security_manager.ssl_manager.create_client_context()
|
414
|
-
|
467
|
+
|
415
468
|
# Make request to backend service
|
416
469
|
timeout = aiohttp.ClientTimeout(total=routing_rule["timeout"])
|
417
|
-
|
470
|
+
|
418
471
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
419
472
|
url = f"{endpoint}{path}"
|
420
|
-
|
473
|
+
|
421
474
|
if method.upper() == "GET":
|
422
|
-
async with session.get(
|
475
|
+
async with session.get(
|
476
|
+
url, headers=backend_headers, ssl=ssl_context
|
477
|
+
) as response:
|
423
478
|
return await self._process_response(response, request_id)
|
424
479
|
elif method.upper() == "POST":
|
425
|
-
async with session.post(
|
480
|
+
async with session.post(
|
481
|
+
url, headers=backend_headers, json=body, ssl=ssl_context
|
482
|
+
) as response:
|
426
483
|
return await self._process_response(response, request_id)
|
427
484
|
elif method.upper() == "PUT":
|
428
|
-
async with session.put(
|
485
|
+
async with session.put(
|
486
|
+
url, headers=backend_headers, json=body, ssl=ssl_context
|
487
|
+
) as response:
|
429
488
|
return await self._process_response(response, request_id)
|
430
489
|
elif method.upper() == "DELETE":
|
431
|
-
async with session.delete(
|
490
|
+
async with session.delete(
|
491
|
+
url, headers=backend_headers, ssl=ssl_context
|
492
|
+
) as response:
|
432
493
|
return await self._process_response(response, request_id)
|
433
494
|
else:
|
434
495
|
raise ValueError(f"Unsupported HTTP method: {method}")
|
435
|
-
|
496
|
+
|
436
497
|
except Exception as e:
|
437
498
|
self.logger.error(f"Request forwarding failed: {str(e)}")
|
438
499
|
return self._create_error_response(
|
439
|
-
"Backend service unavailable",
|
440
|
-
ErrorCodes.GENERAL_ERROR,
|
441
|
-
503,
|
442
|
-
request_id
|
500
|
+
"Backend service unavailable", ErrorCodes.GENERAL_ERROR, 503, request_id
|
443
501
|
)
|
444
|
-
|
502
|
+
|
445
503
|
def _select_endpoint(self, service: str, endpoints: List[str]) -> str:
|
446
504
|
"""
|
447
505
|
Select endpoint using load balancer algorithm.
|
448
|
-
|
506
|
+
|
449
507
|
Args:
|
450
508
|
service: Service name
|
451
509
|
endpoints: List of available endpoints
|
452
|
-
|
510
|
+
|
453
511
|
Returns:
|
454
512
|
str: Selected endpoint
|
455
513
|
"""
|
456
514
|
if not endpoints:
|
457
515
|
raise ValueError(f"No endpoints available for service: {service}")
|
458
|
-
|
516
|
+
|
459
517
|
if self.load_balancer["algorithm"] == "round_robin":
|
460
518
|
# Simple round-robin implementation
|
461
519
|
if service not in self.current_endpoint_index:
|
462
520
|
self.current_endpoint_index[service] = 0
|
463
|
-
|
521
|
+
|
464
522
|
endpoint = endpoints[self.current_endpoint_index[service]]
|
465
|
-
self.current_endpoint_index[service] = (
|
523
|
+
self.current_endpoint_index[service] = (
|
524
|
+
self.current_endpoint_index[service] + 1
|
525
|
+
) % len(endpoints)
|
466
526
|
return endpoint
|
467
527
|
else:
|
468
528
|
# Default to first endpoint
|
469
529
|
return endpoints[0]
|
470
|
-
|
471
|
-
async def _process_response(
|
530
|
+
|
531
|
+
async def _process_response(
|
532
|
+
self, response: aiohttp.ClientResponse, request_id: str
|
533
|
+
) -> Dict[str, Any]:
|
472
534
|
"""
|
473
535
|
Process response from backend service.
|
474
|
-
|
536
|
+
|
475
537
|
Args:
|
476
538
|
response: Response from backend service
|
477
539
|
request_id: Request ID
|
478
|
-
|
540
|
+
|
479
541
|
Returns:
|
480
542
|
Dict[str, Any]: Processed response
|
481
543
|
"""
|
482
544
|
try:
|
483
545
|
response_data = await response.json()
|
484
|
-
|
546
|
+
|
485
547
|
# Add gateway headers
|
486
548
|
response_headers = {
|
487
549
|
"X-Gateway": "true",
|
488
550
|
"X-Request-ID": request_id,
|
489
551
|
"X-Response-Time": str(response.headers.get("X-Response-Time", "")),
|
490
|
-
**DEFAULT_SECURITY_HEADERS
|
552
|
+
**DEFAULT_SECURITY_HEADERS,
|
491
553
|
}
|
492
|
-
|
554
|
+
|
493
555
|
return {
|
494
556
|
"success": True,
|
495
557
|
"status_code": response.status,
|
496
558
|
"headers": response_headers,
|
497
559
|
"data": response_data,
|
498
560
|
"request_id": request_id,
|
499
|
-
"timestamp": datetime.
|
561
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
500
562
|
}
|
501
|
-
|
563
|
+
|
502
564
|
except Exception as e:
|
503
565
|
self.logger.error(f"Response processing failed: {str(e)}")
|
504
566
|
return self._create_error_response(
|
505
|
-
"Response processing failed",
|
506
|
-
ErrorCodes.GENERAL_ERROR,
|
507
|
-
500,
|
508
|
-
request_id
|
567
|
+
"Response processing failed", ErrorCodes.GENERAL_ERROR, 500, request_id
|
509
568
|
)
|
510
|
-
|
511
|
-
def _create_error_response(
|
512
|
-
|
569
|
+
|
570
|
+
def _create_error_response(
|
571
|
+
self,
|
572
|
+
message: str,
|
573
|
+
error_code: int,
|
574
|
+
status_code: int,
|
575
|
+
request_id: str,
|
576
|
+
details: Optional[str] = None,
|
577
|
+
) -> Dict[str, Any]:
|
513
578
|
"""
|
514
579
|
Create error response.
|
515
|
-
|
580
|
+
|
516
581
|
Args:
|
517
582
|
message: Error message
|
518
583
|
error_code: Error code
|
519
584
|
status_code: HTTP status code
|
520
585
|
request_id: Request ID
|
521
586
|
details: Additional error details
|
522
|
-
|
587
|
+
|
523
588
|
Returns:
|
524
589
|
Dict[str, Any]: Error response
|
525
590
|
"""
|
@@ -530,18 +595,19 @@ class APIGatewayExample:
|
|
530
595
|
"status_code": status_code,
|
531
596
|
"request_id": request_id,
|
532
597
|
"details": details,
|
533
|
-
"timestamp": datetime.
|
598
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
534
599
|
}
|
535
|
-
|
600
|
+
|
536
601
|
def _generate_request_id(self) -> str:
|
537
602
|
"""Generate unique request ID for tracing."""
|
538
603
|
import uuid
|
604
|
+
|
539
605
|
return str(uuid.uuid4())
|
540
|
-
|
606
|
+
|
541
607
|
def _log_security_event(self, event_type: str, details: Dict[str, Any]):
|
542
608
|
"""
|
543
609
|
Log security event.
|
544
|
-
|
610
|
+
|
545
611
|
Args:
|
546
612
|
event_type: Type of security event
|
547
613
|
details: Event details
|
@@ -559,49 +625,48 @@ class APIGatewayExample:
|
|
559
625
|
"service": details.get("service"),
|
560
626
|
"success": details.get("success"),
|
561
627
|
"request_id": details.get("request_id"),
|
562
|
-
**details
|
563
|
-
}
|
628
|
+
**details,
|
629
|
+
},
|
564
630
|
)
|
565
631
|
except Exception as e:
|
566
632
|
self.logger.error(f"Failed to log security event: {str(e)}")
|
567
|
-
|
633
|
+
|
568
634
|
async def health_check(self) -> Dict[str, Any]:
|
569
635
|
"""
|
570
636
|
Perform health check for the API Gateway.
|
571
|
-
|
637
|
+
|
572
638
|
Returns:
|
573
639
|
Dict[str, Any]: Health check result
|
574
640
|
"""
|
575
641
|
try:
|
576
642
|
# Check security manager health
|
577
643
|
security_healthy = self.security_manager is not None
|
578
|
-
|
644
|
+
|
579
645
|
# Check rate limiter health
|
580
646
|
rate_limit_healthy = self.security_manager.rate_limiter is not None
|
581
|
-
|
647
|
+
|
582
648
|
# Check routing rules
|
583
649
|
routing_healthy = len(self.routing_rules) > 0
|
584
|
-
|
650
|
+
|
585
651
|
# Check SSL configuration
|
586
|
-
ssl_healthy = self.config.ssl.enabled and os.path.exists(
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
routing_healthy,
|
592
|
-
|
593
|
-
|
594
|
-
|
652
|
+
ssl_healthy = self.config.ssl.enabled and os.path.exists(
|
653
|
+
self.config.ssl.cert_file
|
654
|
+
)
|
655
|
+
|
656
|
+
overall_healthy = all(
|
657
|
+
[security_healthy, rate_limit_healthy, routing_healthy, ssl_healthy]
|
658
|
+
)
|
659
|
+
|
595
660
|
return {
|
596
661
|
"status": "healthy" if overall_healthy else "unhealthy",
|
597
662
|
"gateway": "api-gateway",
|
598
|
-
"timestamp": datetime.
|
663
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
599
664
|
"checks": {
|
600
665
|
"security_manager": security_healthy,
|
601
666
|
"rate_limiter": rate_limit_healthy,
|
602
667
|
"routing_rules": routing_healthy,
|
603
|
-
"ssl_configuration": ssl_healthy
|
604
|
-
}
|
668
|
+
"ssl_configuration": ssl_healthy,
|
669
|
+
},
|
605
670
|
}
|
606
671
|
except Exception as e:
|
607
672
|
self.logger.error(f"Health check failed: {str(e)}")
|
@@ -609,46 +674,48 @@ class APIGatewayExample:
|
|
609
674
|
"status": "unhealthy",
|
610
675
|
"gateway": "api-gateway",
|
611
676
|
"error": str(e),
|
612
|
-
"timestamp": datetime.
|
677
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
613
678
|
}
|
614
|
-
|
679
|
+
|
615
680
|
async def get_metrics(self) -> Dict[str, Any]:
|
616
681
|
"""
|
617
682
|
Get API Gateway metrics.
|
618
|
-
|
683
|
+
|
619
684
|
Returns:
|
620
685
|
Dict[str, Any]: Metrics data
|
621
686
|
"""
|
622
687
|
try:
|
623
688
|
# Get rate limiter metrics
|
624
689
|
rate_limit_stats = self.security_manager.rate_limiter.get_statistics()
|
625
|
-
|
690
|
+
|
626
691
|
# Get routing statistics
|
627
692
|
routing_stats = {
|
628
693
|
"total_routes": len(self.routing_rules),
|
629
|
-
"services": list(
|
630
|
-
|
694
|
+
"services": list(
|
695
|
+
set(rule["service"] for rule in self.routing_rules.values())
|
696
|
+
),
|
697
|
+
"load_balancer_algorithm": self.load_balancer["algorithm"],
|
631
698
|
}
|
632
|
-
|
699
|
+
|
633
700
|
return {
|
634
701
|
"gateway": "api-gateway",
|
635
|
-
"timestamp": datetime.
|
702
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
636
703
|
"rate_limiting": rate_limit_stats,
|
637
704
|
"routing": routing_stats,
|
638
|
-
"security": self.get_security_status()
|
705
|
+
"security": self.get_security_status(),
|
639
706
|
}
|
640
707
|
except Exception as e:
|
641
708
|
self.logger.error(f"Failed to get metrics: {str(e)}")
|
642
709
|
return {
|
643
710
|
"gateway": "api-gateway",
|
644
711
|
"error": str(e),
|
645
|
-
"timestamp": datetime.
|
712
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
646
713
|
}
|
647
|
-
|
714
|
+
|
648
715
|
def get_security_status(self) -> Dict[str, Any]:
|
649
716
|
"""
|
650
717
|
Get security framework status.
|
651
|
-
|
718
|
+
|
652
719
|
Returns:
|
653
720
|
Dict[str, Any]: Security status information
|
654
721
|
"""
|
@@ -660,103 +727,103 @@ class APIGatewayExample:
|
|
660
727
|
"permissions_enabled": self.config.permissions.enabled,
|
661
728
|
"logging_enabled": self.config.logging.enabled,
|
662
729
|
"auth_methods": self.config.auth.methods,
|
663
|
-
"timestamp": datetime.
|
730
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
664
731
|
}
|
665
732
|
|
666
733
|
|
667
734
|
# Example usage and testing
|
668
735
|
class APIGatewayExampleTest:
|
669
736
|
"""Test class for API Gateway example functionality."""
|
670
|
-
|
737
|
+
|
671
738
|
@staticmethod
|
672
739
|
async def test_authentication():
|
673
740
|
"""Test authentication functionality."""
|
674
741
|
gateway = APIGatewayExample()
|
675
|
-
|
742
|
+
|
676
743
|
# Test API key authentication
|
677
744
|
headers = {"X-API-Key": "gateway_key_123"}
|
678
745
|
auth_result = gateway.authenticate_request(headers)
|
679
746
|
assert auth_result.is_valid
|
680
747
|
assert auth_result.username == "gateway-admin"
|
681
748
|
assert "gateway" in auth_result.roles
|
682
|
-
|
749
|
+
|
683
750
|
print("✅ API Key authentication test passed")
|
684
|
-
|
751
|
+
|
685
752
|
@staticmethod
|
686
753
|
async def test_permissions():
|
687
754
|
"""Test permission checking."""
|
688
755
|
gateway = APIGatewayExample()
|
689
|
-
|
756
|
+
|
690
757
|
# Test gateway permissions
|
691
758
|
gateway_roles = ["gateway"]
|
692
759
|
client_roles = ["client"]
|
693
760
|
admin_roles = ["admin"]
|
694
|
-
|
761
|
+
|
695
762
|
# Gateway should have gateway permissions
|
696
763
|
assert gateway.check_permissions(gateway_roles, ["read", "write"])
|
697
|
-
|
764
|
+
|
698
765
|
# Client should have client permissions
|
699
766
|
assert gateway.check_permissions(client_roles, ["read"])
|
700
|
-
|
767
|
+
|
701
768
|
# Admin should have all permissions
|
702
769
|
assert gateway.check_permissions(admin_roles, ["read", "write", "delete"])
|
703
|
-
|
770
|
+
|
704
771
|
print("✅ Permission checking test passed")
|
705
|
-
|
772
|
+
|
706
773
|
@staticmethod
|
707
774
|
async def test_rate_limiting():
|
708
775
|
"""Test rate limiting functionality."""
|
709
776
|
gateway = APIGatewayExample()
|
710
|
-
|
777
|
+
|
711
778
|
# Test rate limiting
|
712
779
|
identifier = "test_client"
|
713
780
|
for i in range(5):
|
714
781
|
is_allowed = gateway.check_rate_limit(identifier)
|
715
782
|
print(f"Request {i+1}: {'Allowed' if is_allowed else 'Blocked'}")
|
716
|
-
|
783
|
+
|
717
784
|
print("✅ Rate limiting test completed")
|
718
|
-
|
785
|
+
|
719
786
|
@staticmethod
|
720
787
|
async def test_routing():
|
721
788
|
"""Test routing functionality."""
|
722
789
|
gateway = APIGatewayExample()
|
723
|
-
|
790
|
+
|
724
791
|
# Test routing rule finding
|
725
792
|
rule = gateway._find_routing_rule("/api/v1/users")
|
726
793
|
assert rule is not None
|
727
794
|
assert rule["service"] == "user-service"
|
728
|
-
|
795
|
+
|
729
796
|
rule = gateway._find_routing_rule("/api/v1/orders")
|
730
797
|
assert rule is not None
|
731
798
|
assert rule["service"] == "order-service"
|
732
|
-
|
799
|
+
|
733
800
|
rule = gateway._find_routing_rule("/unknown/path")
|
734
801
|
assert rule is None
|
735
|
-
|
802
|
+
|
736
803
|
print("✅ Routing test passed")
|
737
|
-
|
804
|
+
|
738
805
|
@staticmethod
|
739
806
|
async def test_health_check():
|
740
807
|
"""Test health check functionality."""
|
741
808
|
gateway = APIGatewayExample()
|
742
|
-
|
809
|
+
|
743
810
|
health = await gateway.health_check()
|
744
811
|
assert "status" in health
|
745
812
|
assert "gateway" in health
|
746
813
|
assert health["gateway"] == "api-gateway"
|
747
|
-
|
814
|
+
|
748
815
|
print("✅ Health check test passed")
|
749
|
-
|
816
|
+
|
750
817
|
@staticmethod
|
751
818
|
async def test_metrics():
|
752
819
|
"""Test metrics functionality."""
|
753
820
|
gateway = APIGatewayExample()
|
754
|
-
|
821
|
+
|
755
822
|
metrics = await gateway.get_metrics()
|
756
823
|
assert "gateway" in metrics
|
757
824
|
assert "timestamp" in metrics
|
758
825
|
assert metrics["gateway"] == "api-gateway"
|
759
|
-
|
826
|
+
|
760
827
|
print("✅ Metrics test passed")
|
761
828
|
|
762
829
|
|
@@ -770,30 +837,27 @@ async def main():
|
|
770
837
|
await APIGatewayExampleTest.test_routing()
|
771
838
|
await APIGatewayExampleTest.test_health_check()
|
772
839
|
await APIGatewayExampleTest.test_metrics()
|
773
|
-
|
840
|
+
|
774
841
|
# Example usage
|
775
842
|
print("\nExample Usage:")
|
776
843
|
gateway = APIGatewayExample()
|
777
|
-
|
844
|
+
|
778
845
|
# Route a request
|
779
846
|
request_data = {
|
780
847
|
"path": "/api/v1/users/123",
|
781
848
|
"method": "GET",
|
782
|
-
"headers": {
|
783
|
-
"X-API-Key": "client_key_456",
|
784
|
-
"X-Forwarded-For": "192.168.1.100"
|
785
|
-
},
|
849
|
+
"headers": {"X-API-Key": "client_key_456", "X-Forwarded-For": "192.168.1.100"},
|
786
850
|
"body": {},
|
787
|
-
"client_ip": "192.168.1.100"
|
851
|
+
"client_ip": "192.168.1.100",
|
788
852
|
}
|
789
|
-
|
853
|
+
|
790
854
|
result = await gateway.route_request(request_data)
|
791
855
|
print(f"Request result: {result}")
|
792
|
-
|
856
|
+
|
793
857
|
# Get health status
|
794
858
|
health = await gateway.health_check()
|
795
859
|
print(f"Health status: {health}")
|
796
|
-
|
860
|
+
|
797
861
|
# Get metrics
|
798
862
|
metrics = await gateway.get_metrics()
|
799
863
|
print(f"Metrics: {metrics}")
|
@@ -801,3 +865,98 @@ async def main():
|
|
801
865
|
|
802
866
|
if __name__ == "__main__":
|
803
867
|
asyncio.run(main())
|
868
|
+
|
869
|
+
# Start HTTP server in background for testing
|
870
|
+
print("\nStarting API Gateway HTTP Server in background...")
|
871
|
+
|
872
|
+
import json
|
873
|
+
import threading
|
874
|
+
import time
|
875
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
876
|
+
|
877
|
+
import requests
|
878
|
+
|
879
|
+
class GatewayHandler(BaseHTTPRequestHandler):
|
880
|
+
def do_GET(self):
|
881
|
+
if self.path == "/health":
|
882
|
+
self.send_response(200)
|
883
|
+
self.send_header("Content-type", "application/json")
|
884
|
+
self.end_headers()
|
885
|
+
response = {"status": "healthy", "gateway": "api-gateway"}
|
886
|
+
self.wfile.write(json.dumps(response).encode())
|
887
|
+
|
888
|
+
elif self.path == "/metrics":
|
889
|
+
self.send_response(200)
|
890
|
+
self.send_header("Content-type", "application/json")
|
891
|
+
self.end_headers()
|
892
|
+
response = {
|
893
|
+
"gateway": "api-gateway",
|
894
|
+
"requests_total": 100,
|
895
|
+
"requests_per_minute": 10,
|
896
|
+
}
|
897
|
+
self.wfile.write(json.dumps(response).encode())
|
898
|
+
|
899
|
+
elif self.path == "/proxy":
|
900
|
+
api_key = self.headers.get("X-API-Key")
|
901
|
+
if not api_key:
|
902
|
+
self.send_response(401)
|
903
|
+
self.send_header("Content-type", "application/json")
|
904
|
+
self.end_headers()
|
905
|
+
response = {"error": "API key required"}
|
906
|
+
self.wfile.write(json.dumps(response).encode())
|
907
|
+
else:
|
908
|
+
self.send_response(200)
|
909
|
+
self.send_header("Content-type", "application/json")
|
910
|
+
self.end_headers()
|
911
|
+
response = {
|
912
|
+
"success": True,
|
913
|
+
"message": "Request proxied successfully",
|
914
|
+
"api_key": api_key,
|
915
|
+
}
|
916
|
+
self.wfile.write(json.dumps(response).encode())
|
917
|
+
|
918
|
+
else:
|
919
|
+
self.send_response(404)
|
920
|
+
self.end_headers()
|
921
|
+
|
922
|
+
def log_message(self, format, *args):
|
923
|
+
# Suppress logging
|
924
|
+
pass
|
925
|
+
|
926
|
+
def run_server():
|
927
|
+
"""Run the HTTP server."""
|
928
|
+
server = HTTPServer(("0.0.0.0", 8080), GatewayHandler)
|
929
|
+
server.serve_forever()
|
930
|
+
|
931
|
+
# Start server in background thread
|
932
|
+
server_thread = threading.Thread(target=run_server, daemon=True)
|
933
|
+
server_thread.start()
|
934
|
+
|
935
|
+
# Wait for server to start
|
936
|
+
time.sleep(2)
|
937
|
+
|
938
|
+
try:
|
939
|
+
# Test server endpoints
|
940
|
+
print("Testing API Gateway server endpoints...")
|
941
|
+
|
942
|
+
# Test health endpoint
|
943
|
+
response = requests.get("http://localhost:8080/health", timeout=5)
|
944
|
+
print(f"Health endpoint: {response.status_code}")
|
945
|
+
|
946
|
+
# Test metrics endpoint
|
947
|
+
response = requests.get("http://localhost:8080/metrics", timeout=5)
|
948
|
+
print(f"Metrics endpoint: {response.status_code}")
|
949
|
+
|
950
|
+
# Test proxy endpoint with API key
|
951
|
+
headers = {"X-API-Key": "test_key_123"}
|
952
|
+
response = requests.get(
|
953
|
+
"http://localhost:8080/proxy", headers=headers, timeout=5
|
954
|
+
)
|
955
|
+
print(f"Proxy endpoint: {response.status_code}")
|
956
|
+
|
957
|
+
print("✅ API Gateway server testing completed successfully")
|
958
|
+
|
959
|
+
except requests.exceptions.RequestException as e:
|
960
|
+
print(f"⚠️ API Gateway server testing failed: {e}")
|
961
|
+
|
962
|
+
print("API Gateway example completed")
|