使用 nginx 與 php 架設 key-value storage 服務

Posted by blueskyson on April 13, 2021

此為 2021 網路管理課程的 lab7

設定 php-fpm

$ sudo apt install php-cli php-fpm

設定 php-fpm 的 port ,設定檔通常在 /etc/php/<版本>/fpm/pool.d

$ sudo vim /etc/php/7.4/fpm/pool.d/www.conf

listen 設為 9000

...

; The address on which to accept FastCGI requests.
; Valid syntaxes are:
;   'ip.add.re.ss:port'    - to listen on a TCP socket to a specific IPv4 address on
;                            a specific port;
;   '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
;                            a specific port;
;   'port'                 - to listen on a TCP socket to all addresses
;                            (IPv6 and IPv4-mapped) on a specific port;
;   '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 9000

...

啟動 php-fpm 並檢查是否正常運作

$ systemctl start php7.4-fpm
$ sudo netstat -anpt | grep php
tcp6       0      0 :::9000                 :::*                    LISTEN      8957/php-fpm: maste

設定 nginx

$ sudo apt install nginx
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

啟動 nginx 的 fastcgi ,修改 /etc/nginx/sites-enabled/default.conf 即可

$ sudo vim /etc/nginx/sites-enabled/default.conf

新增 location ~ \.php$ { ... } 區塊以啟動 fastcgi

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name _;

        location / {
                try_files $uri $uri/ =404;
        }

        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass 127.0.0.1:9000;
                index index.php index.html;
        }
}

重啟 nginx

$ sudo nginx -s reload

在 nginx 的 root (預設是 /var/www/html) 中新增測試檔案 index.php

$ cat /var/www/html/index.php
<?php
    echo "hello";
?>

測試 fastcgi 是否成功啟用

$ curl http://localhost/index.php
hello

以上便完成初步的 php 測試

實作 key-value storage

預期的功能是:

  • 使用 http POST /set/<KEY> body: value=<VALUE> 來新增或複寫 key-value
  • 使用 http GET /get/<KEY> 取得 value

因為只是一個小作業,所以不會用到功能較強的 MySQL,而是讓 key 作為檔名,value 作為內容儲存在目錄 /var/www/lab7/data 中,首先創建目錄

$ sudo mkdir /var/www/lab7
$ sudo mkdir /var/www/lab7/data

SET

以 regular expression 設定動態的網址,使用 http 的 POST /set/<KEY> body: value=<VALUE> 來儲存 key-value 。所以在 /etc/nginx/sites-enabled/default.conf 的 server 區塊新增以下:

location ~ ^(/set)/[^/]+ {
        root /var/www/lab7;
        include fastcgi.conf;
        try_files /set.php $uri =404;
        fastcgi_pass 127.0.0.1:9000;
}
  • location ~ ^(/set)/[^/]+ 代表以 /set/ 開頭,然後由一個以上的 / 以外的字元組成的連結,用其接受 /set/ 的規則
  • root 此區塊的的根目錄,我設定在 /var/www/lab7
  • include 加入 fastcgi 設定檔,/etc/nginx/fastcgi.conf 中定義一些參數,如 REQUEST_URI,這些參數在 php 腳本中透過 $_SERVER[] 取得
  • try_files 確認檔案是否存在,如果 set.php 不存在,就導向到 404
  • fastcgi_pass 導向 php7.4-fpm 的接口

再來我們撰寫 php 腳本以實行 SET 服務,其會將 key-value 的檔案存放在 /var/www/lab7/data

$ sudo vim /var/www/lab7/set.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
if ($_SERVER['REQUEST_METHOD'] != "POST") {
        echo "Failed. Please use POST body: value=<VALUE>\n";
        return;
}

$value = $_POST['value'];
if (strlen($value) == 0 and empty($value)) {
        echo "Failed. POST body: value=empty()\n";
        return;
} elseif (strlen($value) > 256) {
        echo strlen($value);
        echo "Failed. Value too long (max length is 256).\n";
        return;
}

$key = substr($_SERVER['REQUEST_URI'], 5);
if (strlen($key) > 256) {
        echo strlen($key);
        echo "Failed. Key too long (max length is 256).\n";
        return;
}

$key = hash('sha512', $key);
$file_name = "data/".$key;
$file = fopen($file_name, "w") or die("Unable to open file!");
fwrite($file, $value);
fclose($file);
echo "OK";
?>

接下來在 php 讀寫檔案方面很有可能遇到 Permission Denied,相關的紀錄可以在 /var/log/nginx/error.log 中查閱。此時我們需要修改 fpm 的設定檔

$ sudo vim /etc/php/7.4/fpm/pool.d/www.conf

listen.mode = 0660 取消註解

listen.mode = 0660

保險起見,將 location 的 root 的擁有者改成 nginx 使用者 (預設是 www-data) ,然後重啟 nginx 和 fpm

$ sudo chown -R www-data:www-data /var/www/lab7
$ sudo service php7.4-fpm restart
$ sudo service nginx restart

我們可以用 curl 來測試 set.php 是否正常運作

$ curl -F "value=world" localhost/set/hello
OK

查看檔案室否成功儲存

$ tree /var/www/lab7
/var/www/lab7
├── data
│   └── hello
└── set.php
$ cat /var/www/lab7/data/hello
world

如果檔案沒有被成功寫入,請再去查看 nginx 的 error log

GET

以 regular expression 設定動態的網址,使用 http 的 POST /set/<KEY> body: value=<VALUE> 來儲存 key-value 。所以在 /etc/nginx/sites-enabled/default.confserver 區塊新增以下:

location ~ ^(/get)/[^/]+ {
        root /var/www/lab7;
        include fastcgi.conf;
        try_files /get.php $uri =404;
        fastcgi_pass 127.0.0.1:9000;
}

撰寫 php 腳本以實行 GET 服務,其會讀取 /var/www/lab7/data 的檔案並打印出內容

$ sudo vim /var/www/lab7/get.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$key = substr($_SERVER['REQUEST_URI'], 5);
if (strlen($key) > 256) {
        echo "key not found!";
        return;
}

$key = hash('sha512', $key);
$file_name = "data/".$key;
if (file_exists($file_name)) {
        $file = fopen($file_name, "r");
        echo fread($file, filesize($file_name));
        fclose($file);
} else {
        echo "key not found!";
}
?>

將 get.php 的擁有者改成 nginx 使用者

$ sudo chown www-data:www-data /var/www/lab7/get.php

測試 GET 是否成功

$ curl localhost/get/hello
world