gRPC-Rust का इस्तेमाल शुरू करना

1. परिचय

इस कोडलैब में, gRPC-Rust का इस्तेमाल करके एक क्लाइंट और सर्वर बनाया जाएगा. ये दोनों, Rust में लिखे गए रूट-मैपिंग ऐप्लिकेशन की बुनियादी ज़रूरतें पूरी करते हैं.

ट्यूटोरियल के आखिर तक, आपके पास एक ऐसा क्लाइंट होगा जो gRPC का इस्तेमाल करके, रिमोट सर्वर से कनेक्ट होता है. इससे आपको मैप पर किसी खास जगह के नाम या पते की जानकारी मिलती है. कोई ऐप्लिकेशन, इस क्लाइंट-सर्वर डिज़ाइन का इस्तेमाल करके किसी रास्ते पर मौजूद लोकप्रिय जगहों की सूची बना सकता है या उनके बारे में खास जानकारी दे सकता है.

सेवा को प्रोटोकॉल बफ़र फ़ाइल में तय किया जाता है. इसका इस्तेमाल क्लाइंट और सर्वर के लिए बॉयलरप्लेट कोड जनरेट करने के लिए किया जाएगा, ताकि वे एक-दूसरे से कम्यूनिकेट कर सकें. इससे आपको इस सुविधा को लागू करने में समय और मेहनत नहीं करनी पड़ेगी.

जनरेट किया गया यह कोड, सर्वर और क्लाइंट के बीच कम्यूनिकेशन की जटिलताओं के साथ-साथ डेटा के क्रमबद्ध और क्रम से हटाने की प्रोसेस को भी मैनेज करता है.

आपको क्या सीखने को मिलेगा

  • किसी सेवा के एपीआई को तय करने के लिए, प्रोटोकॉल बफ़र का इस्तेमाल कैसे करें.
  • ऑटोमेटेड कोड जनरेशन का इस्तेमाल करके, Protocol Buffers की परिभाषा से gRPC पर आधारित क्लाइंट और सर्वर बनाने का तरीका.
  • gRPC के साथ क्लाइंट-सर्वर कम्यूनिकेशन के बारे में जानकारी.

यह कोडलैब, उन Rust डेवलपर के लिए है जो gRPC का इस्तेमाल पहली बार कर रहे हैं या gRPC के बारे में फिर से जानकारी पाना चाहते हैं. इसके अलावा, यह उन लोगों के लिए भी है जो डिस्ट्रिब्यूटेड सिस्टम बनाना चाहते हैं. इसके लिए, gRPC का अनुभव होना ज़रूरी नहीं है.

2. शुरू करने से पहले

ज़रूरी शर्तें

पक्का करें कि आपने ये इंस्टॉल किए हों:

  • GCC. यहां दिए गए निर्देशों का पालन करें
  • Rust, वर्शन 1.89.0. इंस्टॉल करने के निर्देशों के लिए, यहां जाएं.

कोड प्राप्त करें

इसलिए, आपको शुरू से काम न करना पड़े, इस कोडलैब में ऐप्लिकेशन के सोर्स कोड का एक स्केफ़ोल्ड दिया गया है. आपको इसे पूरा करना होगा. यहां दिए गए तरीके से, ऐप्लिकेशन को पूरा करने का तरीका जानें. इसमें, बॉयलरप्लेट gRPC कोड जनरेट करने के लिए, प्रोटोकॉल बफ़र कंपाइलर प्लगिन का इस्तेमाल करने का तरीका भी शामिल है.

सबसे पहले, कोडलैब की वर्किंग डायरेक्ट्री बनाएं और उसमें cd करें:

mkdir grpc-rust-getting-started && cd grpc-rust-getting-started

कोडलैब को डाउनलोड और एक्सट्रैक्ट करें:

curl -sL https://github.com/grpc-ecosystem/grpc-codelabs/archive/refs/heads/v1.tar.gz \
  | tar xvz --strip-components=4 \
  grpc-codelabs-1/codelabs/grpc-rust-getting-started/start_here

इसके अलावा, सिर्फ़ कोडलैब डायरेक्ट्री वाली .zip फ़ाइल डाउनलोड करके, उसे मैन्युअल तरीके से अनज़िप किया जा सकता है.

अगर आपको लागू करने के लिए टाइप नहीं करना है, तो पूरा सोर्स कोड GitHub पर उपलब्ध है.

3. सेवा के बारे में जानकारी देना

