OpenAPI: RESTful APIの仕様を記述するフォーマット
Swagger: OpenAPIを使用するツール
## Swagger Editor
$ git clone https://github.com/swagger-api/swagger-editor.git
$ cd swagger-editor
$ npm start
http://192.168.56.10:3001/

なんだこれは…
随机应变 ABCD: Always Be Coding and … : хороший
OpenAPI: RESTful APIの仕様を記述するフォーマット
Swagger: OpenAPIを使用するツール
## Swagger Editor
$ git clone https://github.com/swagger-api/swagger-editor.git
$ cd swagger-editor
$ npm start
http://192.168.56.10:3001/

なんだこれは…
swaggerのbasic structure
https://swagger.io/docs/specification/basic-structure/
$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar require zircote/swagger-php
$ php composer.phar exec openapi -h
openapiファイルの作成
$ vendor/bin/openapi -o ${出力先} –format ${出力フォーマット} ${スキャン対象ディレクトリ}
$ vendor/bin/openapi -o openapi.json –format json app/Http/Controller/Api
<?php
use OpenApi\Annotations as OA;
class OpenApi {}
class MyController {
public funtion getResource(){
}
}
https://zircote.github.io/swagger-php/guide/
The idea is to add swagger-php annotations or attributes next to the relevant PHP code in your application. These will contain the details about your API and swagger-php will convert those into machine-readable OpenAPI documentation.
require("vendor/autoload.php");
$openapi = \OpenApi\Generator::scan(["project"]);
header('Content-Type: application/x-yaml');
echo $openapi->toYaml();
annotation:あるデータに対して関連する情報(メタデータ)を注釈として付与すること
/**
* @OA\Get(
* tags={"Common"},
* path="/api/user",
* @OA\Response(
* response="200".
* description="success",
* @OA\JsonContent(ref="#/components/schemas/user_responder")
* ),
* @OA\Response(
* response="204",
* description="there is no authorized user",
* @OA\JsonContent(ref="#/components/schemas/204_no_content")
* )
* )
*/
schema:
/**
* @OA\Schema(
* schema="user_responder",
* required={"id", "name", "email","created_at"},
* @OA\Property(
* property="id",
* type="integer",
* description="userId",
* example-"1"
* ),
* @OA\Property(
* property="name",
* type="string",
* description="username",
* example="sample taro"
* ),
*
*)
*/
annotationはパスとレスポンスで、schemaは入力値を定義しとるんかな。
どちっかというと、swaggerがよくわかってない。
$ npm init
$ npm install –include=dev openapi-generator-cli
npm ERR! code E404
npm ERR! 404 Not Found – GET https://registry.npmjs.org/openapi-generator-cli – Not found
npm ERR! 404
npm ERR! 404 ‘openapi-generator-cli@*’ is not in this registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
npm ERR! A complete log of this run can be found in:
あれ? イマイチよくわからない
create table person ( id int, first_name varchar(50), last_name varchar(50), gender varchar(7), birth_day date );
testdb=# \d person
Table “public.person”
Column | Type | Collation | Nullable | Default
————+———————–+———–+———-+———
id | integer | | |
first_name | character varying(50) | | |
last_name | character varying(50) | | |
gender | character varying(7) | | |
birth_day | date | | |
### テーブルに制約をつける
create table person ( id BIGSERIAL not null primary key, first_name varchar(50) not null, last_name varchar(50) not null, gender varchar(7) not null, birth_day date not null, email varchar(150) );
bigserial 整数を自動決定
primary key キーとして扱う
not null 空白を許可しない
### テーブルにデータを挿入
insert into person (
first_name,
last_name,
gender,
birth_day
) values ('Anne', 'Smith', 'Femail', DATE '1988-01-09');
insert into person (
first_name,
last_name,
gender,
birth_day,
email
) values ('Jake', 'Jone', 'Male', DATE '1990-01-10', 'jake@gmail.com');
testdb=# select * from person;
id | first_name | last_name | gender | birth_day | email
—-+————+———–+——–+————+—————-
1 | Anne | Smith | Femail | 1988-01-09 |
2 | Jake | Jone | Male | 1990-01-10 | jake@gmail.com
(2 rows)
データの自動生成
https://mockaroo.com/


