טרנסקוד של HTTP/JSON ל-gRPC

‫Cloud Endpoints תומך בתעבורת נתונים בין פרוטוקולים, כך שלקוחות יכולים לגשת ל-gRPC API באמצעות HTTP/JSON. ה-Extensible Service Proxy ‏ (ESP) מבצע המרה של HTTP/JSON ל-gRPC.

במדריך הזה נסביר:

  • איך משתמשים בהערות בקובץ .proto כדי לציין המרת נתונים מ-HTTP/JSON ל-gRPC
  • איך פורסים את השירות ב-Endpoints כדי להשתמש בתכונה הזו
  • איפה אפשר למצוא מידע נוסף על תכנון והטמעה של המרת קידוד לשירותי gRPC

ההנחה היא שכבר השלמתם את ההדרכות שלנו בנושא gRPC, ושאתם מכירים את המושגים הבסיסיים של נקודות קצה ל-APIs של gRPC.

תכנון API שמתאים לשינוי קידוד

טרנסקודינג כולל מיפוי של בקשות HTTP/JSON והפרמטרים שלהן לשיטות gRPC ולפרמטרים שלהן ולסוגי ההחזרה. לכן, למרות שאפשר למפות בקשת HTTP/JSON לכל שיטת API שרירותית, כדאי לעשות זאת אם ה-API של gRPC בנוי בגישה מבוססת-משאבים, בדיוק כמו API מסורתי של REST ב-HTTP. במילים אחרות, אתם מתכננים את שירות ה-API כך שישתמש במספר קטן של שיטות סטנדרטיות, שמתאימות לפעלים של HTTP כמו GET ו-PUT, שפועלים על משאבי השירות ועל אוספים של משאבים, שהם בעצמם סוג של משאב. השיטות הסטנדרטיות האלה הן List,‏ Get,‏ Create,‏ Update ו-Delete.

במידת הצורך, יכולים להיות ל-API גם כמה שיטות מותאמות אישית לא סטנדרטיות, אבל המיפוי שלהן קצת יותר מסובך.

במדריך לעיצוב API יש מידע נוסף על עיצוב ממוקד משאבים ועל מיפויים סטנדרטיים של המרת קידוד. מדריך התכנון הזה הוא תקן התכנון שבו אנחנו משתמשים ב-Google כשאנחנו מתכננים ממשקי API ציבוריים כמו Cloud APIs. לא צריך לפעול לפי המדריך הזה כדי להשתמש בתעתיק gRPC, אבל אנחנו ממליצים מאוד לעשות זאת. באופן ספציפי, הדפים הבאים יכולים לעזור לכם להבין את עקרונות העיצוב האלה ולהוסיף מיפויים שימושיים של המרות קידוד לשיטות שלכם:

אפשר להיעזר גם בדף העיון הבא:

בהמשך המסמך הזה, נשתמש בדוגמה של חנות הספרים שבה השתמשנו בהדרכות שלנו, שבה כבר נעשה שימוש בעקרונות האלה. בספרייה יש אוספים של משאבי 'ספרים' ב'מדפים', והמשתמשים יכולים List, Get, Create או Delete.

איפה מגדירים המרת קידוד

התכונה gRPC transcoding מופעלת כברירת מחדל, ואפשר להשתמש בה בלי לבצע שום הגדרה. פועלים לפי ההוראות לפריסת שירות באמצעות המרת קידוד. לאחר מכן, כששולחים בקשת HTTP ‏POST לנתיב כתובת ה-URL ‏GRPC_SERVICE_FULL_NAME/METHOD_NAME> עם ערכי השדות של הודעת הבקשה של ה-method (אם יש כאלה) בפורמט JSON בגוף בקשת ה-HTTP,‏ ESP שולח את הודעת הבקשה ל-method המתאים של gRPC. בדוגמה הקודמת, GRPC_SERVICE_FULL_NAME הוא השם המלא של שירות gRPC ו-METHOD_NAME הוא שם השיטה.

