Skip to content
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
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
php: ['8.1', '8.2', '8.3', '8.4']
name: PHP ${{ matrix.php }}
steps:
- uses: actions/checkout@v4
Expand All @@ -18,10 +18,11 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: posix, pcntl
env:
phpts: ts
- name: Run tests
run: |
git clone https://github.com/markreedz/mrloop.git mrloop && \
git clone https://github.com/axboe/liburing.git liburing && cd liburing && make && sudo make install && \
cd ../ && git clone https://github.com/h2o/picohttpparser.git picohttp && \
phpize && ./configure --with-mrloop="$(pwd)/mrloop" --with-picohttp="$(pwd)/picohttp" && \
cd ../ && phpize && ./configure --with-mrloop="$(pwd)/mrloop" && \
make && make test
228 changes: 1 addition & 227 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,16 @@ PHP has, in recent years, seen an emergence of eventware built atop potent multi
- Linux Kernel 5.4.1 or newer
- [mrloop](https://github.com/markreedz/mrloop)
- [liburing](https://github.com/axboe/liburing)
- [picohttpparser](https://github.com/h2o/picohttpparser)

## Installation

It is important to have all the aforelisted requirements at the ready before attempting to install `ext-mrloop`. The directives in the snippet to follow should allow you to build the extension's shared object file (`mrloop.so`).

```sh
$ git clone https://github.com/ace411/mrloop.git <mrloop-dir>
$ git clone https://github.com/h2o/picohttpparser.git <picohttp-dir>
$ git clone https://github.com/ringphp/php-mrloop.git <dir>
$ cd <dir>
$ ./configure --with-mrloop=<mrloop-dir> --with-picohttp=<picohttp-dir>
$ phpize && ./configure --with-mrloop=<mrloop-dir>
$ make && sudo make install
```

Expand Down Expand Up @@ -64,8 +62,6 @@ class Mrloop
callable $callback,
): void
public writev(int|resource $fd, string $message): void
public static parseHttpRequest(string $request, int $headerlimit = 100): iterable
public static parseHttpResponse(string $response, int $headerlimit = 100): iterable
public addTimer(float $interval, callable $callback): void
public addPeriodicTimer(float $interval, callable $callback): void
public futureTick(callable $callback): void
Expand All @@ -80,8 +76,6 @@ class Mrloop
- [`Mrloop::addWriteStream`](#mrloopaddwritestream)
- [`Mrloop::tcpServer`](#mrlooptcpserver)
- [`Mrloop::writev`](#mrloopwritev)
- [`Mrloop::parseHttpRequest`](#mrloopparsehttprequest)
- [`Mrloop::parseHttpResponse`](#mrloopparsehttpresponse)
- [`Mrloop::addTimer`](#mrloopaddtimer)
- [`Mrloop::addPeriodicTimer`](#mrloopaddperiodictimer)
- [`Mrloop::futureTick`](#mrloopfuturetick)
Expand Down Expand Up @@ -378,226 +372,6 @@ Listening on port 8080

```

### `Mrloop::parseHttpRequest`

```php
public static Mrloop::parseHttpRequest(
string $request,
int $headerlimit = 100,
): iterable
```

Parses an HTTP request.

> This is a function that utilizes the `picohttpparser` API.

**Parameter(s)**

- **request** (string) - The HTTP request to parse.
- **headerlimit** (int) - The number of headers to parse.
> The default limit is `100`.

**Return value(s)**

The parser will throw an exception in the event that an invalid HTTP request is encountered and will output a hashtable with the contents enumerated below otherwise.

- **body** (string) - The request body.
- **headers** (iterable) - An associative array containing request headers.
- **method** (string) - The request method.
- **path** (string) - The request path.

```php
use ringphp\Mrloop;

$loop = Mrloop::init();

$loop->tcpServer(
8080,
null,
null,
function (mixed ...$args) {
[$message,] = $args;
$response = static fn (
string $message,
int $code = 200,
string $mime = 'text/plain',
) =>
\sprintf(
"HTTP/1.1 %d %s\r\ncontent-type: %s\r\ncontent-length: %d\r\n\r\n%s\r\n",
$code,
($code === 200 ? 'OK' : 'Internal Server Error'),
$mime,
\strlen($message),
$message,
);

try {
$request = Mrloop::parseHttpRequest($message);

return $response('Hello, user');
} catch (\Throwable $err) {
return $response(
'HTTP parser error',
500,
);
}
},
);

$loop->run();
```

The example above will produce output similar to that in the snippet to follow.

```
Listening on port 8080

```

### `Mrloop::parseHttpResponse`

```php
public static Mrloop::parseHttpResponse(
string $response,
int $headerlimit = 100,
): iterable
```

Parses an HTTP response.

> This function also utilizes the `picohttpparser` API.

**Parameter(s)**

- **response** (string) - The HTTP response to parse.
- **headerlimit** (int) - The number of headers to parse.
> The default limit is `100`.

**Return value(s)**

The parser will throw an exception in the event that an invalid HTTP response is encountered and will output a hashtable with the contents enumerated below otherwise.

- **body** (string) - The response body.
- **headers** (iterable) - An associative array containing response headers.
- **status** (int) - The response status code.
- **reason** (string) - The response reason phrase.

```php
use ringphp\Mrloop;

$loop = Mrloop::init();

$loop->addWriteStream(
$sock = \stream_socket_client('tcp://www.example.com:80'),
"GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n",
null,
function ($nbytes) use ($loop, $sock) {
$loop->addReadStream(
$sock,
null,
null,
null,
function ($data, $res) use ($sock, $loop) {
var_dump(Mrloop::parseHttpResponse($data));

\fclose($sock);
},
);
},
);

$loop->run();
```

The example above will produce output similar to that in the snippet to follow.

```
array(4) {
["reason"]=>
string(2) "OK"
["status"]=>
int(200)
["body"]=>
string(1256) "<!doctype html>
<html>
<head>
<title>Example Domain</title>

<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;

}
div {
width: 600px;
margin: 5em auto;
padding: 2em;
background-color: #fdfdff;
border-radius: 0.5em;
box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
}
a:link, a:visited {
color: #38488f;
text-decoration: none;
}
@media (max-width: 700px) {
div {
margin: 0 auto;
width: auto;
}
}
</style>
</head>

<body>
<div>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples in documents. You may use this
domain in literature without prior coordination or asking for permission.</p>
<p><a href="https://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
"
["headers"]=>
array(13) {
["Accept-Ranges"]=>
string(5) "bytes"
["Age"]=>
string(6) "506325"
["Cache-Control"]=>
string(14) "max-age=604800"
["Content-Type"]=>
string(24) "text/html; charset=UTF-8"
["Date"]=>
string(29) "Wed, 30 Oct 2024 15:37:43 GMT"
["Etag"]=>
string(17) ""3147526947+gzip""
["Expires"]=>
string(29) "Wed, 06 Nov 2024 15:37:43 GMT"
["Last-Modified"]=>
string(29) "Thu, 17 Oct 2019 07:18:26 GMT"
[""]=>
string(16) "ECAcc (dcd/7D5A)"
["Vary"]=>
string(15) "Accept-Encoding"
["X-Cache"]=>
string(3) "HIT"
["Content-Length"]=>
string(4) "1256"
["Connection"]=>
string(5) "close"
}
}

```

### `Mrloop::addTimer`

```php
Expand Down
18 changes: 2 additions & 16 deletions config.m4
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
dnl ext-mrloop config.m4 file
dnl mrloop extension for PHP (c) 2024 Lochemem Bruno Michael

dnl PHP_ARG_ENABLE([mrloop],
dnl [for mrloop support],
Expand All @@ -11,12 +11,6 @@ PHP_ARG_WITH([mrloop],
[specify path to mrloop library])],
[no])

PHP_ARG_WITH([picohttp],
[for picohttp library],
[AS_HELP_STRING([--with-picohttp],
[specify path to picohttp library])],
[no])

if test "$PHP_MRLOOP" != "no"; then
dnl add PHP version check
PHP_VERSION=$($PHP_CONFIG --vernum)
Expand Down Expand Up @@ -51,15 +45,7 @@ if test "$PHP_MRLOOP" != "no"; then
AC_MSG_ERROR(Please download mrloop)
fi

AC_MSG_CHECKING([for picohttpparser package])
if test -s "$PHP_PICOHTTP/picohttpparser.c"; then
AC_MSG_RESULT(found picohttpparser package)
else
AC_MSG_RESULT(picohttpparser is not downloaded)
AC_MSG_ERROR(Please download picohttpparser)
fi

CFLAGS="-g -O3 -luring -I$PHP_MRLOOP/ -I$PHP_PICOHTTP/"
CFLAGS="-g -O3 -luring -I$PHP_MRLOOP/"
AC_DEFINE(HAVE_MRLOOP, 1, [ Have mrloop support ])

PHP_NEW_EXTENSION(mrloop, php_mrloop.c, $ext_shared)
Expand Down
26 changes: 6 additions & 20 deletions mrloop_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nbytes, IS_LONG, 0, "null")
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_parseHttpRequest, 0, 0, 2)
ZEND_ARG_TYPE_INFO(0, request, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, header_limit, IS_LONG, 0, "100")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_parseHttpResponse, 0, 0, 2)
ZEND_ARG_TYPE_INFO(0, response, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, header_limit, IS_LONG, 0, "100")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Mrloop_addSignal, 0, 0, 2)
ZEND_ARG_TYPE_INFO(0, signal, IS_LONG, 0)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
Expand Down Expand Up @@ -70,11 +60,9 @@ ZEND_METHOD(Mrloop, run);
ZEND_METHOD(Mrloop, addTimer);
ZEND_METHOD(Mrloop, addPeriodicTimer);
ZEND_METHOD(Mrloop, tcpServer);
ZEND_METHOD(Mrloop, parseHttpRequest);
ZEND_METHOD(Mrloop, addSignal);
ZEND_METHOD(Mrloop, addReadStream);
ZEND_METHOD(Mrloop, addWriteStream);
ZEND_METHOD(Mrloop, parseHttpResponse);
ZEND_METHOD(Mrloop, writev);
ZEND_METHOD(Mrloop, futureTick);

Expand All @@ -85,11 +73,9 @@ static const zend_function_entry class_Mrloop_methods[] = {
PHP_ME(Mrloop, addTimer, arginfo_class_Mrloop_addTimer, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, addPeriodicTimer, arginfo_class_Mrloop_addPeriodicTimer, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, tcpServer, arginfo_class_Mrloop_tcpServer, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, parseHttpRequest, arginfo_class_Mrloop_parseHttpRequest, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(Mrloop, addSignal, arginfo_class_Mrloop_addSignal, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, addReadStream, arginfo_class_Mrloop_addReadStream, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, addWriteStream, arginfo_class_Mrloop_addWriteStream, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, parseHttpResponse, arginfo_class_Mrloop_parseHttpResponse, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
PHP_ME(Mrloop, writev, arginfo_class_Mrloop_writev, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, futureTick, arginfo_class_Mrloop_futureTick, ZEND_ACC_PUBLIC)
PHP_FE_END};
PHP_ME(Mrloop, addSignal, arginfo_class_Mrloop_addSignal, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, addReadStream, arginfo_class_Mrloop_addReadStream, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, addWriteStream, arginfo_class_Mrloop_addWriteStream, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, writev, arginfo_class_Mrloop_writev, ZEND_ACC_PUBLIC)
PHP_ME(Mrloop, futureTick, arginfo_class_Mrloop_futureTick, ZEND_ACC_PUBLIC)
PHP_FE_END};
14 changes: 0 additions & 14 deletions php_mrloop.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,6 @@ PHP_METHOD(Mrloop, tcpServer)
}
/* }}} */

/* {{{ proto array Mrloop::parseHttpRequest( string request [, int headerlimit = 100 ] ) */
PHP_METHOD(Mrloop, parseHttpRequest)
{
php_mrloop_parse_http_request(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */

/* {{{ proto array Mrloop::parseHttpResponse( string request [, int headerlimit = 100 ] ) */
PHP_METHOD(Mrloop, parseHttpResponse)
{
php_mrloop_parse_http_response(INTERNAL_FUNCTION_PARAM_PASSTHRU);
}
/* }}} */

/* {{{ proto void Mrloop::addSignal( int signal [, callable callback ] ) */
PHP_METHOD(Mrloop, addSignal)
{
Expand Down
Loading
Loading