経緯
PHP のルータパッケージ FastRoute をほぼ素に近い状態(ペラペラのオレオレフレームワークへ組み込み)で使用し、 JavaScript の AJAX通信 用パッケージ ky
から POST メソッドでデータの書き込みの通信を行おうとしたしたところ、 CORS で捕まってしまったので対応策をメモしておきます。
状況としてはこの記事の通信を行おうとしたときの話になります。
対策
とりあえずサクッと実装したかったので「これでひとまず回避した」というような方法になります。
index.php
まずはネット上の FastRoute を最低限で使う場合のサンプルコードにオレオレフレームワーク用のクラスの読み込みやインスタンス化等の処理を付け足した全部のアクセス処理をいったん受け付けるディスパッチャ部分。
今回の記事で一番フォーカスしたいのは FastRoute のハンドラの定義部分です。HTTPメソッドごと (POST
と OPTIONS
) に異なるメソッド(クラスのメソッド)を割り当てて、別処理を走らせるようにしました(両方ともメソッドなのでややこしい)。
<?php
declare(strict_types=1);
$APP_BASE_PATH = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
// require 系諸々
use FastRoute;
use Monolog\Logger;
use Monolog\Handler\RotatingFileHandler;
use App\Domain\DomainModelSample\DomainModelSample;
use App\Repository\DomainModelSample\RepositoryDomainModelSample;
// 読み込んだクラスのインスタンス化
$handlers = function(FastRoute\RouteCollector $r) use ($APP_BASE_PATH) {
$r->addRoute(['POST'], $APP_BASE_PATH . 'domainmodel/postsample/id', 'App\Repository\DomainModelSample\RepositoryDomainModelSample::write');
$r->addRoute(['OPTIONS'], $APP_BASE_PATH . 'domainmodel/postsample/id', 'App\Repository\DomainModelSample\RepositoryDomainModelSample::options');
};
$dispatcher = FastRoute\simpleDispatcher($handlers);
$uri = $_SERVER['REQUEST_URI'];
$method = $_SERVER['REQUEST_METHOD'];
$args = [];
switch ( $_SERVER['REQUEST_METHOD'] ) {
case 'GET':
$args = &$_GET;
break;
case 'POST':
$args = &$_POST;
break;
default:
$args = [];
break;
}
if (false !== $pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
$uri = rawurldecode($uri);
$routeInfo = $dispatcher->dispatch($method, $uri);
switch($routeInfo[0]) {
case FastRoute\Dispatcher::NOT_FOUND:
$View->error(404);
break;
case FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
$allowedMethods = $routeInfo[1];
$View->error(405);
break;
case FastRoute\Dispatcher::FOUND:
$handler = $routeInfo[1];
$vars = $routeInfo[2];
list($class, $method) = explode('::', $handler, 2);
$callable;
switch ($class) {
case 'App\Repository\RepositorySample':
$callable = $RepositorySample;
break;
// 略
default:
$callable = new $class;
break;
}
$View->show(
call_user_func_array(
[
$callable,
$method
],
[
$vars
]
)
);
break;
}
Repository/DomainModelSample/RepositoryDomainModelSample.php
別処理の実装部分がこちら。
options
メソッドは、HTTPメソッド OPTIONS
の方は最低限の HTTP 200 OK のレスポンスを返すだけのダミー的な処理になっています。
本番処理である write
メソッド は HTTPメソッド POST
を受け付けて実際に処理を流す方です。
<?php
declare(strict_types=1);
namespace App\Repository\DomainModelSample;
class DomainModelSample extends Model
{
// コンストラクタ等、略
/**
* @return array
*/
public function write(): array
{
$jsonStr = file_get_contents('php://input');
$jsonArray = $this->helper->jsonDecode($jsonStr);
if(array_key_exists('user_id', $jsonArray)) {
$userId = (int)$jsonArray['user_id'];
}
if($userId === $some_value) {
// some proccessing...
}
return $someArray;
}
/**
* @return array
*/
public function options(): array
{
$data = [
'statusCode' => 200,
'data' => true,
];
return $data;
}
}
これで CORS を回避できました。実際は HTTPレスポンスヘッダ をセットする MVC の View ぽい部分もあります(下記)が、これを書いた上での上述の回避策を入れて漸く回避、という感じになりました。
<?php
declare(strict_types=1);
namespace App;
use Lukasoppermann\Httpstatus\Httpstatus as HttpStatus;
use Monolog\Logger;
class View
{
protected $helper;
protected $origin;
protected $Httpstatus;
// コンストラクタ等は略
/**
* @return bool
*/
public function setHeaders(): bool
{
header('Content-Type: application/json');
header('Access-Control-Allow-Credentials: false');
header('Access-Control-Allow-Origin: ' . $this->origin);
header('Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept, Origin, Authorization');
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0');
header('Pragma: no-cache');
return true;
}
}
とりあえずの実装なのでセキュリティは考えていない点はご留意を。