לדוגמה, אם שולחים POST לכתובת ה-URL של ListShelves באופן הבא:

curl -XPOST http://mydomain/endpoints.examples.bookstore.Bookstore/ListShelves

מקבלים רשימה עדכנית של המדפים בפורמט JSON.

עם זאת, מומלץ מאוד להגדיר מיפויים באופן מפורש, כפי שמתואר בהמשך המסמך, מבחינת עיצוב של ממשק HTTP.

תקן ההגדרה של gRPC API להגדרת שירות מאפשר לכם לציין בדיוק איך הנתונים צריכים להיות מתורגמים מ-HTTP/JSON ל-gRPC. יש שתי דרכים לעשות את זה: באמצעות הערות ישירות בקובץ .proto, וב-YAML כחלק מקובץ ההגדרות של gRPC API. מומלץ להשתמש בהערות proto כדי להקל על הקריאה והתחזוקה. מידע נוסף על הגדרות YAML ומתי צריך להשתמש בהן זמין במאמר הגדרת המרת קידוד ב-YAML.

דוגמה לשימוש בגישה המומלצת מחנות הספרים:

// Returns a specific bookstore shelf.
rpc GetShelf(GetShelfRequest) returns (Shelf) {
  // Client example - returns the first shelf:
  //   curl http://DOMAIN_NAME/v1/shelves/1
  option (google.api.http) = { get: "/v1/shelves/{shelf}" };
}

...
// Request message for GetShelf method.
message GetShelfRequest {
  // The ID of the shelf resource to retrieve.
  int64 shelf = 1;
}

ההערה מציינת ל-ESP שבקשת HTTP GET עם כתובת ה-URL http://mydomain/v1/shelves/1 קוראת ל-method GetShelf() של שרת ה-gRPC, עם GetShelfRequest שמכיל את מזהה המדף המבוקש shelf (במקרה הזה, 1).

הוספת מיפויים של המרת קידוד

בקטע הזה מתוארות כמה הערות נוספות למיפוי מתוך הדוגמה של חנות הספרים. יש שני קובצי proto לדוגמה בדוגמה של חנות הספרים, כדי שתוכלו לפרוס אותה עם מיפויים של המרת קידוד או בלי מיפויים כאלה, ולהשוות את ההבדלים בין קובצי proto:

מדריך מקיף יותר להגדרת מיפויים של המרת קידוד זמין במאמרים בנושא שיטות סטנדרטיות, שיטות מותאמות אישית וחומר עזר בנושא כללי HTTP.

מיפוי של אמצעי תשלום List

השיטה List מוגדרת בקובץ .proto עם סוג התגובה שלה:

  // Returns a list of all shelves in the bookstore.
  rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {
    // Define HTTP mapping.
    // Client example (Assuming your service is hosted at the given 'DOMAIN_NAME'):
    //   curl http://DOMAIN_NAME/v1/shelves
    option (google.api.http) = { get: "/v1/shelves" };
  }
...
message ListShelvesResponse {
  // Shelves in the bookstore.
  repeated Shelf shelves = 1;
}

ההערה המודגשת מציינת את מיפוי ה-HTTP של השיטה הזו.

  • option (google.api.http) מציין שהשיטה הזו היא הערה של מיפוי HTTP של gRPC.
  • get מציין שהשיטה הזו ממופה לבקשת HTTP GET.
  • "/v1/shelves" היא תבנית של נתיב כתובת ה-URL (מצורפת לדומיין של השירות) שמשמשת את בקשת GET לקריאה של השיטה הזו. נתיב כתובת ה-URL נקרא גם נתיב המשאב, כי הוא בדרך כלל מציין את ה "דבר" או המשאב שרוצים להשתמש בו. במקרה הזה, כל המשאבים של המדף בחנות הספרים שלנו.

