webサーバー

PHP

【Node.js】ExpressでPHPを処理する

更新日:2022/09/30

Node.jsのExpress はWebサーバーを構築するためのフレーワークモジュールです。
当然のことですがJavaScriptに準ずる言語で記述する必要があります。
しかしPHPで記述されたコードを有効活用したいケースがあります。

そこで今回は、ExpressでPHPを処理する方法を紹介します。

 

単純なPHP処理

PHPの処理は、基本的には子プロセスで実行して結果を受け取ります。


const {exec} = require("child_process");

app.get(/\.php$/,(req, res) =>{

  const p = exec(`php .${req.path}`,(error, stdout, stderr) => {
    if (error) {
      res.send(`PHP Error:${error}`);
    }else{
      res.send(stdout);
    }
  });
});

上のソースコードとPHPファイルが同じ階層にあるケースを想定しています。

execはシェルでコマンドを実行して、結果をコールバックで返します。

実行するコマンドは php で、引数に URLのパスをファイル名として指定しています。
ただしreq.pathは先頭に / が含まれるので、その前に .(ドット)を付けています。

GETやPOSTなどのパラメータを処理しなくていいい時は、起動等のオーバーヘッドを気にしないならこれで十分ですね。

 

GETやPOSTなどを処理する

PHPファイルのコードで、GETやPOSTなどのパラメータやブラウザからアップロードされてきたファイル等を処理している場合は、PHPを処理できるWebサーバーに任せてしまうのが手っ取り早いです。

PHP組み込みサーバーを使用する

PHPには標準でWebサーバー機能が組み込まれています。
今回はこの機能を使用してみます。

PHP組み込みサーバーのコマンド

PHP -S ホスト名:ポート -t ドキュメントルート

-t オプションは任意です。
起動時のディレクトリがドキュメントルートになりますが、 -t オプションがあるとこちらが使用されます。

実際のところ、このサーバーはアプリケーション開発の支援用なので制約があります。

詳しくはこちらを見てください。
ビルトインウェブサーバー | php.net

実務的なシステムはApache等のWebサーバーを用意した方がいいですね。
だったらApacheで受けて、Expressポートにリバースプロキシした方がいいのか…

ディレクトリ構成

今回は次のような構成でコードを作成してみます。

ワークスペースディレクトリ
   ┣ front
   ┃  ┣ resource
   ┃  ┃   ┣ js
   ┃  ┃   ┃  ┗ test.js
   ┃  ┃   ┗ image
   ┃  ┗ test.php
   ┣ app.js
   ┗ php-request.js

front はブラウザ表示する phpコードとjsなどのリソースを格納しているディレクトリです。
このディレクトリをドキュメントルートとして、PHPサーバーを起動します。

app.js はExpressのルーティングを行います。
php-request.js はPHPサーバーの起動と、サーバーへの要求を行います。

app.js

app.jsは、次のようなコードです。

app.js


const express = require("express");
const app = express();
const port = 3000;

app
  .use( (()=>{
    const raw = express.raw({ type:'*/*', limit: "10mb" });
    const urlencoded = express.urlencoded({extended: true, limit: "10mb"});

    return (req, res, next)=> 
       /\.php$/.test(req.path) ? raw(req, res, next) : urlencoded(req, res, next);
    
  })() )

  .all(/\.php$/, require("./php-request")())

  .get("/",(req, res) => {
    res.json(req.query); // GETパラメーターの確認
  })

  .use("/js",express.static('./front/resource/js'))
  .use("/image",express.static('./front/resource/image'))

  .listen(port, () => {
    console.log(`start on port ${port}`)
  });

最初の use() はリクエストのパラメーターを取得して解析しています。
ただしphpのリクエストは、生データのまま取得します。

2番目の use() は、リクエスト対象が php の時、php-request.php 内の処理を呼び出しています。

php-request.php

php-request.phpは、次のようなコードです。

php-request.php


const httpRequest = require('http').request;
const {spawn} = require("child_process");

const host = "127.0.0.1";
const port = 5000;
const rootDir = ".\\front";
const phpPath = "C:\\php8.1\\php.exe";

module.exports = ()=>{
    // PHP組み込みサーバー起動
    const p = spawn(phpPath,["-S",`${host}:${port}`],{cwd:rootDir});
    // p.stdout.on("data",data=>console.log(data.toString()));
    // p.stderr.on("data",data=>console.log(data.toString()));

    const option = { host:host,port:port,};
    
    return  (request,response)=>{
        option.path =  request.originalUrl;
        option.headers = request.headers;
        option.method = request.method;

        const rq = httpRequest(option,
            res=>{
                res.pipe(response);
            }).on("error", e => response.sendStatus(503));
        if( request.body instanceof Buffer ) rq.write(request.body);
        rq.end();
    }
}

PHPの組み込みサーバーを起動した後、サーバーにリクエストする関数を返しています。

返した関数はExpressから呼び出されるので、requestとresponseの二つの引数を持っています。
この関数内では http.request で PHPサーバーに要求を送っています。

POSTはBodyにパラメーターがセットされます。
そこで request.body の内容を、writeでPHPサーバーに渡しています。
Bufferのインスタンスかどうかチェックしているのは、GETのとき普通のオブジェクトがセットされていて、そのまま指定するとエラーになるからです。

重要なのが最後の rq.end() です。
これが無いと、要求が完了しません。
気が付くまで半日かかりました…

なお、このコードは最低限のことしかやっていません。
PHPサーバーの起動確認とか必要です。

PHPサーバーは起動時にメッセージを出力するのでこれを確認すればいいのですが、環境によっては標準エラーに出力されていたり、出力先が不明だったりと、非常にメンドクサイです。
サーバーに要求を送って、応答を確認した方が速い気がします。

同一ポートで起動してもエラーを出さないので、既に起動しているかどうかのチェックも必要かもしれません。

またPHPの組み込みサーバーはシングルスレッドです。
ということは、交通整理?が必要です。

いろいろやることがあるので、勉強にはなりそうですね。

更新日:2022/09/30

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。