router = $router ?? new Router; $this->addObjectHandler( fn(object $object) => $object instanceof Stream, function(HttpResponseBuilder $responseBuilder, object $object) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypeStream(); $responseBuilder->setContent(new StreamContent($object)); } ); $this->addObjectHandler( fn(object $object) => $object instanceof JsonSerializable || $object instanceof stdClass, function(HttpResponseBuilder $responseBuilder, object $object) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypeJson(); $responseBuilder->setContent(new JsonContent($object)); } ); $this->addObjectHandler( fn(object $object) => $object instanceof IBencodeSerialisable, function(HttpResponseBuilder $responseBuilder, object $object) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypePlain(); $responseBuilder->setContent(new BencodedContent($object)); } ); } public function getRouter(): Router { return $this->router; } public function addObjectHandler(callable $match, callable $handler): void { $this->objectHandlers[] = [$match, $handler]; } public function addErrorHandler(int $code, callable $handler): void { $this->errorHandlers[$code] = $handler; } public function setDefaultErrorHandler(callable $handler): void { $this->defaultErrorHandler = $handler; } public function restoreDefaultErrorHandler(): void { $this->defaultErrorHandler = [self::class, 'defaultErrorHandler']; } public function dispatch(?HttpRequest $request = null, array $args = []): void { $request ??= HttpRequest::fromRequest(); $responseBuilder = new HttpResponseBuilder; $handlers = null; try { $handlers = $this->router->resolve($request->getMethod(), $request->getPath(), array_merge([ $responseBuilder, $request, ], $args)); } catch(RoutePathNotFoundException $ex) { $statusCode = 404; } catch(RouteMethodNotSupportedException $ex) { $statusCode = 405; } catch(Exception $ex) { if(Environment::isDebug()) throw $ex; } if($handlers === null) { $this->errorPage($responseBuilder, $request, $statusCode ?? 500); } else { $result = $handlers->run(); if(is_int($result)) { if(!$responseBuilder->hasStatusCode() && $result >= 100 && $result < 600) { $this->errorPage($responseBuilder, $request, $result); } elseif(!$responseBuilder->hasContent()) { $responseBuilder->setContent(new StringContent((string)$result)); } } elseif(!$responseBuilder->hasContent()) { if(is_array($result)) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypeJson(); $responseBuilder->setContent(new JsonContent($result)); } elseif(is_object($result) && !($result instanceof IString)) { foreach($this->objectHandlers as $info) if($info[0]($result)) { $info[1]($responseBuilder, $result); break; } } if(!$responseBuilder->hasContent() && $result !== null) { $charset = ($result instanceof WString) ? $result->getEncoding() : null; $result = (string)$result; $responseBuilder->setContent(new StringContent($result)); if(!$responseBuilder->hasContentType()) { if(strtolower(substr($result, 0, 5)) === 'setTypeXML($charset ?? WString::getDefaultEncoding()); elseif(strtolower(substr($result, 0, 14)) === 'setTypeHTML($charset ?? WString::getDefaultEncoding()); else $responseBuilder->setTypePlain($charset ?? 'us-ascii'); } } } } self::output($responseBuilder->toResponse()); } public static function defaultErrorHandler( HttpResponseBuilder $responseBuilder, HttpRequest $request, int $code, string $message ): void { $responseBuilder->setTypeHTML(); $responseBuilder->setContent(new StringContent(sprintf( '