לדוגמה, אם לקוח קורא ל-method הזה על ידי שליחת GET לכתובת ה-URL ‏http://mydomain/v1/shelves, ‏ ESP קורא ל-method של gRPC ‏ListShelves(). הקצה העורפי של gRPC מחזיר את המדפים, ו-ESP ממיר אותם לפורמט JSON ומחזיר אותם ללקוח.

מיפוי של אמצעי תשלום Get

השיטה GetShelf של חנות הספרים מוגדרת בקובץ .proto עם סוגי הבקשות והתגובות שלה:

// Returns a specific bookstore shelf.
rpc GetShelf(GetShelfRequest) returns (Shelf) {
  // Client example - returns the first shelf:
  //   curl http://DOMAIN_NAME/v1/shelves/1
  option (google.api.http) = { get: "/v1/shelves/{shelf}" };
}

...
// Request message for GetShelf method.
message GetShelfRequest {
  // The ID of the shelf resource to retrieve.
  int64 shelf = 1;
}
...
// A shelf resource.
message Shelf {
  // A unique shelf id.
  int64 id = 1;
  // A theme of the shelf (fiction, poetry, etc).
  string theme = 2;
}

ההערה המודגשת מציינת את מיפוי ה-HTTP של השיטה הזו.

  • option (google.api.http) מציין שהשיטה הזו היא הערה של מיפוי HTTP של gRPC.
  • get מציין שהשיטה הזו ממופה לבקשת HTTP GET.
  • "/v1/shelves/{shelf}" הוא נתיב כתובת ה-URL של הבקשה, כמו קודם, אבל הוא מציין /v1/shelves/ ואז {shelf}. הסימון בסוגריים מסולסלים מציין ל-ESP שכל מה שמופיע ב-{shelf} הוא הערך שצריך לספק ל-shelf בפרמטר GetShelfRequest של השיטה.

אם לקוח קורא לשיטה הזו על ידי שליחת GET לכתובת ה-URL http://mydomain/v1/shelves/4, ‏ ESP יוצר GetShelfRequest עם ערך shelf של 4 ואז קורא לשיטת gRPC‏ GetShelf() עם הערך הזה. הקצה העורפי של gRPC מחזיר את Shelf המבוקש עם המזהה 4, ש-ESP ממיר לפורמט JSON ומחזיר ללקוח.

בשיטה הזו, הלקוח צריך לספק רק ערך אחד של שדה בקשה, shelf, שאתם מציינים בתבנית נתיב כתובת ה-URL באמצעות הסימון של סוגריים מסולסלים ל'לכידה'. אפשר גם לתעד כמה חלקים של כתובת ה-URL אם צריך כדי לזהות את המשאב המבוקש. לדוגמה, בשיטה GetBook הלקוח צריך לציין גם את מזהה המדף וגם את מזהה הספר בכתובת ה-URL:

// Returns a specific book.
rpc GetBook(GetBookRequest) returns (Book) {
  // Client example - get the first book from the second shelf:
  //   curl http://DOMAIN_NAME/v1/shelves/2/books/1
  option (google.api.http) = { get: "/v1/shelves/{shelf}/books/{book}" };
}
...
// Request message for GetBook method.
message GetBookRequest {
  // The ID of the shelf from which to retrieve a book.
  int64 shelf = 1;
  // The ID of the book to retrieve.
  int64 book = 2;
}

בנוסף לערכים מילוליים ולסוגריים ללכידת ערכי שדות, בתבניות של נתיבי כתובות ה-URL אפשר להשתמש בתווים כלליים כדי לציין שצריך ללכוד כל דבר בחלק הזה של כתובת ה-URL. הסימון {shelf} שבו השתמשנו בדוגמה הקודמת הוא קיצור של {shelf=*}. מידע נוסף על הכללים של תבניות נתיבים זמין במאמר בנושא כללי HTTP.

במקרה של סוג ה-method הזה, לא מצוין גוף בקשת HTTP. הנחיות נוספות למיפוי של שיטות Get, כולל שימוש בפרמטרים של שאילתות, זמינות במאמר שיטות סטנדרטיות.

