Skip to content

Fix object state and improve http server example. #115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/http-client/tests/php/test.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
->cookie_store(true)
->build();

$response = $client->get("https://httpbin.org/ip")->send();
$response = $client->get("https://example.com/")->send();
var_dump([
"status" => $response->status(),
"headers" => $response->headers(),
Expand Down
10 changes: 6 additions & 4 deletions examples/http-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ license = { workspace = true }
crate-type = ["lib", "cdylib"]

[dependencies]
hyper = { version = "0.14.23", features = ["http1", "runtime", "server"] }
hyper = { version = "0.14.26", features = ["http1", "runtime", "server"] }
axum = "0.6.16"
phper = { version = "0.11.0", path = "../../phper" }
thiserror = "1.0.37"
tokio = { version = "1.22.0", features = ["full"] }
thiserror = "1.0.40"
tokio = { version = "1.27.0", features = ["full"] }
reqwest = { version = "0.11.16", features = ["blocking"] }

[dev-dependencies]
phper-test = { version = "0.11.0", path = "../../phper-test" }
reqwest = "0.11.13"
reqwest = "0.11.16"
15 changes: 12 additions & 3 deletions examples/http-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

Http server example.

Power by [tokio](https://crates.io/crates/tokio) and [hyper](https://crates.io/crates/hyper).
Power by [tokio](https://crates.io/crates/tokio) and [axum](https://crates.io/crates/axum).

Because PHP is a single threaded model (NTS), so tokio runtime uses current thread scheduler.

This is just a demo program, and if it want to be as powerful as `swoole`,
it need to work hard on multiprocessing and asynchronous components.

## Environment

Expand All @@ -23,10 +28,14 @@ cargo build --release
cargo test --release
```

## Install
## Run

```bash
cp target/release/libhttp_server.so `${PHP_CONFIG:=php-config} --extension-dir`
# Start web server:
php -d "extension=target/release/libhttp_server.so" examples/http-server/tests/php/test.php

# Request:
curl -i http://127.0.0.1:9000/
```

## License
Expand Down
9 changes: 9 additions & 0 deletions examples/http-server/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@ use std::error::Error;

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

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

impl HttpServerError {
pub fn new(e: impl Into<Box<dyn Error>>) -> Self {
Self(e.into())
}
}

impl Throwable for HttpServerError {
fn get_class(&self) -> &ClassEntry {
ClassEntry::from_globals(EXCEPTION_CLASS_NAME).unwrap_or_else(|_| exception_class())
Expand All @@ -32,8 +39,10 @@ impl From<HttpServerError> for phper::Error {
}
}

/// Register the class `HttpServer\HttpServerException` by `ClassEntity`.
pub fn make_exception_class() -> ClassEntity<()> {
let mut class = ClassEntity::new(EXCEPTION_CLASS_NAME);
// As an Exception class, inheriting from the base Exception class is important.
class.extends(exception_class);
class
}
20 changes: 2 additions & 18 deletions examples/http-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,22 @@ use crate::{
server::make_server_class,
};
use phper::{modules::Module, php_get_module};
use std::{mem::forget, sync::Arc};
use tokio::runtime;

pub mod errors;
pub mod request;
pub mod response;
pub mod server;
pub mod utils;

#[php_get_module]
pub fn get_module() -> Module {
// Add module info.
let mut module = Module::new(
env!("CARGO_CRATE_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_AUTHORS"),
);

let rt = runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
let rt = Arc::new(rt);
let rt_ = rt.clone();

module.on_module_init(move || {
let guard = rt_.enter();
forget(guard);
});
module.on_module_shutdown(move || {
drop(rt);
});

// Register classes.
module.add_class(make_exception_class());
module.add_class(make_server_class());
module.add_class(make_request_class());
Expand Down
28 changes: 24 additions & 4 deletions examples/http-server/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,36 @@
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

use phper::classes::{ClassEntity, Visibility};
use phper::{
arrays::ZArray,
classes::{ClassEntity, ClassEntry, Visibility},
objects::ZObject,
};
use std::convert::Infallible;

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

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

class.add_property("header", Visibility::Public, ());
class.add_property("server", Visibility::Public, ());
class.add_property("data", Visibility::Private, ());
// Register the http headers field with public visibility.
class.add_property("headers", Visibility::Public, ());

// Register the http body field with public visibility.
class.add_property("data", Visibility::Public, ());

// Register the constructor method with public visibility, initialize the
// headers with empty array.
class.add_method("__construct", Visibility::Public, |this, _arguments| {
this.set_property("headers", ZArray::new());
Ok::<_, Infallible>(())
});

class
}

/// New the object with class `HttpServer\HttpRequest`.
pub fn new_request_object() -> phper::Result<ZObject> {
ClassEntry::from_globals(HTTP_REQUEST_CLASS_NAME)?.new_object([])
}
30 changes: 24 additions & 6 deletions examples/http-server/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,56 @@
// See the Mulan PSL v2 for more details.

use crate::errors::HttpServerError;
use hyper::{header::HeaderName, http::HeaderValue, Body, Response};
use axum::{
body::Body,
http::{HeaderName, HeaderValue, Response},
};
use phper::{
classes::{ClassEntity, Visibility},
classes::{ClassEntity, ClassEntry, Visibility},
functions::Argument,
objects::ZObject,
};

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

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

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

// Inject the header into inner response state.
let response: &mut Response<Body> = this.as_mut_state();
response.headers_mut().insert(
HeaderName::from_bytes(name).map_err(|e| HttpServerError(Box::new(e)))?,
HeaderValue::from_bytes(value).map_err(|e| HttpServerError(Box::new(e)))?,
HeaderName::from_bytes(name).map_err(HttpServerError::new)?,
HeaderValue::from_bytes(value).map_err(HttpServerError::new)?,
);

Ok::<_, phper::Error>(())
})
.argument(Argument::by_val("data"));
.argument(Argument::by_val("name"))
.argument(Argument::by_val("value"));

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

class
}

/// New the object with class `HttpServer\HttpResponse`.
pub fn new_response_object() -> phper::Result<ZObject> {
ClassEntry::from_globals(HTTP_RESPONSE_CLASS_NAME)?.new_object([])
}
Loading