How to: add a custom page-type handler, that manipulates the response headers

Hi! My use-case: I have many page-type handlers to deal with ajax-stuff, config-loading, and so on. Until V12 it was possible to use this simple pattern:

custom = PAGE
custom {
    typeNum = 213123
    10 = USER
    10.userFunc = some_callable
}

And in the callable i had the possibility to add HTTP Headers via the TSFE object by manipulating the config array. But this will be impossible very soon. In V13 this is deprecated and in V14 it will be gone i fear.

I know, i can add a middleware, but i already added a lot of middlewares and in my opinion it is not a good pattern, to use a middleware for everything. It feels clumsy and requires more boilerplate.

I already opened a feature request for this a few years ago - i suggested, that you can add a psr request handler as static route. This would be much nicer IMO.

See here: Making sure you're not a bot!
But that got closed without a real solution.

routes:
  simpleExample:
    route: simple
    type: PHPRequestHandler
    requestHandler: Vendor\Package\Controller\SomeController->dispatch

An alternative could be a “psr-handler” for page-types.

custom = PAGE
custom {
    typeNum = 213123
    requestHandler = some_callable
}

Will a psr middleware really be the only way to programmatically handle a psr request object in the future?

A page type gives additional advantages like using the default caching mechanism. And there are situations where i need to add variable headers - i cant do this in the static page.config.additionalHeaders configuration.

I think the practical answer in v13/v14 is yes, the response should be finalized outside the old PAGE/userFunc pattern.

If you need dynamic headers, I would move only the header logic into a PSR-15 middleware and keep the page-type route as the signal. In other words:

  • let `typeNum` or route attributes identify the endpoint
  • generate the content in your controller / page-type logic as before
  • add or override headers in a middleware late in the stack, only when that request matches your custom type

That keeps the middleware pretty small, and you still get TYPO3’s normal response handling instead of mutating TSFE internals that are being removed.

If caching is the main reason to stay with PAGE, another option is to return a proper PSR-7 response from the endpoint and set the headers there, but I would not expect TYPO3 to bring back TSFE-style header mutation for page types.

So, not a very satisfying answer, but I would treat middleware / PSR response handling as the forward-compatible path now.

1 Like

Hi @majpay!

Just checking in to hear if @taylorbrooksps0319’s answer answered your question. If so, I’ll mark the thread as solved.

PS! Also thanks to @taylorbrooksps0319 for a great first post. Thank you for your contribution! Keep up the good work.

That one was closed in favour of another change to reduce duplicated things and the other issue already had a pending change (not merged yet). That was commented as closing reson that way.

And in the callable i had the possibility to add HTTP Headers via the TSFE object by manipulating the config array. But this will be impossible very soon. In V13 this is deprecated and in V14 it will be gone i fear.

I’m curious how how you have used the config affray to manipulate headers. Sounds kind of a weired hack and not a good solution.

TSFE is gone v14 for good.

In case you are using a extbase plugin either with the USER/USER_INT or EXTBASE content object there and the raw response only from that action is what should be outputted (no other content, headers and similar, than It could be a solution to:

  • create a full response object with the content and headers
  • throw a \TYPO3\CMS\Core\Http\PropagateResponseException with the response object attached (constructor)

That short ciruits the content object rendering stack (COR) and use the reponse attached to the exception as response send back to the browser.

The exception solution is already possible since… dunno TYPO3 v11 or so, and in earlier version with the ImmediateResponseException instead up to the point middlewares has been introduced (v9).

For TYPO3 v14 you should be able to simply set your custom headers to the response returned by the extbase controller action and that should be applied to the final response, see following changelog and change for this:

https://review.typo3.org/c/Packages/TYPO3.CMS/+/90471/23/typo3/sysext/core/Documentation/Changelog/14.0/Breaking-107324-StreamlinePSR7ResponseHeaderHandling.rst

This is not available for USER/USER_INT content object and handlers. Main reason for this is the design of the Content Object Rendering stack which only has string as input/passout to create the content. The whole stack would need a refactoring, for example returning response objects instead of strings and concetating on that - still requiring a well thought merging strategy when multiple content elemets are rendered and similar - but that was and is not really switchable.

Over the different core versions there has been a continues work on streamlining things to make the COR free to do such kind of a change and over the time the thinking is to return a concrete return DTO and not only a response object and dealing with that in a better way - albeit not a detaild and conceptual finished design. V14 contains a lot of changes in that area to work even more into that direction and hopefully it is manageble to connect the several loose ends and makte that seemless transferable.

routes:
simpleExample:
route: simple
type: PHPRequestHandler
requestHandler: Vendor\Package\Controller\SomeController->dispatch

Using static routes has a couple of issues on that:

  • It is handled quite early, which means no TypoScript loaded, evaulated or any page cache
  • For a generic route implementation there was a pending / draft change and the discussion was never really tackled to satisfy multiple reasons, where to configure for which case. Using site config handles this only after the site resolving/matching, not after the page resolving or preparation (TSFE preparation in older versions). Does not help with global routes and handlers.
  • That early extbase is not possible and one of the concerns was that allowing it at this early stage even with douzons of warnings would leed to developers using handlers that early and doing extbase stuff and crying because all that page resolving, TypoScript setup and stuff is not in place (which is loaded really really long time later in the stack)
  • At that early stage page cache is also not available.

And no one picked that up or worked on it. On the other side, and even if that is taken often as an unpolar answer:

Adding custom handlers to static routing or an enhanced version to tackle all the questions is basically nothing else than a middleware - jsut wrapped as an additional sublevel handled by a single midlware in a exact place. Doing it directly as middlewares allows to adjust the middleware at the requried place earlier or later in the stack having more bootstraped or lesser and is the flexiblste solution anyway.

For your use-case I’d say that you have two possible solution which may be of use for you, namely the PropagateResponseException approach or the v14 simple use extbase as return point.

Without extbase controllers/bootstrap that is not directly possible. However, you could implement a custom requesthandler like the extbase handler and use it for an USER/USER_INT content object as hookpoint and implement the stuff with the response parking and passthrough like it has been added to the extbase request handlernad it will done at the outer point - at least for v14. TBH I guess that this could work, not done that myselfe (still using the exception approch myself).

1 Like

Another caveat:

If you decide to add a custom middleware, that will handle a special route, you might get stuck again:

A common approach might be a middleware, that runs after “typo3/cms-frontend/site” but before “typo3/cms-frontend/page-resolver”. Because then you have a SiteRouteResult with a “tail” - essentially the unmatched route, which you can further assert to intercept the request handling to return some json for example.

But often, when you need to use something from the ContentObjectRenderer or some extbase feature, it will fall apart because at that point, the typoscript will not be available but it is essential for many TYPO3 frontend apis.

So you need to run your middleware later, but then you will trigger a 404 because the URL could not be matched. Thats the reason i decided for a pageType in the first place, because in its very core, typo3 is still a little bit of a mess. Everything dressed in new cloth but still some kind of circular dependency hell :smiley: (no offense)

My next try is the “Extbase” Handler. So i stick with the Page-Type-based approach but insteadof creating a USER object, i create a EXTBASE Object? Let’s try….