{"id":18550285,"url":"https://github.com/xp-forge/web","last_synced_at":"2026-04-25T22:02:50.661Z","repository":{"id":18942060,"uuid":"81863219","full_name":"xp-forge/web","owner":"xp-forge","description":"Web applications for the XP Framework","archived":false,"fork":false,"pushed_at":"2025-07-19T16:13:17.000Z","size":930,"stargazers_count":2,"open_issues_count":6,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-27T07:56:30.582Z","etag":null,"topics":["async","development-webserver","http-middleware","http-server","php","php7","php8","routing","web","websocket-server","xp-framework"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xp-forge.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-02-13T19:35:28.000Z","updated_at":"2025-07-13T07:51:23.000Z","dependencies_parsed_at":"2023-12-02T11:27:56.984Z","dependency_job_id":"26e5ba47-ee9f-4723-9cf5-47372002562d","html_url":"https://github.com/xp-forge/web","commit_stats":{"total_commits":660,"total_committers":3,"mean_commits":220.0,"dds":0.00303030303030305,"last_synced_commit":"6f3886c6975c31f1b7e9bf7693976299dfc15022"},"previous_names":[],"tags_count":90,"template":false,"template_full_name":null,"purl":"pkg:github/xp-forge/web","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xp-forge%2Fweb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xp-forge%2Fweb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xp-forge%2Fweb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xp-forge%2Fweb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xp-forge","download_url":"https://codeload.github.com/xp-forge/web/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xp-forge%2Fweb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32278249,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T18:29:39.964Z","status":"ssl_error","status_checked_at":"2026-04-25T18:29:32.149Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async","development-webserver","http-middleware","http-server","php","php7","php8","routing","web","websocket-server","xp-framework"],"created_at":"2024-11-06T21:04:06.115Z","updated_at":"2026-04-25T22:02:50.639Z","avatar_url":"https://github.com/xp-forge.png","language":"PHP","readme":"Web applications for the XP Framework\n========================================================================\n\n[![Build status on GitHub](https://github.com/xp-forge/web/workflows/Tests/badge.svg)](https://github.com/xp-forge/web/actions)\n[![XP Framework Module](https://raw.githubusercontent.com/xp-framework/web/master/static/xp-framework-badge.png)](https://github.com/xp-framework/core)\n[![BSD Licence](https://raw.githubusercontent.com/xp-framework/web/master/static/licence-bsd.png)](https://github.com/xp-framework/core/blob/master/LICENCE.md)\n[![Requires PHP 7.4+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-7_4plus.svg)](http://php.net/)\n[![Supports PHP 8.0+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-8_0plus.svg)](http://php.net/)\n[![Latest Stable Version](https://poser.pugx.org/xp-forge/web/version.svg)](https://packagist.org/packages/xp-forge/web)\n\nLow-level functionality for serving HTTP requests, including the `xp web` runner.\n\nExample\n-------\n\n```php\nuse web\\Application;\n\nclass Service extends Application {\n\n  public function routes() {\n    return [\n      '/hello' =\u003e function($req, $res) {\n        $res-\u003eanswer(200, 'OK');\n        $res-\u003esend('Hello '.$req-\u003eparam('name', 'Guest'), 'text/plain');\n      }\n    ];\n  }\n}\n```\n\nRun it using:\n\n```bash\n$ xp -supervise web Service\n@xp.web.srv.Standalone(HTTP @ peer.ServerSocket(Resource id #61 -\u003e tcp://127.0.0.1:8080))\n# ...\n```\n\nSupports a development webserver which is slower but allows an easy edit/save/reload development process. It uses the [PHP development server](http://php.net/features.commandline.webserver) in the background.\n\n```bash\n$ xp -supervise web -m develop Service\n@xp.web.srv.Develop(HTTP @ `php -S [...] -t /home/example/devel/shorturl`)\n# ...\n```\n\nNow open the website at http://localhost:8080/hello\n\nServer models\n-------------\nThe server models (*selectable via `-m \u003cmodel\u003e[,argument[,argument...]]` on the command line*) are:\n\n* **async** (*the default*): A single-threaded web server. Handlers can yield control back to the server to serve other clients during lengthy operations such as file up- and downloads.\n* **prefork**: Much like Apache, forks a given number of children to handle HTTP requests. Requires the `pcntl` extension. Use `prefork,children=\u003cn\u003e` to control the number of child processes.\n* **develop**: As mentioned above, built ontop of the PHP development webserver. Application code is recompiled and application setup performed from scratch on every request, errors and debug output are handled by the [development console](https://github.com/xp-forge/web/pull/35). Use `develop,workers=\u003cn\u003e` to control the number of worker processes.\n\nRequest and response\n--------------------\nThe `web.Request` class provides the following basic functionality:\n\n```php\nuse web\\Request;\n\n$request= ...\n\n$request-\u003emethod();       // The HTTP method, e.g. \"GET\"\n$request-\u003euri();          // The request URI, a util.URI instance\n\n$request-\u003eheaders();      // All request headers as a map\n$request-\u003eheader($name);  // The value of a single header\n\n$request-\u003ecookies();      // All cookies\n$request-\u003ecookie($name);  // The value of a single cookie\n\n$request-\u003eparams();       // All request parameters as a map\n$request-\u003eparam($name);   // The value of a single parameter\n```\n\nThe `web.Response` class provides the following basic functionality:\n\n```php\nuse web\\{Response, Cookie};\n\n$response= ...\n\n// Set status code, header(s) and cookie(s)\n$response-\u003eanswer($status);\n$response-\u003eheader($name, $value);\n$response-\u003ecookie(new Cookie($name, $value));\n\n// Sends body using a given content type\n$response-\u003esend($body, $type);\n\n// Transfers an input stream using a given content type. Uses\n// chunked transfer-encoding.\nyield from $response-\u003etransmit($in, $type);\n\n// Same as above, but specifies content length before-hand\nyield from $response-\u003etransmit($in, $type, $size);\n```\n\nBoth *Request* and *Response* have a `stream()` method for accessing the underlying in- and output streams.\n\nHandlers\n--------\nA handler (*also referred to as middleware in some frameworks*) is a function which receives a request and response and uses the above functionality to handle communication.\n\n```php\nuse web\\Handler;\n\n$redirect= new class() implements Handler {\n\n  public function handle($req, $res) {\n    $req-\u003estatus(302);\n    $req-\u003eheader('Location', 'https://example.com/');\n  }\n};\n```\n\nThis library comes with `web.handler.FilesFrom` - a handler for serving files. It takes care of conditional requests (*with If-Modified-Since*) as well requests for content ranges, and makes use of the asynchronous capabilities if available, see [here](https://github.com/xp-forge/web/pull/72).\n\nFilters\n-------\nFilters wrap around handlers and can perform tasks before and after the handlers are invoked. You can use the request's `pass()` method to pass values - handlers can access these using `value($name)` / `values()`.\n\n```php\nuse web\\Filter;\nuse util\\profiling\\Timer;\nuse util\\log\\{Logging, LogCategory};\n\n$timer= new class(Logging::all()-\u003etoConsole()) implements Filter {\n  private $timer;\n\n  public function __construct(private LogCategory $cat) {\n    $this-\u003etimer= new Timer();\n  }\n\n  public function filter($request, $response, $invocation) {\n    $this-\u003etimer-\u003estart();\n    try {\n      yield from $invocation-\u003eproceed($request, $response);\n    } finally {\n      $this-\u003ecat-\u003edebugf('%s: %.3f seconds', $request-\u003euri(), $this-\u003etimer-\u003eelapsedTime());\n    }\n  }\n}\n```\n\n*By using `yield from`, you guarantee asynchronous handlers will have completely executed before the time measurement is run on in the `finally` block.*\n\nFile uploads\n------------\nFile uploads are handled by the request's `multipart()` method. In contrast to how PHP works, file uploads are streamed and your handler starts running with the first byte transmitted!\n\n```php\nuse io\\Folder;\n\n$uploads= new Folder('...');\n$handler= function($req, $res) use($uploads) {\n  if ($multipart= $req-\u003emultipart()) {\n\n    // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100\n    if ('100-continue' === $req-\u003eheader('Expect')) {\n      $res-\u003ehint(100, 'Continue');\n    }\n\n    // Transmit files to uploads directory asynchronously\n    $files= [];\n    $bytes= 0;\n    foreach ($multipart-\u003efiles() as $name =\u003e $file) {\n      $files[]= $name;\n      $bytes+= yield from $file-\u003etransmit($uploads);\n    }\n\n    // Do something with files and bytes...\n  }\n};\n```\n\nEarly hints\n-----------\nAn experimental status code with which headers can be sent to a client early along for it to be able to make optimizations, e.g. preloading scripts and stylesheets.\n\n```php\n$handler= function($req, $res) {\n  $res-\u003eheader('Link', [\n    '\u003c/main.css\u003e; rel=preload; as=style',\n    '\u003c/script.js\u003e; rel=preload; as=script'\n  ]);\n  $res-\u003ehint(103);\n\n  // Do some processing here to render $html\n  $html= ...\n\n  $res-\u003eanswer(200, 'OK');\n  $res-\u003esend($html, 'text/html; charset=utf-8');\n}\n```\n\nSee https://evertpot.com/http/103-early-hints\n\nInternal redirects\n------------------\nOn top of external redirects which are triggered by the 3XX status codes, requests can also be redirected internally using the `dispatch()` method. This has the benefit of not requiring clients to perfom an additional request.\n\n```php\nuse web\\Application;\n\nclass Site extends Application {\n\n  public function routes() {\n    return [\n      '/home' =\u003e function($req, $res) {\n        // Home page\n      },\n      '/' =\u003e function($req, $res) {\n        // Routes are re-evaluated as if user had called /home\n        return $req-\u003edispatch('/home');\n      },\n    ];\n  }\n}\n```\n\nWebSockets\n----------\nTo use two-way interactive communication sessions between the user's browser and our server, route to the *WebSocket* handler as follows:\n\n```php\nuse web\\Application;\nuse web\\handler\\WebSocket;\n\nclass Ws extends Application {\n\n  public function routes() {\n    return [\n      '/ws/echo' =\u003e new WebSocket(function($conn, $payload) {\n        $conn-\u003esend('You said: '.$payload);\n      }),\n      '/'   =\u003e function($request, $response) {\n        $html= \u003c\u003c\u003c'HTML'\n          \u003c!-- Shortened for brevity --\u003e\n          \u003cscript type=\"module\"\u003e\n            const socket = new WebSocket(`ws://${location.host}/ws/echo`);\n            socket.addEventListener('open', e =\u003e socket.send('Hello World!'));\n            socket.addEventListener('message', e =\u003e console.log(e.data));\n          \u003c/script\u003e\n        HTML;\n        $res-\u003esend($html, 'text/html; charset=utf-8');\n      }\n    ];\n  }\n}\n```\n\nSee https://developer.mozilla.org/en-US/docs/Web/API/WebSocket\n\nLogging\n-------\nBy default, logging goes to standard output and will be visible in the console the `xp web` command was invoked from. It can be influenced via the command line as follows:\n\n* `-l server.log`: Writes to the file server.log, creating it if necessary\n* `-l -`: Writes to standard output\n* `-l - -l server.log`: Writes to both of the above\n\nMore fine-grained control as well as integrating with [the logging library](https://github.com/xp-framework/logging) can be achieved from inside the application, see [here](https://github.com/xp-forge/web/pull/48).\n\nPerformance\n-----------\nBecause the code for the web application is only compiled once when using production servers, we achieve lightning-fast request/response roundtrip times:\n\n![Network console screenshot](https://github.com/xp-forge/web/assets/696742/2707a921-8ae2-4884-ae33-59972a8e7a12)\n\nSee also\n--------\nThis library provides for the very basic functionality. To create web frontends or REST APIs, have a look at the following libraries built ontop of this:\n\n* [Web frontends](https://github.com/xp-forge/frontend)\n* [Sessions](https://github.com/xp-forge/sessions)\n* [Authentication](https://github.com/xp-forge/web-auth)\n* [REST APIs](https://github.com/xp-forge/rest-api)\n* [Run XP web applications on AWS lambda using API Gateway](https://github.com/xp-forge/lambda-ws)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxp-forge%2Fweb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxp-forge%2Fweb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxp-forge%2Fweb/lists"}