diff --git a/src/GapicClientTrait.php b/src/GapicClientTrait.php index a8dd6782a..ace0d621f 100644 --- a/src/GapicClientTrait.php +++ b/src/GapicClientTrait.php @@ -80,6 +80,7 @@ trait GapicClientTrait Call::CLIENT_STREAMING_CALL => 'startClientStreamingCall', Call::SERVER_STREAMING_CALL => 'startServerStreamingCall', ]; + private bool $isNewClient; /** * Initiates an orderly shutdown in which preexisting calls continue but new @@ -385,6 +386,12 @@ private function setClientOptions(array $options) $options['restVersion'] = Version::getApiCoreVersion(); } + // Set "client_library_name" depending on client library surface being used + $userAgentHeader = sprintf( + 'gcloud-php-%s/%s', + $this->isNewClientSurface() ? 'new' : 'legacy', + $options['gapicVersion'] + ); $this->agentHeader = AgentHeader::buildAgentHeader( $this->pluckArray([ 'libName', @@ -392,6 +399,8 @@ private function setClientOptions(array $options) 'gapicVersion' ], $options) ); + $this->agentHeader['User-Agent'] = [$userAgentHeader]; + self::validateFileExists($options['descriptorsConfigPath']); $descriptors = require($options['descriptorsConfigPath']); $this->descriptors = $descriptors['interfaces'][$this->serviceName]; @@ -469,6 +478,14 @@ private function createTransport( $configForSpecifiedTransport['clientCertSource'] = $clientCertSource; switch ($transport) { case 'grpc': + // Setting the user agent for gRPC requires special handling + if (isset($this->agentHeader['User-Agent'])) { + if ($configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] ??= '') { + $configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] .= ' '; + } + $configForSpecifiedTransport['stubOpts']['grpc.primary_user_agent'] .= + $this->agentHeader['User-Agent'][0]; + } return GrpcTransport::build($apiEndpoint, $configForSpecifiedTransport); case 'grpc-fallback': return GrpcFallbackTransport::build($apiEndpoint, $configForSpecifiedTransport); @@ -1051,4 +1068,12 @@ protected function modifyStreamingCallable(callable &$callable) { // Do nothing - this method exists to allow callable modification by partial veneers. } + + /** + * @internal + */ + private function isNewClientSurface(): bool + { + return $this->isNewClient ?? $this->newClient = substr(__CLASS__, -10) === 'BaseClient'; + } } diff --git a/tests/Tests/Unit/GapicClientTraitTest.php b/tests/Tests/Unit/GapicClientTraitTest.php index 3c98429e4..9c2f5f756 100644 --- a/tests/Tests/Unit/GapicClientTraitTest.php +++ b/tests/Tests/Unit/GapicClientTraitTest.php @@ -872,7 +872,7 @@ public function setClientOptionsData() } $expectedProperties = [ 'serviceName' => 'test.interface.v1.api', - 'agentHeader' => AgentHeader::buildAgentHeader([]), + 'agentHeader' => AgentHeader::buildAgentHeader([]) + ['User-Agent' => ['gcloud-php-legacy/']], 'retrySettings' => $expectedRetrySettings, ]; return [ @@ -1356,7 +1356,8 @@ public function testUserProjectHeaderIsSetWhenProvidingQuotaProject() $this->isInstanceOf(Call::class), $this->equalTo([ 'headers' => AgentHeader::buildAgentHeader([]) + [ - 'X-Goog-User-Project' => [$quotaProject] + 'X-Goog-User-Project' => [$quotaProject], + 'User-Agent' => ['gcloud-php-legacy/'] ], 'credentialsWrapper' => $credentialsWrapper ]) @@ -1718,6 +1719,25 @@ public function testMtlsClientOptionWithDefaultClientCertSource() $this->assertTrue(is_callable($options['clientCertSource'])); $this->assertEquals(['foo', 'foo'], $options['clientCertSource']()); } + + public function testSurfaceAgentHeaders() + { + // V1 does not contain new headers + $client = new GapicClientTraitRestOnly([ + 'gapicVersion' => '0.0.2', + ]); + $agentHeader = $client->getAgentHeader(); + $this->assertStringContainsString(' gapic/0.0.2 ', $agentHeader['x-goog-api-client'][0]); + $this->assertEquals('gcloud-php-legacy/0.0.2', $agentHeader['User-Agent'][0]); + + // V2 contains new headers + $client = new GapicV2SurfaceClient([ + 'gapicVersion' => '0.0.1', + ]); + $agentHeader = $client->getAgentHeader(); + $this->assertStringContainsString(' gapic/0.0.1 ', $agentHeader['x-goog-api-client'][0]); + $this->assertEquals('gcloud-php-new/0.0.1', $agentHeader['User-Agent'][0]); + } } class GapicClientTraitStub @@ -1884,6 +1904,11 @@ private static function defaultTransport() { return 'rest'; } + + public function getAgentHeader() + { + return $this->agentHeader; + } } class GapicClientTraitOperationsStub extends GapicClientTraitStub @@ -1902,3 +1927,38 @@ public function getOperation($name, $arg1, $arg2) { } } + +class GapicV2SurfaceBaseClient +{ + use GapicClientTrait; + + public function __construct(array $options = []) + { + $clientOptions = $this->buildClientOptions($options); + $this->setClientOptions($clientOptions); + } + + public static function getClientDefaults() + { + return [ + 'apiEndpoint' => 'test.address.com:443', + 'serviceName' => 'test.interface.v1.api', + 'clientConfig' => __DIR__ . '/testdata/test_service_client_config.json', + 'descriptorsConfigPath' => __DIR__.'/testdata/test_service_descriptor_config.php', + 'transportConfig' => [ + 'rest' => [ + 'restClientConfigPath' => __DIR__.'/testdata/test_service_rest_client_config.php', + ] + ], + ]; + } + + public function getAgentHeader() + { + return $this->agentHeader; + } +} + +class GapicV2SurfaceClient extends GapicV2SurfaceBaseClient +{ +}