Working with Response

About Response

When route handler (or controller) is executed, dispatcher will call its handleRequest() and pass response object which instance of IResponseinterface. For information about controller, read Working with Controller.

Your route handler or controller is required to return response object. You can either return response object given by dispatcher object or create new one.

IResponse is mostly used in conjunction with view. Read Working with Views to learn more about view in Fano Framework.

Writing response body

IResponse interface has body() method which will return IResponseStream interface instance. IResponseStream is descendant of IStreamAdapter interface which add additional methods to simplify writing string to stream and also reading string from it.

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
var respBody : IResponseStream;
begin
   respBody := response.body();
   respBody.write(
        '<html>' +
        '<head><title>Hello world</title></head>' +
        '<body><p>Hello world</p></body>' +
        '/<html>'
    )
    result := response;
end;

Should you need, content of stream as string, you can call read() method.

var response : IResponse;
    resp : string;
...
resp := response.body().read();

To output content of stream including response header, you can call write() method. All built-in implementations of IResponse interface just output to STDOUT.

var response : IResponse;
...
response.write();

Setting response header

IResponse provides collection of response header through header() method. It returns instance of IHeaders interface.

  • setHeader() method set value of a HTTP response header.
  • addHeader() method add value of a HTTP response header.
  • getHeader() method get value of a HTTP response header.
  • has() method test if HTTP response header is set.
  • writeHeaders() method output all headers.

For example, to set Content-Type header with value of application/json,

var response : IResponse;
...
response.headers().setHeader('Content-Type', 'application/json');

If header is already set, calling setHeader() will overwrite previous value. If you want to add new header and not overwriting previous value, use addHeader().

response.headers().addHeader('Content-Type', 'application/json');

To add cookie to response, you can add Set-Cookie header.

response.headers().addHeader('Set-Cookie', 'mycookie=cookieisnice');

You can also use TCookie helper class which can build cookie header for you. TCookieFactory class is factory class for TCookie.

var acookie : ICookie;
....
acookie :=TCookieFactory.create()
    .name('mycookie')
    .value('cookieisnice')
    .domain('.myapp.fano')
    .maxAge(60 * 60)
    .secure(true)
    .httpOnly(true)
    .sameSite('Strict')
    .build();
response.headers().addHeader(
    'Set-Cookie',
    acookie.serialize()
);

Built-in IResponse implementation

Fano Framework comes with several built-in IResponse implementations to simplify task regarding response.

  • TResponse, this class is mostly what you get from dispatcher when controller is invoked.
  • TJSONResponse. This is response that you may use to output JSON format. It set Content-Type header to application/json.
  • TBinaryResponse.This is response that you may need to send binary data to browser, such as image response. See Fano App Image demo application to see how to return binary response.
  • TFileResponse is response for serving static files.
  • TRedirectResponse is response for doing HTTP redirection. Read Redirection section on this document.
  • THttpCodeResponse is response for setting up HTTP status manually. Read Response with HTTP Status Code for more information.
  • TNotModifiedResponse is response for HTTP 304. Read Not modified response for more information.

Redirection Response

To simplify send redirection response, Fano Framework provides TRedirectResponse.

For example, in controller

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
var headers : IHeaders;
begin
    //redirect to fanoframework.github.io
    result := TRedirectResponse.create(
        response.headers(),
        'https://fanoframework.github.io'
    );
end;

By default, above code will redirect browser to https://fanoframework.github.io with HTTP 302 status. If you need to use different HTTP status code, set it in constructor’s third parameter

//redirect to fanoframework.github.io with HTTP 301
result := TRedirectResponse.create(
    response.headers(),
    'https://fanoframework.github.io',
    301
);

JSON response

To output JSON response, you can use TJsonResponse as shown in following code

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
begin
    result := TJsonResponse.create(
        response.headers(),
        '{ "foo" : "bar" }'
    );
end;

You can also output JSON from TJSONData class or TObject with RTTI information with help of TJsonSerializeable and TJsonRttiSerializeable class as shown in following code

uses
    fpjson;

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
const FREE_PERSON_OBJ_AUTOMATICALLY = true;
var
    person : TJSONObject;
begin
    person := TJSONObject.create();
    person.Add('FirstName', 'John');
    person.Add('LastName', 'Doe');
    result := TJsonResponse.create(
        response.headers,
        TJsonSerializeable.create(person, FREE_PERSON_OBJ_AUTOMATICALLY)
    );
end;

or using TObject with RTTI information.

//make sure to add RTTI information
{$M+}

type

TPerson = class
private
    fFirstName : string;
    fLastName : string;
published
    property firstName : string read fFirstName write fFirstName;
    property lastName : string read fLastName write fLastName;
end;

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
const FREE_PERSON_OBJ_AUTOMATICALLY = true;
var
    person : TPerson;
begin
    person := TPerson.create();
    person.firstName := 'John';
    person.lastName := 'Doe';
    result := TJsonResponse.create(
        response.headers,
        TJsonRttiSerializeable.create(person, FREE_PERSON_OBJ_AUTOMATICALLY)
    );
end;

Binary response

To output binary response such as image, you can use TBinaryResponse as shown in following code,

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
var mem : TStream;
begin
    ...
    result := TBinaryResponse.create(
        response.headers(),
        'image/png',
        TResponseStream.create(mem)
    );
end;

In the code above mem is assume to contain PNG file binary data. TResponseStream is adapter class that implements IResponseStream interface to be able to read TStream as string.

To be able to display other binary format, just pass correct Content-Type header. For example, to output PDF document

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
var mem : TStream;
    pdf : TPDFDocument;
begin
    //build pdf document and create stream
    ...
    pdf.saveToStream(mem);
    mem.seek(0, soFromBeginning);
    result := TBinaryResponse.create(
        response.headers(),
        'application/pdf',
        TResponseStream.create(mem)
    );
end;

Please note that TPDFDocument is part of Free Pascal fcl-pdf library.

Static file response

To output response from existing file, you can use TFileResponse. Serving static files page explains in detail how to serve static files with TFileResponse.

Response with HTTP Status Code

To return response with a HTTP status code, you can either calling setHeader() as shown in following code,

response.headers().setHeader('Status', '400 Bad Request');

or you can use THttpCodeResponse,

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
begin
    result := THttpCodeResponse.create(
        400,
        'Bad Request',
        response.headers().clone() as IHeaders
    );
end;

Any exceptions will be handled as HTTP 500 error except EInvalidRequest, ENotFound, EMethodNotAllowed and EInvalidMethod exceptions which will be handled as HTTP 400, 404, 405 and 501 respectively.

function TMyController.handleRequest(
    const request : IRequest;
    const response : IResponse;
    const args : IRouteArgsReader
) : IResponse;
begin
    raise ENotFound.create('Resource not found');
end;

Not modified response

TNotModifiedResponse is descendant of THttpCodeResponse class which is specifically for handling HTTP 304 response and used when adding http cache header.

Explore more