מיפוי של אמצעי תשלום Create

ה-method‏ CreateShelf של חנות הספרים ממופה ל-HTTP‏ POST.

  // Creates a new shelf in the bookstore.
  rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
    // Client example:
    //   curl -d '{"theme":"Music"}' http://DOMAIN_NAME/v1/shelves
    option (google.api.http) = {
      post: "/v1/shelves"
      body: "shelf"
    };
  }
...
// Request message for CreateShelf method.
message CreateShelfRequest {
  // The shelf resource to create.
  Shelf shelf = 1;
}
...
// A shelf resource.
message Shelf {
  // A unique shelf id.
  int64 id = 1;
  // A theme of the shelf (fiction, poetry, etc).
  string theme = 2;
}
  • option (google.api.http) מציין שהשיטה הזו היא הערה של מיפוי HTTP של gRPC.
  • post מציין שהשיטה הזו ממופה לבקשת HTTP POST.
  • "/v1/shelves" הוא נתיב כתובת ה-URL של הבקשה, כמו קודם.
  • body: "shelf" משמש בגוף בקשת ה-HTTP כדי לציין את המשאב שרוצים להוסיף, בפורמט JSON.

לדוגמה, אם לקוח הפעיל את השיטה הזו כך:

curl -d '{"theme":"Music"}' http://DOMAIN_NAME/v1/shelves

מערכת ESP משתמשת בגוף ה-JSON כדי ליצור ערך Shelf עם הנושא "Music" עבור CreateShelfRequest, ואז קוראת לשיטת CreateShelf() gRPC. שימו לב שהלקוח לא מספק את הערך id של Shelf. מזהי המדפים של חנות הספרים מסופקים על ידי השירות כשיוצרים מדף חדש. אתם מספקים את סוג המידע הזה למשתמשים בשירות שלכם בתיעוד של ה-API.

שימוש בתו כללי בגוף ההודעה

אפשר להשתמש בשם המיוחד * במיפוי הגוף כדי לציין שכל שדה שלא קשור לתבנית הנתיב צריך להיות ממופה לגוף הבקשה. ההגדרה הזו מאפשרת להשתמש בהגדרה החלופית הבאה של השיטה CreateShelf.

  // Creates a new shelf in the bookstore.
  rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
    // Client example:
    //   curl -d '{"shelf_theme":"Music", "shelf_size": 20}' http://DOMAIN_NAME/v1/shelves/123
    option (google.api.http) = {
      post: "/v1/shelves/{shelf_id}"
      body: "*"
    };
  }
...
// Request message for CreateShelf method.
message CreateShelfRequest {
  // A unique shelf id.
  int64 shelf_id = 1;
  // A theme of the shelf (fiction, poetry, etc).
  string shelf_theme = 2;
  // The size of the shelf
  int64 shelf_size = 3;
}
  • option (google.api.http) מציין שהשיטה הזו היא הערה של מיפוי HTTP של gRPC.
  • post מציין שהשיטה הזו ממופה לבקשת HTTP‏ POST.
  • "/v1/shelves/{shelf_id}" הוא נתיב כתובת ה-URL של הבקשה. הערך בשדה {shelf_id} הוא הערך של השדה shelf_id ב-CreateShelfRequest.
  • body: "*" משמש בגוף בקשת ה-HTTP כדי לציין את כל שדות הבקשה שנותרו, מלבד shelf_id בדוגמה הזו, והם shelf_theme ו-shelf_size. לכל השדות בגוף ה-JSON עם שני השמות האלה, המערכת תשתמש בערכים שלהם בשדות המתאימים של CreateShelfRequest.

לדוגמה, אם לקוח הפעיל את השיטה הזו כך:

curl -d '{"shelf_theme":"Music", "shelf_size": 20}' http://DOMAIN_NAME/v1/shelves/123