सबसे पहले, आपको प्रोटोकॉल बफ़र का इस्तेमाल करके, ऐप्लिकेशन की gRPC सेवा, उसके आरपीसी तरीके, और उसके अनुरोध और जवाब के मैसेज टाइप तय करने होंगे. आपकी सेवा से ये सुविधाएं मिलेंगी:

  • GetFeature नाम की एक आरपीसी विधि, जिसे सर्वर लागू करता है और क्लाइंट कॉल करता है.
  • Point और Feature मैसेज टाइप, डेटा स्ट्रक्चर होते हैं. GetFeature तरीके का इस्तेमाल करते समय, क्लाइंट और सर्वर के बीच इनका आदान-प्रदान होता है. क्लाइंट, सर्वर को GetFeature अनुरोध में मैप के कोऑर्डिनेट Point के तौर पर देता है. इसके जवाब में सर्वर, उन कोऑर्डिनेट पर मौजूद जानकारी के साथ Feature भेजता है.

इस आरपीसी तरीके और इसके मैसेज टाइप को, दिए गए सोर्स कोड की proto/route_guide.proto फ़ाइल में तय किया जाएगा.

प्रोटोकॉल बफ़र को आम तौर पर, protobufs के नाम से जाना जाता है. gRPC की शब्दावली के बारे में ज़्यादा जानने के लिए, gRPC के मुख्य कॉन्सेप्ट, आर्किटेक्चर, और लाइफ़साइकल देखें.

सेवा का तरीका

सबसे पहले, हम अपनी सेवा के तरीकों के बारे में बताएंगे. इसके बाद, हम अपने मैसेज टाइप Point और Feature के बारे में बताएंगे. proto/routeguide.proto फ़ाइल में service स्ट्रक्चर होता है, जिसका नाम RouteGuide होता है. यह ऐप्लिकेशन की सेवा के ज़रिए उपलब्ध कराए गए एक या उससे ज़्यादा तरीकों के बारे में बताता है.

RouteGuide की परिभाषा में, rpc तरीका GetFeature जोड़ें. जैसा कि पहले बताया गया है, यह तरीका निर्देशांकों के दिए गए सेट से किसी जगह का नाम या पता ढूंढता है. इसलिए, दिए गए Point के लिए GetFeature को Feature वापस लाने के लिए कहें:

service RouteGuide {
  // Definition of the service goes here

  // Obtains the feature at a given position.
  rpc GetFeature(Point) returns (Feature) {}
}

यह एक यूनेरी आरपीसी तरीका है: एक सिंपल आरपीसी, जिसमें क्लाइंट सर्वर को अनुरोध भेजता है और जवाब मिलने का इंतज़ार करता है. यह लोकल फ़ंक्शन कॉल की तरह होता है.

मैसेज के टाइप

सोर्स कोड की proto/route_guide.proto फ़ाइल में, सबसे पहले Point मैसेज टाइप तय करें. Point, मैप पर अक्षांश-देशांतर के निर्देशांकों के जोड़े को दिखाता है. इस कोडलैब के लिए, पूर्णांकों का इस्तेमाल करें:

message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

1 और 2 नंबर, message स्ट्रक्चर में मौजूद हर फ़ील्ड के लिए यूनीक आईडी नंबर होते हैं.

इसके बाद, Feature मैसेज टाइप तय करें. Feature, Point में बताई गई जगह पर मौजूद किसी चीज़ के नाम या डाक पते के लिए string फ़ील्ड का इस्तेमाल करता है:

message Feature {
  // The name or address of the feature.
  string name = 1;

  // The point where the feature is located.
  Point location = 2;
}

4. क्लाइंट और सर्वर कोड जनरेट करना

हमने जनरेट की गई डायरेक्ट्री में मौजूद .proto फ़ाइल से जनरेट किया गया कोड आपको पहले ही दे दिया है.

किसी भी प्रोजेक्ट की तरह, हमें उन डिपेंडेंसी के बारे में सोचना होगा जो हमारे कोड के लिए ज़रूरी हैं. Rust प्रोजेक्ट के लिए, डिपेंडेंसी Cargo.toml में होंगी. हमने Cargo.toml फ़ाइल में ज़रूरी डिपेंडेंसी पहले ही लिस्ट कर दी हैं.

अगर आपको .proto फ़ाइल से कोड जनरेट करने का तरीका जानना है, तो ये निर्देश देखें.

