Skip to content

Commit f9a9be6

Browse files
Qardclaude
andauthored
Migrate to http-handler and http-rewriter (#55)
* Migrate to http-handler and http-rewriter * Fix native module loading to support CI artifact structure The CI downloads artifacts to npm/<platform>/binding.node but the code was only looking for php.<platform>.node in the root directory. This change tries the npm directory first (for CI/published builds) and falls back to the root path (for local development builds). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Rename Rust library from php-node to php Rename the Rust crate and library from php-node/php_node to php for better clarity and consistency. Updated all references throughout the codebase including doctests. Changes: - Cargo.toml: Changed package name from 'php-node' to 'php' - Cargo.toml: Changed lib name from 'php_node' to 'php' - src/lib.rs: Updated doctest to use 'php::' instead of 'php_node::' - src/embed.rs: Updated all doctests to use 'php::' prefix - src/main.rs: Updated imports to use 'php::' prefix - CLAUDE.md: Updated documentation to reflect single-crate structure and removed references to old multi-crate workspace All tests pass with the new naming. --------- Co-authored-by: Claude <[email protected]>
1 parent 61249d9 commit f9a9be6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1534
-5032
lines changed

CLAUDE.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
@platformatic/php-node is a Node.js native addon that embeds PHP within the same process as Node.js, enabling seamless communication without network overhead. It's built with Rust using NAPI-RS for safe and performant bindings.
8+
9+
## Essential Commands
10+
11+
### Development
12+
```bash
13+
# Build release version
14+
npm run build
15+
16+
# Build debug version (faster compilation, includes debug symbols)
17+
npm run build:debug
18+
19+
# Run all tests
20+
npm test
21+
22+
# Run specific test file
23+
npx ava __test__/headers.spec.mjs
24+
25+
# Lint JavaScript code
26+
npm run lint
27+
28+
# Create universal binary (macOS)
29+
npm run universal
30+
31+
# Version bump
32+
npm run version
33+
```
34+
35+
### Rust-specific builds
36+
```bash
37+
# Build with proper rpath for linking libphp
38+
RUSTFLAGS="-C link-args=-Wl,-rpath,\$ORIGIN" npm run build
39+
40+
# Run Rust tests
41+
cargo test
42+
43+
# Run binary directly
44+
cargo run
45+
```
46+
47+
## Architecture
48+
49+
### Multi-language Structure
50+
- **Rust** (`/src`): Single-crate implementation
51+
- PHP embedding and SAPI implementation
52+
- NAPI bindings exposing Rust to Node.js (when `napi-support` feature is enabled)
53+
- Binary target for standalone testing (`src/main.rs`)
54+
- Library target for NAPI usage (`src/lib.rs`)
55+
- **JavaScript**: Node.js API layer (`index.js`, `index.d.ts`)
56+
- **PHP**: Embedded runtime via libphp.{so,dylib}
57+
58+
### Key Components
59+
60+
1. **PHP Class** (`index.js`): Main entry point for creating PHP environments
61+
- Manages rewriter rules for URL routing
62+
- Handles request/response lifecycle
63+
- Supports both sync and async request handling
64+
65+
2. **Request/Response Model**: Web standards-compatible implementation
66+
- `Request` class with headers, body, method
67+
- `Response` class with status, headers, body
68+
- `Headers` class with case-insensitive header handling
69+
70+
3. **Rewriter System**: Apache mod_rewrite-like functionality
71+
- Conditional rules with regex patterns
72+
- Environment variable support
73+
- Rule chaining with [L], [R], [C] flags
74+
75+
4. **SAPI Implementation**: Custom PHP SAPI in Rust
76+
- Direct Zend API usage for performance
77+
- Thread-safe with TSRM support
78+
- Reusable PHP environments across requests
79+
80+
## Critical Development Notes
81+
82+
1. **System Dependencies Required**:
83+
- Linux: `libssl-dev libcurl4-openssl-dev libxml2-dev libsqlite3-dev libonig-dev re2c libpq5`
84+
- macOS: `openssl@3 curl sqlite libxml2 oniguruma postgresql@16`
85+
86+
2. **PHP Runtime**: Must have `libphp.so` (Linux) or `libphp.dylib` (macOS) in project root
87+
88+
3. **Testing**: AVA framework with 3-minute timeout due to PHP startup overhead
89+
90+
4. **Type Definitions**: `index.d.ts` is auto-generated by NAPI-RS - do not edit manually
91+
92+
5. **Platform Support**: x64 Linux, x64/arm64 macOS (pre-built binaries in `/npm`)
93+
94+
6. **Recent Architecture Changes**:
95+
- Consolidated from multi-crate workspace to single crate named `php`
96+
- NAPI support is now feature-gated with `napi-support` feature
97+
- Binary target supports both library (`rlib`) and dynamic library (`cdylib`) outputs
98+
99+
## Common Tasks
100+
101+
### Adding New NAPI Functions
102+
1. Implement in Rust under `src/` with `#[cfg(feature = "napi-support")]`
103+
2. Use `#[napi]` attributes for exposed functions/classes
104+
3. Run `npm run build` to regenerate TypeScript definitions
105+
106+
### Modifying Request/Response Handling
107+
- Core logic in `src/sapi.rs` and `src/embed.rs`
108+
- JavaScript wrapper in `index.js`
109+
- Request/response types from `http-handler` crate
110+
111+
### Debugging PHP Issues
112+
- Check INTERNALS.md for PHP embedding details
113+
- Use `npm run build:debug` for debug symbols
114+
- PHP superglobals set via `SG(...)` macro in Rust code
115+
116+
### Working with Rewriter Rules
117+
The rewriter system supports Apache mod_rewrite-like functionality:
118+
- Create rules with conditions (header, host, method, path, query)
119+
- Apply rewriters (header, href, method, path, query, status)
120+
- Use flags like [L] (last), [R] (redirect), [C] (chain)
121+
- Example: `new Rewriter([{ conditions: [{type: 'path', args: ['^/old/(.*)$']}], rewriters: [{type: 'path', args: ['^/old/(.*)$', '/new/$1']}] }])`
122+
123+
## Project Files Reference
124+
125+
- `index.js`: Main JavaScript API, exports PHP, Request, Response, Headers, Rewriter classes
126+
- `src/lib.rs`: Library entry point, exports core types and NAPI bindings
127+
- `src/main.rs`: Binary entry point for standalone testing
128+
- `src/embed.rs`: Core `Embed` type for handling PHP requests
129+
- `src/sapi.rs`: PHP SAPI implementation (low-level PHP integration)
130+
- `src/runtime.rs`: NAPI runtime implementation (when `napi-support` feature enabled)
131+
- `__test__/*.spec.mjs`: Test files for each component

Cargo.toml

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,51 @@
1-
[workspace]
2-
resolver = "2"
3-
members = [
4-
"crates/lang_handler",
5-
"crates/php",
6-
"crates/php_node"
7-
]
1+
[package]
2+
edition = "2021"
3+
name = "php"
4+
version = "1.4.0"
5+
authors = ["Platformatic Inc. <[email protected]> (https://platformatic.dev)"]
6+
license = "MIT"
7+
repository = "https://github.com/platformatic/php-node"
8+
9+
[features]
10+
default = []
11+
napi-support = ["dep:napi", "dep:napi-derive", "dep:napi-build", "http-handler/napi-support", "http-rewriter/napi-support"]
12+
13+
[lib]
14+
name = "php"
15+
crate-type = ["cdylib", "rlib"]
16+
path = "src/lib.rs"
17+
18+
[[bin]]
19+
name = "php-main"
20+
path = "src/main.rs"
21+
22+
[dependencies]
23+
async-trait = "0.1.88"
24+
bytes = "1.10.1"
25+
hostname = "0.4.1"
26+
ext-php-rs = { version = "0.14.0", features = ["embed"] }
27+
http-handler = { git = "https://github.com/platformatic/http-handler.git" }
28+
# http-handler = { path = "../http-handler" }
29+
http-rewriter = { git = "https://github.com/platformatic/http-rewriter.git" }
30+
# http-rewriter = { path = "../http-rewriter" }
31+
libc = "0.2.171"
32+
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
33+
napi = { version = "3", default-features = false, features = ["napi4"], optional = true }
34+
napi-derive = { version = "3", optional = true }
35+
once_cell = "1.21.0"
36+
tokio = { version = "1.45", features = ["rt", "macros", "rt-multi-thread"] }
37+
regex = "1.0"
38+
39+
[dev-dependencies]
40+
tokio-test = "0.4"
41+
42+
[build-dependencies]
43+
autotools = "0.2"
44+
bindgen = "0.69.4"
45+
cc = "1.1.7"
46+
downloader = "0.2.8"
47+
file-mode = "0.1.2"
48+
napi-build = { version = "2.2.1", optional = true }
849

950
# [profile.release]
1051
# lto = true

__test__/rewriter.spec.mjs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import test from 'ava'
33
import { Request, Rewriter } from '../index.js'
44

55
const docroot = import.meta.dirname
6+
const filename = import.meta.filename.replace(docroot, '')
67

78
test('existence condition', (t) => {
89
const req = new Request({
910
method: 'GET',
10-
url: 'http://example.com/util.mjs',
11+
url: `http://example.com${filename}`,
1112
headers: {
1213
TEST: ['foo']
1314
}
@@ -16,7 +17,7 @@ test('existence condition', (t) => {
1617
const rewriter = new Rewriter([
1718
{
1819
conditions: [
19-
{ type: 'exists' }
20+
{ type: 'exists', args: [] }
2021
],
2122
rewriters: [
2223
{
@@ -196,7 +197,7 @@ test('header rewriting', (t) => {
196197
test('href rewriting', (t) => {
197198
const rewriter = new Rewriter([{
198199
rewriters: [
199-
{ type: 'href', args: [ '^(.*)$', '/index.php?route=${1}' ] }
200+
{ type: 'href', args: [ '^(.*)$', '/index.php?route=$1' ] }
200201
]
201202
}])
202203

@@ -213,7 +214,7 @@ test('href rewriting', (t) => {
213214
test('method rewriting', (t) => {
214215
const rewriter = new Rewriter([{
215216
rewriters: [
216-
{ type: 'method', args: ['GET', 'POST'] }
217+
{ type: 'method', args: ['POST'] }
217218
]
218219
}])
219220

build.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::env;
2+
3+
#[cfg(feature = "napi-support")]
4+
extern crate napi_build;
5+
6+
fn main() {
7+
#[cfg(feature = "napi-support")]
8+
napi_build::setup();
9+
10+
// Check for manual PHP_RPATH override, otherwise try LD_PRELOAD_PATH,
11+
// and finally fallback to hard-coded /usr/local/lib path.
12+
//
13+
// PHP_RPATH may also be $ORIGIN to instruct the build to search for libphp
14+
// in the same directory as the *.node bindings file.
15+
let php_rpath = env::var("PHP_RPATH")
16+
.or_else(|_| env::var("LD_PRELOAD_PATH"))
17+
.unwrap_or("/usr/local/lib".to_string());
18+
19+
println!("cargo:rustc-link-search={php_rpath}");
20+
println!("cargo:rustc-link-lib=dylib=php");
21+
println!("cargo:rustc-link-arg=-Wl,-rpath,{php_rpath}");
22+
}

crates/lang_handler/Cargo.toml

Lines changed: 0 additions & 22 deletions
This file was deleted.

crates/lang_handler/build.rs

Lines changed: 0 additions & 7 deletions
This file was deleted.

crates/lang_handler/src/handler.rs

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)