create table person ( id BIGSERIAL not null primary key, first_name varchar(50) not null, last_name varchar(50) not null, gender varchar(7) not null, email varchar(150), date_of_birth date not null, country varchar(50) not null );
$ \i /home/vagrant/dev/app/person.sql
$ select * from person;
create table person ( id BIGSERIAL not null primary key, first_name varchar(50) not null, last_name varchar(50) not null, gender varchar(7) not null, email varchar(150), date_of_birth date not null, country varchar(50) not null, car_id BIGINT REFERENCES car (id), unique(car_id) );
create table car ( id BIGSERIAL not null primary key, make varchar(50) not null, model varchar(50) not null, price varchar(50) not null );
BIGINT REFERENCESで関連づけるのか。なるほど、勉強になるね。
実行時にロックファイルを作成して、実行が終わったら削除する
<?php
$lock_file = "./test.lock";
if (!file_exists($lock_file)){
$fp = fopen($lock_file, "w+");
for($i = 0; $i < 50; $i++){
sleep(1);
}
fclose($fp);
unlink($lock_file);
} else {
echo "Already running!!";
}
ただし、実行中にエラーがあることがあるので、排他ロックをするという方法もある。
$file = fopen("test.lock", "w+");
if (flock($file, LOCK_EX + LOCK_NB)){
for($i = 0; $i < 50; $i++){
sleep(1);
}
flock($file, LOCK_UN);
} else {
echo "Already running!!";
}
LOCK_EX: 排他的なロック。このフラグ単体だけの場合、他プロセスがアクセスした時に解除されるまでブロックするという挙動になる
テーブルを作成
$ CREATE TABLE test(image bytea);
bytea型はバイナリデータを保存。1Gまで可能
https://www.postgresql.jp/document/9.6/html/datatype-binary.html
画像データの登録
$dsn = "pgsql:dbname=testdb host=localhost port=5432";
$user = "user1";
$password = "password";
try {
$dbh = new PDO($dsn, $user, $password);
print('connection success<br>');
$stmt = $dbh->prepare("insert into test (image) values (:image)");
$fp = fopen("images.jpeg", "rb");
$stmt->bindParam(':image', $fp, PDO::PARAM_LOB);
$stmt->execute();
} catch(PDOException $e){
print("Error:".$e->getMessage());
die();
}
画像の表示
$dsn = "pgsql:dbname=testdb host=localhost port=5432";
$user = "user1";
$password = "password";
try {
$dbh = new PDO($dsn, $user, $password);
$stmt = $dbh->prepare("select image from test");
$stmt->execute();
$stmt->bindColumn("image", $image, PDO::PARAM_LOB);
$stmt->fetch();
header("Content-Type: image/jpeg");
fpassthru($image);
unset($db);
} catch(PDOException $e){
print("Error:".$e->getMessage());
die();
}
なるほど、DBに画像保存も可能なのね。
データサイズが気になるが、軽量な画像であれば良さそう
### 接続
$dsn = "pgsql:dbname=testdb host=localhost port=5432";
$user = "user1";
$password = "password";
try {
$dbh = new PDO($dsn, $user, $password);
print('接続に成功しました');
} catch(PDOException $e){
print("Error:".$e->getMessage());
die();
}
### sql実行
$dsn = "pgsql:dbname=testdb host=localhost port=5432";
$user = "user1";
$password = "password";
try {
$dbh = new PDO($dsn, $user, $password);
print('接続に成功しました<br>');
$sql = 'select * from department';
foreach($dbh->query($sql) as $row){
print($row['department_code'].":");
print($row['department_name'].",");
}
} catch(PDOException $e){
print("Error:".$e->getMessage());
die();
}
$ php index.php
接続に成功しました
a:営業部,b:総務部,d:経理部,e:人事部,f:物流部,
PDOはmysqlと然程変わらないですな
PostgreSQLは削除フラグがついて見えなくなっているだけのため、削除データを定期的にきれいにする必要があり、この処理をVACUUMという。
$ psql –version
psql (PostgreSQL) 14.2 (Ubuntu 14.2-1.pgdg20.04+1)
$ sudo -u postgres psql
psql (14.2 (Ubuntu 14.2-1.pgdg20.04+1))
Type “help” for help.
postgres=# \l
postgres=# \c testdb;
testdb=# \d department;
testdb=# select * from department;
testdb=# delete from department where department_code = ‘c’;
testdb=# vacuum verbose department;
INFO: vacuuming “public.department”
INFO: scanned index “pk_department” to remove 1 row versions
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO: table “department”: removed 1 dead item identifiers in 1 pages
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO: index “pk_department” now contains 5 row versions in 2 pages
DETAIL: 1 index row versions were removed.
0 index pages were newly deleted.
0 index pages are currently deleted, of which 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO: table “department”: found 1 removable, 5 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 747
Skipped 0 pages due to buffer pins, 0 frozen pages.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM
vacuum ${table_name} でテーブルをvacuumできる。
なるほど。
autovacuumの設定はpostgres.confで設定されている。
Apacheのモジュールとして使用しているPHPの設定を.htaccessで変更する場合、php_valueとphp_flagで設定する。
php_flagは論理値(true, false)を設定し、php_valueはそれ以外の値を設定する
php_value, php_flag共にPHP_INI_ALLまたはPHP_INI_PERDIRの設定オプションに対して利用できる
セットされている値をクリアしたい場合、php_valueにnoneを値として設定することでクリアできる
よく設定する項目
php_value memory_limit 128M
php_value memory_limit -1
php_value post_max_size 64M
php_value upload_max_filesize 10M
php_value max_execution_time 60
php_value mbstring.internal_encoding UTF-8
php_value mbstring.detect_order UTF-8, SJIS-win, SJIS, eucJP-win, EUC-JP, JIS, ASCII
php_value mbstring.language Japanese
php_value output_buffering off
php_value max_input_vars 2000
php_flag session.cookie_secure On
php_flag session.cookie_httponly On
ini_set("session.cookie_path", "/path/");
php_value date.timezone "Asia/Tokyo"
php_flag short_open_tag On
php_flag display_errors On
php_value error_reporting 6135 // all error
php_flag log_errors On
php_value error_log "./logs/error.log"
### テスト
/etc/apache2/apache2.conf
<Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory>
.htaccess
php_flag log_errors On php_value error_log /var/www/html/error.log
$ sudo systemctl restart apache2
error.log
[31-Dec-2022 03:49:59 UTC] PHP Parse error: syntax error, unexpected end of file, expecting ‘;’ or ‘,’ in /var/www/html/app.php on line 5
[31-Dec-2022 03:50:00 UTC] PHP Parse error: syntax error, unexpected end of file, expecting ‘;’ or ‘,’ in /var/www/html/app.php on line 5
OK!
AWSのMFAで使用されるGoogle Authenticatorのワンタイムパスワード(TOTP)は、サーバとクライアントで共有する秘密鍵と現在時刻から確認コードを計算するアルゴリズム。
80ビットの秘密鍵をQRコード(16文字のBase32※(A-Z, 2-7))としてブラウザ上に表示し、サーバとクライアントに同じ秘密鍵が保存される。現在時刻は30秒ごとに値が変わるカウンターに変更されてから確認コードの6桁の数字が計算されてアプリに表示される。
※32種類の英数字のみを用いてバイナリデータを示す
TOTPに対応するサービスはGoogle, Amazon, Microsoft, Twitter, Instagram, Facebook, Dropbox, Github, WordPress, Slackなど。
K: 秘密鍵
TC: 現在時刻(UNIX TIME)
X: 時間ステップ(30)
T0: カウント開始時刻(0)
N: トークンの長さ(6)
ハッシュアルゴリズム: SHA-1
T = floor((TC – T0) / X)
H = HMAC-SHA-1(K, T)
TOTP = Truncate(H)
※RFC 4226 に定められている20バイト文字列から10進数N桁のトークンを得る関数
TOTPの実装方法についてClassmethodさんの解説
https://dev.classmethod.jp/articles/totp-implementation-pure-python/
自分でもやろうと思ったが、HMAC-SHA-1以降のTruncate関数の実装のところがうまくできない。
import os
import base64
import time
import math
import hmac
import hashlib
private_key = os.urandom(5) # 5byte = 80bit
private_key_base32 = (base64.b32encode(private_key))
t1 = time.time()
h = math.floor((t1 - 0) // 30)
sig = hmac.new(private_key_base32, str(h).encode('utf-8'), hashlib.sha1).hexdigest()
MFAが共通の秘密鍵とUnixTime、SHA-1のハッシュ関数からTruncate関数で10進数に変換して30秒ごとに表示しているということがわかりました。UNIXタイムを認証に使う場合、サーバ側とクライアント側でUnixタイムがずれていないことが前提となるため、どうやって担保するのが良いかわかりませんでしたが、アプリケーション側のユーザ認証でもAWSのMFAと同じような秘密鍵を共有した認証の仕組みが作れたら仕組みとして面白いのかなと思いました。
参考サイト
https://sekika.github.io/2016/03/26/GoogleAuthenticator/