जनरेट किए गए कोड में ये शामिल हैं:

  • मैसेज टाइप Point और Feature के लिए स्ट्रक्ट की परिभाषाएं.
  • हमें सेवा की इस विशेषता को लागू करना होगा: route_guide_server::RouteGuide.
  • सर्वर को कॉल करने के लिए, हम इस क्लाइंट टाइप का इस्तेमाल करेंगे: route_guide_client::RouteGuideClient<T>.

इसके बाद, हम सर्वर साइड पर GetFeature तरीके को लागू करेंगे, ताकि जब क्लाइंट कोई अनुरोध भेजे, तो सर्वर उसका जवाब दे सके.

5. सेवा लागू करना

src/server/server.rs में, जनरेट किए गए कोड को gRPC के include_generated_proto! मैक्रो की मदद से स्कोप में लाया जा सकता है. साथ ही, RouteGuide ट्रेट और Point को इंपोर्ट किया जा सकता है.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

pub use grpc_pb::{
    route_guide_server::{RouteGuideServer, RouteGuide},
    Point, Feature,
};

हम अपनी सेवा को दिखाने के लिए, struct को तय करके शुरुआत कर सकते हैं. फ़िलहाल, हम इसे src/server/server.rs पर कर सकते हैं:

#[derive(Debug)]
pub struct RouteGuideService {
    features: Vec<Feature>,
}

अब हमें जनरेट किए गए कोड से route_guide_server::RouteGuide ट्रेट को लागू करना होगा.

यूनरी आरपीसी

RouteGuideService, हमारी सभी सेवा के तरीकों को लागू करता है. सर्वर साइड पर मौजूद get_feature फ़ंक्शन, मुख्य काम करता है: यह क्लाइंट से Point मैसेज लेता है और Feature मैसेज में, जानी-पहचानी जगहों की सूची से जगह की जानकारी दिखाता है. यहां src/server/server.rs में फ़ंक्शन को लागू करने का तरीका बताया गया है:

#[tonic::async_trait]
impl RouteGuide for RouteGuideService {
    async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
        println!("GetFeature = {:?}", request);
        let requested_point = request.get_ref();
        for feature in self.features.iter() {
            if feature.location().latitude() == requested_point.latitude() {
                if feature.location().longitude() == requested_point.longitude(){
                    return Ok(Response::new(feature.clone()))
                };
            };    
        }
        Ok(Response::new(Feature::default()))
    }
}

इस तरीके में, दिए गए Point के लिए सही जानकारी के साथ Feature ऑब्जेक्ट भरें. इसके बाद, इसे वापस भेजें.

इस तरीके को लागू करने के बाद, हमें एक gRPC सर्वर भी शुरू करना होगा, ताकि क्लाइंट हमारी सेवा का इस्तेमाल कर सकें. main()को इससे बदलें.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = "[::1]:10000".parse().unwrap();
    println!("RouteGuideServer listening on: {addr}");
    let route_guide = RouteGuideService {
        features: load(),
    };
    let svc = RouteGuideServer::new(route_guide);
    Server::builder().add_service(svc).serve(addr).await?;
    Ok(())
}

यहां सिलसिलेवार तरीके से बताया गया है कि main() में क्या हो रहा है:

  1. वह पोर्ट तय करें जिसका इस्तेमाल हमें क्लाइंट के अनुरोधों को सुनने के लिए करना है
  2. सहायता करने वाले फ़ंक्शन load() को कॉल करके, सुविधाओं से भरा RouteGuideService बनाएं
  3. हमने जो सेवा बनाई है उसका इस्तेमाल करके, RouteGuideServer::new() की मदद से gRPC सर्वर का इंस्टेंस बनाएं.
  4. gRPC सर्वर के साथ, हमारी सेवा को लागू करने की सुविधा रजिस्टर करें.
  5. पोर्ट करने की जानकारी के साथ सर्वर पर serve() को कॉल करें, ताकि प्रोसेस बंद होने तक इंतज़ार किया जा सके.

6. क्लाइंट बनाना

इस सेक्शन में, हम src/client/client.rs में RouteGuide सेवा के लिए Rust क्लाइंट बनाने का तरीका जानेंगे.

src/server/server.rs में किए गए काम को आगे बढ़ाते हुए, हम जनरेट किए गए कोड को gRPC के include_generated_code! मैक्रो के ज़रिए स्कोप में ला सकते हैं. साथ ही, RouteGuideClient टाइप को इंपोर्ट कर सकते हैं.

mod grpc_pb {
    grpc::include_generated_proto!("generated", "routeguide");
}

