Response

The response is actually a support\Response object. To facilitate the creation of this object, Webman provides some helper functions.

Return an arbitrary response

Example

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman');
    }
}

The implementation of the response function is as follows:

function response($body = '', $status = 200, $headers = array())
{
    return new Response($status, $headers, $body);
}

You can also create an empty response object first and then use $response->cookie(), $response->header(), $response->withHeaders(), and $response->withBody() to set the return content as appropriate.

public function hello(Request $request)
{
    // Create an object
    $response = response();

    // .... Business logic omitted

    // Set cookie
    $response->cookie('foo', 'value');

    // .... Business logic omitted

    // Set HTTP headers
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... Business logic omitted

    // Set the data to return
    $response->withBody('The returned data');
    return $response;
}

Return JSON

Example

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return json(['code' => 0, 'msg' => 'ok']);
    }
}

The implementation of the json function is as follows:

function json($data, $options = JSON_UNESCAPED_UNICODE)
{
    return new Response(200, ['Content-Type' => 'application/json'], json_encode($data, $options));
}

Return XML

Example

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        $xml = <<<XML
               <?xml version='1.0' standalone='yes'?>
               <values>
                   <truevalue>1</truevalue>
                   <falsevalue>0</falsevalue>
               </values>
               XML;
        return xml($xml);
    }
}

The implementation of the xml function is as follows:

function xml($xml)
{
    if ($xml instanceof SimpleXMLElement) {
        $xml = $xml->asXML();
    }
    return new Response(200, ['Content-Type' => 'text/xml'], $xml);
}

Return a view

Create a file app/controller/FooController.php as follows:

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return view('foo/hello', ['name' => 'webman']);
    }
}

Create a file app/view/foo/hello.html as follows:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>webman</title>
</head>
<body>
hello <?=htmlspecialchars($name)?>
</body>
</html>

Redirect

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return redirect('/user');
    }
}

The implementation of the redirect function is as follows:

function redirect($location, $status = 302, $headers = [])
{
    $response = new Response($status, ['Location' => $location]);
    if (!empty($headers)) {
        $response->withHeaders($headers);
    }
    return $response;
}

Header settings

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman', 200, [
            'Content-Type' => 'application/json',
            'X-Header-One' => 'Header Value' 
        ]);
    }
}

You can also use the header and withHeaders methods to set headers individually or in bulk.

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman')
        ->header('Content-Type', 'application/json')
        ->withHeaders([
            'X-Header-One' => 'Header Value 1',
            'X-Header-Tow' => 'Header Value 2',
        ]);
    }
}

You can also set headers in advance and set the data to return last.

public function hello(Request $request)
{
    // Create an object
    $response = response();

    // .... Business logic omitted

    // Set HTTP headers
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... Business logic omitted

    // Set the data to return
    $response->withBody('The returned data');
    return $response;
}

Set cookie

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response('hello webman')
        ->cookie('foo', 'value');
    }
}

You can also set the cookie in advance, then set the data to return.

public function hello(Request $request)
{
    // Create an object
    $response = response();

    // .... Business logic omitted

    // Set cookie
    $response->cookie('foo', 'value');

    // .... Business logic omitted

    // Set the data to return
    $response->withBody('The returned data');
    return $response;
}

The complete parameters for the cookie method are as follows:

cookie($name, $value = '', $max_age = 0, $path = '', $domain = '', $secure = false, $http_only = false)

Return a file stream

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response()->file(public_path() . '/favicon.ico');
    }
}
  • Webman supports sending very large files.
  • For large files (over 2M), Webman will not read the entire file into memory at once but will read the file in chunks and send it at appropriate times.
  • Webman will optimize the file read and send speed according to the client's receiving speed, ensuring the fastest file transfer while minimizing memory usage.
  • Data sending is non-blocking and will not affect the processing of other requests.
  • The file method will automatically add the if-modified-since header and check it on the next request; if the file has not been modified, it will return 304 to save bandwidth.
  • The sent file will automatically be sent to the browser with the appropriate Content-Type header.
  • If the file does not exist, it will automatically turn into a 404 response.

Download a file

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response()->download(public_path() . '/favicon.ico', 'filename.ico');
    }
}

The download method is essentially the same as the file method, with the distinction that:

  1. Setting a filename for the downloaded file will cause the file to be downloaded instead of displayed in the browser.
  2. The download method will not check the if-modified-since header.

Get output

Some libraries directly print the file content to standard output, which means that the data is printed in the command-line terminal and not sent to the browser. In this case, we need to capture the data into a variable using ob_start(); and ob_get_clean();, and then send the data to the browser, for example:

<?php

namespace app\controller;

use support\Request;

class ImageController
{
    public function get(Request $request)
    {
        // Create an image
        $im = imagecreatetruecolor(120, 20);
        $text_color = imagecolorallocate($im, 233, 14, 91);
        imagestring($im, 1, 5, 5,  'A Simple Text String', $text_color);

        // Start capturing output
        ob_start();
        // Output the image
        imagejpeg($im);
        // Retrieve the image content
        $image = ob_get_clean();

        // Send the image
        return response($image)->header('Content-Type', 'image/jpeg');
    }
}

Chunked responses

Sometimes we want to send responses in chunks. You can refer to the example below.

<?php

namespace app\controller;

use support\Request;
use support\Response;
use Workerman\Protocols\Http\Chunk;
use Workerman\Timer;

class IndexController
{
    public function index(Request $request): Response
    {
        // Get the connection
        $connection = $request->connection;
        // Send HTTP body periodically
        $timer = Timer::add(1, function () use ($connection, &$timer) {
            static $i = 0;
            if ($i++ < 10) {
                // Send HTTP body
                $connection->send(new Chunk($i));
            } else {
                // Remove unnecessary timer to prevent memory leaks
                Timer::del($timer);
                // Send an empty Chunk to notify the client that the response has ended
                $connection->send(new Chunk(''));
            }
        });
        // Output an HTTP header with Transfer-Encoding: chunked, the HTTP body will be sent asynchronously
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }

}

If you are calling a large model, refer to the example below.

composer require webman/openai
<?php
namespace app\controller;
use support\Request;

use Webman\Openai\Chat;
use Workerman\Protocols\Http\Chunk;

class ChatController
{
    public function completions(Request $request)
    {
        $connection = $request->connection;
        // If you can't access https://api.openai.com, you can use https://api.openai-proxy.com instead
        $chat = new Chat(['apikey' => 'sk-xx', 'api' => 'https://api.openai.com']);
        $chat->completions(
            [
                'model' => 'gpt-3.5-turbo',
                'stream' => true,
                'messages' => [['role' => 'user', 'content' => 'hello']],
            ], [
            'stream' => function($data) use ($connection) {
                // When the OpenAI API returns data, forward it to the browser
                $connection->send(new Chunk(json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"));
            },
            'complete' => function($result, $response) use ($connection) {
                // Check for errors when the response ends
                if (isset($result['error'])) {
                    $connection->send(new Chunk(json_encode($result, JSON_UNESCAPED_UNICODE) . "\n"));
                }
                // Return an empty chunk to signify the end of the response
                $connection->send(new Chunk(''));
            },
        ]);
        // First return an HTTP header, and the subsequent data will be returned asynchronously
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }
}