Skip to content

Commit 74446ae

Browse files
committed
feat: add ProxyAwareMiddleware to handle proxy-forwarded headers
1 parent fad9ade commit 74446ae

File tree

1 file changed

+134
-0
lines changed

1 file changed

+134
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Server\Transports\Middleware;
6+
7+
use Psr\Http\Message\ResponseInterface;
8+
use Psr\Http\Message\ServerRequestInterface;
9+
use Psr\Http\Server\MiddlewareInterface;
10+
use Psr\Http\Server\RequestHandlerInterface;
11+
12+
/**
13+
* Middleware that modifies incoming requests to reflect proxy-forwarded headers
14+
* This allows the rest of the application to work with the "real" external URI
15+
*/
16+
final readonly class ProxyAwareMiddleware implements MiddlewareInterface
17+
{
18+
public function __construct(
19+
private bool $trustProxy = true,
20+
) {}
21+
22+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
23+
{
24+
if (!$this->trustProxy) {
25+
return $handler->handle($request);
26+
}
27+
28+
$uri = $request->getUri();
29+
$hasProxyHeaders = false;
30+
31+
// Get proxy-forwarded scheme (protocol)
32+
$scheme = $this->getForwardedScheme($request);
33+
if ($scheme !== null && $scheme !== $uri->getScheme()) {
34+
$uri = $uri->withScheme($scheme);
35+
$hasProxyHeaders = true;
36+
}
37+
38+
// Get proxy-forwarded host
39+
$host = $this->getForwardedHost($request);
40+
if ($host !== null && $host !== $uri->getHost()) {
41+
$uri = $uri->withHost($host);
42+
$hasProxyHeaders = true;
43+
}
44+
45+
// Get proxy-forwarded port
46+
$port = $this->getForwardedPort($request);
47+
if ($port !== null) {
48+
// Only set port if it's not the default for the scheme
49+
if (!$this->isDefaultPort($uri->getScheme(), $port)) {
50+
$uri = $uri->withPort($port);
51+
} else {
52+
$uri = $uri->withPort(null);
53+
}
54+
$hasProxyHeaders = true;
55+
}
56+
57+
// Only create a new request if we actually found proxy headers
58+
if ($hasProxyHeaders) {
59+
$request = $request->withUri($uri);
60+
}
61+
62+
return $handler->handle($request);
63+
}
64+
65+
/**
66+
* Get the forwarded scheme from proxy headers
67+
*/
68+
private function getForwardedScheme(ServerRequestInterface $request): ?string
69+
{
70+
// Check X-Forwarded-Proto header (most common)
71+
$proto = $request->getHeaderLine('X-Forwarded-Proto');
72+
if (!empty($proto)) {
73+
return \strtolower(\trim(\explode(',', $proto)[0]));
74+
}
75+
76+
// Check X-Forwarded-Ssl header
77+
$ssl = $request->getHeaderLine('X-Forwarded-Ssl');
78+
if (\strtolower(\trim($ssl)) === 'on') {
79+
return 'https';
80+
}
81+
82+
// Check X-Forwarded-Scheme header
83+
$scheme = $request->getHeaderLine('X-Forwarded-Scheme');
84+
if (!empty($scheme)) {
85+
return \strtolower(\trim($scheme));
86+
}
87+
88+
return null;
89+
}
90+
91+
/**
92+
* Get the forwarded host from proxy headers
93+
*/
94+
private function getForwardedHost(ServerRequestInterface $request): ?string
95+
{
96+
// Check X-Forwarded-Host header
97+
$host = $request->getHeaderLine('X-Forwarded-Host');
98+
if (!empty($host)) {
99+
// Take the first host if multiple are specified
100+
return \trim(\explode(',', $host)[0]);
101+
}
102+
103+
return null;
104+
}
105+
106+
/**
107+
* Get the forwarded port from proxy headers
108+
*/
109+
private function getForwardedPort(ServerRequestInterface $request): ?int
110+
{
111+
// Check X-Forwarded-Port header
112+
$port = $request->getHeaderLine('X-Forwarded-Port');
113+
if (!empty($port)) {
114+
$portValue = \trim(\explode(',', $port)[0]);
115+
if (\is_numeric($portValue)) {
116+
return (int) $portValue;
117+
}
118+
}
119+
120+
return null;
121+
}
122+
123+
/**
124+
* Check if the port is the default for the given scheme
125+
*/
126+
private function isDefaultPort(string $scheme, int $port): bool
127+
{
128+
return match (\strtolower($scheme)) {
129+
'http' => $port === 80,
130+
'https' => $port === 443,
131+
default => false,
132+
};
133+
}
134+
}

0 commit comments

Comments
 (0)