‫ESP משתמש בגוף ה-JSON ובתבנית הנתיב כדי ליצור CreateShelfRequest{shelf_id: 123 shelf_theme: "Music" shelf_size: 20}, ואז משתמש בו כדי לקרוא ל-method‏ CreateShelf() של gRPC. פרטים נוספים מופיעים במאמר בנושא HttpRule.

הגדרת המרת קידוד ב-YAML

גישה חלופית היא לציין את המיפויים מ-HTTP ל-gRPC בקובץ ה-YAML של הגדרת ה-API של gRPC ולא בקובץ .proto. יכול להיות שתצטרכו להגדיר המרת קידוד בקובץ YAML אם יש לכם הגדרת API אחת של proto שמשמשת בכמה שירותים, עם מיפויים שונים שצוינו לכל שירות.

המאפיינים rules בקטע http של קובץ ה-YAML מציינים איך למפות בקשות HTTP/JSON לשיטות gRPC:

http:
  rules:
  ...
  #
  # 'GetShelf' is available via the GET HTTP verb and '/shelves/{shelf}' URL
  # path, where {shelf} is the value of the 'shelf' field of 'GetShelfRequest'
  # protobuf message.
  #
  # Client example - returns the first shelf:
  #   curl http://DOMAIN_NAME/v1/shelves/1
  #
  - selector: endpoints.examples.bookstore.Bookstore.GetShelf
    get: /v1/shelves/{shelf}
  ...

דוגמה מלאה יותר לשימוש בגישה הזו בשירות Bookstore מופיעה במאמר api_config_http.yaml.

פריסת שירות שמשתמש בהמרת קידוד

פריסת שירות gRPC שמשתמש בתרגום קוד דומה מאוד לפריסה של כל שירות gRPC אחר, עם הבדל משמעותי אחד. בקטע Tutorials (הדרכות), יש דוגמה שנדרשת כדי לאשר בקשות gRPC מהלקוח לדוגמה. עם זאת, אם רוצים שחנות הספרים תקבל גם בקשות HTTP, צריך לבצע הגדרה נוספת של ESP. לקוחות משתמשים בפרוטוקול HTTP1.1 כדי לשלוח בקשות JSON/HTTP ל-ESP, ולכן צריך להגדיר את ESP לשימוש ב-SSL (יציאת ה-SSL יכולה לתמוך בשני סוגי הבקשות) או להפעיל יציאה מיוחדת כדי לקבל את הקריאות האלה. הפריסה זהה ברובה לזו שמוסברת במדריך לסביבה שבחרתם.

מוודאים שכללי ה-HTTP נפרסו

אם כבר הורדתם את הדוגמה של חנות הספרים מתוך המדריכים, שימו לב שאתם צריכים להוריד גרסה קצת שונה של קובץ .proto עם הערות, http_bookstore.proto. בנוסף, צריך לשכפל את מאגר googleapis מ-GitHub לפני שמריצים את protoc, כי צריך את annotations.proto בנתיב הכלול.

    git clone https://github.com/googleapis/googleapis

    GOOGLEAPIS_DIR=<your-local-googleapis-folder>

לאחר מכן יוצרים .pb מאפיין חדש מ-http_bookstore.proto כשפורסים את ההגדרה ב-Endpoints:

    protoc \
        --include_imports \
        --include_source_info \
        --proto_path=${GOOGLEAPIS_DIR} \
        --proto_path=. \
        --descriptor_set_out=api_descriptor.pb \
        http_bookstore.proto

אם אתם משתמשים בשיטה החלופית להגדרת מיפויים של HTTP בקובץ ה-YAML של הגדרות ה-gRPC API, אתם צריכים גם לוודא שהכללים הרלוונטיים נפרסים כשפורסים את ההגדרות ב-Endpoints. כדי לנסות את זה בשירות Bookstore, הכללים הבסיסיים שלו נמצאים בקובץ api_config.yaml והכללים מבוססי-HTTP שלו נמצאים בקובץ api_config_http.yaml:

    gcloud endpoints services deploy api_descriptor.pb api_config.yaml api_config_http.yaml

