- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
228 lines
5.3 KiB
PHP
228 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Http;
|
|
|
|
use App\Framework\Http\Url\Url;
|
|
use App\Framework\Http\Url\UrlFactory;
|
|
use App\Framework\UserAgent\UserAgent;
|
|
|
|
/**
|
|
* Kapselt Server-Environment-Daten aus $_SERVER
|
|
*/
|
|
final readonly class ServerEnvironment
|
|
{
|
|
public function __construct(
|
|
private array $serverData = []
|
|
) {
|
|
}
|
|
|
|
public static function fromGlobals(): self
|
|
{
|
|
return new self($_SERVER);
|
|
}
|
|
|
|
public function get(string|ServerKey $key, mixed $default = null): mixed
|
|
{
|
|
if ($key instanceof ServerKey) {
|
|
$key = $key->value;
|
|
}
|
|
|
|
return $this->serverData[$key] ?? $default;
|
|
}
|
|
|
|
public function has(string|ServerKey $key): bool
|
|
{
|
|
! $key instanceof ServerKey ?: $key = $key->value;
|
|
|
|
return array_key_exists($key, $this->serverData);
|
|
}
|
|
|
|
// Häufig verwendete Server-Informationen als typisierte Methoden
|
|
public function getRemoteAddr(): IpAddress
|
|
{
|
|
$ip = $this->get(ServerKey::REMOTE_ADDR, '0.0.0.0');
|
|
|
|
return new IpAddress($ip);
|
|
}
|
|
|
|
public function getUserAgent(): UserAgent
|
|
{
|
|
$userAgentString = $this->get(ServerKey::HTTP_USER_AGENT, '');
|
|
|
|
return UserAgent::fromString($userAgentString);
|
|
}
|
|
|
|
public function getServerName(): string
|
|
{
|
|
return $this->get(ServerKey::SERVER_NAME, '');
|
|
}
|
|
|
|
public function getServerPort(): int
|
|
{
|
|
return (int) $this->get(ServerKey::SERVER_PORT, 80);
|
|
}
|
|
|
|
public function getRequestUri(): Url
|
|
{
|
|
$uriString = $this->get(ServerKey::REQUEST_URI, '/');
|
|
|
|
return UrlFactory::parse($uriString);
|
|
}
|
|
|
|
public function getScriptName(): string
|
|
{
|
|
return $this->get(ServerKey::SCRIPT_NAME, '');
|
|
}
|
|
|
|
public function getHttpHost(): string
|
|
{
|
|
return $this->get(ServerKey::HTTP_HOST, '');
|
|
}
|
|
|
|
public function isHttps(): bool
|
|
{
|
|
return $this->get(ServerKey::HTTPS) === 'on' ||
|
|
$this->get(ServerKey::SERVER_PORT) === '443' ||
|
|
$this->get(ServerKey::HTTP_X_FORWARDED_PROTO) === 'https';
|
|
}
|
|
|
|
public function getRequestMethod(): Method
|
|
{
|
|
$methodString = $this->get(ServerKey::REQUEST_METHOD, 'GET');
|
|
|
|
return Method::tryFrom($methodString) ?? Method::GET;
|
|
|
|
}
|
|
|
|
public function getQueryString(): string
|
|
{
|
|
return $this->get(ServerKey::QUERY_STRING, '');
|
|
}
|
|
|
|
public function getClientIp(): IpAddress
|
|
{
|
|
return new IpAddress($this->getClientIpString());
|
|
}
|
|
|
|
public function getProtocol(): ServerProtocol
|
|
{
|
|
$protocol = $this->get(ServerKey::SERVER_PROTOCOL);
|
|
|
|
// Handle null values in CLI context
|
|
if ($protocol === null) {
|
|
return ServerProtocol::HTTP_1_0;
|
|
}
|
|
|
|
return ServerProtocol::tryFrom($protocol) ?? ServerProtocol::HTTP_1_0;
|
|
}
|
|
|
|
private function getClientIpString(): string
|
|
{
|
|
// Priorisierte IP-Erkennung
|
|
$candidates = [
|
|
ServerKey::HTTP_X_REAL_IP,
|
|
ServerKey::HTTP_X_FORWARDED_FOR,
|
|
ServerKey::REMOTE_ADDR,
|
|
];
|
|
|
|
foreach ($candidates as $key) {
|
|
$ip = $this->get($key);
|
|
if ($ip) {
|
|
// Bei X-Forwarded-For kann es mehrere IPs geben
|
|
if ($key === ServerKey::HTTP_X_FORWARDED_FOR) {
|
|
$ips = explode(',', $ip);
|
|
|
|
return trim($ips[0]);
|
|
}
|
|
|
|
return $ip;
|
|
}
|
|
}
|
|
|
|
return '0.0.0.0';
|
|
}
|
|
|
|
public function getReferer(): string
|
|
{
|
|
return $this->get(ServerKey::HTTP_REFERER, '');
|
|
}
|
|
|
|
public function getRefererUri(): Url
|
|
{
|
|
$referer = $this->getReferer();
|
|
|
|
return UrlFactory::parse($referer);
|
|
}
|
|
|
|
/**
|
|
* Prüft ob ein Referer gesetzt ist
|
|
*/
|
|
public function hasReferer(): bool
|
|
{
|
|
return ! empty($this->get(ServerKey::HTTP_REFERER, null));
|
|
}
|
|
|
|
/**
|
|
* Prüft ob der Referer von der gleichen Domain stammt
|
|
*/
|
|
public function isRefererSameDomain(): bool
|
|
{
|
|
$referer = $this->getReferer();
|
|
|
|
if (empty($referer)) {
|
|
return false;
|
|
}
|
|
|
|
$refererHost = parse_url($referer, PHP_URL_HOST);
|
|
$currentHost = $this->getHttpHost();
|
|
|
|
return $refererHost === $currentHost;
|
|
}
|
|
|
|
/**
|
|
* Sichere Referer-URL für Redirects
|
|
*/
|
|
public function getSafeRefererUrl(string $fallback = '/'): string
|
|
{
|
|
$referer = $this->getReferer();
|
|
|
|
// Kein Referer gesetzt
|
|
if (empty($referer)) {
|
|
return $fallback;
|
|
}
|
|
|
|
// Nur interne Referer erlauben (CSRF-Schutz)
|
|
if (! $this->isRefererSameDomain()) {
|
|
return $fallback;
|
|
}
|
|
|
|
return $referer;
|
|
}
|
|
|
|
/**
|
|
* Get WebSocket key for WebSocket handshake
|
|
*/
|
|
public function getWebSocketKey(): ?string
|
|
{
|
|
return $this->get(ServerKey::HTTP_SEC_WEBSOCKET_KEY);
|
|
}
|
|
|
|
/**
|
|
* Check if request is XMLHttpRequest (AJAX)
|
|
*/
|
|
public function isXmlHttpRequest(): bool
|
|
{
|
|
return $this->get(ServerKey::HTTP_X_REQUESTED_WITH) === 'XMLHttpRequest';
|
|
}
|
|
|
|
/**
|
|
* Check if request has SPA header
|
|
*/
|
|
public function isSpaRequest(): bool
|
|
{
|
|
return $this->has(ServerKey::HTTP_X_SPA_REQUEST);
|
|
}
|
|
}
|