nih/http-kernel

Minimal HTTP kernel for PSR-7, PSR-11, PSR-15 and PSR-17 based applications.

Maintainers

Package info

github.com/nih-soft/http-kernel

Documentation

pkg:composer/nih/http-kernel

Statistics

Installs: 5

Dependents: 2

Suggesters: 0

Stars: 3

Open Issues: 0

0.1.4 2026-04-18 21:02 UTC

This package is auto-updated.

Last update: 2026-04-18 21:04:10 UTC


README

Minimal HTTP kernel for applications built around PSR-7, PSR-15 and PSR-17.

nih/http-kernel is for applications that want a thin, explicit HTTP runtime without adopting a full microframework. It uses NIH container/router packages by default and gives you ordered bootstraps, a main application pipeline, a separate error pipeline, and a safe deferred handoff model for post-response or legacy execution.

If you want a ready-to-run minimal application with conventional project layout around this kernel, start with nih/app-skeleton. The examples below focus on the kernel-facing pieces.

Why use it

  • small application surface: services, routes, pipeline, errorPipeline, fatal
  • boot-time configuration stays separate from request-time execution
  • errorPipeline is independent from the normal application pipeline
  • optional PSR-3 error logging through ErrorLoggingMiddleware
  • built on focused packages: nih/container, nih/router, nih/middleware-dispatcher
  • supports post-response and legacy handoff through DeferredCallableResponse

When to choose it

Use this package when:

  • you want your own PSR-based application skeleton instead of a full framework
  • you need explicit control over bootstrap order, middleware composition, routing, and error rendering
  • you want gradual migration from legacy code

This package is probably not the best fit when:

  • you want a batteries-included microframework with a larger built-in ecosystem
  • you prefer framework conventions over assembling your own application structure

Requirements

  • PHP 8.4 - 8.5

Installation

composer require nih/http-kernel

If you want a ready-to-use starter project with a complete minimal application, see nih/app-skeleton.

If you need a legacy fallback when routing returns NOT_FOUND, see nih/legacy-gateway.

Quick start

This example shows the smallest routed application. GET / dispatches HomeAction, and unmatched routes fall through to the default NotFoundHandler from RoutingBootstrap.

public/index.php

<?php

declare(strict_types=1);

use App\Bootstrap\AppBootstrap;
use NIH\Container\ContainerConfig;
use NIH\HttpKernel\Bootstrap\ErrorHandlingBootstrap;
use NIH\HttpKernel\Bootstrap\Psr17Bootstrap;
use NIH\HttpKernel\Bootstrap\RoutingBootstrap;
use NIH\HttpKernel\HttpRunner;

require dirname(__DIR__) . '/vendor/autoload.php';

$deferred = (new HttpRunner(new ContainerConfig(shared: true)))
    ->boot([
        Psr17Bootstrap::class,
        ErrorHandlingBootstrap::class,
        RoutingBootstrap::class,
        AppBootstrap::class,
    ])
    ->run();

if ($deferred !== null) {
    $deferred();
}

boot() accepts an ordered list of bootstrap class names implementing BootstrapInterface.

src/Bootstrap/AppBootstrap.php

<?php

declare(strict_types=1);

namespace App\Bootstrap;

use App\Action\HomeAction;
use NIH\HttpKernel\Bootstrap\BootstrapInterface;
use NIH\HttpKernel\HttpApplication;
use NIH\Router\Middleware\RouteMatchMiddleware;

final class AppBootstrap implements BootstrapInterface
{
    public static function boot(HttpApplication $app): void
    {
        $app->routes->path('/')
            ->action('', HomeAction::class, '__invoke', ['GET']);

        $app->services->auto(RouteMatchMiddleware::class)
            ->argument('useRequestSite', false);
    }
}

src/Action/HomeAction.php

<?php

declare(strict_types=1);

namespace App\Action;

use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;

final readonly class HomeAction
{
    public function __construct(
        private ResponseFactoryInterface $responseFactory,
        private StreamFactoryInterface $streamFactory,
    ) {
    }

    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        $response = $this->responseFactory->createResponse()
            ->withHeader('Content-Type', 'text/plain; charset=utf-8');

        return $response->withBody(
            $this->streamFactory->createStream('hello from nih/http-kernel'),
        );
    }
}

RoutingBootstrap appends RouteMatchMiddleware and RouteDispatchMiddleware, and keeps NotFoundHandler as the fallback final handler.

The RouteMatchMiddleware override keeps this starter host-agnostic by matching only the request path. If your application uses site() trees, remove that override and configure sites explicitly.

With the default NIH container, class-name action targets and their constructor dependencies are resolved via autowiring by default.

For a routed example and more customization recipes, see docs/recipes.md.

Core package APIs

nih/http-kernel stays intentionally small and delegates most configuration APIs to three focused packages:

  • nih/container: ContainerConfig, service definitions, autowiring, aliases, shared vs non-shared services
  • nih/router: RouterConfig, route trees, route matching, URL generation, routing middlewares
  • nih/middleware-dispatcher: Pipeline, middleware ordering, final handlers, request-time dispatch

Important constraints

  • bootstraps must only configure HttpApplication; they must not start output buffering, emit responses, mutate globals, or install PHP handlers
  • the front controller must execute the closure returned by HttpRunner::run() when the response is deferred
  • application code may temporarily mutate the PHP error-handler and exception-handler stacks during request handling; before HttpRunner::run() returns, the runner unwinds them back to the handlers that were active before the run

Mental model

The package is built around two classes:

  • NIH\HttpKernel\HttpApplication: mutable boot-time configuration
  • NIH\HttpKernel\HttpRunner: runtime orchestrator

HttpApplication intentionally exposes only:

Typical lifecycle:

  1. bootstrap classes configure HttpApplication
  2. HttpRunner creates the request from globals and runs the main pipeline
  3. thrown exceptions are routed through errorPipeline
  4. deferred responses return a closure to the caller
  5. fatal shutdown handling remains a separate fallback path

Default bootstraps

Order matters. The package currently provides:

  • Psr17Bootstrap
  • ErrorHandlingBootstrap
  • RoutingBootstrap

Psr17Bootstrap

Registers default PSR-17 HTTP message factories.

ErrorHandlingBootstrap

Configures the default errorPipeline:

  • appends ErrorFormatMiddleware
  • sets PlainTextErrorHandler as the final error handler
  • wires default handlers for:
    • application/json
    • application/xml
    • text/xml
    • text/html
    • text/plain

Negotiated JSON/XML/HTML handlers are used only when the client sends a specific Accept header. An empty Accept header or Accept: */* falls through to the final PlainTextErrorHandler.

RoutingBootstrap

Configures router services and the default main pipeline:

  • RouteMatchMiddleware
  • RouteDispatchMiddleware
  • NotFoundHandler as final handler

RouteMatcher and UrlGenerator are wired against HttpApplication->routes.

Related packages

  • nih/app-skeleton: minimal starter project with config/app.php, a thin public/index.php, and app-specific bootstraps
  • nih/legacy-gateway: optional legacy handoff module that falls through to a legacy entrypoint only when routing returned NOT_FOUND

Further documentation

  • docs/recipes.md: routed example, error customization, deferred callbacks, fatal override, legacy handoff
  • docs/runtime.md: request creation, error-pipeline contract, deferred handoff, fatal shutdown behavior

Development

  • composer install
  • composer test
  • composer validate --strict --no-check-lock

Unit tests live under tests/Unit/. Runtime and fatal subprocess coverage lives under tests/E2E/.