שימוש ב-SSL

אם SSL מופעל לתקשורת בין הלקוחות לבין ESP, הלקוחות יכולים להשתמש באותה יציאה כדי לבצע שיחות gRPC או HTTP1.1. במאמר הפעלת SSL מוסבר איך להגדיר SSL לשירות Endpoints.

מציינים יציאה ל-ESP כדי לקבל קריאות SSL באמצעות הדגל --ssl_port בקובץ ההגדרות של Google Kubernetes Engine ‏ (GKE) או הפקודה docker run (Compute Engine/Docker).

    args: [
      "--http_port", "8080",
      "--ssl_port", "443",  # enable SSL port at 443 to serve https requests
      "--backend",  "grpc://127.0.0.1:8081",  # gRPC backend.
      "--service", "SERVICE_NAME",
      "--rollout_strategy", "managed",
    ]

הגדרת יציאה של HTTP1.1

אם לא משתמשים ב-SSL, צריך להגדיר יציאה נפרדת לבקשות HTTP1.1 כי gRPC ו-HTTP1.1 לא יכולים לחלוק את אותה יציאה בלי SSL. משתמשים בדגל --http_port בקובץ התצורה של GKE או בפקודה docker run כדי לציין יציאה לקבלת קריאות HTTP1.1. אם רוצים ש-ESP יקבל גם קריאות gRPC, צריך להשתמש גם בדגל --http2_port כדי לציין יציאת gRPC.

    args: [
      "--http_port", "8080",  # for HTTP 1.1
      "--http2_port", "8090",  # for gRPC
      "--backend", "grpc://127.0.0.1:8081",  # gRPC backend.
      "--service", "SERVICE_NAME",
      "--rollout_strategy", "managed",
    ]

התקשרות לשירות באמצעות המרת קידוד

בקטע הזה מוסבר איך להגדיר את השירות ואיך לבצע קריאות HTTP לשירות.

הגדרת השירות

ההנחה היא שכבר סיימתם את המדריכים הבסיסיים לשירות gRPC בסביבה שבחרתם, ושיש לכם אשכול GKE או מופע Compute Engine להרצת הדוגמה.

  1. קודם כול, מוודאים שפרסתם את ההגדרה של שירות חנות הספרים עם HTTP ב-Endpoints, כמו שמתואר במאמר מוודאים שכללי ה-HTTP נפרסו.
  2. פורסים את ה-backend ואת ה-ESP כמו שמתואר במדריך לפלטפורמה שבחרתם, באמצעות הדגל --http_port כדי להפעיל יציאה לבקשות HTTP1.1:

ביצוע קריאות HTTP לשירות

  1. מקבלים את כתובת ה-IP החיצונית של ספק ה-ESP ומגדירים אותה ל-$ESP_IP.
  2. שולחים את בקשת ה-HTTP הבאה עם curl

    curl http://$ESP_IP/v1/shelves
    

    (או להשתמש באותה כתובת URL עם https:// אם משתמשים ב-SSL). השרת משיב עם:

    {"shelves":[{"id":"1","theme":"Fiction"},{"id":"2","theme":"Fantasy"}]}
    

    אם הפלט מציג תגובה בינארית, צריך לבדוק את הגדרת היציאה כי יכול להיות שאתם מגיעים ליציאת HTTP2 במקום ליציאת HTTP.

  3. נסו שיטה אחרת.CreateCreateShelf דורש מפתח API, ולכן צריך ליצור מפתח לפרויקט ולהגדיר אותו כ-$KEY. עכשיו מתקשרים:

    curl -d '{"theme":"Music"}' http://$ESP_IP/v1/shelves?key=$KEY
    

    אם תתקשרו שוב אל GetShelves, המדף החדש יופיע.