diff --git a/.php_cs b/.php_cs index 6d2c8f7..79f16f9 100644 --- a/.php_cs +++ b/.php_cs @@ -33,7 +33,7 @@ return PhpCsFixer\Config::create() 'blank_line_before_return' => true, 'cast_spaces' => true, 'class_definition' => ['singleLine' => true], - 'concat_space' => ['spacing' => "none"], // Different from symfony (none) + 'concat_space' => ['spacing' => "none"], 'declare_equal_normalize' => true, 'function_typehint_space' => true, 'hash_to_slash_comment' => true, diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9a026..e4ef54c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## 2.4.0 (2017-09-19) + +### Added + +- Added support for basic authentication in connection servlet_address. + + ```php + 'http://user:pass@localhost:8080/MyJavaBridge/servlet.phpjavabridge' + ]); + ``` + +- Added `AuthenticationException` (ConnectionException). + +- Logger now logs java exceptions (NoSuchProcedureException,... at error level). + +### Fixed + +- Notice and warnings when a BrokenConnection is encountered. + ## 2.3.0 (2017-09-19) ### Improved diff --git a/doc/bridge_connection.md b/doc/bridge_connection.md index 0bde552..eebd891 100644 --- a/doc/bridge_connection.md +++ b/doc/bridge_connection.md @@ -19,7 +19,7 @@ use Soluble\Japha\Bridge\Adapter as BridgeAdapter; use Soluble\Japha\Bridge\Exception as BridgeException; $options = [ - 'servlet_address' => 'localhost:8080/MyJavaBridge/servlet.phpjavabridge' + 'servlet_address' => 'http://localhost:8080/MyJavaBridge/servlet.phpjavabridge' ]; try { @@ -38,11 +38,11 @@ The `Soluble\Japha\Bridge\Adapter` constructor requires `$options`, an associati | Parameter | Description | |------------------|------------------------------------------| -|`servlet_address` | Servlet address: <host>:<port>/<uri> | -|`driver` | (Optional) defaults to `pjb62` driver implementation. | +|`servlet_address` | In the form: `http(s)://://servlet.phpjavabridge` | + !!! tip - The `servlet_address` <uri> should ends with the **'servlet.phpjavabridge'** file, - i.e: 'localhost:8080/path/servlet.phpjavabridge'. + Since v2.4.0, you can also provide basic auth in the `servlet_address`, i.e. + `http://user:password@localhost:8083/JavaBridge/servlet.phpjavabridge`. | Advanced params | Description | @@ -51,6 +51,7 @@ The `Soluble\Japha\Bridge\Adapter` constructor requires `$options`, an associati |`java_recv_size` | Socket read buffer, by default `8192`. | |`java_log_level` | To enable java side logging level, by default `null`. | |`force_simple_xml_parser` | By default `false`: force the Use the php xml parser instead of native xml_parser(). | +|`driver` | Defaults to `pjb62` driver implementation. | |`java_prefer_values` | By default `true`, see warning below. | @@ -106,6 +107,7 @@ During initialization with the BridgeAdapter, the following exceptions could hap | ExceptionClass | Description | |------------------------------------------|-----------------------------| |`Soluble\Japha\Bridge\Exception\ConnectionException` | Server not available *(network port is unreachable)* | +|`Soluble\Japha\Bridge\Exception\AuthenticationException` | Invalid credentials given in basic auth *(check config)* | |`Soluble\Japha\Bridge\Exception\ConfigurationException` | Invalid connection parameter *(check config)* | |`Soluble\Japha\Bridge\Exception\UnsupportedDriverException` | Specified driver is not supported *(check config)* | |`Soluble\Japha\Bridge\Exception\InvalidArgumentException` | Invalid argument in constructor *(check usage)* | diff --git a/doc/language_exceptions.md b/doc/language_exceptions.md index bc0e9c7..ea512cc 100644 --- a/doc/language_exceptions.md +++ b/doc/language_exceptions.md @@ -15,6 +15,9 @@ the Java/JVM exception, you can use the following methods: For example: java.lang.java.lang.NoSuchMethodException, ... - `JavaException::getStackTrace()` will give you the JVM stacktrace. +!!! tip + From version 2.4.0, JavaExceptions are logged. See how to inject + a PSR-3 logger in the [bridge_connection section](./bridge_connection.md) ```php protocol->handler->shutdownBrokenConnection( sprintf( - 'Parser error, check the backend for details, $name: %s, '. - '$st params: %s', + 'Parser error, check the backend for details, "$name": %s, "$st": %s', $name, json_encode($st) ) diff --git a/src/Soluble/Japha/Bridge/Driver/Pjb62/PjbProxyClient.php b/src/Soluble/Japha/Bridge/Driver/Pjb62/PjbProxyClient.php index afc0e06..6f52df2 100644 --- a/src/Soluble/Japha/Bridge/Driver/Pjb62/PjbProxyClient.php +++ b/src/Soluble/Japha/Bridge/Driver/Pjb62/PjbProxyClient.php @@ -11,6 +11,7 @@ namespace Soluble\Japha\Bridge\Driver\Pjb62; +use Monolog\Logger; use Soluble\Japha\Bridge\Exception; use Soluble\Japha\Interfaces; use Soluble\Japha\Bridge\Driver\ClientInterface; @@ -26,6 +27,8 @@ class PjbProxyClient implements ClientInterface */ protected static $instance; + protected static $unregistering = false; + /** * @var array */ @@ -227,6 +230,8 @@ protected function loadClient(): void * Return Pjb62 internal client. * * @return Client + * + * @throws Exception\BrokenConnectionException */ public static function getClient(): Client { @@ -298,8 +303,6 @@ public function invokeMethod(?Interfaces\JavaType $object = null, string $method */ public function inspect(Interfaces\JavaType $object): string { - //$client = self::getClient(); - //return $client->invokeMethod(0, "inspect", array($object)); return self::getClient()->invokeMethod(0, 'inspect', [$object]); } @@ -478,8 +481,6 @@ public function getOptions(): ArrayObject /** * Return specific option. * - * @param $name - * * @return mixed */ public function getOption(string $name) @@ -491,17 +492,57 @@ public function getOption(string $name) return $this->options[$name]; } + public function getLogger(): LoggerInterface + { + return $this->logger; + } + + /** + * @throws Exception\BrokenConnectionException|Exception\AuthenticationException + */ + public static function unregisterAndThrowBrokenConnectionException(string $message = null, int $code = null): void + { + if (self::$instance !== null) { + $message = $message ?? 'undefined messsage'; + + switch ($code) { + case 401: + $exception = new Exception\AuthenticationException(sprintf( + 'Java bridge authentication failure: code: %s', + $code + )); + break; + default: + $exception = new Exception\BrokenConnectionException(sprintf( + 'Java bridge broken connection: "%s" (code: %s)', + $message, + $code + )); + } + try { + self::$instance->getLogger()->critical(sprintf( + '[soluble-japha] BrokenConnectionException to "%s": "%s" (code: "%s")', + self::$instance->getOption('servlet_address'), + $message, + $code ?? '?' + )); + } catch (\Throwable $e) { + // discard logger errors + } + + self::unregisterInstance(); + throw $exception; + } + } + /** * Clean up PjbProxyClient instance. */ public static function unregisterInstance(): void { - if (self::$client !== null) { - // TODO check with sessions - /* - if (session_id()) { - session_write_close(); - }*/ + if (!self::$unregistering && self::$client !== null) { + self::$unregistering = true; + if (self::$client->preparedToSendBuffer) { self::$client->sendBuffer .= self::$client->preparedToSendBuffer; } @@ -517,7 +558,7 @@ public static function unregisterInstance(): void // ADDED AN IF TO CHECK THE CHANNEL In CASE OF // if (isset(self::$client->protocol->handler->channel) && - false === strpos(get_class(self::getClient()->protocol->handler->channel), '/EmptyChannel/')) { + false === strpos(get_class(self::getClient()->protocol->handler->channel), '/EmptyChannel/')) { try { self::$client->protocol->keepAlive(); } catch (\Throwable $e) { @@ -528,6 +569,7 @@ public static function unregisterInstance(): void self::$client = null; self::$instance = null; self::$instanceOptionsKey = null; + self::$unregistering = false; } } } diff --git a/src/Soluble/Japha/Bridge/Driver/Pjb62/Proxy/DefaultThrowExceptionProxyFactory.php b/src/Soluble/Japha/Bridge/Driver/Pjb62/Proxy/DefaultThrowExceptionProxyFactory.php index 1b27841..b9c85d0 100644 --- a/src/Soluble/Japha/Bridge/Driver/Pjb62/Proxy/DefaultThrowExceptionProxyFactory.php +++ b/src/Soluble/Japha/Bridge/Driver/Pjb62/Proxy/DefaultThrowExceptionProxyFactory.php @@ -109,12 +109,25 @@ protected function getExceptionFromResult(Pjb62\Exception\JavaException $result) $message, $cause, $stackTrace, - $javaExceptionClass, + $javaExceptionClass, $code, $driverException, null ); + $this->logException($e, $exceptionClass); + return $e; } + + protected function logException(\Throwable $e, string $exceptionClass): void + { + $this->logger->error(sprintf( + '[soluble-japha] Encountered exception %s: %s, code %s (%s)', + $exceptionClass, + $e->getMessage(), + $e->getCode() ?? '?', + get_class($e) + )); + } } diff --git a/src/Soluble/Japha/Bridge/Driver/Pjb62/SimpleHttpTunnelHandler.php b/src/Soluble/Japha/Bridge/Driver/Pjb62/SimpleHttpTunnelHandler.php index 893400f..b39ca03 100644 --- a/src/Soluble/Japha/Bridge/Driver/Pjb62/SimpleHttpTunnelHandler.php +++ b/src/Soluble/Japha/Bridge/Driver/Pjb62/SimpleHttpTunnelHandler.php @@ -88,10 +88,12 @@ public function createChannel() $this->createSimpleChannel(); } - public function shutdownBrokenConnection(string $msg = ''): void + public function shutdownBrokenConnection(string $msg = '', int $code = null): void { - fclose($this->socket); - $this->dieWithBrokenConnection($msg); + if (is_resource($this->socket)) { + fclose($this->socket); + } + PjbProxyClient::unregisterAndThrowBrokenConnectionException($msg, $code); } /** @@ -161,7 +163,9 @@ public function read(int $size): string $this->parseHeaders(); } - if (isset($this->headers['http_error'])) { + $http_error = $this->headers['http_error'] ?? null; + + if ($http_error !== null) { $str = null; if (isset($this->headers['transfer_chunked'])) { $str = $this->fread($this->java_recv_size); @@ -176,7 +180,12 @@ public function read(int $size): string $str = fread($this->socket, $this->java_recv_size); } $str = ($str === false || $str === null) ? '' : $str; - $this->shutdownBrokenConnection($str); + + if ($http_error === 401) { + $this->shutdownBrokenConnection('Authentication exception', 401); + } else { + $this->shutdownBrokenConnection($str); + } } $response = $this->fread($this->java_recv_size); @@ -233,7 +242,7 @@ public function write(string $data): ?int if ($count === false) { $this->shutdownBrokenConnection('Cannot write to socket, broken connection handle'); } - $flushed = fflush($this->socket); + $flushed = @fflush($this->socket); if ($flushed === false) { $this->shutdownBrokenConnection('Cannot flush to socket, broken connection handle'); } @@ -241,14 +250,18 @@ public function write(string $data): ?int return (int) $count; } - protected function parseHeaders() + protected function parseHeaders(): void { $this->headers = []; - $line = trim(fgets($this->socket, $this->java_recv_size)); + $res = @fgets($this->socket, $this->java_recv_size); + if ($res === false) { + $this->shutdownBrokenConnection('Cannot parse headers, socket cannot be read.'); + } + $line = trim($res); $ar = explode(' ', $line); $code = ((int) $ar[1]); - if ($code != 200) { + if ($code !== 200) { $this->headers['http_error'] = $code; } while ($str = trim(fgets($this->socket, $this->java_recv_size))) { @@ -293,8 +306,6 @@ protected function parseHeaders() */ protected function getSimpleChannel() { - // Originally bug found in Pjb - //return new ChunkedSocketChannel($this->socket, $this->protocol, $this->host); return new ChunkedSocketChannel($this->socket, $this->host, $this->java_recv_size, $this->java_send_size); } diff --git a/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketChannel.php b/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketChannel.php index 4273ea2..9510414 100644 --- a/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketChannel.php +++ b/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketChannel.php @@ -81,17 +81,23 @@ public function fwrite(string $data): ?int public function fread(int $size): ?string { - $read = fread($this->peer, $size); + $read = @fread($this->peer, $size); if ($read === false) { - PjbProxyClient::unregisterInstance(); - throw new BrokenConnectionException('Broken socket communication with the php-java-bridge (read)'); + PjbProxyClient::unregisterAndThrowBrokenConnectionException( + sprintf( + 'Broken socket communication with the php-java-bridge while reading socket (%s).', + __METHOD__ + ) + ); + } else { + return $read; } - - return $read; } public function shutdownBrokenConnection(?string $msg = ''): void { - fclose($this->peer); + if (is_resource($this->peer)) { + fclose($this->peer); + } } } diff --git a/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketHandler.php b/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketHandler.php index 556e133..da4b7d1 100644 --- a/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketHandler.php +++ b/src/Soluble/Japha/Bridge/Driver/Pjb62/SocketHandler.php @@ -37,8 +37,6 @@ namespace Soluble\Japha\Bridge\Driver\Pjb62; -use Soluble\Japha\Bridge\Driver\Pjb62\Exception\BrokenConnectionException; - class SocketHandler { /** @@ -101,24 +99,15 @@ public function keepAlive(): void $this->channel->keepAlive(); } - public function dieWithBrokenConnection(string $msg = ''): void + public function shutdownBrokenConnection(string $msg = '', int $code = null): void { - if ($msg === '') { - $msg = 'Unkown error: please see back end log for detail'; - } + $msg = $msg ?? 'Broken connection: Unkown error, please see back end log for detail'; // Log error $client = $this->protocol->getClient(); + $client->getLogger()->critical("[soluble-japha] $msg\" (".__METHOD__.')'); - $client->getLogger()->critical("[soluble-japha] Broken connection: $msg, check the backend log for details\" (".__METHOD__.')'); - - PjbProxyClient::unregisterInstance(); - throw new BrokenConnectionException("Broken connection: $msg, check the backend log for details"); - } - - public function shutdownBrokenConnection(string $msg = ''): void - { $this->channel->shutdownBrokenConnection(); - $this->dieWithBrokenConnection($msg); + PjbProxyClient::unregisterAndThrowBrokenConnectionException($msg, $code); } } diff --git a/src/Soluble/Japha/Bridge/Exception/AuthenticationException.php b/src/Soluble/Japha/Bridge/Exception/AuthenticationException.php new file mode 100644 index 0000000..69cdd7a --- /dev/null +++ b/src/Soluble/Japha/Bridge/Exception/AuthenticationException.php @@ -0,0 +1,17 @@ +java('java.lang.String'); } catch (\Exception $e) { - die('Error connecting: '.$e->getMessage()); + die(sprintf( + 'Error connecting: %s (%s)', + $e->getMessage(), + get_class($e) + )); } $end_connection_time = $bm->getTimeMs(); $connection_time = $bm->getFormattedTimeMs($start_connection_time, $end_connection_time); diff --git a/test/src/SolubleTest/Japha/Bridge/ErrorLoggerTest.php b/test/src/SolubleTest/Japha/Bridge/ErrorLoggerTest.php index 9ff6e04..0125651 100644 --- a/test/src/SolubleTest/Japha/Bridge/ErrorLoggerTest.php +++ b/test/src/SolubleTest/Japha/Bridge/ErrorLoggerTest.php @@ -57,7 +57,7 @@ protected function setUp() $this->adapter = new Adapter([ 'driver' => 'Pjb62', 'servlet_address' => $this->servlet_address, - ]); + ], $this->logger); } /** @@ -73,7 +73,7 @@ public function testServerDownLogConnectionException() PjbProxyClient::unregisterInstance(); $logged = false; try { - $ba = new Adapter([ + new Adapter([ 'driver' => 'pjb62', //'servlet_address' => $this->servlet_address . 'urldoesnotexists' 'servlet_address' => 'http://127.0.0.1:12345/servlet.phpjavabridge' @@ -90,8 +90,8 @@ public function testServerDownLogConnectionException() } } - /* - public function testNoSuchMethodExceptionMustBeLogged() { + public function testNoSuchMethodExceptionMustBeLogged() + { $ba = $this->adapter; try { @@ -99,12 +99,9 @@ public function testNoSuchMethodExceptionMustBeLogged() { $string->anInvalidMethod(); self::assertFalse(true, 'This code cannot be reached'); } catch (Exception\NoSuchMethodException $e) { - - $mustContain = '[soluble-japha] Cannot connect to php-java-bridge server'; - $logged = $this->loggerTestHandler->hasCriticalThatContains($mustContain); + $logged = $this->loggerTestHandler->hasErrorThatContains('Soluble\Japha\Bridge\Exception\NoSuchMethodException') + && $this->loggerTestHandler->hasErrorThatContains('Invoke failed: [[o:String]]->anInvalidMethod.'); self::assertTrue($logged, 'Assert that logger actually logs connection exception'); - } - - }*/ + } }