Mengonfigurasi otomatis dan menjalankan server gRPC tertanam dengan kacang yang mendukung @GRpcService sebagai bagian dari aplikasi spring-boot (video pendek)
Pengguna Gradle
disarankan untuk menerapkan plugin :
plugins {
id " io.github.lognet.grpc-spring-boot " version ' 5.1.5 '
}
Plugin gradle io.github.lognet.grpc-spring-boot secara dramatis menyederhanakan pengaturan proyek.
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 '
}
Secara default, starter menarik io.grpc:grpc-netty-shaded
sebagai ketergantungan transitif, jika Anda terpaksa menggunakan ketergantungan grpc-netty
murni:
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)
Pastikan untuk menarik versi yang cocok dengan rilisnya.
Kehadiran kedua perpustakaan di classpath juga didukung dengan properti grpc.netty-server.on-collision-prefer-shaded-netty
.
Jika Anda menggunakan plugin Spring Boot Dependency Management, plugin tersebut mungkin tidak menggunakan versi yang sama dengan versi awal kompilasi, sehingga menyebabkan masalah ketidakcocokan biner.
Dalam hal ini Anda harus secara paksa dan eksplisit mengatur versi grpc
yang akan digunakan (lihat matriks versi di sini):
configurations . all {
resolutionStrategy . eachDependency { details ->
if ( " io.grpc " . equalsIgnoreCase(details . requested . group)) {
details . useVersion " 1.58.0 "
}
}
}
Catatan rilis dengan matriks kompatibilitas dapat ditemukan di sini |
Ikuti panduan ini untuk membuat stub dan antarmuka server dari file .proto
Anda.
Jika Anda menggunakan maven - gunakan tautan ini.
Beri anotasi pada implementasi antarmuka server Anda dengan @org.lognet.springboot.grpc.GRpcService
Secara opsional konfigurasikan port server di application.yml/properties
Anda. Pelabuhan bawaannya adalah 6565
.
grpc :
port : 6565
Port acak dapat ditentukan dengan mengatur port ke 0 .Port aktual yang sedang digunakan kemudian dapat diambil dengan menggunakan anotasi @LocalRunningGrpcPort pada bidang int yang akan memasukkan port yang sedang berjalan (dikonfigurasi secara eksplisit atau dipilih secara acak) |
Aktifkan refleksi server secara opsional (lihat https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md)
grpc :
enableReflection : true
Secara opsional, tetapkan urutan fase startup (defaultnya adalah Integer.MAX_VALUE
).
grpc :
start-up-phase : XXX
Secara opsional, tetapkan jumlah detik untuk menunggu panggilan yang sudah ada sebelumnya selesai selama penutupan server dengan baik. Panggilan baru akan ditolak selama waktu ini. Nilai negatif setara dengan masa tenggang yang tidak terbatas. Nilai defaultnya adalah 0
(berarti jangan menunggu).
grpc :
shutdownGrace : 30
Properti server khusus netty dapat ditentukan di bawah awalan grpc.netty-server
.
Dengan mengonfigurasi salah satu nilai grpc.netty-server.xxxx
Anda secara implisit menyetel transport menjadi berbasis 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)
Properti tipe Duration
dapat dikonfigurasi dengan format nilai string yang dijelaskan di sini.
Properti tipe DataSize
dapat dikonfigurasi dengan nilai string yang dijelaskan di sini
Terkena pada IP jaringan eksternal dengan port khusus.
Format nilai string properti tipe SocketAddress
:
host:port
(jika nilai port
kurang dari 1, gunakan nilai acak)
host:
(menggunakan port grpc default, 6565
)
Terkena pada IP jaringan internal juga dengan port yang telah ditentukan 6767
.
Jika Anda memiliki pustaka netty shaded
dan pure
dalam dependensi, pilih jenis NettyServerBuilder
yang harus dibuat. Ini adalah jenis yang akan diteruskan ke GRpcServerBuilderConfigurer
(lihat Konfigurasi Server gRPC Kustom), defaultnya adalah true
(yaitu io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder
; io.grpc.netty.NettyServerBuilder
if false
)
Starter juga mendukung in-process server
, yang harus digunakan untuk tujuan pengujian:
grpc :
enabled : false (1)
inProcessServerName : myTestServer (2)
Menonaktifkan server default ( NettyServer
).
Mengaktifkan server in-process
.
Jika Anda mengaktifkan NettyServer dan server in-process , keduanya akan berbagi instance HealthStatusManager dan GRpcServerBuilderConfigurer yang sama (lihat Konfigurasi Server gRPC Kustom). |
Dalam proyek grpc-spring-boot-starter-demo
Anda dapat menemukan contoh yang berfungsi penuh dengan pengujian integrasi.
Definisi layanan dari file .proto
terlihat seperti ini:
service Greeter {
rpc SayHello ( HelloRequest ) returns ( HelloReply ) {}
}
Perhatikan kelas io.grpc.examples.GreeterGrpc.GreeterImplBase
yang dihasilkan yang memperluas io.grpc.BindableService
.
Yang perlu Anda lakukan hanyalah memberi anotasi pada implementasi layanan Anda dengan @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 ();
}
}
Starter mendukung registrasi dua jenis pencegat: Global dan Per Service .
Dalam kedua kasus tersebut, pencegat harus mengimplementasikan antarmuka io.grpc.ServerInterceptor
.
Per layanan
@ GRpcService ( interceptors = { LogInterceptor . class })
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
LogInterceptor
akan dipakai melalui pabrik pegas jika ada kacang bertipe LogInterceptor
, atau melalui konstruktor tanpa argumen sebaliknya.
Global
@ GRpcGlobalInterceptor
public class MyInterceptor implements ServerInterceptor {
// ommited
}
Anotasi pada metode pabrik konfigurasi Java juga didukung:
@ 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 );
}
};
}
}
Layanan tertentu juga memiliki kesempatan untuk menonaktifkan pencegat global :
@ GRpcService ( applyGlobalInterceptors = false )
public class GreeterService extends GreeterGrpc . GreeterImplBase {
// ommited
}
Pencegat global dapat dipesan menggunakan anotasi @Ordered
atau @Priority
Spring. Mengikuti semantik pengurutan Spring, nilai urutan yang lebih rendah memiliki prioritas lebih tinggi dan akan dieksekusi pertama kali dalam rantai pencegat.
@ 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
}
Pemula menggunakan interseptor bawaan untuk mengimplementasikan penanganan kesalahan, Security
Musim Semi, Validation
, dan integrasi Metrics
. Urutannya juga dapat dikontrol oleh properti di bawah ini:
grpc.recovery.interceptor-order
(kesalahan menangani pesanan interseptor, defaultnya adalah Ordered.HIGHEST_PRECEDENCE
)
grpc.security.auth.interceptor-order
( defaultnya adalah Ordered.HIGHEST_PRECEDENCE+1
)
grpc.validation.interceptor-order
( defaultnya adalah Ordered.HIGHEST_PRECEDENCE+10
)
grpc.metrics.interceptor-order
( defaultnya adalah Ordered.HIGHEST_PRECEDENCE+20
)
Ini memberi Anda kemampuan untuk mengatur urutan interseptor bawaan dan kustom Anda yang diinginkan.
Teruslah membaca!!! Masih ada lagi
Cara kerja pencegat grpc adalah dengan mencegat panggilan dan mengembalikan pendengar panggilan server, yang pada gilirannya juga dapat mencegat pesan permintaan, sebelum meneruskannya ke penangan panggilan layanan sebenarnya:
Dengan menyetel properti grpc.security.auth.fail-fast
ke false
semua pencegat hilir serta semua pencegat hulu (On_Message) akan tetap dieksekusi jika terjadi kegagalan otentikasi/otorisasi
Dengan asumsi interceptor_2
adalah securityInterceptor
:
Untuk autentikasi/otorisasi yang gagal dengan grpc.security.auth.fail-fast=true
(default):
Untuk autentikasi/otorisasi yang gagal dengan grpc.security.auth.fail-fast=false
:
Permulaan ini didukung oleh proyek spring-cloud-sleuth
.
Silakan lanjutkan untuk menyelidiki integrasi grpc.
Dengan menyertakan ketergantungan org.springframework.boot:spring-boot-starter-actuator
, starter akan mengumpulkan metrik server gRPC, yang dikelompokkan berdasarkan
method
- metode layanan gRPC FQN (Nama Penuh Memenuhi Syarat)
result
- Kode status respons
address
- alamat lokal server (jika Anda membuka alamat pendengaran tambahan, dengan properti grpc.netty-server.additional-listen-addresses
)
Setelah mengonfigurasi eksportir pilihan Anda, Anda akan melihat timer
bernama grpc.server.calls
.
Dengan mendefinisikan kacang GRpcMetricsTagsContributor
dalam konteks aplikasi, Anda dapat menambahkan tag khusus ke timer grpc.server.calls
.
Anda juga dapat menggunakan kacang RequestAwareGRpcMetricsTagsContributor
untuk menandai panggilan unary dan streaming .
Demo ada di sini
Pertahankan dispersi tetap rendah agar tidak meningkatkan kardinalitas metrik. |
RequestAwareGRpcMetricsTagsContributor
masih dapat dijalankan untuk autentikasi yang gagal jika pencegat metric
memiliki prioritas lebih tinggi daripada pencegat security
dan grpc.security.auth.fail-fast
disetel ke false
.
Kasus ini tercakup dalam tes ini.
Pastikan untuk membaca bab pemesanan Interceptors. |
Pastikan untuk menyertakan dependensi di bawah ini:
implementation "org.springframework.boot:spring-boot-starter-actuator"
implementation "io.micrometer:micrometer-registry-prometheus"
implementation 'org.springframework.boot:spring-boot-starter-web'
Konfigurasi:
management :
metrics :
export :
prometheus :
enabled : true
endpoints :
web :
exposure :
include : " * "
Titik akhir /actuator/metrics
dan /actuator/prometheus
standar akan merender metrik grpc.server.calls
(lihat demo di sini).
Proposal penghapusan GRPC |
Starter dapat dikonfigurasi secara otomatis untuk memvalidasi pesan layanan gRPC permintaan/respons. Silakan lanjutkan ke Menerapkan validasi pesan untuk detail konfigurasi.
Pemula secara internal mendefinisikan kacang bertipe java.util.function.Consumer
yang sedang dipertimbangkan untuk registri fungsi ketika spring-cloud-stream
berada di classpath, yang tidak diinginkan ( spring-cloud-stream
mendaftarkan saluran secara otomatis jika Anda memilikinya satu kacang Konsumen/Pemasok/Fungsi dalam konteks aplikasi, jadi Anda sudah memilikinya jika menggunakan starter ini bersama dengan spring-cloud-stream
).
Oleh karena itu, disarankan untuk menggunakan properti spring.cloud.function.definition
dalam aplikasi siap produksi dan tidak bergantung pada penemuan otomatis.
Silakan merujuk ke demo GRPC Kafka Stream, yang penting adalah baris ini.
Starter menyediakan dukungan bawaan untuk mengautentikasi dan mengotorisasi pengguna yang memanfaatkan integrasi dengan kerangka Spring Security.
Silakan merujuk ke bagian Integrasi Keamanan Musim Semi untuk detail tentang penyedia autentikasi yang didukung dan opsi konfigurasi.
Keamanan transportasi dapat dikonfigurasi menggunakan sertifikat akar bersama dengan jalur kunci privatnya:
grpc :
security :
cert-chain : classpath:cert/server-cert.pem
private-key : file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem
Nilai kedua properti dalam bentuk yang didukung oleh ResourceEditor.
Sisi klien harus dikonfigurasi sesuai:
(( NettyChannelBuilder ) channelBuilder )
. useTransportSecurity ()
. sslContext ( GrpcSslContexts . forClient (). trustManager ( certChain ). build ());
Pemula ini akan menarik ketergantungan io.netty:netty-tcnative-boringssl-static
secara default untuk mendukung SSL.
Jika Anda memerlukan dukungan SSL/TLS lainnya, harap kecualikan ketergantungan ini dan ikuti Panduan Keamanan.
Jika penyesuaian yang lebih mendetail diperlukan untuk penyiapan keamanan, harap gunakan konfigurasi khusus yang dijelaskan dalam Konfigurasi Server gRPC Khusus |
Untuk mencegat instance io.grpc.ServerBuilder
yang digunakan untuk membangun io.grpc.Server
, Anda dapat menambahkan bean yang mewarisi dari org.lognet.springboot.grpc.GRpcServerBuilderConfigurer
ke konteks Anda dan mengganti metode configure
.
Beberapa konfigurasi juga didukung.
Pada saat pemanggilan metode configure
, semua layanan yang ditemukan, termasuk pencegatnya, telah ditambahkan ke pembuat yang diteruskan.
Dalam penerapan metode configure
, Anda dapat menambahkan konfigurasi khusus Anda:
@ 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 ) ;
}
};
}
Jika Anda mengaktifkan NettyServer dan server in-process , metode configure akan dipanggil pada instance konfigurasi yang sama.Jika Anda perlu membedakan serverBuilder yang diteruskan, Anda dapat memeriksa jenisnya.Ini adalah batasan saat ini. |
GRpcServerInitializedEvent
diterbitkan saat server dinyalakan, Anda dapat menggunakannya menggunakan API pegas biasa.
Mulai dari versi 5.1.0
, spring-boot-starter-gradle-plugin mengintegrasikan plugin protoc reaktif-grpc SalesForce :
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id " io.github.lognet.grpc-spring-boot "
}
grpcSpringBoot {
reactiveFeature . set( ReactiveFeature . REACTOR ) // or ReactiveFeature.RX
}
Berikut adalah tes dan layanan sampel grpc reaktif.
Pemula mendaftarkan GRpcExceptionHandlerInterceptor
yang bertanggung jawab untuk menyebarkan pengecualian yang diberikan layanan ke penangan kesalahan.
Metode penanganan kesalahan dapat didaftarkan dengan membuat kacang beranotasi @GRpcServiceAdvice
dengan metode yang dianotasi dengan anotasi @GRpcExceptionHandler
.
Ini dianggap sebagai penangan kesalahan global
dan metode dengan parameter tipe pengecualian yang paling dekat dengan hierarki tipe dengan pengecualian yang dilemparkan akan dipanggil.
Tanda tangan penangan kesalahan harus mengikuti pola di bawah ini:
Jenis pengembalian | Parameter 1 | Parameter 2 |
---|---|---|
io.grpc.Status | jenis | 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 ){
}
}
Anda dapat memiliki kacang advice
dan metode penangan sebanyak yang Anda inginkan selama keduanya tidak mengganggu satu sama lain dan tidak membuat ambiguitas jenis pengecualian yang ditangani.
Kacang layanan grpc
juga ditemukan untuk penangan kesalahan, yang memiliki prioritas lebih tinggi daripada metode penanganan kesalahan global yang ditemukan di kacang @GRpcServiceAdvice
. Metode penanganan kesalahan tingkat layanan dianggap private
dan dipanggil hanya ketika pengecualian diberikan oleh layanan ini :
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
}
}
Karena sifat API layanan grpc
yang tidak mengizinkan pelemparan pengecualian yang dicentang, jenis pengecualian runtime khusus disediakan untuk membungkus pengecualian yang dicentang. Itu kemudian dibuka ketika mencari metode penangan.
Saat melontarkan pengecualian GRpcRuntimeExceptionWrapper
, Anda juga dapat meneruskan objek hint
yang kemudian dapat diakses dari objek scope
dalam metode handler
.
Pengecualian waktu proses dapat ditampilkan apa adanya dan tidak perlu dibungkus.
Dapatkan objek petunjuk.
Kirim header khusus ke klien.
Kegagalan otentikasi disebarkan melalui AuthenticationException
dan kegagalan otorisasi - melalui AccessDeniedException
.
Kegagalan validasi disebarkan melalui ConstraintViolationException
: untuk permintaan yang gagal - dengan Status.INVALID_ARGUMENT
sebagai petunjuk , dan untuk respons yang gagal - dengan Status.FAILED_PRECONDITION
sebagai petunjuk.
Demonya ada di sini
Berkat dukungan konfigurasi Validasi Bean melalui deskriptor penerapan XML, batasan untuk kelas yang dihasilkan dapat diberikan melalui XML alih-alih melengkapi pesan yang dihasilkan dengan kompiler protoc
khusus.
Tambahkan ketergantungan org.springframework.boot:spring-boot-starter-validation
ke proyek Anda.
Buat file deklarasi META-INF/validation.xml
dan batasan. (IntelliJ IDEA memiliki dukungan pelengkapan otomatis yang bagus untuk mengotorisasi file xml batasan validasi kacang)
Lihat juga contoh dari dokumentasi validator Hibernate
Anda dapat menemukan konfigurasi demo dan pengujian terkait di sini
Perhatikan, pesan request
dan response
sedang divalidasi.
Jika metode gRPC Anda menggunakan jenis pesan permintaan dan respons yang sama, Anda dapat menggunakan grup validasi org.lognet.springboot.grpc.validation.group.RequestMessage
dan org.lognet.springboot.grpc.validation.group.ResponseMessage
untuk menerapkan logika validasi yang berbeda :
...
< 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 >
...
Terapkan batasan ini hanya untuk pesan request
Terapkan batasan ini hanya untuk pesan response
Perhatikan juga batasan khusus lintas bidang dan penggunaannya:
< bean class = " io.grpc.examples.GreeterOuterClass$Person " >
< class >
< constraint annotation = " org.lognet.springboot.grpc.demo.PersonConstraint " />
</ class >
<!-- ... -->
</ bean >
Seperti yang dijelaskan dalam bab pengurutan Interceptors, Anda dapat memberikan interseptor validation
dengan prioritas lebih tinggi daripada interseptor security
dan menyetel properti grpc.security.auth.fail-fast
ke false
.
Dalam skenario ini, jika panggilan tidak diautentikasi dan tidak valid, klien akan mendapatkan status respons Status.INVALID_ARGUMENT
alih-alih Status.PERMISSION_DENIED/Status.UNAUTHENTICATED
. Demo ada di sini
Meskipun metode rpc Anda masih mungkin dianotasi dengan @Transactional
(dengan spring.aop.proxy-target-class=true
jika tidak diaktifkan secara default), kemungkinan besar akan terjadi perilaku yang tidak dapat diprediksi. Pertimbangkan penerapan metode grpc di bawah ini:
@ 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)
}
Metode ini dianotasi sebagai @Transactional
, Spring akan melakukan transaksi beberapa saat setelah metode kembali
Respons dikembalikan ke penelepon
Metode kembali, transaksi akhirnya dilakukan.
Secara teoritis, dan seperti yang Anda lihat - secara praktis, ada rentang waktu kecil ketika klien (jika latensi jaringan minimal, dan server grpc Anda mendorong peralihan konteks tepat setelah <2>) dapat mencoba mengakses database melalui panggilan grpc lain sebelumnya transaksi dilakukan.
Solusi untuk mengatasi situasi ini adalah dengan mengeksternalisasikan logika transaksional ke dalam kelas layanan terpisah:
@ 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 ();
}
}
Metode pelayanan bersifat transaksional
Transaksi akhirnya dilakukan.
Balasan setelah transaksi dilakukan.
Dengan mengikuti pendekatan ini Anda juga memisahkan lapisan transport dan logika bisnis yang kini dapat diuji secara terpisah.
Skema | Ketergantungan |
---|---|
Dasar |
|
Pembawa |
|
Kebiasaan |
|
Konfigurasi keamanan GRPC mengikuti prinsip dan API yang sama dengan konfigurasi keamanan Spring WEB, ini diaktifkan secara default jika Anda memiliki ketergantungan org.springframework.security:spring-security-config
di classpath Anda.
Anda dapat menggunakan anotasi @Secured
pada layanan/metode untuk melindungi titik akhir Anda, atau dengan menggunakan API dan mengganti default (yang mendahului anotasi @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 )
}
}
atau gabungkan API
dengan anotasi @Secured
.
Konfigurasi default ini mengamankan metode/layanan GRPC yang dianotasi dengan anotasi org.springframework.security.access.annotation.@Secured
.
Membiarkan nilai anotasi kosong ( @Secured({})
) berarti : hanya authenticate
, tidak ada otorisasi yang akan dilakukan.
Jika kacang JwtDecoder
ada dalam konteks Anda, kacang tersebut juga akan mendaftarkan JwtAuthenticationProvider
untuk menangani validasi klaim autentikasi.
BasicAuthSchemeSelector
dan BearerTokenAuthSchemeSelector
juga secara otomatis terdaftar untuk mendukung otentikasi dengan nama pengguna/kata sandi dan token pembawa.
Dengan menyetel grpc.security.auth.enabled
ke false
, keamanan GRPC dapat dimatikan.
Kustomisasi konfigurasi keamanan GRPC dilakukan dengan memperluas GrpcSecurityConfigurerAdapter
(Berbagai contoh konfigurasi dan skenario pengujian ada di sini.)
@ 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 )
}
}
Dapatkan objek konfigurasi otorisasi
MethodDefinition
metode sayHello
diperbolehkan untuk pengguna yang diautentikasi dengan otoritas SCOPE_profile
.
Gunakan JwtAuthenticationProvider
untuk memvalidasi klaim pengguna (token BEARER
) terhadap server sumber daya yang dikonfigurasi dengan properti spring.security.oauth2.resourceserver.jwt.issuer-uri
.
Seseorang dapat menyambungkan penyedia autentikasi pesanan Anda sendiri dengan mengimplementasikan antarmuka 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 );
}
});
}
}
Amankan semua metode layanan.
Daftarkan AuthenticationSchemeSelector
Anda sendiri.
Berdasarkan header otorisasi yang diberikan - kembalikan objek Authentication
sebagai klaim (belum diautentikasi)
Daftarkan AuthenticationProvider
Anda sendiri yang mendukung validasi MyAuthenticationObject
Validasi authentication
yang disediakan dan kembalikan objek Authentication
yang divalidasi dan diautentikasi
AuthenticationSchemeSelector
juga dapat didaftarkan dengan mendefinisikan Spring bean dalam konteks aplikasi Anda:
@ Bean
public AuthenticationSchemeSelector myCustomSchemeSelector (){
return authHeader ->{
// your logic here
};
}
Bagian dukungan konfigurasi sisi klien menjelaskan cara meneruskan skema otorisasi khusus dan klaim dari klien GRPC.
Mulai dari versi 4.5.9
Anda juga dapat menggunakan anotasi standar @PreAuthorize
dan @PostAuthorize
pada metode layanan grpc dan jenis layanan grpc.
Jenis Panggilan | Referensi objek masukan | Referensi objek keluaran | Mencicipi |
---|---|---|---|
Unary | Berdasarkan nama parameter | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void unary ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Aliran masukan, | | | @ Override
@ PreAuthorize ( "#p0.getAge()<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > inStream ( StreamObserver < Assignment > responseObserver ) {
} |
Permintaan tunggal, | Berdasarkan nama parameter | | @ Override
@ PreAuthorize ( "#person.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public void outStream ( Person person , StreamObserver < Assignment > responseObserver ) {
} |
Aliran Bidi | | | @ Override
@ PreAuthorize ( "#p0.age<12" )
@ PostAuthorize ( "returnObject.description.length()>0" )
public StreamObserver < Person > bidiStream ( StreamObserver < Assignment > responseObserver ) {
} |
Untuk mendapatkan objek Authentication
dalam implementasi metode aman , silakan gunakan cuplikan di bawah ini
final Authentication auth = GrpcSecurity . AUTHENTICATION_CONTEXT_KEY . get ();
Mulai dari 4.5.6
, objek Authentication
juga dapat diperoleh melalui Spring API standar :
final Authentication auth = SecurityContextHolder . getContext (). getAuthentication ();
Dengan menambahkan ketergantungan io.github.lognet:grpc-client-spring-boot-starter
ke aplikasi klien java grpc Anda, Anda dapat dengan mudah mengonfigurasi kredensial per saluran atau per panggilan:
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
}
}
Buat pencegat klien
Saluran intersepsi
Mengaktifkan/menonaktifkan format biner:
Jika true
, header autentikasi dikirim dengan kunci Authorization-bin
menggunakan marshaller biner.
Jika false
, header otentikasi dikirim dengan kunci Authorization
menggunakan marshaller ASCII.
Menyediakan fungsi pembuat token (Silakan lihat misalnya.)
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 ();
}
}
Buat kredensial panggilan dengan skema dasar
Buat rintisan layanan
Lampirkan kredensial panggilan ke panggilan tersebut
AuthHeader
juga dapat dibuat dengan skema otorisasi yang dipesan lebih dahulu :
AuthHeader
. builder ()
. authScheme ( "myCustomAuthScheme" )
. tokenSupplier (()-> generateMyCustomToken ())
Pemula mendaftarkan implementasi default HealthServiceImpl.
Anda dapat menyediakannya sendiri dengan mendaftarkan kacang ManagedHealthStatusService dalam konteks aplikasi Anda.
Jika Anda memiliki org.springframework.boot:spring-boot-starter-actuator
dan org.springframework.boot:spring-boot-starter-web
di classpath, starter akan menampilkan:
indikator kesehatan grpc
di bawah titik akhir /actuator/health
.
/actuator/grpc
.
Hal ini dapat dikontrol oleh titik akhir standar dan konfigurasi kesehatan.
Mulai dari versi 3.3.0
, starter akan secara otomatis mendaftarkan server grpc yang sedang berjalan di registri Konsul jika org.springframework.cloud:spring-cloud-starter-consul-discovery
ada di classpath dan spring.cloud.service-registry.auto-registration.enabled
TIDAK disetel ke false
.
Nama layanan terdaftar akan diawali dengan grpc-
, yaitu grpc-${spring.application.name}
agar tidak mengganggu nama layanan web terdaftar standar jika Anda memilih untuk menjalankan server Grpc
dan Web
tertanam.
ConsulDiscoveryProperties
terikat dari properti konfigurasi yang diawali dengan spring.cloud.consul.discovery
dan kemudian nilainya ditimpa oleh properti yang diawali grpc.consul.discovery
(jika disetel). Hal ini memungkinkan Anda untuk memiliki konfigurasi penemuan konsul terpisah untuk layanan rest
dan grpc
jika Anda memilih untuk mengekspos keduanya dari aplikasi Anda.
spring :
cloud :
consul :
discovery :
metadata :
myKey : myValue (1)
tags :
- myWebTag (2)
grpc :
consul :
discovery :
tags :
- myGrpcTag (3)
Layanan rest
dan grpc
terdaftar dengan metadata myKey=myValue
Layanan istirahat terdaftar di myWebTag
Layanan Grpc terdaftar di myGrpcTag
Menyetel spring.cloud.consul.discovery.register-health-check
(atau grpc.consul.discovery.register-health-check
) ke true
akan mendaftarkan layanan pemeriksaan kesehatan GRPC ke Konsul.
Ada 4 mode registrasi yang didukung:
SINGLE_SERVER_WITH_GLOBAL_CHECK
(bawaan)
Dalam mode ini, server grpc yang berjalan didaftarkan sebagai layanan tunggal dengan pemeriksaan grpc
tunggal dengan serviceId
kosong.
Harap dicatat bahwa implementasi default tidak melakukan apa pun dan hanya mengembalikan status SERVING
. Anda mungkin ingin memberikan penerapan Pemeriksaan kesehatan khusus untuk mode ini.
SINGLE_SERVER_WITH_CHECK_PER_SERVICE
Dalam mode ini, server grpc yang berjalan didaftarkan sebagai layanan tunggal dengan pemeriksaan untuk setiap layanan grpc
yang ditemukan.
STANDALONE_SERVICES
Dalam mode ini setiap layanan grpc yang ditemukan didaftarkan sebagai layanan tunggal dengan pemeriksaan tunggal. Setiap layanan terdaftar ditandai dengan nama layanannya sendiri.
NOOP
- tidak ada layanan grpc yang terdaftar. Mode ini berguna jika Anda melayani layanan rest
dan grpc
di aplikasi Anda, tetapi karena alasan tertentu, hanya layanan rest
yang harus didaftarkan ke Konsul.
grpc :
consule :
registration-mode : SINGLE_SERVER_WITH_CHECK_PER_SERVICE
Saat membangun layanan siap produksi, sarannya adalah memiliki proyek terpisah untuk API gRPC layanan Anda yang hanya menampung kelas yang dihasilkan proto untuk penggunaan sisi server dan klien.
Anda kemudian akan menambahkan proyek ini sebagai ketergantungan implementation
ke gRPC client
dan proyek gRPC server
Anda.
Untuk mengintegrasikan Eureka
cukup ikuti panduan hebat dari Spring.
Di bawah ini adalah bagian penting dari konfigurasi untuk proyek server dan klien.
Tambahkan eureka starter sebagai ketergantungan proyek server Anda bersama dengan kelas yang dihasilkan dari file proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Konfigurasikan server gRPC untuk mendaftarkan dirinya ke Eureka.
spring :
application :
name : my-service-name (1)
ServiceId
Eureka secara default adalah nama aplikasi pegas, berikan sebelum layanan mendaftarkan dirinya ke Eureka.
grpc :
port : 6565 (1)
eureka :
instance :
nonSecurePort : ${grpc.port} (2)
client :
serviceUrl :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (3)
Tentukan nomor port tempat gRPC mendengarkan.
Daftarkan port layanan eureka agar sama dengan grpc.port
sehingga klien mengetahui ke mana harus mengirim permintaan.
Tentukan URL registri, sehingga layanan akan mendaftar sendiri.
Ekspos layanan gRPC sebagai bagian dari Aplikasi 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 );
}
}
Tambahkan eureka starter sebagai ketergantungan proyek klien Anda bersama dengan kelas yang dihasilkan dari file proto
:
dependencies {
implementation( ' org.springframework.cloud:spring-cloud-starter-eureka ' )
implementation project( " :yourProject-api " )
}
Konfigurasikan klien untuk menemukan registri layanan eureka:
eureka :
client :
register-with-eureka : false (1)
service-url :
defaultZone : http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ (2)
false
jika proyek ini tidak dimaksudkan sebagai layanan kepada klien lain.
Tentukan URL registri, sehingga klien ini akan mengetahui di mana mencari layanan yang diperlukan.
@ EnableEurekaClient
@ SpringBootApplication
public class GreeterServiceConsumerApplication {
public static void main ( String [] args ) {
SpringApplication . run ( GreeterServiceConsumerApplication . class , args );
}
}
Gunakan EurekaClient untuk mendapatkan koordinat instance layanan gRPC dari Eureka dan gunakan layanan ini:
@ 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)
}
}
Dapatkan informasi tentang contoh my-service-name
.
Bangun channel
yang sesuai.
Buat rintisan menggunakan channel
.
Panggil layanan tersebut.
Apache 2.0