Direct Write Output Response Message

Hi All,

I decided to create an app, that can create formatted address books and calendars for devices like sip desktop phones or services like xibo, and other services that don’t speak CardDAV and CalDAV.

My question is how do I write that response directly to the output stream in NC?

The reason for my question is that there is no point in constructing these responses in memory, then sending them to the output. These responses can be sent directly to the output as they are generated.

This is a test method, I’ve tried

/**
	 * @NoCSRFRequired
	 * @NoAdminRequired
	 */
	public function snom(string $id):  DataResponse|null {

		// evaluate, if token exists
		if (empty($this->request->getParam('token'))) {
			return null;
		}

		if (!$this->DataService->permitService($id, $this->request->getParam('token'), 'SNOM')) {
			return null;
		}

		print('print: This is a test');

		return new DataResponse('');
	}

This method works but generates a bunch of unwanted binary data at the end.

Any suggestions?

Sebastian

Well, there is a reason for the in memory usage. There are functionality that are run before and after the actual app that will alter the response of the server. Most is related to the headers but other things might be installed as well (see Middleware in the developer documentation).

If you directly output your content this will cause trouble with the middlewares installed or your answer will be cashed in memory as well (PHP output buffering). I do not know what is the case in the current server implementation, but neither will bring you all the benefit as far as I see.

1 Like

I agree that in most cases this is a desired scenario. But in this case this is just unneeded extra overhead. This app just generates simple, text/xml files with contact/calendar info, for devices to consume. So I was just thinking that writing it in a way that uses the least resources possible might be best, instead of processing the full NC stack on every request.

Thank you! This got me to the answer I needed.

Looks like the best solution is to create my own streaming response class. Now just need to figure out how the callback will work.

https://docs.nextcloud.com/server/23/developer_manual/basics/controllers.html#streamed-and-lazily-rendered-responses

1 Like

I had a similar case a few weeks ago and solved it by adding a own response class: https://github.com/nextcloud/serverinfo/blob/master/lib/PhpInfoResponse.php

There is indeed some default logic attached to DataResponse.

Well, I’ve solved it.

Here is the Controller Code:

/**
     * @NoAdminRequired
     * @NoCSRFRequired
     * @PublicPage
     */
	public function snom(string $id) {

		// evaluate, if token exists
		if (empty($this->request->getParam('token'))) {
			return null;
		}

		if (!$this->DataService->authorize($id, $this->request->getParam('token'), 'SNOM')) {
			return null;
		}

		return new StreamResponse($this->generateData(), 'text/xml; charset=UTF-8');

	}

	// Define a generator function to generate your data
    function generateData() {
        for ($i = 0; $i < 10; $i++) {
            yield "Chunk $i\n";
            sleep(1); // Simulate some processing time
        }
    }

And here is the custom response:

namespace OCA\Data\Http;

use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\ICallbackResponse;
use OCP\AppFramework\Http\IOutput;

class StreamResponse extends Response implements ICallbackResponse
{
    protected $generator;

    public function __construct($generator, $contentType, $statusCode = Http::STATUS_OK)
    {
        parent::__construct();

        $this->generator = $generator;
        
        $this->setStatus($statusCode);
        $this->cacheFor(0);
        $this->addHeader('Content-Type', $contentType);

    }

	public function callback(IOutput $output) {

        foreach ($this->generator as $chunk) {
            print($chunk);
            flush();
        }

	}
}

This generates the content on the fly and sends it back directly to the client. Not sure if this the best way to do it, but its what I’ve come up with.

Sebastian

1 Like