Skip to content

Commit d50bcc0

Browse files
authored
Fix object state and improve http server example. (#115)
1 parent e1dc248 commit d50bcc0

File tree

14 files changed

+241
-165
lines changed

14 files changed

+241
-165
lines changed

examples/http-client/tests/php/test.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
->cookie_store(true)
2525
->build();
2626

27-
$response = $client->get("https://httpbin.org/ip")->send();
27+
$response = $client->get("https://example.com/")->send();
2828
var_dump([
2929
"status" => $response->status(),
3030
"headers" => $response->headers(),

examples/http-server/Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ license = { workspace = true }
2121
crate-type = ["lib", "cdylib"]
2222

2323
[dependencies]
24-
hyper = { version = "0.14.23", features = ["http1", "runtime", "server"] }
24+
hyper = { version = "0.14.26", features = ["http1", "runtime", "server"] }
25+
axum = "0.6.16"
2526
phper = { version = "0.11.0", path = "../../phper" }
26-
thiserror = "1.0.37"
27-
tokio = { version = "1.22.0", features = ["full"] }
27+
thiserror = "1.0.40"
28+
tokio = { version = "1.27.0", features = ["full"] }
29+
reqwest = { version = "0.11.16", features = ["blocking"] }
2830

2931
[dev-dependencies]
3032
phper-test = { version = "0.11.0", path = "../../phper-test" }
31-
reqwest = "0.11.13"
33+
reqwest = "0.11.16"

examples/http-server/README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
Http server example.
44

5-
Power by [tokio](https://crates.io/crates/tokio) and [hyper](https://crates.io/crates/hyper).
5+
Power by [tokio](https://crates.io/crates/tokio) and [axum](https://crates.io/crates/axum).
6+
7+
Because PHP is a single threaded model (NTS), so tokio runtime uses current thread scheduler.
8+
9+
This is just a demo program, and if it want to be as powerful as `swoole`,
10+
it need to work hard on multiprocessing and asynchronous components.
611

712
## Environment
813

@@ -23,10 +28,14 @@ cargo build --release
2328
cargo test --release
2429
```
2530

26-
## Install
31+
## Run
2732

2833
```bash
29-
cp target/release/libhttp_server.so `${PHP_CONFIG:=php-config} --extension-dir`
34+
# Start web server:
35+
php -d "extension=target/release/libhttp_server.so" examples/http-server/tests/php/test.php
36+
37+
# Request:
38+
curl -i http://127.0.0.1:9000/
3039
```
3140

3241
## License

examples/http-server/src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,17 @@ use std::error::Error;
1616

1717
const EXCEPTION_CLASS_NAME: &str = "HttpServer\\HttpServerException";
1818

19+
/// Wraps any Error, implements `Throwable` and `Into<php::Error>`.
1920
#[derive(Debug, thiserror::Error)]
2021
#[error(transparent)]
2122
pub struct HttpServerError(pub Box<dyn Error>);
2223

24+
impl HttpServerError {
25+
pub fn new(e: impl Into<Box<dyn Error>>) -> Self {
26+
Self(e.into())
27+
}
28+
}
29+
2330
impl Throwable for HttpServerError {
2431
fn get_class(&self) -> &ClassEntry {
2532
ClassEntry::from_globals(EXCEPTION_CLASS_NAME).unwrap_or_else(|_| exception_class())
@@ -32,8 +39,10 @@ impl From<HttpServerError> for phper::Error {
3239
}
3340
}
3441

42+
/// Register the class `HttpServer\HttpServerException` by `ClassEntity`.
3543
pub fn make_exception_class() -> ClassEntity<()> {
3644
let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME);
45+
// As an Exception class, inheriting from the base Exception class is important.
3746
class.extends(exception_class);
3847
class
3948
}

examples/http-server/src/lib.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,22 @@ use crate::{
1313
server::make_server_class,
1414
};
1515
use phper::{modules::Module, php_get_module};
16-
use std::{mem::forget, sync::Arc};
17-
use tokio::runtime;
1816

1917
pub mod errors;
2018
pub mod request;
2119
pub mod response;
2220
pub mod server;
23-
pub mod utils;
2421

2522
#[php_get_module]
2623
pub fn get_module() -> Module {
24+
// Add module info.
2725
let mut module = Module::new(
2826
env!("CARGO_CRATE_NAME"),
2927
env!("CARGO_PKG_VERSION"),
3028
env!("CARGO_PKG_AUTHORS"),
3129
);
3230

33-
let rt = runtime::Builder::new_multi_thread()
34-
.enable_all()
35-
.build()
36-
.unwrap();
37-
let rt = Arc::new(rt);
38-
let rt_ = rt.clone();
39-
40-
module.on_module_init(move || {
41-
let guard = rt_.enter();
42-
forget(guard);
43-
});
44-
module.on_module_shutdown(move || {
45-
drop(rt);
46-
});
47-
31+
// Register classes.
4832
module.add_class(make_exception_class());
4933
module.add_class(make_server_class());
5034
module.add_class(make_request_class());

examples/http-server/src/request.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,36 @@
88
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
99
// See the Mulan PSL v2 for more details.
1010

11-
use phper::classes::{ClassEntity, Visibility};
11+
use phper::{
12+
arrays::ZArray,
13+
classes::{ClassEntity, ClassEntry, Visibility},
14+
objects::ZObject,
15+
};
16+
use std::convert::Infallible;
1217

1318
pub const HTTP_REQUEST_CLASS_NAME: &str = "HttpServer\\HttpRequest";
1419

20+
/// Register the class `HttpServer\HttpRequest` by `ClassEntity`.
1521
pub fn make_request_class() -> ClassEntity<()> {
1622
let mut class = ClassEntity::new(HTTP_REQUEST_CLASS_NAME);
1723

18-
class.add_property("header", Visibility::Public, ());
19-
class.add_property("server", Visibility::Public, ());
20-
class.add_property("data", Visibility::Private, ());
24+
// Register the http headers field with public visibility.
25+
class.add_property("headers", Visibility::Public, ());
26+
27+
// Register the http body field with public visibility.
28+
class.add_property("data", Visibility::Public, ());
29+
30+
// Register the constructor method with public visibility, initialize the
31+
// headers with empty array.
32+
class.add_method("__construct", Visibility::Public, |this, _arguments| {
33+
this.set_property("headers", ZArray::new());
34+
Ok::<_, Infallible>(())
35+
});
2136

2237
class
2338
}
39+
40+
/// New the object with class `HttpServer\HttpRequest`.
41+
pub fn new_request_object() -> phper::Result<ZObject> {
42+
ClassEntry::from_globals(HTTP_REQUEST_CLASS_NAME)?.new_object([])
43+
}

examples/http-server/src/response.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,38 +9,56 @@
99
// See the Mulan PSL v2 for more details.
1010

1111
use crate::errors::HttpServerError;
12-
use hyper::{header::HeaderName, http::HeaderValue, Body, Response};
12+
use axum::{
13+
body::Body,
14+
http::{HeaderName, HeaderValue, Response},
15+
};
1316
use phper::{
14-
classes::{ClassEntity, Visibility},
17+
classes::{ClassEntity, ClassEntry, Visibility},
1518
functions::Argument,
19+
objects::ZObject,
1620
};
1721

1822
pub const HTTP_RESPONSE_CLASS_NAME: &str = "HttpServer\\HttpResponse";
1923

24+
/// Register the class `HttpServer\HttpResponse` by `ClassEntity`, with the
25+
/// inner state `Response<Body>`.
2026
pub fn make_response_class() -> ClassEntity<Response<Body>> {
2127
let mut class = ClassEntity::new_with_default_state_constructor(HTTP_RESPONSE_CLASS_NAME);
2228

29+
// Register the header method with public visibility, accept `name` and `value`
30+
// parameters.
2331
class
2432
.add_method("header", Visibility::Public, |this, arguments| {
2533
let name = arguments[0].expect_z_str()?.to_bytes();
2634
let value = arguments[1].expect_z_str()?.to_bytes();
2735

36+
// Inject the header into inner response state.
2837
let response: &mut Response<Body> = this.as_mut_state();
2938
response.headers_mut().insert(
30-
HeaderName::from_bytes(name).map_err(|e| HttpServerError(Box::new(e)))?,
31-
HeaderValue::from_bytes(value).map_err(|e| HttpServerError(Box::new(e)))?,
39+
HeaderName::from_bytes(name).map_err(HttpServerError::new)?,
40+
HeaderValue::from_bytes(value).map_err(HttpServerError::new)?,
3241
);
42+
3343
Ok::<_, phper::Error>(())
3444
})
35-
.argument(Argument::by_val("data"));
45+
.argument(Argument::by_val("name"))
46+
.argument(Argument::by_val("value"));
3647

48+
// Register the end method with public visibility, accept `data` parameters.
3749
class
3850
.add_method("end", Visibility::Public, |this, arguments| {
51+
// Inject the body content into inner response state.
3952
let response: &mut Response<Body> = this.as_mut_state();
40-
*response.body_mut() = arguments[0].as_z_str().unwrap().to_bytes().to_vec().into();
53+
*response.body_mut() = arguments[0].expect_z_str()?.to_bytes().to_vec().into();
4154
Ok::<_, phper::Error>(())
4255
})
4356
.argument(Argument::by_val("data"));
4457

4558
class
4659
}
60+
61+
/// New the object with class `HttpServer\HttpResponse`.
62+
pub fn new_response_object() -> phper::Result<ZObject> {
63+
ClassEntry::from_globals(HTTP_RESPONSE_CLASS_NAME)?.new_object([])
64+
}

0 commit comments

Comments
 (0)