التكوين التلقائي لخادم gRPC المضمن وتشغيله باستخدام الفاصوليا الممكّنة @GRpcService كجزء من تطبيق التشغيل الربيعي (فيديو قصير)
يُنصح مستخدمو Gradle
بتطبيق المكون الإضافي:
plugins {
id " io.github.lognet.grpc-spring-boot " version ' 5.1.5 '
}
يعمل البرنامج الإضافي io.github.lognet.grpc-spring-boot gradle على تبسيط عملية إعداد المشروع بشكل كبير.
repositories {
mavenCentral()
// maven { url "https://oss.sonatype.org/content/repositories/snapshots" } // for snapshot builds
}
dependencies {
implementation ' io.github.lognet:grpc-spring-boot-starter:5.1.5 '
}
افتراضيًا، يسحب المبتدئ io.grpc:grpc-netty-shaded
باعتباره تبعية متعدية، إذا كنت مجبرًا على استخدام تبعية grpc-netty
خالصة:
implementation ( ' io.github.lognet:grpc-spring-boot-starter:5.1.5 ' ) {
exclude group : ' io.grpc ' , module : ' grpc-netty-shaded '
}
implementation ' io.grpc:grpc-netty:1.58.0 ' // (1)
تأكد من سحب النسخة المطابقة للإصدار.
يتم أيضًا دعم وجود كلتا المكتبتين في classpath باستخدام خاصية grpc.netty-server.on-collision-prefer-shaded-netty
.
إذا كنت تستخدم البرنامج الإضافي Spring Boot Dependency Management، فقد لا يسحب نفس الإصدار الذي تم تجميع الإصدار الذي بدأ عليه، مما يتسبب في مشكلة عدم التوافق الثنائي.
في هذه الحالة، ستحتاج إلى تعيين إصدار grpc
بشكل قسري وصريح لاستخدامه (انظر مصفوفة الإصدار هنا ):
configurations . all {
resolutionStrategy . eachDependency { details ->
if ( " io.grpc " . equalsIgnoreCase(details . requested . group)) {
details . useVersion " 1.58.0 "
}
}
}
يمكن العثور على ملاحظات الإصدار مع مصفوفة التوافق هنا |
اتبع هذا الدليل لإنشاء كعب الروتين وواجهة (واجهات) الخادم من ملف (ملفات) .proto
الخاص بك.
إذا كنت مكدسًا مع maven - استخدم هذا الرابط.
قم بتعليق تنفيذ (عمليات) واجهة الخادم الخاصة بك باستخدام @org.lognet.springboot.grpc.GRpcService
قم بشكل اختياري بتكوين منفذ الخادم في application.yml/properties
. المنفذ الافتراضي هو 6565
.
grpc :
port : 6565
يمكن تعريف المنفذ العشوائي عن طريق ضبط المنفذ على 0 .يمكن بعد ذلك استرداد المنفذ الفعلي المستخدم باستخدام التعليق التوضيحي @LocalRunningGrpcPort في الحقل int والذي سيدخل المنفذ قيد التشغيل (تم تكوينه بشكل صريح أو تحديده عشوائيًا) |
تمكين انعكاس الخادم بشكل اختياري (راجع https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md)
grpc :
enableReflection : true
اختياريًا، قم بتعيين ترتيب مرحلة بدء التشغيل (الإعداد الافتراضي هو Integer.MAX_VALUE
).
grpc :
start-up-phase : XXX
اختياريًا، قم بتعيين عدد الثواني التي ستنتظرها حتى انتهاء المكالمات الموجودة مسبقًا أثناء إيقاف تشغيل الخادم بسلاسة. سيتم رفض المكالمات الجديدة خلال هذا الوقت. القيمة السالبة تعادل فترة سماح لا نهائية. القيمة الافتراضية هي 0
(يعني لا تنتظر).
grpc :
shutdownGrace : 30
يمكن تحديد خصائص الخادم الخاصة بـ Netty ضمن بادئة grpc.netty-server
.
من خلال تكوين إحدى قيم grpc.netty-server.xxxx
فإنك تقوم ضمنيًا بتعيين النقل ليكون مستندًا إلى Netty.
grpc :
netty-server :
keep-alive-time : 30s (1)
max-inbound-message-size : 10MB (2)
primary-listen-address : 10.10.15.23:0 (3)
additional-listen-addresses :
- 192.168.0.100:6767 (4)
on-collision-prefer-shaded-netty : false (5)
يمكن تكوين خصائص نوع Duration
بتنسيق قيمة السلسلة الموضح هنا.
يمكن تكوين خصائص نوع DataSize
بقيمة السلسلة الموضحة هنا
مكشوف على IP الشبكة الخارجية مع منفذ مخصص.
تنسيق قيمة سلسلة خصائص نوع SocketAddress
:
host:port
(إذا كانت قيمة port
أقل من 1، يستخدم قيمة عشوائية)
host:
(يستخدم منفذ grpc الافتراضي، 6565
)
مكشوف على IP للشبكة الداخلية بالإضافة إلى المنفذ المحدد مسبقًا 6767
.
في حالة وجود مكتبات netty shaded
pure
في التبعيات، اختر نوع NettyServerBuilder
الذي يجب إنشاؤه. هذا هو النوع الذي سيتم تمريره إلى GRpcServerBuilderConfigurer
(راجع تكوين خادم gRPC المخصص)، الإعداد الافتراضي هو true
(على سبيل المثال io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
; io.grpc.netty.NettyServerBuilder
إذا كان false
)
يدعم المبدئ أيضًا in-process server
، والذي يجب استخدامه لأغراض الاختبار:
grpc :
enabled : false (1)
inProcessServerName : myTestServer (2)
تعطيل الخادم الافتراضي ( NettyServer
).
تمكين الخادم in-process
.
إذا قمت بتمكين كل من NettyServer والخادم in-process ، فسيتشاركان نفس مثيل HealthStatusManager و GRpcServerBuilderConfigurer (راجع تكوين خادم gRPC المخصص). |
في مشروع grpc-spring-boot-starter-demo
يمكنك العثور على أمثلة وظيفية بالكامل مع اختبارات التكامل.
يبدو تعريف الخدمة من ملف .proto
كما يلي:
service Greeter {
rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}
لاحظ فئة io.grpc.examples.GreeterGrpc.GreeterImplBase
التي تم إنشاؤها والتي تمتد إلى io.grpc.BindableService
.
كل ما عليك فعله هو إضافة تعليق توضيحي على تنفيذ الخدمة باستخدام @org.lognet.springboot.grpc.GRpcService
@ GRpcService
public static class GreeterService extends GreeterGrpc . GreeterImplBase {
@ Override
public void sayHello ( GreeterOuterClass . HelloRequest request , StreamObserver < GreeterOuterClass . HelloReply > responseObserver ) {
final GreeterOuterClass . HelloReply . Builder replyBuilder = GreeterOuterClass . HelloReply . newBuilder (). setMessage ( "Hello " + request . getName ());
responseObserver . onNext ( replyBuilder . build ());
responseObserver . onCompleted ();
}
}
يدعم المبدئ تسجيل نوعين من الاعتراضات: العالمية ولكل خدمة .
في كلتا الحالتين يجب على المعترض تنفيذ واجهة io.grpc.ServerInterceptor
.
لكل خدمة
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
سيتم إنشاء مثيل LogInterceptor
عبر مصنع الربيع إذا كان هناك حبة من النوع LogInterceptor
، أو عبر مُنشئ no-args بخلاف ذلك.
عالمي
@ GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
// ommited
}
يتم أيضًا دعم التعليق التوضيحي على طريقة Java config Factory:
@ Configuration
public class MyConfig {
@ Bean
@ GRpcGlobalInterceptor
public ServerInterceptor globalInterceptor (){
return new ServerInterceptor (){
@ Override
public < ReqT , RespT > ServerCall . Listener < ReqT > interceptCall ( ServerCall < ReqT , RespT > call , Metadata headers , ServerCallHandler < ReqT , RespT > next ) {
// your logic here
return next . startCall ( call , headers );
}
};
}
}
تتمتع الخدمة المعينة أيضًا بفرصة تعطيل المعترضات العالمية:
@ GRpcService ( applyGlobalInterceptors = false )
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
يمكن طلب المعترضات العالمية باستخدام تعليقات Spring's @Ordered
أو @Priority
. وفقًا لدلالات الترتيب الخاصة بـ Spring، تتمتع القيم ذات الترتيب الأدنى بأولوية أعلى وسيتم تنفيذها أولاً في سلسلة الاعتراض.
@ GRpcGlobalInterceptor
@ Order ( 10 )
public class A implements ServerInterceptor {
// will be called before B
}
@ GRpcGlobalInterceptor
@ Order ( 20 )
public class B implements ServerInterceptor {
// will be called after A
}
يستخدم المبتدئ أجهزة اعتراضية مدمجة لتنفيذ معالجة الأخطاء وSpring Security
Validation
وتكامل Metrics
. يمكن أيضًا التحكم في ترتيبهم من خلال الخصائص التالية:
grpc.recovery.interceptor-order
(خطأ في معالجة أمر الاعتراض، الإعداد الافتراضي هو Ordered.HIGHEST_PRECEDENCE
)
grpc.security.auth.interceptor-order
(الإعداد الافتراضي هو Ordered.HIGHEST_PRECEDENCE+1
)
grpc.validation.interceptor-order
(الإعداد الافتراضي هو Ordered.HIGHEST_PRECEDENCE+10
)
grpc.metrics.interceptor-order
(الإعداد الافتراضي هو Ordered.HIGHEST_PRECEDENCE+20
)
يمنحك هذا القدرة على إعداد الترتيب المطلوب لأجهزة الاعتراض المضمنة والمخصصة.
استمر في القراءة !!! هناك المزيد
الطريقة التي يعمل بها أداة اعتراض grpc هي أنها تعترض المكالمة وتعيد مستمع مكالمات الخادم، والذي بدوره يمكنه اعتراض رسالة الطلب أيضًا، قبل إعادة توجيهها إلى معالج استدعاء الخدمة الفعلي:
من خلال تعيين خاصية grpc.security.auth.fail-fast
على false
سيتم تنفيذ جميع اعتراضات المصب وكذلك جميع اعتراضات المنبع (On_Message) في حالة فشل المصادقة/الترخيص
بافتراض أن interceptor_2
هو securityInterceptor
:
في حالة فشل المصادقة/التفويض باستخدام grpc.security.auth.fail-fast=true
(افتراضي):
في حالة فشل المصادقة/التفويض باستخدام grpc.security.auth.fail-fast=false
:
تم دعم هذا الأمر أصلاً بواسطة مشروع spring-cloud-sleuth
.
يرجى الاستمرار في تكامل grpc.
من خلال تضمين تبعية org.springframework.boot:spring-boot-starter-actuator
، سيجمع المبتدئ مقاييس خادم gRPC، مقسمة حسب
method
- طريقة خدمة gRPC FQN (الاسم المؤهل بالكامل)
result
- رمز حالة الاستجابة
address
- العنوان المحلي للخادم (إذا كشفت عن عناوين استماع إضافية، باستخدام خاصية grpc.netty-server.additional-listen-addresses
)
بعد تكوين المُصدِّر الذي تختاره، من المفترض أن ترى timer
المسمى grpc.server.calls
.
من خلال تحديد وحدة GRpcMetricsTagsContributor
في سياق التطبيق الخاص بك، يمكنك إضافة علامات مخصصة إلى مؤقت grpc.server.calls
.
يمكنك أيضًا استخدام وحدة RequestAwareGRpcMetricsTagsContributor
لوضع علامة على المكالمات الأحادية والمتدفقة .
التجريبي هنا
أبقِ التشتت منخفضًا حتى لا يؤدي إلى تفجير أصل المقياس. |
لا يزال من الممكن تنفيذ RequestAwareGRpcMetricsTagsContributor
في حالة المصادقة الفاشلة إذا كان للاعتراض metric
أسبقية أعلى من اعتراض security
وتم تعيين grpc.security.auth.fail-fast
على false
.
يتم تغطية هذه الحالة من خلال هذا الاختبار.
تأكد من قراءة فصل طلب المعترضين. |
تأكد من تضمين التبعيات أدناه:
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'
إعدادات :
management :
metrics :
export :
prometheus :
enabled : true
endpoints :
web :
exposure :
include : " * "
ستعرض نقاط النهاية القياسية /actuator/metrics
و /actuator/prometheus
مقاييس grpc.server.calls
(انظر العرض التوضيحي هنا).
اقتراح إلغاء GRPC |
يمكن تكوين المبتدئ تلقائيًا للتحقق من صحة رسائل خدمة gRPC للطلب/الاستجابة. يرجى الاستمرار في تنفيذ التحقق من صحة الرسالة للحصول على تفاصيل التكوين.
يقوم المبدئ داخليًا بتعريف الفول من النوع java.util.function.Consumer
الذي يتم أخذه في الاعتبار لتسجيل الوظائف عندما يكون spring-cloud-stream
على مسار الفئة، وهو أمر غير مرغوب فيه (يقوم spring-cloud-stream
بتسجيل القناة تلقائيًا إذا كان لديك بالضبط حبة مستهلك/مورد/وظيفة واحدة في سياق التطبيق، لذلك لديك بالفعل واحدة إذا كنت تستخدم هذا المبدئ مع spring-cloud-stream
).
وبناءً على ذلك، يوصى باستخدام خاصية spring.cloud.function.definition
في التطبيقات الجاهزة للإنتاج وعدم الاعتماد على الاكتشاف التلقائي.
يرجى الرجوع إلى العرض التوضيحي لـ GRPC Kafka Stream، الجزء الأساسي هو هذا الخط.
يوفر المبتدئ دعمًا مدمجًا لمصادقة المستخدمين وتفويضهم من خلال الاستفادة من التكامل مع إطار عمل Spring Security.
يرجى الرجوع إلى الأقسام الخاصة بـ Spring Security Integration للحصول على تفاصيل حول موفري المصادقة المعتمدين وخيارات التكوين.
يمكن تكوين أمان النقل باستخدام شهادة الجذر مع مسار المفتاح الخاص الخاص بها:
grpc :
security :
cert-chain : classpath:cert/server-cert.pem
private-key : file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem
قيمة كلتا الخاصيتين في شكل مدعوم من قبل ResourceEditor.
يجب تكوين جانب العميل وفقًا لذلك:
(( NettyChannelBuilder ) channelBuilder )
. useTransportSecurity ()
. sslContext ( GrpcSslContexts . forClient (). trustManager ( certChain ). build ());
سيقوم هذا المبدئ بسحب تبعية io.netty:netty-tcnative-boringssl-static
افتراضيًا لدعم SSL.
إذا كنت بحاجة إلى دعم SSL/TLS آخر، فيرجى استبعاد هذه التبعية واتباع دليل الأمان.
إذا كانت هناك حاجة إلى ضبط أكثر تفصيلاً لإعداد الأمان، فيرجى استخدام أداة التهيئة المخصصة الموضحة في تكوين خادم gRPC المخصص |
لاعتراض مثيل io.grpc.ServerBuilder
المستخدم لإنشاء io.grpc.Server
، يمكنك إضافة حبة ترث من org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
إلى السياق الخاص بك وتجاوز طريقة configure
.
كما يتم دعم تكوينات متعددة.
بحلول وقت استدعاء طريقة configure
، تمت إضافة جميع الخدمات المكتشفة، بما في ذلك اعتراضاتها، إلى الباني الذي تم تمريره.
في تنفيذ طريقة configure
، يمكنك إضافة التكوين المخصص الخاص بك:
@ Component
public class MyGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer {
@ Override
public void configure ( ServerBuilder <?> serverBuilder ){
serverBuilder
. executor ( YOUR EXECUTOR INSTANCE )
. useTransportSecurity ( YOUR TRANSPORT SECURITY SETTINGS );
(( NettyServerBuilder ) serverBuilder ) // cast to NettyServerBuilder (which is the default server) for further customization
. sslContext ( GrpcSslContexts // security fine tuning
. forServer (...)
. trustManager (...)
. build ())
. maxConnectionAge (...)
. maxConnectionAgeGrace (...);
}
};
}
@ Component
public class MyCustomCompressionGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer {
@ Override
public void configure ( ServerBuilder <?> serverBuilder ){
serverBuilder
. compressorRegistry ( YOUR COMPRESSION REGISTRY )
. decompressorRegistry ( YOUR DECOMPRESSION REGISTRY ) ;
}
};
}
إذا قمت بتمكين كل من NettyServer والخوادم in-process ، فسيتم استدعاء أسلوب configure على نفس مثيل المهيئ.إذا كنت بحاجة إلى التمييز بين serverBuilder الذي تم تمريره، فيمكنك التحقق من النوع.هذا هو القيد الحالي. |
يتم نشر GRpcServerInitializedEvent
عند بدء تشغيل الخادم، ويمكنك استهلاكه باستخدام Spring API العادي.
بدءًا من الإصدار 5.1.0
، يدمج البرنامج المساعد Spring-boot-starter-gradle-plugin البرنامج الإضافي reactive-grpc protoc الخاص بـ SalesForce:
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id " io.github.lognet.grpc-spring-boot "
}
grpcSpringBoot {
reactiveFeature . set( ReactiveFeature . REACTOR ) // or ReactiveFeature.RX
}
فيما يلي الاختبارات وخدمة عينة grpc التفاعلية.
يقوم المبدئ بتسجيل GRpcExceptionHandlerInterceptor
المسؤول عن نشر استثناء الخدمة إلى معالجات الأخطاء.
يمكن تسجيل طريقة معالجة الأخطاء من خلال وجود حبة مشروحة @GRpcServiceAdvice
مع طرق مشروحة بتعليقات توضيحية @GRpcExceptionHandler
.
تعتبر هذه بمثابة معالجات أخطاء global
ويتم استدعاء الطريقة مع معلمة نوع الاستثناء الأقرب حسب التسلسل الهرمي للنوع إلى الاستثناء الذي تم طرحه.
يجب أن يتبع توقيع معالج الأخطاء النمط التالي:
نوع الإرجاع | المعلمة 1 | المعلمة 2 |
---|---|---|
io.grpc.Status | أي نوع | GRpcExceptionScope |
@ GRpcServiceAdvice
class MyHandler1 {
@ GRpcExceptionHandler
public Status handle ( MyCustomExcpetion exc , GRpcExceptionScope scope ){
}
@ GRpcExceptionHandler
public Status handle ( IllegalArgumentException exc , GRpcExceptionScope scope ){
}
}
@ GRpcServiceAdvice
class MyHandler2 {
@ GRpcExceptionHandler
public Status anotherHandler ( NullPointerException npe , GRpcExceptionScope scope ){
}
}
يمكنك الحصول على أي عدد تريده من أدوات advice
وأساليب المعالج طالما أنها لا تتداخل مع بعضها البعض ولا تخلق غموضًا في نوع الاستثناء المعالج.
تم اكتشاف وحدة خدمة grpc
أيضًا لمعالجات الأخطاء، ولها أسبقية أعلى من طرق معالجة الأخطاء العامة المكتشفة في وحدات @GRpcServiceAdvice
. تعتبر أساليب معالجة الأخطاء على مستوى الخدمة private
ولا يتم استدعاؤها إلا عندما يتم طرح الاستثناء بواسطة هذه الخدمة:
class SomeException extends Exception {
}
class SomeRuntimeException extends RuntimeException {
}
@ GRpcService
public class HelloService extends GreeterGrpc . GreeterImplBase {
@ Override
public void sayHello ( GreeterOuterClass . HelloRequest request , StreamObserver < GreeterOuterClass . HelloReply > responseObserver ) {
...
throw new GRpcRuntimeExceptionWrapper ( new SomeException ()) ; // (1)
//or
throw new GRpcRuntimeExceptionWrapper ( new SomeException (), "myHint" ); // (2)
//or
throw new SomeRuntimeException (); //(3)
}
@ GRpcExceptionHandler
public Status privateHandler ( SomeException npe , GRpcExceptionScope scope ){
// INVOKED when thrown from HelloService service
String myHint = scope . getHintAs ( String . class ); // (4)
scope . getResponseHeaders (). put ( Metadata . Key . of ( "custom" , Metadata . ASCII_STRING_MARSHALLER ), "Value" ); // (5)
}
@ GRpcExceptionHandler
public Status privateHandler ( SomeRuntimeException npe , GRpcExceptionScope scope ){
// INVOKED when thrown from HelloService service
}
}
@ GRpcServiceAdvice
class MyHandler {
@ GRpcExceptionHandler
public Status anotherHandler ( SomeException npe , GRpcExceptionScope scope ){
// NOT INVOKED when thrown from HelloService service
}
@ GRpcExceptionHandler
public Status anotherHandler ( SomeRuntimeException npe , GRpcExceptionScope scope ){
// NOT INVOKED when thrown from HelloService service
}
}
نظرًا لأن طبيعة واجهة برمجة تطبيقات خدمة grpc
التي لا تسمح بطرح الاستثناء المحدد، يتم توفير نوع استثناء وقت التشغيل الخاص لتغليف الاستثناء المحدد. يتم بعد ذلك فكها عند البحث عن طريقة المعالج.
عند طرح استثناء GRpcRuntimeExceptionWrapper
، يمكنك أيضًا تمرير كائن hint
الذي يمكن الوصول إليه بعد ذلك من كائن scope
في طريقة handler
.
يمكن طرح استثناء وقت التشغيل كما هو ولا يحتاج إلى تغليفه.
الحصول على كائن التلميح.
إرسال رؤوس مخصصة للعميل.
يتم نشر فشل المصادقة عبر AuthenticationException
وفشل التفويض - عبر AccessDeniedException
.
يتم نشر فشل التحقق من الصحة عبر ConstraintViolationException
: للطلب الفاشل - باستخدام Status.INVALID_ARGUMENT
كتلميح، وللاستجابة الفاشلة - باستخدام Status.FAILED_PRECONDITION
كتلميح.
العرض التوضيحي هنا
بفضل دعم تكوين التحقق من صحة Bean عبر واصف نشر XML، من الممكن توفير القيود للفئات التي تم إنشاؤها عبر XML بدلاً من التحكم في الرسائل التي تم إنشاؤها باستخدام مترجم protoc
مخصص.
أضف تبعية org.springframework.boot:spring-boot-starter-validation
إلى مشروعك.
قم بإنشاء META-INF/validation.xml
وملف (ملفات) إعلانات القيود. (يتمتع IntelliJ IDEA بدعم رائع للإكمال التلقائي لتخويل قيود التحقق من صحة الفول في ملفات XML)
راجع أيضًا نماذج من وثائق مدقق Hibernate
يمكنك العثور على التكوين التجريبي والاختبارات المقابلة هنا
لاحظ أنه يتم التحقق من صحة رسائل request
response
.
إذا كانت طريقة gRPC تستخدم نفس نوع رسالة الطلب والاستجابة، فيمكنك استخدام org.lognet.springboot.grpc.validation.group.RequestMessage
و org.lognet.springboot.grpc.validation.group.ResponseMessage
مجموعات التحقق من الصحة لتطبيق منطق تحقق مختلف :
...
< getter name = " someField " >
<!-- should be empty for request message -->
< constraint annotation = " javax.validation.constraints.Size " >
< groups >
< value >org.lognet.springboot.grpc.validation.group.RequestMessage</ value > (1)
</ groups >
< element name = " min " >0</ element >
< element name = " max " >0</ element >
</ constraint >
<!-- should NOT be empty for response message -->
< constraint annotation = " javax.validation.constraints.NotEmpty " >
< groups >
< value >org.lognet.springboot.grpc.validation.group.ResponseMessage</ value > (2)
</ groups >
</ constraint >
</ getter >
...
قم بتطبيق هذا القيد فقط على رسالة request
قم بتطبيق هذا القيد فقط على رسالة response
لاحظ أيضًا القيد المخصص للمجالات المشتركة واستخدامه:
< bean class = " io.grpc.examples.GreeterOuterClass$Person " >
< class >
< constraint annotation = " org.lognet.springboot.grpc.demo.PersonConstraint " />
</ class >
<!-- ... -->
</ bean >
كما هو موضح في فصل ترتيب المعترضات، يمكنك إعطاء معترض validation
أسبقية أعلى من معترض security
وتعيين الخاصية grpc.security.auth.fail-fast
إلى false
.
في هذا السيناريو، إذا كان الاتصال غير مصادق عليه وغير صالح، فسيحصل العميل على حالة الاستجابة Status.INVALID_ARGUMENT
بدلاً من حالة الاستجابة Status.PERMISSION_DENIED/Status.UNAUTHENTICATED
. التجريبي هنا
على الرغم من أنه لا يزال من الممكن إضافة تعليقات توضيحية إلى أساليب RPC باستخدام @Transactional
(باستخدام spring.aop.proxy-target-class=true
إذا لم يتم تمكينه افتراضيًا)، فمن المحتمل أن تحصل على سلوك غير متوقع. خذ بعين الاعتبار تنفيذ طريقة grpc أدناه:
@ GRpcService
class MyGrpcService extends ...{
@ Autowired
private MyJpaRepository repo ;
@ Transactional //(1)
public void rpcCall ( Req request , StreamOvserver < Res > observer ) {
Res response = // Database operations via repo
observer . onNext ( response ); //(2)
observer . onCompleted ();
} //(3)
}
تم شرح الطريقة باسم @Transactional
، وسيقوم Spring بتنفيذ المعاملة في وقت ما بعد إرجاع الطرق
يتم إرجاع الرد إلى المتصل
إرجاع الأساليب، والمعاملة ملتزمة في نهاية المطاف .
من الناحية النظرية، وكما ترون - من الناحية العملية، هناك فترة زمنية صغيرة عندما يتمكن العميل (إذا كان زمن استجابة الشبكة في حده الأدنى، وكان خادم grpc الخاص بك يشجع تبديل السياق مباشرة بعد <2>) من محاولة الوصول إلى قاعدة البيانات عبر مكالمة grpc أخرى قبل الصفقة ملتزمة.
الحل للتغلب على هذا الموقف هو إضفاء الطابع الخارجي على منطق المعاملات في فئة خدمة منفصلة:
@ Service
class MyService {
@ Autowired
private MyJpaRepository repo ;
@ Transactional //(1)
public Res doTransactionalWork (){
// Database operations via repo
return result ;
} //(2)
}
@ GRpcService
class MyGrpcService extends ...{
@ Autowired
private MyService myService ;
public void rpcCall ( Req request , StreamOvserver < Res > observer ) {
Res response = myService . doTransactionalWork ();
observer . onNext ( response ); //(3)
observer . onCompleted ();
}
}
طريقة الخدمة هي المعاملات
يتم الالتزام بالمعاملة في النهاية .
الرد بعد إتمام المعاملة.
باتباع هذا الأسلوب، يمكنك أيضًا فصل طبقة النقل ومنطق الأعمال الذي يمكن الآن اختباره بشكل منفصل.
مخطط | التبعيات |
---|---|
أساسي |
|
حامل |
|
مخصص |
|
يتبع تكوين أمان GRPC نفس المبادئ وواجهات برمجة التطبيقات مثل تكوين أمان Spring WEB، ويتم تمكينه افتراضيًا إذا كان لديك تبعية org.springframework.security:spring-security-config
في مسار الفصل الخاص بك.
يمكنك استخدام التعليق التوضيحي @Secured
على الخدمات/الطرق لحماية نقاط النهاية الخاصة بك، أو باستخدام واجهة برمجة التطبيقات (API) وتجاوز الإعدادات الافتراضية (التي تسبق التعليق التوضيحي @Secured
):
@ Configuration
class MySecurityCfg extends GrpcSecurityConfigurerAdapter {
@ Override
public void configure ( GrpcSecurity builder ) throws Exception {
MethodsDescriptor <?,?> adminMethods = MyServiceGrpc . getSomeMethod ();
builder
. authorizeRequests ()
. methods ( adminMethods ). hasAnyRole ( "admin" )
. anyMethodExcluding ( adminMethods ). hasAnyRole ( "user" )
. withSecuredAnnotation ();( 1 )
}
}
أو ادمج API
مع التعليقات @Secured
.
يعمل هذا التكوين الافتراضي على تأمين أساليب/خدمات GRPC الموضحة بـ org.springframework.security.access.annotation.@Secured
.
ترك قيمة التعليق التوضيحي فارغة ( @Secured({})
) يعني: authenticate
فقط، ولن يتم إجراء أي تفويض.
إذا كانت حبة JwtDecoder
موجودة في السياق الخاص بك، فسوف تقوم أيضًا بتسجيل JwtAuthenticationProvider
للتعامل مع التحقق من صحة مطالبة المصادقة.
يتم أيضًا تسجيل BasicAuthSchemeSelector
و BearerTokenAuthSchemeSelector
تلقائيًا لدعم المصادقة باستخدام اسم المستخدم/كلمة المرور والرمز المميز لحاملها.
من خلال ضبط grpc.security.auth.enabled
على false
، يمكن إيقاف تشغيل أمان GRPC.
يتم تخصيص تكوين أمان GRPC عن طريق توسيع GrpcSecurityConfigurerAdapter
(توجد هنا أمثلة تكوين مختلفة وسيناريوهات اختبار.)
@ Configuration
public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter {
@ Autowired
private JwtDecoder jwtDecoder ;
@ Override
public void configure ( GrpcSecurity builder ) throws Exception {
builder . authorizeRequests ()( 1 )
. methods ( GreeterGrpc . getSayHelloMethod ()). hasAnyAuthority ( "SCOPE_profile" )( 2 )
. and ()
. authenticationProvider ( JwtAuthProviderFactory . withAuthorities ( jwtDecoder ));( 3 )
}
}
الحصول على كائن تكوين الترخيص
يُسمح MethodDefinition
أسلوب sayHello
للمستخدمين المصادق عليهم باستخدام سلطة SCOPE_profile
.
استخدم JwtAuthenticationProvider
للتحقق من صحة مطالبة المستخدم (رمز BEARER
) مقابل خادم الموارد الذي تم تكوينه باستخدام خاصية spring.security.oauth2.resourceserver.jwt.issuer-uri
.
من الممكن توصيل موفر المصادقة المخصص الخاص بك عن طريق تنفيذ واجهة AuthenticationSchemeSelector
.
@ Configuration
public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter {
@ Override
public void configure ( GrpcSecurity builder ) throws Exception {
builder . authorizeRequests ()
. anyMethod (). authenticated () //(1)
. and ()
. authenticationSchemeSelector ( new AuthenticationSchemeSelector () { //(2)
@ Override
public Optional < Authentication > getAuthScheme ( CharSequence authorization ) {
return new MyAuthenticationObject (); // (3)
}
})
. authenticationProvider ( new AuthenticationProvider () { // (4)
@ Override
public Authentication authenticate ( Authentication authentication ) throws AuthenticationException {
MyAuthenticationObject myAuth = ( MyAuthenticationObject ) authentication ;
//validate myAuth
return MyValidatedAuthenticationObject ( withAuthorities ); //(5)
}
@ Override
public boolean supports ( Class <?> authentication ) {
return MyAuthenticationObject . class . isInstance ( authentication );
}
});
}
}
تأمين كافة وسائل الخدمات.
قم بتسجيل AuthenticationSchemeSelector
الخاص بك.
بناءً على رأس التفويض المقدم - قم بإرجاع كائن Authentication
كمطالبة (لم تتم المصادقة عليه بعد)
قم بتسجيل AuthenticationProvider
الخاص بك والذي يدعم التحقق من صحة MyAuthenticationObject
التحقق من authentication
المقدمة وإرجاع كائن Authentication
تم التحقق من صحته ومصادقته
يمكن أيضًا تسجيل AuthenticationSchemeSelector
عن طريق تعريف Spring Bean في سياق التطبيق الخاص بك:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
يشرح قسم دعم التكوين من جانب العميل كيفية اجتياز نظام التفويض المخصص والمطالبة من عميل GRPC.
بدءًا من الإصدار 4.5.9
يمكنك أيضًا استخدام التعليقات التوضيحية القياسية @PreAuthorize
و @PostAuthorize
على أساليب خدمة grpc وأنواع خدمات grpc.
نوع المكالمة | مرجع كائن الإدخال | مرجع كائن الإخراج | عينة |
---|---|---|---|
أحادي | حسب اسم المعلمة | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void unary ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
دفق الإدخال, | | | @ Override
@ PreAuthorize ( "#p0.getAge()<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > inStream ( StreamObserver < Assignment > responseObserver ) {
} |
طلب واحد، | حسب اسم المعلمة | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void outStream ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
تيار بيدي | | | @ Override
@ PreAuthorize ( "#p0.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > bidiStream ( StreamObserver < Assignment > responseObserver ) {
} |
للحصول على كائن Authentication
في تنفيذ الطريقة الآمنة ، يرجى استخدام المقتطف أدناه
final Authentication auth = GrpcSecurity . AUTHENTICATION_CONTEXT_KEY . get ();
بدءًا من 4.5.6
، يمكن أيضًا الحصول على كائن Authentication
عبر Spring API القياسي:
final Authentication auth = SecurityContextHolder . getContext (). getAuthentication ();
من خلال إضافة تبعية io.github.lognet:grpc-client-spring-boot-starter
إلى تطبيق عميل Java grpc، يمكنك بسهولة تكوين بيانات الاعتماد لكل قناة أو لكل مكالمة:
class MyClient {
public void doWork (){
final AuthClientInterceptor clientInterceptor = new AuthClientInterceptor (( 1 )
AuthHeader . builder ()
. bearer ()
. binaryFormat ( true )( 3 )
. tokenSupplier ( this :: generateToken )( 4 )
);
Channel authenticatedChannel = ClientInterceptors . intercept (
ManagedChannelBuilder . forAddress ( "host" , 6565 ). build (), clientInterceptor ( 2 )
);
// use authenticatedChannel to invoke GRPC service
}
private ByteBuffer generateToken (){ ( 4 )
// generate bearer token against your resource server
}
}
إنشاء اعتراضية العميل
اعتراض القناة
تشغيل/إيقاف التنسيق الثنائي:
عندما true
، يتم إرسال رأس المصادقة مع مفتاح Authorization-bin
باستخدام المنظم الثنائي.
عندما تكون false
، يتم إرسال رأس المصادقة مع مفتاح Authorization
باستخدام منظم ASCII.
توفير وظيفة مولد الرمز المميز (يُرجى الرجوع إليها على سبيل المثال.)
class MyClient {
public void doWork (){
AuthCallCredentials callCredentials = new AuthCallCredentials ( ( 1 )
AuthHeader . builder (). basic ( "user" , "pwd" . getBytes ())
);
final SecuredGreeterGrpc . SecuredGreeterBlockingStub securedFutureStub = SecuredGreeterGrpc . newBlockingStub ( ManagedChannelBuilder . forAddress ( "host" , 6565 ));( 2 )
final String reply = securedFutureStub
. withCallCredentials ( callCredentials )( 3 )
. sayAuthHello ( Empty . getDefaultInstance ()). getMessage ();
}
}
إنشاء بيانات اعتماد الاتصال باستخدام المخطط الأساسي
إنشاء كعب الخدمة
قم بإرفاق بيانات اعتماد المكالمة بالمكالمة
يمكن أيضًا إنشاء AuthHeader
باستخدام نظام ترخيص مخصص:
AuthHeader
. builder ()
. authScheme ( "myCustomAuthScheme" )
. tokenSupplier (()-> generateMyCustomToken ())
يقوم المبدئ بتسجيل التنفيذ الافتراضي لـ HealthServiceImpl.
يمكنك توفير ما تملكه عن طريق تسجيل حبة ManagedHealthStatusService في سياق التطبيق الخاص بك.
إذا كان لديك org.springframework.boot:spring-boot-starter-actuator
و org.springframework.boot:spring-boot-starter-web
في مسار الفصل، فسيكشف المبتدئ:
مؤشر صحة grpc
ضمن /actuator/health
endpoint.
/actuator/grpc
نقطة النهاية.
يمكن التحكم في ذلك من خلال نقاط النهاية القياسية والتكوين الصحي.
بدءًا من الإصدار 3.3.0
، سيقوم المبتدئ بالتسجيل التلقائي لخادم grpc قيد التشغيل في سجل Consul إذا كان org.springframework.cloud:spring-cloud-starter-consul-discovery
موجودًا في classpath و spring.cloud.service-registry.auto-registration.enabled
لم يتم تعيين spring.cloud.service-registry.auto-registration.enabled
على false
.
سيتم بدء اسم الخدمة المسجلة بـ grpc-
,ie grpc-${spring.application.name}
حتى لا يتداخل مع اسم خدمة الويب المسجلة القياسية إذا اخترت تشغيل كل من خوادم Grpc
وخوادم Web
المضمنة.
يتم ربط ConsulDiscoveryProperties
بخصائص التكوين التي تسبقها spring.cloud.consul.discovery
ثم تتم الكتابة فوق القيم بواسطة خصائص grpc.consul.discovery
البادئة (إذا تم تعيينها). يسمح لك هذا بالحصول على تكوين منفصل لاكتشاف القنصل لخدمات rest
و grpc
إذا اخترت الكشف عن كليهما من تطبيقك.
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
يتم تسجيل كل من خدمات rest
و grpc
ببيانات التعريف myKey=myValue
يتم تسجيل خدمات الراحة في myWebTag
يتم تسجيل خدمات Grpc في myGrpcTag
سيؤدي تعيين spring.cloud.consul.discovery.register-health-check
(أو grpc.consul.discovery.register-health-check
) إلى true
إلى تسجيل خدمة فحص صحة GRPC لدى Consul.
هناك 4 أوضاع تسجيل مدعومة:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(افتراضي)
في هذا الوضع، يتم تسجيل خادم grpc قيد التشغيل كخدمة واحدة مع فحص grpc
واحد مع serviceId
فارغ.
يرجى ملاحظة أن التنفيذ الافتراضي لا يفعل شيئًا ويعيد ببساطة حالة SERVING
. قد ترغب في توفير تطبيق التحقق من الصحة المخصص لهذا الوضع.
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
في هذا الوضع، يتم تسجيل خادم grpc قيد التشغيل كخدمة واحدة مع التحقق من كل خدمة grpc
مكتشفة.
STANDALONE_SERVICES
في هذا الوضع، يتم تسجيل كل خدمة grpc مكتشفة كخدمة واحدة بفحص واحد. يتم تمييز كل خدمة مسجلة باسم الخدمة الخاص بها.
NOOP
- لم يتم تسجيل أي خدمات grpc. يعد هذا الوضع مفيدًا إذا كنت تخدم خدمات rest
و grpc
في تطبيقك، ولكن لسبب ما، يجب تسجيل خدمات rest
فقط لدى Consul.
grpc :
consule :
registration-mode : SINGLE_SERVER_WITH_CHECK_PER_SERVICE
عند إنشاء خدمات جاهزة للإنتاج، فإن النصيحة هي أن يكون لديك مشروع منفصل لواجهة برمجة تطبيقات gRPC لخدمتك (خدماتك) التي تحتوي فقط على الفئات التي تم إنشاؤها بشكل أولي للاستخدام من جانب الخادم والعميل.
ستضيف بعد ذلك هذا المشروع باعتباره تبعية implementation
gRPC client
ومشاريع gRPC server
.
لدمج Eureka
ما عليك سوى اتباع الدليل الرائع من Spring.
فيما يلي الأجزاء الأساسية للتكوينات لكل من مشاريع الخادم والعميل.
أضف eureka starter باعتباره تبعية لمشروع الخادم الخاص بك مع الفئات التي تم إنشاؤها من الملفات proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
قم بتكوين خادم gRPC لتسجيل نفسه لدى Eureka.
spring :
application :
name : my-service-name (1)
ServiceId
Eureka بشكل افتراضي هو اسم التطبيق الربيعي، قم بتوفيره قبل أن تسجل الخدمة نفسها لدى Eureka.
grpc :
port : 6565 (1)
eureka :
instance :
nonSecurePort : ${grpc.port} (2)
client :
serviceUrl :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
حدد رقم المنفذ الذي يستمع إليه gRPC.
قم بتسجيل منفذ خدمة eureka ليكون هو نفس منفذ grpc.port
حتى يعرف العميل مكان إرسال الطلبات إليه.
حدد عنوان URL للتسجيل، بحيث تقوم الخدمة بتسجيل نفسها به.
اكشف عن خدمة gRPC كجزء من تطبيق Spring Boot.
@ SpringBootApplication
@ EnableEurekaClient
public class EurekaGrpcServiceApp {
@ GRpcService
public static class GreeterService extends GreeterGrpc . GreeterImplBase {
@ Override
public void sayHello ( GreeterOuterClass . HelloRequest request , StreamObserver < GreeterOuterClass . HelloReply > responseObserver ) {
}
}
public static void main ( String [] args ) {
SpringApplication . run ( DemoApp . class , args );
}
}
أضف eureka starter باعتباره تبعية لمشروع العميل الخاص بك مع الفئات التي تم إنشاؤها من ملفات proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
قم بتكوين العميل للعثور على سجل خدمة eureka:
eureka :
client :
register-with-eureka : false (1)
service-url :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
false
إذا لم يكن المقصود من هذا المشروع أن يكون بمثابة خدمة لعميل آخر.
حدد عنوان URL للتسجيل، حتى يعرف هذا العميل مكان البحث عن الخدمة المطلوبة.
@ EnableEurekaClient
@ SpringBootApplication
public class GreeterServiceConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( GreeterServiceConsumerApplication . class , args );
}
}
استخدم EurekaClient للحصول على إحداثيات مثيل خدمة gRPC من Eureka واستهلاك الخدمة:
@ EnableEurekaClient
@ Component
public class GreeterServiceConsumer {
@ Autowired
private EurekaClient client ;
public void greet ( String name ) {
final InstanceInfo instanceInfo = client . getNextServerFromEureka ( "my-service-name" , false ); //(1)
final ManagedChannel channel = ManagedChannelBuilder . forAddress ( instanceInfo . getIPAddr (), instanceInfo . getPort ())
. usePlaintext ()
. build (); //(2)
final GreeterServiceGrpc . GreeterServiceFutureStub stub = GreeterServiceGrpc . newFutureStub ( channel ); //(3)
stub . greet ( name ); //(4)
}
}
احصل على معلومات حول مثيل my-service-name
.
بناء channel
وفقا لذلك.
إنشاء كعب باستخدام channel
.
استدعاء الخدمة.
أباتشي 2.0