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: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ More examples, including the Go server side, can be found in [this directory](./

### Codecs

JSON is the default codec. Use `withCodec()` to switch to `MsgpackCodec`, `ProtobufCodec` or `RawCodec` from the `Spiral\Goridge\RPC\Codec` namespace.
JSON is the default codec. Use `withCodec()` to switch to `ProtobufCodec` or `RawCodec` from the `Spiral\Goridge\RPC\Codec` namespace.

> [!WARNING]
> `MsgpackCodec` is deprecated and will be removed in the next major version: RoadRunner 3 (Goridge v4) does not support MessagePack.

## Features

Expand All @@ -71,7 +74,7 @@ JSON is the default codec. Use `withCodec()` to switch to `MsgpackCodec`, `Proto
- very fast (300k calls per second on Ryzen 1700X over 20 threads)
- native `net/rpc` integration, ability to connect to existing application(s)
- standalone protocol usage
- structured data transfer using JSON, MessagePack or Protobuf, and raw payloads
- structured data transfer using JSON or Protobuf, and raw payloads
- `[]byte` transfer, including big payloads
- asynchronous calls over a pool of relays with `MultiRPC`
- service, message and transport level error handling
Expand Down
5 changes: 1 addition & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"relay",
"socket",
"pipes",
"msgpack",
"protobuf"
],
"license": "MIT",
Expand Down Expand Up @@ -72,10 +71,8 @@
"spiral/goridge": "self.version"
},
"suggest": {
"ext-msgpack": "MessagePack codec support",
"ext-protobuf": "Protobuf codec support",
"google/protobuf": "(^3.22 || ^4.0 || ^5.0) Protobuf codec support",
"rybakit/msgpack": "(^0.7) MessagePack codec support"
"google/protobuf": "(^3.22 || ^4.0 || ^5.0) Protobuf codec support"
},
"autoload": {
"psr-4": {
Expand Down
5 changes: 4 additions & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.19.2@fa716e443b23e6fa35bf58907e67ca692ec3c00a">
<files psalm-version="6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c">
<file src="src/RPC/Codec/MsgpackCodec.php">
<DeprecatedConstant>
<code><![CDATA[Frame::CODEC_MSGPACK]]></code>
</DeprecatedConstant>
<MissingClosureParamType>
<code><![CDATA[$options]]></code>
<code><![CDATA[$options]]></code>
Expand Down
7 changes: 7 additions & 0 deletions src/Frame.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,14 @@
*/
public const CODEC_RAW = 0x04;
public const CODEC_JSON = 0x08;

/**
* @deprecated RoadRunner 3 (Goridge v4) does not support MessagePack: the server drops the connection
* on a frame with this flag. Use {@see self::CODEC_JSON} (the default) or {@see self::CODEC_PROTO}
* instead. The constant will be removed in the next major version.
*/
public const CODEC_MSGPACK = 0x10;

public const CODEC_GOB = 0x20;
public const CODEC_PROTO = 0x80;
/*#@-*/
Expand Down Expand Up @@ -79,7 +86,7 @@
/** @var non-empty-string $header */
$header = \pack(
'CCL',
self::VERSION << 4 | (\count($frame->options) + 3),

Check warning on line 89 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "ShiftLeft": @@ @@ /** @var non-empty-string $header */ $header = \pack( 'CCL', - self::VERSION << 4 | (\count($frame->options) + 3), + self::VERSION >> 4 | (\count($frame->options) + 3), $frame->flags, \strlen((string) $frame->payload), );

Check warning on line 89 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "IncrementInteger": @@ @@ /** @var non-empty-string $header */ $header = \pack( 'CCL', - self::VERSION << 4 | (\count($frame->options) + 3), + self::VERSION << 5 | (\count($frame->options) + 3), $frame->flags, \strlen((string) $frame->payload), );
$frame->flags,
\strlen((string) $frame->payload),
);
Expand All @@ -90,7 +97,7 @@
$header .= \pack('LCC', \crc32($header), $frame->byte10, $frame->byte11);
}

return $header . (string) $frame->payload;

Check warning on line 100 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "CastString": @@ @@ $header .= \pack('LCC', \crc32($header), $frame->byte10, $frame->byte11); } - return $header . (string) $frame->payload; + return $header . $frame->payload; } /**
}

/**
Expand Down Expand Up @@ -121,7 +128,7 @@
*/
public static function initFrame(array $header, string $body): Frame
{
$unpacked = \unpack('L*', \substr($body, 0, $header[1] * 4));

Check warning on line 131 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "IncrementInteger": @@ @@ */ public static function initFrame(array $header, string $body): Frame { - $unpacked = \unpack('L*', \substr($body, 0, $header[1] * 4)); + $unpacked = \unpack('L*', \substr($body, 0, $header[1] * 5)); /** * optimize?

/**
* optimize?
Expand All @@ -130,8 +137,8 @@
$options = $unpacked === false ? [] : \array_values($unpacked);

$frame = new self(\substr($body, $header[1] * 4), $options, $header[0]);
$frame->byte10 = $header[3] ?? 0;

Check warning on line 140 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "IncrementInteger": @@ @@ $options = $unpacked === false ? [] : \array_values($unpacked); $frame = new self(\substr($body, $header[1] * 4), $options, $header[0]); - $frame->byte10 = $header[3] ?? 0; + $frame->byte10 = $header[3] ?? 1; $frame->byte11 = $header[4] ?? 0; return $frame;

Check warning on line 140 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "DecrementInteger": @@ @@ $options = $unpacked === false ? [] : \array_values($unpacked); $frame = new self(\substr($body, $header[1] * 4), $options, $header[0]); - $frame->byte10 = $header[3] ?? 0; + $frame->byte10 = $header[3] ?? -1; $frame->byte11 = $header[4] ?? 0; return $frame;
$frame->byte11 = $header[4] ?? 0;

Check warning on line 141 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "IncrementInteger": @@ @@ $frame = new self(\substr($body, $header[1] * 4), $options, $header[0]); $frame->byte10 = $header[3] ?? 0; - $frame->byte11 = $header[4] ?? 0; + $frame->byte11 = $header[4] ?? 1; return $frame; }

Check warning on line 141 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "DecrementInteger": @@ @@ $frame = new self(\substr($body, $header[1] * 4), $options, $header[0]); $frame->byte10 = $header[3] ?? 0; - $frame->byte11 = $header[4] ?? 0; + $frame->byte11 = $header[4] ?? -1; return $frame; }

return $frame;
}
Expand All @@ -139,7 +146,7 @@
public function setFlag(int ...$flag): void
{
foreach ($flag as $f) {
if ($f > 255) {

Check warning on line 149 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "GreaterThan": @@ @@ public function setFlag(int ...$flag): void { foreach ($flag as $f) { - if ($f > 255) { + if ($f >= 255) { throw new InvalidArgumentException('Flags can be byte only'); }
throw new InvalidArgumentException('Flags can be byte only');
}

Expand All @@ -149,7 +156,7 @@

public function hasFlag(int $flag): bool
{
if ($flag > 255) {

Check warning on line 159 in src/Frame.php

View workflow job for this annotation

GitHub Actions / Infection

Escaped Mutant for Mutator "GreaterThan": @@ @@ public function hasFlag(int $flag): bool { - if ($flag > 255) { + if ($flag >= 255) { throw new InvalidArgumentException('Flags can be byte only'); }
throw new InvalidArgumentException('Flags can be byte only');
}

Expand Down
13 changes: 13 additions & 0 deletions src/RPC/Codec/MsgpackCodec.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@
/**
* @psalm-type PackHandler = \Closure(mixed): string
* @psalm-type UnpackHandler = \Closure(string, mixed|null): mixed
*
* @deprecated RoadRunner 3 (Goridge v4) does not support MessagePack: the server drops the connection
* on a MessagePack frame. Use the default {@see JsonCodec} or {@see ProtobufCodec} instead.
* The codec will be removed in the next major version.
*/
final class MsgpackCodec implements CodecInterface
{
private const DEPRECATION = 'Spiral\Goridge\RPC\Codec\MsgpackCodec is deprecated: RoadRunner 3 (Goridge v4)'
. ' does not support MessagePack. Use JsonCodec or ProtobufCodec instead.'
. ' The codec will be removed in the next major version.';

/**
* @var PackHandler
* @psalm-suppress PropertyNotSetInConstructor Reason: Initialized via {@see initPacker()}
Expand All @@ -31,6 +39,9 @@ final class MsgpackCodec implements CodecInterface
*/
public function __construct()
{
// Silenced: the notice reaches only error handlers that collect deprecations, the default one stays quiet.
@\trigger_error(self::DEPRECATION, \E_USER_DEPRECATED);

$this->initPacker();
}

Expand Down Expand Up @@ -76,6 +87,8 @@ private function initPacker(): void
$this->pack = static fn(mixed $payload): string => MessagePack::pack($payload);

$this->unpack = static fn(string $payload, $options = null): mixed => MessagePack::unpack($payload, $options);

return;
}

throw new \LogicException('Could not initialize codec, please install msgpack extension or library');
Expand Down
43 changes: 0 additions & 43 deletions tests/Goridge/MsgPackMultiRPCTest.php

This file was deleted.

32 changes: 19 additions & 13 deletions tests/Goridge/MsgPackRPCTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,35 @@

namespace Spiral\Goridge\Tests;

use Spiral\Goridge\Exception\TransportException;
use Spiral\Goridge\RPC\Codec\MsgpackCodec;
use Spiral\Goridge\RPC\Exception\ServiceException;
use Spiral\Goridge\RPC\RPC;
use Testo\Expect;
use Spiral\Goridge\RPC\RPC as GoridgeRPC;
use Spiral\Goridge\SocketRelay;
use Testo\Assert;
use Testo\Test;

/**
* Goridge v4 refuses MessagePack frames, which is why {@see MsgpackCodec} is deprecated.
*/
#[Test]
final class MsgPackRPCTest extends \Spiral\Goridge\Tests\RPC
final class MsgPackRPCTest
{
/**
* @throws \Exception
*/
public function testJsonException(): void
public function testServerDropsConnectionOnMsgpackFrame(): void
{
Expect::exception(ServiceException::class);
$rpc = (new GoridgeRPC($this->makeRelay()))->withCodec(new MsgpackCodec());

$conn = $this->makeRPC();
try {
$rpc->call('Service.Ping', 'ping');
Assert::fail('The server should have refused the MessagePack frame');
} catch (TransportException $e) {
Assert::notBlank($e->getMessage());
}

$conn->call('Service.Process', random_bytes(256));
Assert::same((new GoridgeRPC($this->makeRelay()))->call('Service.Ping', 'ping'), 'pong');
}

protected function makeRPC(): RPC
private function makeRelay(): SocketRelay
{
return (new RPC($this->makeRelay()))->withCodec(new MsgpackCodec());
return new SocketRelay(RPC::SOCK_ADDR, RPC::SOCK_PORT, RPC::SOCK_TYPE);
}
}
21 changes: 19 additions & 2 deletions tests/Goridge/MsgpackCodecTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use Testo\Assert;
use Testo\Codecov\Covers;
use Testo\Core\Exception\SkipTest;
use Testo\Skip;
use Testo\Test;

#[Test]
Expand All @@ -29,7 +28,6 @@ public function testRoundTripWithExtension(): void
Assert::same($codec->decode($codec->encode($payload)), $payload);
}

#[Skip('MsgpackCodec::initPacker() lacks a return after the rybakit/msgpack branch, so without ext-msgpack the constructor always throws LogicException')]
public function testRoundTripWithLibrary(): void
{
if (\extension_loaded('msgpack')) {
Expand All @@ -42,4 +40,23 @@ public function testRoundTripWithLibrary(): void
Assert::same($codec->getIndex(), Frame::CODEC_MSGPACK);
Assert::same($codec->decode($codec->encode($payload)), $payload);
}

public function testConstructorTriggersDeprecation(): void
{
$notices = [];
\set_error_handler(static function (int $level, string $message) use (&$notices): bool {
$notices[] = [$level, $message];
return true;
});

try {
new MsgpackCodec();
} finally {
\restore_error_handler();
}

Assert::count($notices, 1);
Assert::same($notices[0][0], \E_USER_DEPRECATED);
Assert::string($notices[0][1])->contains('MsgpackCodec is deprecated');
}
}
24 changes: 3 additions & 21 deletions tests/Goridge/MultiRPC.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
use Spiral\Goridge\ConnectedRelayInterface;
use Spiral\Goridge\Exception\TransportException;
use Spiral\Goridge\RelayInterface;
use Spiral\Goridge\RPC\Codec\JsonCodec;
use Spiral\Goridge\RPC\Codec\MsgpackCodec;
use Spiral\Goridge\RPC\Codec\RawCodec;
use Spiral\Goridge\RPC\Exception\CodecException;
use Spiral\Goridge\RPC\Exception\RPCException;
Expand Down Expand Up @@ -700,10 +698,7 @@ public function testHandlesCloneCorrectly(): void
{
$this->rpc->preConnectRelays();

// This is to support the MsgPackMultiRPC Tests
$property = new \ReflectionProperty(GoridgeMultiRPC::class, 'codec');
$codec = $property->getValue($this->rpc);
$clonedRpc = $this->rpc->withCodec($codec instanceof MsgpackCodec ? new JsonCodec() : new MsgpackCodec());
$clonedRpc = $this->rpc->withCodec(new RawCodec());

$property = new \ReflectionProperty(GoridgeMultiRPC::class, 'freeRelays');
foreach ($property->getValue() as $relay) {
Expand All @@ -719,27 +714,14 @@ public function testHandlesCloneCorrectly(): void
}

for ($i = 0; $i < 50; $i++) {
$clonedIds[] = $clonedRpc->callAsync('Service.Echo', 'Hello');
$clonedIds[] = $clonedRpc->callAsync('Service.EchoBinary', 'Hello');
}
// Wait 100ms for the response(s)
usleep(100 * 1000);

// Can use wrong RPC for response (unfortunately, but there's no easy solution)
try {
$response = $this->rpc->getResponse($clonedIds[0]);
$property = new \ReflectionProperty(GoridgeMultiRPC::class, 'codec');

if ($property->getValue($this->rpc) instanceof MsgpackCodec) {
// Msgpack internally does not throw an error, only returns the encoded response because of course why
// would normal error handling be something that is important in a library.
// Locally this returned the number 34, but I'm not sure if there's some variation in that
// so we test on the expected response.
// This also notifies PHPUnit since msgpack logs a warning.
if ($response !== 'Hello') {
throw new CodecException("msgpack is a big meany");
}
}

$this->rpc->getResponse($clonedIds[0]);
\Testo\Assert::fail("Should've thrown an Exception due to wrong codec");
} catch (CodecException $exception) {
\Testo\Assert::false(empty($exception->getMessage()));
Expand Down
2 changes: 1 addition & 1 deletion tests/test-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Image page: <https://hub.docker.com/_/golang>
FROM golang:1.22-alpine as builder
FROM golang:1.27-alpine as builder

# Copy server sources into image
COPY ./go.mod ./go.sum ./server.go /src/
Expand Down
11 changes: 4 additions & 7 deletions tests/test-server/go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
module test_server

go 1.22.2
go 1.27

require github.com/roadrunner-server/goridge/v3 v3.8.2
require github.com/roadrunner-server/goridge/v4 v4.0.0

require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/roadrunner-server/errors v1.4.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
google.golang.org/protobuf v1.34.0 // indirect
github.com/roadrunner-server/errors v1.5.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
)
36 changes: 12 additions & 24 deletions tests/test-server/go.sum
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/roadrunner-server/errors v1.4.0 h1:Odjg3VZrj1q5Y8ILwoN+JgERyv0pkhrWPNOM4h68iQ8=
github.com/roadrunner-server/errors v1.4.0/go.mod h1:78PvraAFj+Sxy5nDmo0S+h6rEMLFIDszWZxA3B0sPAs=
github.com/roadrunner-server/goridge/v3 v3.8.2 h1:4TpIJAMylMIVTva/L/STB4ZvYNVoQ77+Syr6abxj95c=
github.com/roadrunner-server/goridge/v3 v3.8.2/go.mod h1:7IIDW50j1saxnOxktFeUPpkSIfyvM/dYopTrbGWXboA=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/roadrunner-server/errors v1.5.0 h1:unG7LKIZrSzkCCF3YLRLA5VyqE0KKomofXVJUXJe00g=
github.com/roadrunner-server/errors v1.5.0/go.mod h1:g9fo/T2C13cWRDR9PW1r0ZAOSQfNhWAZawyfkGiaHuI=
github.com/roadrunner-server/goridge/v4 v4.0.0 h1:hyhDQggB8wYfT6tE3yso8lezOprz9BFrkjjzglAlG2Q=
github.com/roadrunner-server/goridge/v4 v4.0.0/go.mod h1:ahS0+nR0wRB64a8ejYaBNIjks88zEMX04Ksg1tVm/Dk=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
2 changes: 1 addition & 1 deletion tests/test-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"strings"
"time"

goridgeRpc "github.com/roadrunner-server/goridge/v3/pkg/rpc"
goridgeRpc "github.com/roadrunner-server/goridge/v4/pkg/rpc"
)

// Service
Expand Down
Loading