Skip to content

Commit 17cbe71

Browse files
authored
Fix SAPI shutdown (#23)
1 parent a649459 commit 17cbe71

File tree

11 files changed

+384
-232
lines changed

11 files changed

+384
-232
lines changed

.github/workflows/CI.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,15 @@ jobs:
485485
- name: List packages
486486
run: ls -R ./npm
487487
shell: bash
488+
# - name: Dev publish
489+
# shell: bash
490+
# run: |
491+
# npm config set //registry.npmjs.org/:_authToken=$NPM_TOKEN
492+
# npm config set scope "@platformatic"
493+
# npm publish --tag dev
494+
# env:
495+
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
496+
# NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
488497
- name: Publish
489498
if: ${{ contains(github.ref, 'main') }}
490499
run: |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ Cargo.lock
196196

197197
*.node
198198
*.dylib
199+
*.so
199200

200201
# Added by static-php-cli
201202
crates/php/buildroot

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ rustfmt.toml
1313
pnpm-lock.yaml
1414
*.node
1515
*.dylib
16+
*.so
1617
!npm/**/*.node
1718
!npm/**/*.dylib
19+
!npm/**/*.so
1820
__test__
1921
renovate.json

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ console.log(response.body.toString())
5858
### `new Php(config)`
5959

6060
* `config` {Object} Configuration object
61+
* `argv` {String[]} Process arguments. **Default:** []
6162
* `docroot` {String} Document root for PHP. **Default:** process.cwd()
6263
* Returns: {Php}
6364

@@ -67,6 +68,7 @@ Construct a new PHP instance to which to dispatch requests.
6768
import { Php } from '@platformatic/php-node'
6869

6970
const php = new Php({
71+
argv: process.argv,
7072
docroot: process.cwd()
7173
})
7274
````

__test__/handler.spec.mjs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ test('Support input/output streams', async (t) => {
1515
t.teardown(() => mockroot.clean())
1616

1717
const php = new Php({
18-
argv: process.argv,
1918
docroot: mockroot.path
2019
})
2120

2221
const req = new Request({
23-
method: 'GET',
22+
method: 'POST',
2423
url: 'http://example.com/index.php',
2524
body: Buffer.from('Hello, from Node.js!')
2625
})
@@ -39,12 +38,10 @@ test('Capture logs', async (t) => {
3938
t.teardown(() => mockroot.clean())
4039

4140
const php = new Php({
42-
argv: process.argv,
4341
docroot: mockroot.path
4442
})
4543

4644
const req = new Request({
47-
method: 'GET',
4845
url: 'http://example.com/index.php'
4946
})
5047

@@ -62,12 +59,10 @@ test('Capture exceptions', async (t) => {
6259
t.teardown(() => mockroot.clean())
6360

6461
const php = new Php({
65-
argv: process.argv,
6662
docroot: mockroot.path
6763
})
6864

6965
const req = new Request({
70-
method: 'GET',
7166
url: 'http://example.com/index.php'
7267
})
7368

@@ -89,12 +84,10 @@ test('Support request and response headers', async (t) => {
8984
t.teardown(() => mockroot.clean())
9085

9186
const php = new Php({
92-
argv: process.argv,
9387
docroot: mockroot.path
9488
})
9589

9690
const req = new Request({
97-
method: 'GET',
9891
url: 'http://example.com/index.php',
9992
headers: {
10093
'X-Test': ['Hello, from Node.js!']
@@ -106,3 +99,33 @@ test('Support request and response headers', async (t) => {
10699
t.is(res.body.toString(), 'Hello, from Node.js!')
107100
t.is(res.headers.get('X-Test'), 'Hello, from PHP!')
108101
})
102+
103+
test('Has expected args', async (t) => {
104+
const mockroot = await MockRoot.from({
105+
'index.php': `<?php
106+
echo "[";
107+
$first = true;
108+
foreach ($argv as $value) {
109+
if ($first) { $first = false; }
110+
else { echo ","; }
111+
echo "\\"$value\\"";
112+
}
113+
echo "]";
114+
?>`
115+
})
116+
t.teardown(() => mockroot.clean())
117+
118+
const php = new Php({
119+
argv: process.argv,
120+
docroot: mockroot.path
121+
})
122+
123+
const req = new Request({
124+
url: 'http://example.com/index.php'
125+
})
126+
127+
const res = await php.handleRequest(req)
128+
t.is(res.status, 200)
129+
130+
t.is(res.body.toString('utf8'), JSON.stringify(process.argv))
131+
})

crates/php/src/embed.rs

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,35 @@
11
use std::{
22
env::Args,
3-
ffi::c_char,
3+
ffi::CString,
4+
ops::DerefMut,
45
path::{Path, PathBuf},
6+
sync::Arc,
57
};
68

79
use ext_php_rs::{
810
error::Error,
9-
ffi::{
10-
_zend_file_handle__bindgen_ty_1, php_execute_script, sapi_get_default_content_type,
11-
zend_file_handle, zend_stream_init_filename,
12-
},
11+
ffi::{php_execute_script, php_module_shutdown, sapi_get_default_content_type},
1312
zend::{try_catch, try_catch_first, ExecutorGlobals, SapiGlobals},
1413
};
1514

1615
use lang_handler::{Handler, Request, Response};
1716

18-
use crate::{
19-
sapi::ensure_sapi,
17+
use super::{
18+
sapi::{ensure_sapi, Sapi},
19+
scopes::{FileHandleScope, RequestScope},
2020
strings::{cstr, nullable_cstr, str_from_cstr, translate_path},
21-
EmbedException, RequestContext, RequestScope, Sapi,
21+
EmbedException, RequestContext,
2222
};
2323

2424
/// Embed a PHP script into a Rust application to handle HTTP requests.
2525
#[derive(Debug)]
2626
pub struct Embed {
2727
docroot: PathBuf,
28+
args: Vec<String>,
29+
30+
// NOTE: This needs to hold the SAPI to keep it alive
31+
#[allow(dead_code)]
32+
sapi: Arc<Sapi>,
2833
}
2934

3035
// An embed instance may be constructed on the main thread and then shared
@@ -90,14 +95,16 @@ impl Embed {
9095
C: AsRef<Path>,
9196
S: AsRef<str> + std::fmt::Debug,
9297
{
93-
ensure_sapi(argv)?;
94-
95-
let docroot = docroot
96-
.as_ref()
98+
let docroot_path = docroot.as_ref();
99+
let docroot = docroot_path
97100
.canonicalize()
98-
.map_err(|_| EmbedException::DocRootNotFound(docroot.as_ref().display().to_string()))?;
101+
.map_err(|_| EmbedException::DocRootNotFound(docroot_path.display().to_string()))?;
99102

100-
Ok(Embed { docroot })
103+
Ok(Embed {
104+
docroot,
105+
args: argv.iter().map(|v| v.as_ref().to_string()).collect(),
106+
sapi: ensure_sapi()?,
107+
})
101108
}
102109

103110
/// Get the docroot used for this Embed instance
@@ -108,7 +115,6 @@ impl Embed {
108115
/// use std::env::current_dir;
109116
/// use php::Embed;
110117
///
111-
///
112118
/// let docroot = current_dir()
113119
/// .expect("should have current_dir");
114120
///
@@ -152,25 +158,21 @@ impl Handler for Embed {
152158
/// //assert_eq!(response.body(), "Hello, world!");
153159
/// ```
154160
fn handle(&self, request: Request) -> Result<Response, Self::Error> {
155-
unsafe {
156-
ext_php_rs::embed::ext_php_rs_sapi_per_thread_init();
157-
}
158-
159161
// Initialize the SAPI module
160-
Sapi::startup()?;
162+
self.sapi.startup()?;
161163

162164
let url = request.url();
163165

164-
// Get code and filename to execute
166+
// Get original request URI and translate to final path
167+
// TODO: Should do this with request rewriting later...
165168
let request_uri = url.path();
166-
let path_translated = cstr(
167-
translate_path(&self.docroot, request_uri)?
168-
.display()
169-
.to_string(),
170-
)?;
169+
let translated_path = translate_path(&self.docroot, request_uri)?
170+
.display()
171+
.to_string();
172+
let path_translated = cstr(translated_path.clone())?;
171173
let request_uri = cstr(request_uri)?;
172174

173-
// Extract request information
175+
// Extract request method, query string, and headers
174176
let request_method = cstr(request.method())?;
175177
let query_string = cstr(url.query().unwrap_or(""))?;
176178

@@ -182,27 +184,16 @@ impl Handler for Embed {
182184
.unwrap_or(0);
183185
let cookie_data = nullable_cstr(headers.get("Cookie"))?;
184186

185-
// Prepare memory stream of the code
186-
let mut file_handle = unsafe {
187-
let mut file_handle = zend_file_handle {
188-
handle: _zend_file_handle__bindgen_ty_1 {
189-
fp: std::ptr::null_mut(),
190-
},
191-
filename: std::ptr::null_mut(),
192-
opened_path: std::ptr::null_mut(),
193-
type_: 0, //ZEND_HANDLE_FP
194-
primary_script: false,
195-
in_list: false,
196-
buf: std::ptr::null_mut(),
197-
len: 0,
198-
};
199-
200-
zend_stream_init_filename(&mut file_handle, path_translated);
201-
202-
// TODO: Make a scope to do zend_destroy_file_handle at the end.
187+
// Prepare argv and argc
188+
let argc = self.args.len() as i32;
189+
let mut argv_ptrs = vec![];
190+
for arg in self.args.iter() {
191+
let string = CString::new(arg.as_bytes())
192+
.map_err(|_| EmbedException::CStringEncodeFailed(arg.to_owned()))?;
193+
argv_ptrs.push(string.into_raw());
194+
}
203195

204-
file_handle
205-
};
196+
let script_name = translated_path.clone();
206197

207198
let response = try_catch_first(|| {
208199
RequestContext::for_request(request.clone());
@@ -214,8 +205,8 @@ impl Handler for Embed {
214205

215206
// Reset state
216207
globals.request_info.proto_num = 110;
217-
globals.request_info.argc = 0;
218-
globals.request_info.argv = std::ptr::null_mut();
208+
globals.request_info.argc = argc;
209+
globals.request_info.argv = argv_ptrs.as_mut_ptr();
219210
globals.request_info.headers_read = false;
220211
globals.sapi_headers.http_response_code = 200;
221212

@@ -237,7 +228,8 @@ impl Handler for Embed {
237228

238229
// Run script in its own try/catch so bailout doesn't skip request shutdown.
239230
try_catch(|| {
240-
if !unsafe { php_execute_script(&mut file_handle) } {
231+
let mut file_handle = FileHandleScope::new(script_name.clone());
232+
if !unsafe { php_execute_script(file_handle.deref_mut()) } {
241233
// return Err(EmbedException::ExecuteError);
242234
}
243235

@@ -275,7 +267,7 @@ impl Handler for Embed {
275267
let mime = if mimetype.is_null() {
276268
default_mime
277269
} else {
278-
str_from_cstr(mimetype as *const c_char).unwrap_or(default_mime)
270+
str_from_cstr(mimetype).unwrap_or(default_mime)
279271
};
280272

281273
RequestContext::current()
@@ -292,6 +284,8 @@ impl Handler for Embed {
292284
})
293285
.unwrap_or(Err(EmbedException::Bailout))?;
294286

287+
RequestContext::reclaim();
288+
295289
Ok(response)
296290
}
297291
}

crates/php/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,3 @@ pub use lang_handler::{Handler, Header, Headers, Request, RequestBuilder, Respon
1313
pub use embed::Embed;
1414
pub use exception::EmbedException;
1515
pub use request_context::RequestContext;
16-
pub(crate) use sapi::Sapi;
17-
pub(crate) use scopes::RequestScope;

0 commit comments

Comments
 (0)