PHP API网关实现与请求路由
API网关是微服务架构的入口。它负责请求路由、认证、限流、日志等功能。今天从零实现一个简单的API网关。
网关的核心功能是接收客户端请求,根据路由规则转发到对应的后端服务。
```php
class APIGateway
{
private array $routes = [];
private array $middlewares = [];
private array $rateLimits = [];
public function addRoute(string $path, string $targetUrl, array $methods = ['GET', 'POST', 'PUT', 'DELETE']): void
{
$this->routes[] = compact('path', 'targetUrl', 'methods');
}
public function addMiddleware(callable $middleware): void
{
$this->middlewares[] = $middleware;
}
public function setRateLimit(string $path, int $maxRequests, int $window): void
{
$this->rateLimits[$path] = compact('maxRequests', 'window');
}
public function handle(): void
{
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// 执行中间件
foreach ($this->middlewares as $middleware) {
$result = $middleware($method, $uri);
if ($result !== null) {
echo json_encode($result);
return;
}
}
// 路由匹配
foreach ($this->routes as $route) {
if (!in_array($method, $route['methods'])) continue;
$pattern = preg_replace('/\{(\w+)\}/', '(\w+)', $route['path']);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $uri, $matches)) {
$this->forward($route['targetUrl'], $method, $uri);
return;
}
}
http_response_code(404);
echo json_encode(['error' => 'Route not found']);
}
private function forward(string $targetUrl, string $method, string $uri): void
{
$url = rtrim($targetUrl, '/') . $uri;
$ch = curl_init($url);
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADER => true,
];
if ($method === 'POST' || $method === 'PUT') {
$options[CURLOPT_CUSTOMREQUEST] = $method;
$options[CURLOPT_POSTFIELDS] = file_get_contents('php://input');
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
curl_close($ch);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
http_response_code($httpCode);
echo $body;
}
}
?>
// 限流中间件
class RateLimitMiddleware
{
private Redis $redis;
private int $maxRequests;
private int $window;
public function __construct(Redis $redis, int $maxRequests = 100, int $window = 60)
{
$this->redis = $redis;
$this->maxRequests = $maxRequests;
$this->window = $window;
}
public function __invoke(string $method, string $uri): ?array
{
$key = 'ratelimit:' . $_SERVER['REMOTE_ADDR'];
$current = $this->redis->get($key);
if ($current === false) {
$this->redis->setex($key, $this->window, 1);
return null;
}
if ($current >= $this->maxRequests) {
$ttl = $this->redis->ttl($key);
header('Retry-After: ' . $ttl);
http_response_code(429);
return ['error' => '请求过于频繁', 'retry_after' => $ttl];
}
$this->redis->incr($key);
return null;
}
}
// 认证中间件
class AuthMiddleware
{
private string $jwtSecret;
public function __construct(string $jwtSecret)
{
$this->jwtSecret = $jwtSecret;
}
public function __invoke(string $method, string $uri): ?array
{
// 公开路由跳过认证
$publicRoutes = ['/api/login', '/api/register', '/health'];
if (in_array($uri, $publicRoutes)) {
return null;
}
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
$token = str_replace('Bearer ', '', $token);
if (empty($token)) {
http_response_code(401);
return ['error' => '未提供认证token'];
}
// 验证JWT
$parts = explode('.', $token);
if (count($parts) !== 3) {
http_response_code(401);
return ['error' => '无效的token格式'];
}
$payload = json_decode(base64_decode($parts[1]), true);
if (!$payload || $payload['exp'] < time()) {
http_response_code(401);
return ['error' => 'token已过期'];
}
return null;
}
}
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$gateway = new APIGateway();
$gateway->addMiddleware(new RateLimitMiddleware($redis));
$gateway->addMiddleware(new AuthMiddleware('your-jwt-secret'));
$gateway->addRoute('/api/users', 'http://user-service:9001');
$gateway->addRoute('/api/orders', 'http://order-service:9002');
$gateway->addRoute('/api/products', 'http://product-service:9003');
$gateway->setRateLimit('/api/users', 60, 60);
$gateway->setRateLimit('/api/orders', 30, 60);
$gateway->handle();
?>
API网关作为系统的统一入口,可以集中处理横切关注点。认证、限流、日志、监控都在网关层实现,后端服务可以专注于业务逻辑。但网关也可能成为性能瓶颈和单点故障,设计时要注意容错和扩展。
PHPAPI网关实现与请求路由
张小明
前端开发工程师
避坑指南:Node-RED处理Modbus-RTU负温度补码与数据解析的完整流程
Node-RED实战:Modbus-RTU负温度补码解析与数据处理的深度剖析在工业物联网和自动化控制领域,Modbus协议因其简单可靠而广受欢迎,但协议中负数的补码表示方式常常成为开发者数据解析路上的"绊脚石"。我曾在一个冷链监控项目中&#…
手把手教你拆解VLP-16激光雷达:从报废件到核心部件全流程(附高清拆解图)
VLP-16激光雷达深度拆解实战指南:从工具准备到部件解析拆解一台工业级激光雷达听起来像是专业工程师的专利?事实上,只要掌握正确的方法和工具,即使是硬件爱好者也能完成这项富有成就感的任务。VLP-16作为机械旋转式激光雷达的经典…
别再傻傻分不清!SAP Dialog程序里SET SCREEN和CALL SCREEN到底怎么选?一个例子讲透
SAP Dialog程序开发:SET SCREEN与CALL SCREEN的实战抉择指南在SAP ABAP开发中,Dialog程序的屏幕跳转逻辑是每个开发者必须掌握的核心技能。SET SCREEN和CALL SCREEN作为两种最常用的屏幕导航方式,看似功能相似,实则在使用场景和执…
避坑指南:SpringBoot集成BlueCove连接蓝牙时,如何解决‘Native Library not available’等常见错误
SpringBoot蓝牙开发避坑实战:从原理到解决方案的深度解析蓝牙技术在现代应用中扮演着重要角色,但Java开发者在使用SpringBoot集成蓝牙功能时,往往会遇到各种"坑"。本文将系统性地剖析这些常见问题的根源,并提供经过验证…
别再手动拖拽了!用ArcGIS Pro二次开发,5分钟搞定全国分幅GDB数据自动合并
告别低效操作:ArcGIS Pro二次开发实现GDB数据智能合并实战指南清晨的第一杯咖啡还没喝完,办公桌上已经堆满了待处理的GDB文件。作为测绘行业的老兵,张工盯着屏幕上近百个分幅数据库文件叹了口气——这已经是本周第三次需要手动合并全国1:5万地…
别再只会用Keil了!用FlyMCU串口给STM32烧录程序的保姆级教程(附ST-LINK对比)
手把手教你用FlyMCU串口烧录STM32程序:告别ST-LINK依赖 第一次接触STM32开发时,大多数教程都会告诉你使用ST-LINK调试器进行程序烧录。但当你手头没有这个专用工具,或者只是想快速验证一个小改动时,难道就只能干等着吗?…