use grpc_pb::{
    route_guide_client::RouteGuideClient,
    Point,
};

कॉल सेवा के तरीके

gRPC-Rust में, आरपीसी, ब्लॉकिंग/सिंक्रोनस मोड में काम करती हैं. इसका मतलब है कि आरपीसी कॉल, सर्वर के जवाब का इंतज़ार करता है. इसके बाद, यह या तो जवाब देगा या गड़बड़ी दिखाएगा.

सेवा के तरीकों को कॉल करने के लिए, हमें सर्वर से कम्यूनिकेट करने के लिए सबसे पहले एक चैनल बनाना होगा. हम इसे बनाने के लिए, सबसे पहले एक एंडपॉइंट बनाते हैं. इसके बाद, उस एंडपॉइंट से कनेक्ट करते हैं. फिर, RouteGuideClient::new() से कनेक्ट होने पर बनाए गए चैनल को इस तरह पास करते हैं:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create endpoint to connect to
    let endpoint = Endpoint::new("http://[::1]:10000")?; 
    let channel = endpoint.connect().await?;             

    // Create a new client
    let mut client = RouteGuideClient::new(channel);
    Ok(())
}

इस फ़ंक्शन में, क्लाइंट बनाते समय, हम ऊपर बनाए गए सामान्य चैनल को जनरेट किए गए कोड स्टब के साथ रैप करते हैं. यह स्टब, .proto सेवा में तय किए गए खास तरीकों को लागू करता है.

सिंपल आरपीसी

सिंपल आरपीसी GetFeature को कॉल करना, लोकल मेथड को कॉल करने जितना ही आसान है. इसे main() में जोड़ो.

println!("*** SIMPLE RPC ***");
let point = proto!(Point{
    latitude: 409_146_138,
    longitude: -746_188_906
});
let response = client
    .get_feature(Request::new(point))
    .await?.into_inner();
Ok(())

जैसा कि आप देख सकते हैं, हम उस स्टब पर तरीके को कॉल करते हैं जो हमें पहले मिला था. हमारे तरीके के पैरामीटर में, हम अनुरोध प्रोटोकॉल बफ़र ऑब्जेक्ट (हमारे मामले में Point) बनाते हैं और उसे भरते हैं. अगर कॉल से कोई गड़बड़ी नहीं होती है, तो हम पहले रिटर्न वैल्यू से सर्वर से मिले रिस्पॉन्स की जानकारी पढ़ सकते हैं.

println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
    response.name(),
    response.location().latitude(),
    response.location().longitude());

कुल मिलाकर, क्लाइंट का main() फ़ंक्शन ऐसा दिखना चाहिए:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    //Create endpoint to connect to
    let endpoint = Endpoint::new("http://[::1]:10000")?; 
    let channel = endpoint.connect().await?;             

    // Create a new client
    let mut client = RouteGuideClient::new(channel); 

    println!("*** SIMPLE RPC ***");
    let point = proto!(Point{
        latitude: 409_146_138,
        longitude: -746_188_906
    });
    let response = client
        .get_feature(Request::new(point))
        .await?.into_inner();

    println!("Response = Name = \"{}\", Latitude = {}, Longitude = {}",
        response.name(),
        response.location().latitude(),
        response.location().longitude());
    Ok(())
}

7. इसे आज़माएं

सबसे पहले, क्लाइंट और सर्वर को चलाने के लिए, उन्हें अपने क्रेट में बाइनरी टारगेट के तौर पर जोड़ें. हमें अपने Cargo.toml में ज़रूरी बदलाव करने होंगे और ये चीज़ें जोड़नी होंगी:

[[bin]]
name = "routeguide-server"
path = "src/server/server.rs"

[[bin]]
name = "routeguide-client"
path = "src/client/client.rs"

इसके बाद, हमारी वर्किंग डायरेक्ट्री से ये कमांड चलाएं:

  1. सर्वर को एक टर्मिनल में चलाएं:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-server 
  1. क्लाइंट को किसी दूसरे टर्मिनल से चलाएं:
RUSTFLAGS="-Awarnings" cargo run --bin routeguide-client

आपको इस तरह का आउटपुट दिखेगा. इसमें टाइमस्टैंप को हटा दिया गया है, ताकि जानकारी साफ़ तौर पर दिख सके:

*** SIMPLE RPC ***

FEATURE: Name = "Berkshire Valley Management Area Trail, Jefferson, NJ, USA", Lat = 409146138, Lon = -746188906

8. आगे क्या करना है