ネイティブルート(Go)#
route.go は Go で書く API ルートです。URL のマッピングは route.php と同じで、ハンドラだけがネイティブバイナリにコンパイルされます。標準ライブラリの http.HandlerFunc の形で func GET / POST / PUT / PATCH / DELETE / HEAD / OPTIONS を宣言するだけで、 main() もフレームワークの import も要りません。CPU 負荷の高いエンドポイント向けです。
// src/Pages/api/hello/route.go
package main
import (
"encoding/json"
"net/http"
)
func GET(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"message": "Hello from Go!"})
}vendor/bin/relayer native:build # route.go を変更するたびに 1 回
curl -i http://localhost:8080/api/helloビルド#
relayer native:build は src/Pages 配下の全 route.go とルート直下の middleware.go を var/cache/native/bin/<slug> にコンパイルします。<slug> は URL パターンの / を __ に置き換えたものです(/api/users/[id] → api__users__[id])。
PATH上のgo(またはGO_BIN=/path/to/go)が必要で、そのためrelayer compileとは別コマンドです。route.goの隣にgo.mod/go.sumがあればそれを使うので、サードパーティのモジュールも使えます。コピーされるのはroute.goとgo.mod/go.sumだけなので、隣のhelper.goはコンパイルされません。共有コードはモジュールとして import します。- 同じ slug になる 2 つのルート、ハンドラのない
route.go、package mainでないroute.goはビルドエラーです。 - リクエスト中に再ビルドはしません。未ビルドのルートは
native:buildを促す JSON500を返し、 dev ではバイナリがroute.goより古いときも同じ応答になります。
雛形の Dockerfile には Go がなく、native:build も実行しません。そのままの本番イメージでは route.go がすべて未ビルドの 500 になるので、Go を入れたビルドステージで native:build を実行し、var/cache/native/bin を最終イメージへコピーしてください。
リクエストの流れ#
常駐する Go サーバはありません。PHP がリクエストごとにバイナリを CGI の子プロセスとして起動します(proc_open)。1 呼び出しのオーバーヘッドは数ミリ秒です。ハンドラからは次のものが見えます。
r.Method、r.URL、r.Header、r.Host、r.RemoteAddr、r.Body。クライアントのProxy:ヘッダは転送しません。r.RemoteAddrのポートは0です。- 動的セグメントは
RELAYER_ROUTE_PARAMSに JSON で、パターンはRELAYER_ROUTE_PATTERNに入ります。
var params map[string]string
json.Unmarshal([]byte(os.Getenv("RELAYER_ROUTE_PARAMS")), ¶ms)
id := params["id"]- PHP プロセスの実際の環境変数も見えますが、
.envの値は 含まれません。Go で必要な設定はシェルやコンテナで実際の環境変数として渡してください。 multipart/form-dataの本文は PHP が解析した内容から組み立て直すので、r.FormFileが使えます。 PHP が拒否したアップロードは JSON500になります。
メソッドと失敗#
メソッドの扱いは route.php と同じです。未宣言の OPTIONS は 204 + Allow、GET があって HEAD が未宣言なら GET を実行して本文を落とし、それ以外の未宣言メソッドは 405 + Allow です。
非 0 終了(panic を含む)、CGI 応答でない出力、タイムアウト超過は JSON 500 になります。stderr は PSR-3 ロガーに記録されます(成功した実行の stderr は warning)。タイムアウトの既定は 30 秒で、 NATIVE_ROUTE_TIMEOUT で変更できます。disable_functions で proc_open を無効にした PHP では動きません。
効かないもの#
route.go にはオートワイヤも PageContext も Request も #[Auth] / requireAuth() もありません。保護はルートミドルウェアで行います。_private フォルダは route.go も除外し、 1 ディレクトリにハンドラは 1 つです(route.go と route.php やページの同居はスキャンエラー)。
middleware.go#
ルートミドルウェアも Go で書けます。src/Pages/middleware.go に func Proxy を 1 つ書くと、 native:build で var/cache/native/bin/middleware にビルドされ、ルートマッチの前にリクエストごとに起動します。
// src/Pages/middleware.go
package main
import "net/http"
func Proxy(w http.ResponseWriter, r *http.Request) {
// 短絡: Relayer-Next が無ければ、これがそのまま最終応答
if r.Header.Get("X-Api-Key") == "" && len(r.URL.Path) >= 4 && r.URL.Path[:4] == "/api" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(401)
w.Write([]byte(`{"error":"missing api key"}`))
return
}
// 続行: Relayer-Next があれば、マッチしたページ / ルートが実行される
w.Header().Set("Relayer-Next", "1")
w.Header().Set("Relayer-Request-Header-X-User", "42") // ルートから見えるリクエストヘッダ
w.Header().Set("X-Proxied-By", "go") // 最終応答にマージされるレスポンスヘッダ
}Relayer-Next: 1がある → 続行。本文は無視します。Relayer-Rewrite-Path: /otherで別のパスにルーティングし、Relayer-Request-Header-<Name>: valueでリクエストヘッダを追加/上書きします。それ以外のレスポンスヘッダは最終応答にマージされます(Status:・Content-Type・Content-Lengthを除く)。Relayer-Nextがない → 短絡。Status:(既定 200)/ ヘッダ / 本文をそのまま送ります。Relayer-*ヘッダはクライアントに届きません。RELAYER_MATCHED_PATTERNに、そのパスがマッチするパターン(404 なら空)が入ります。middleware.phpとmiddleware.goが両方あるとRuntimeExceptionになります。- 失敗はページへのリクエストでも JSON
500です。 - すべての ページとルートに数ミリ秒が乗ります。
/_defer/*と/_profilerはこの外で動くので、それらも覆う検査は guard に置きます。
改訂履歴 (1)
- v0.31.0: 新規ページ