レスポンス

レスポンスは実際にはsupport\Responseオブジェクトであり、このオブジェクトを簡単に作成するために、webmanはいくつかのヘルパー関数を提供しています。

任意のレスポンスを返す

<?php
namespace app\controller;

use support\Request;

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

response関数の実装は以下の通りです:

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

空のresponseオブジェクトを作成し、適切な場所で$response->cookie() $response->header() $response->withHeaders() $response->withBody()を使用して返す内容を設定することもできます。

public function hello(Request $request)
{
    // オブジェクトを作成
    $response = response();

    // .... ビジネスロジック省略

    // cookieを設定
    $response->cookie('foo', 'value');

    // .... ビジネスロジック省略

    // HTTPヘッダを設定
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... ビジネスロジック省略

    // 返すデータを設定
    $response->withBody('返すデータ');
    return $response;
}

JSONを返す

<?php
namespace app\controller;

use support\Request;

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

json関数の実装は以下の通りです:

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

XMLを返す

<?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);
    }
}

xml関数の実装は以下の通りです:

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

ビューを返す

新しいファイル app/controller/FooController.php は以下の通りです。

<?php
namespace app\controller;

use support\Request;

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

新しいファイル app/view/foo/hello.html は以下の通りです。

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

リダイレクト

<?php
namespace app\controller;

use support\Request;

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

redirect関数の実装は以下の通りです:

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

ヘッダ設定

<?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' 
        ]);
    }
}

headerwithHeadersメソッドを利用して、個別または一括でヘッダを設定することもできます。

<?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',
        ]);
    }
}

あらかじめヘッダを設定し、最後に返すデータを設定することもできます。

public function hello(Request $request)
{
    // オブジェクトを作成
    $response = response();

    // .... ビジネスロジック省略

    // HTTPヘッダを設定
    $response->header('Content-Type', 'application/json');
    $response->withHeaders([
                'X-Header-One' => 'Header Value 1',
                'X-Header-Tow' => 'Header Value 2',
            ]);

    // .... ビジネスロジック省略

    // 返すデータを設定
    $response->withBody('返すデータ');
    return $response;
}

Cookieを設定

<?php
namespace app\controller;

use support\Request;

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

あらかじめCookieを設定し、最後に返すデータを設定することもできます。

public function hello(Request $request)
{
    // オブジェクトを作成
    $response = response();

    // .... ビジネスロジック省略

    // Cookieを設定
    $response->cookie('foo', 'value');

    // .... ビジネスロジック省略

    // 返すデータを設定
    $response->withBody('返すデータ');
    return $response;
}

cookieメソッドの完全なパラメータは以下の通りです:

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

ファイルストリームを返す

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function hello(Request $request)
    {
        return response()->file(public_path() . '/favicon.ico');
    }
}
  • webmanは超大ファイルの送信をサポートしています
  • 大きなファイル(2Mを超える)に対して、webmanはファイル全体を一度にメモリに読み込むことはせず、適切なタイミングでファイルを分割して読み取り、送信します
  • webmanはクライアントの受信速度に基づいてファイルの読み取り送信速度を最適化し、最速の送信を実現しつつメモリ使用量を最小限に抑えます
  • データ送信はノンブロッキングであり、他のリクエストの処理に影響を与えません
  • fileメソッドは自動的にif-modified-sinceヘッダを追加し、次のリクエスト時にif-modified-sinceヘッダをチェックし、ファイルが変更されていない場合は直接304を返して帯域幅を節約します
  • 送信されたファイルは自動的に適切なContent-Typeヘッダでブラウザに送信されます
  • ファイルが存在しない場合は、自動的に404レスポンスに変わります

ファイルをダウンロード

<?php
namespace app\controller;

use support\Request;

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

downloadメソッドはfileメソッドと基本的に同様ですが、以下の違いがあります:

  1. ダウンロードするファイル名を設定すると、ファイルはブラウザに表示されるのではなくダウンロードされます
  2. downloadメソッドはif-modified-sinceヘッダをチェックしません

出力を取得

一部のライブラリはファイルの内容を標準出力に直接出力します。つまり、データはコマンドライン端末に出力され、ブラウザには送信されません。この場合、ob_start(); ob_get_clean();を使用してデータを変数にキャプチャし、そのデータをブラウザに送信する必要があります。例えば:

<?php

namespace app\controller;

use support\Request;

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

        // 出力キャプチャを開始
        ob_start();
        // 画像を出力
        imagejpeg($im);
        // 画像の内容を取得
        $image = ob_get_clean();

        // 画像を送信
        return response($image)->header('Content-Type', 'image/jpeg');
    }
}

分割レスポンス

時には、レスポンスを分割して送信したい場合があります。以下の例を参考にしてください。

<?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
    {
        // 接続を取得
        $connection = $request->connection;
        // HTTPパケットを定期的に送信
        $timer = Timer::add(1, function () use ($connection, &$timer) {
            static $i = 0;
            if ($i++ < 10) {
                // HTTPパケットを送信
                $connection->send(new Chunk($i));
            } else {
                // 不要なタイマーを削除し、メモリリークを防ぐ
                Timer::del($timer);
                // 空のChunkパケットを出力し、クライアントにレスポンスの終了を通知
                $connection->send(new Chunk(''));
            }
        });
        // 最初に`Transfer-Encoding: chunked`を含むHTTPヘッダを出力し、HTTPパケットを非同期で送信
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }

}

大規模モデルを呼び出す場合は、以下の例を参考にしてください。

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;
        // https://api.openai.com 国内からアクセスできない場合は、https://api.openai-proxy.com を使用
        $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) {
                // openaiインターフェースからデータが返されたとき、ブラウザに転送
                $connection->send(new Chunk(json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"));
            },
            'complete' => function($result, $response) use ($connection) {
                // レスポンス終了時にエラーがあるかどうかを確認
                if (isset($result['error'])) {
                    $connection->send(new Chunk(json_encode($result, JSON_UNESCAPED_UNICODE) . "\n"));
                }
                // 空のchunkを返すことでレスポンスの終了を示す
                $connection->send(new Chunk(''));
            },
        ]);
        // 最初にHTTPヘッダを返し、その後データを非同期に返します
        return response()->withHeaders([
            "Transfer-Encoding" => "chunked",
        ]);
    }
}