นับตั้งแต่เปิดตัว gRPC มันก็ได้รับความนิยมอย่างมากในหมู่นักพัฒนา API เหตุผลที่ gRPC ได้รับความนิยมอย่างมากคือการสนับสนุนการใช้งานหลายภาษา (เซิร์ฟเวอร์และไคลเอนต์สามารถเขียนเป็นภาษาแยกกันได้) การมุ่งเน้นไปที่ประสิทธิภาพตั้งแต่เริ่มต้น (gRPC ขึ้นอยู่กับ HTTP/2) และชุดเครื่องมือที่ยอดเยี่ยม (การใช้ gRPC protobuf สำหรับคำอธิบายข้อความและบริการและไคลเอนต์สามารถสร้างได้โดยอัตโนมัติโดยไม่ต้องเขียนโค้ดแม้แต่บรรทัดเดียว)
ย้อนกลับไปเมื่อหลายปีก่อน ความดีนี้จำกัดอยู่เฉพาะบนมือถือและเซิร์ฟเวอร์เท่านั้น และไม่มีให้สำหรับนักพัฒนาส่วนหน้า และพวกเขายังคงต้องใช้อินเทอร์เฟซ REST นี่เป็นเพราะข้อจำกัด HTTP/2 ในเบราว์เซอร์ ตัวอย่างเช่น
- คำขอในเบราว์เซอร์ไม่สามารถบังคับให้เป็น HTTP/2 ได้
- เฟรม HTTP/2 ไม่พร้อมใช้งานกับไลบรารีโดยตรง
มาถึง "grpc-web" ซึ่งเป็นความพยายามของ Google และ "ไม่น่าจะเป็นไปได้" ในการใช้ข้อกำหนด gRPC สำหรับเบราว์เซอร์ ข้อมูลจำเพาะนี้ใช้ HTTP/1.x กับพร็อกซีเกตเวย์ เช่น Envoy (ซึ่งแปล HTTP/1.x เป็น HTTP/2 อย่างโปร่งใสที่เซิร์ฟเวอร์ gRPC คาดหวัง)
gRPC-web รองรับเฉพาะ Unary และสตรีมมิ่งฝั่งเซิร์ฟเวอร์ในโหมด grpcwebtext
(เราจะกล่าวถึงความหมายในส่วนต่อๆ ไป)
ในโพสต์นี้ เราจะใช้งานเซิร์ฟเวอร์ gRPC ใน golang และใช้งานไคลเอนต์ใน JavaScript โดยใช้ gRPC-web เราจะดูตัวอย่างการเรียก API การสตรีมทั้งแบบเอกนารีและฝั่งเซิร์ฟเวอร์
มาเริ่มกันเลย
สารบัญ
- ข้อกำหนดเบื้องต้น
- คำจำกัดความของโปรโตบุฟ
- การใช้ API เซิร์ฟเวอร์ Unary
- กำลังใช้ Unary API
- แก้ไขข้อผิดพลาดในการสื่อสารกับ Envoy
- การใช้สตรีมมิ่งฝั่งเซิร์ฟเวอร์
- บทสรุป
ข้อกำหนดเบื้องต้น
เราจำเป็นต้องติดตั้งเครื่องมือบางอย่างเพื่อให้สามารถผ่านโพสต์นี้ได้สำเร็จ
VS Code
หรือโปรแกรมแก้ไขโค้ดที่คุณเลือกเพื่อเขียนโค้ดของเรา- คอมไพเลอร์และเครื่องมือ "Golang"
- Python3 — เราจะใช้เซิร์ฟเวอร์ HTTP ของ python เพื่อให้บริการลูกค้าของเรา
- Node (npm มาพร้อมโหนดล่วงหน้า) และเครื่องมือ npx
- นักเทียบท่า — เราจะใช้งานพร็อกซี Envoy เป็นคอนเทนเนอร์นักเทียบท่า
protoc
คอมไพเลอร์ protobuf- Golang "แพ็คเกจ gRPC" และ "ปลั๊กอิน protobuf protoc"
protoc-gen-grpc-web
plugin ของ gRPC-web
ปฏิบัติตามคำแนะนำในการติดตั้งสำหรับเครื่องมือแต่ละชิ้น ฉันกำลังข้ามคำแนะนำที่นี่เพื่อความกระชับ แต่โปรดแจ้งให้เราทราบหากคุณพบข้อผิดพลาด เรายินดีที่จะช่วยเหลือ
โพสต์นี้ไม่ใช่โพสต์แนะนำสำหรับภาษาหรือกรอบงานใดๆ ที่ใช้ที่นี่ ฉันจะลิงก์เนื้อหาเบื้องต้นสำหรับแต่ละเนื้อหาเพื่อให้คุณได้รับข้อมูลที่รวดเร็ว
คำจำกัดความของโปรโตบุฟ
เราจะเริ่มต้นด้วยการสร้างคำจำกัดความของ protobuf สำหรับบริการของเรา สร้างไฟล์ calculator.proto
และวางไว้ในโฟลเดอร์ protos
สำหรับตอนนี้ เราจะเพิ่มคำจำกัดความบริการ Unary และพัฒนาเพื่อเพิ่มการสตรีมฝั่งเซิร์ฟเวอร์ในส่วนที่ 2
grpc-web
├── protos
│ └── calculator.proto
├── server
└── client
เราได้เพิ่มข้อความสองข้อความ ได้แก่ AddRequest
ซึ่งต้องใช้ตัวเลขสองตัวในการเพิ่ม AddResponse
ที่ส่งคืนผลลัพธ์ของการบวก และบริการเครื่องคิดเลขด้วยการเรียก rpc เพียงครั้งเดียว Add
ซึ่งรับ AddRequest
และส่งกลับ AddResponse
การใช้ API เซิร์ฟเวอร์ Unary
เราจะคอมไพล์ไฟล์ calculator.proto
เพื่อไปโค้ดด้วยความช่วยเหลือของ protoc และไปที่ปลั๊กอิน protobuf
สร้างโฟลเดอร์ calculatorpb
ภายในโฟลเดอร์เซิร์ฟเวอร์และดำเนินการคำสั่งต่อไปนี้
protoc calculator.proto --go_out=plugins=grpc:../server/calculatorpb/
สิ่งนี้จะสร้างไฟล์ calculator.pb.go
และโครงสร้างไดเร็กทอรีของคุณจะมีลักษณะเช่นนี้
grpc-web
├── protos
│ └── calculator.proto
├── server
│ └── calculatorpb
│ └── calculator.pb.go
└── client
ตอนนี้เริ่มต้นโมดูล go โดยดำเนินการคำสั่งต่อไปนี้ในโฟลเดอร์ server
go mod init github.com/kaysush/grpc-calculator
สิ่งนี้จะเพิ่มไฟล์ go.mod
ไปยังโฟลเดอร์ server
ของคุณ การสร้างโมดูลจากโฟลเดอร์ server
จะทำให้แน่ใจว่าเราสามารถเข้าถึงแพ็คเกจในไฟล์ calculator.pb.go
ในไฟล์ server.go
ของเรา ซึ่งเราจะเพิ่มในขั้นตอนถัดไป
มาเริ่มใช้งานเซิร์ฟเวอร์กันเถอะ
grpc-web
├── protos
│ └── calculator.proto
├── server
│ ├── calculatorpb
│ │ └── calculator.pb.go
│ ├── go.mod
│ └── server.go
└── client
เรียกใช้ server.go
go run server.go
คุณควรเห็นข้อความ Starting Calculator server
บนหน้าจอ
ขณะนี้เซิร์ฟเวอร์ gRPC พร้อมที่จะให้บริการคำขอบนพอร์ต 50551
แล้ว
กำลังใช้ Unary API
ขณะนี้เซิร์ฟเวอร์ของเรากำลังทำงานอยู่ เรามาปรับใช้ไคลเอ็นต์ของเราโดยใช้ gRPC-web
กัน
คอมไพล์ไฟล์ calculator.proto
สำหรับ JavaScript โดยใช้ปลั๊กอิน proto-gen-grpc-web
protoc calculator.proto --js_out=import_style=commonjs,binary:../client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:../client
สิ่งนี้จะสร้าง calculator_pb.js
(ซึ่งมีคำจำกัดความของข้อความ) และ calculator_grpc_web_pb.js
(ซึ่งมีการใช้งานลูกค้าของเรา)
grpc-web
├── protos
│ └── calculator.proto
├── server
│ ├── calculatorpb
│ │ └── calculator.pb.go
│ ├── go.mod
│ └── server.go
└── client
├── calculator_grpc_web_pb.js
└── calculator_pb.js
เพิ่มไฟล์ package.json
ลงในโฟลเดอร์ client
โดยมีเนื้อหาดังต่อไปนี้
{ "name": "grpc-calculator", "version": "0.1.0", "description": "gRPC-Web Calculator", "devDependencies": { "@grpc/proto-loader": "^0.3.0", "google-protobuf": "^3.6.1", "grpc": "^1.15.0", "grpc-web": "^1.0.0", "webpack": "^4.16.5", "webpack-cli": "^3.1.0" } }
เพิ่มไฟล์ client.js
ซึ่งมีการใช้งานไคลเอ็นต์ของเราเชื่อมต่อกับเซิร์ฟเวอร์ gRPC ของเรา
จำไว้ว่าฉันบอกคุณว่าคุณไม่จำเป็นต้องเขียนโค้ดใดๆ เพื่อใช้งานไคลเอ็นต์ด้วย gRPC
ตอนนี้ให้เราแพ็คเกจโค้ด JavaScript ของเราเป็นไฟล์ย่อเล็กสุด
npm install npx webpack client.js
สิ่งนี้ควรดาวน์โหลดการขึ้นต่อกันทั้งหมดของเราที่กล่าวถึงในไฟล์ package.json
จากนั้นจึงทำแพ็กเกจ client.js
ลงใน dist/main.js
พร้อมการขึ้นต่อกันทั้งหมด
สร้างไฟล์ index.html ซึ่งอ้างอิงไฟล์ dist/main.js
ของเราและทำการเรียก gRPC
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Calculator - gRPC-Web</title> <script src="dist/main.js"></script> </head> <body> </body> </html>
โครงสร้างไดเร็กทอรีของคุณควรมีลักษณะเช่นนี้ในขณะนี้
grpc-web
├── protos
│ └── calculator.proto
├── server
│ ├── calculatorpb
│ │ └── calculator.pb.go
│ ├── go.mod
│ └── server.go
└── client
├── dist
│ └── main.js
├── calculator_grpc_web_pb.js
├── calculator_pb.js
├── client.js
├── package.json
└── index.html
จากไดเร็กทอรีไคลเอนต์ให้เริ่มเซิร์ฟเวอร์ python
เพื่อเริ่มให้บริการไฟล์ของเรา
python3 -m http.server 8081 &
ไปที่ "http://localhost:8081" แล้วเปิดคอนโซล
คุณควรเห็นข้อผิดพลาดในคอนโซล คุณจะได้รับข้อผิดพลาดต่อไปนี้หนึ่งข้อหรือทั้งหมด
- คำขอข้ามแหล่งกำเนิดสินค้าถูกบล็อก
- ERR_CONNECTION_REFUSED
มีสองปัญหาในการใช้งานเซิร์ฟเวอร์และไคลเอนต์ปัจจุบันของเรา
- เซิร์ฟเวอร์ของเราไม่อนุญาตคำขอ CORS และ
- แม้ว่าจะรองรับ CORS แต่
gRPC-web
ก็ไม่รองรับ HTTP/2 ดังนั้นเราจึงต้องการพร็อกซีเกตเวย์เช่นEnvoy
เพื่อแปลคำขอ HTTP/1.x ของเราที่มาจาก gRPC-web สำหรับเซิร์ฟเวอร์ gRPC ของเรา
แก้ไขข้อผิดพลาดในการสื่อสารกับ Envoy
ตามที่กล่าวไว้ในหัวข้อก่อนหน้านี้ เราจำเป็นต้องวาง Envoy ไว้ระหว่างไคลเอนต์และเซิร์ฟเวอร์ของเราเพื่อการแปลที่โปร่งใสระหว่าง HTTP/1.x และ HTTP/2
เราจะใช้นักเทียบท่าเพื่อเรียกใช้พร็อกซีทูตของเรา เราจะใช้ envoy.yaml มาตรฐานจาก gRPC-web hello world demo (แน่นอนว่าพอร์ตต่างๆ จะเปลี่ยนไปตามการใช้งานของเรา)
สร้างไฟล์นักเทียบท่า
FROM envoyproxy/envoy:latest COPY ./envoy.yaml /etc/envoy/envoy.yaml CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml
สร้างภาพนักเทียบท่า
docker build -t my-envoy:1.0 .
ตอนนี้รันคอนเทนเนอร์นักเทียบท่าทูต
docker run -d -p 8080:8080 -p 9901:9901 --network=host my-envoy:1.0
เมื่อทูตทำงาน เราจำเป็นต้องเปลี่ยนพอร์ตใน client.js
ของเรา ตอนนี้แทนที่จะเชื่อมต่อกับเซิร์ฟเวอร์ gRPC โดยตรงบน 50551
เราจะเชื่อมต่อกับทูตบน 8080
จัดแพคเกจ client.js
ใหม่อีกครั้ง
npx webpack client.js
โหลดเบราว์เซอร์อีกครั้งที่ "http://localhost:8081" และเปิดคอนโซล
ตอนนี้คุณควรเห็นผลลัพธ์ของคุณแล้ว
การใช้สตรีมมิ่งฝั่งเซิร์ฟเวอร์
แก้ไขไฟล์ calculator.proto
และเพิ่มการเรียก rpc การสตรีมฝั่งเซิร์ฟเวอร์เพื่อสร้างหมายเลขฟีโบนักชี
protoc calculator.proto --go_out=plugins=grpc:../server/calculatorpb/Re-compile our proto files for bothgo
andjs
. protoc calculator.proto --js_out=import_style=commonjs,binary:../client --grpc-web_out=import_style=commonjs,mode=grpcwebtext:../client
เพิ่มการใช้งานเซิร์ฟเวอร์ใน server.go
เพิ่มการโทรแบบสตรีมมิ่งใน client.js
ของเรา
รีแพ็คให้ลูกค้า.
npx webpack client.js
โหลดเบราว์เซอร์อีกครั้ง และคุณจะเห็นตัวเลขฟีโบนัชชีสตรีมไปยังไคลเอนต์ของคุณ
บทสรุป
gRPC-web เป็นข้อกำหนดที่เป็นผู้ใหญ่มากสำหรับการเชื่อมต่อกับการออกจาก gRPC API โดยตรงจากเบราว์เซอร์โดยไม่มีอินเทอร์เฟซ REST ใดๆ อยู่ระหว่างนั้น เนื่องจากไมโครเซอร์วิสใช้ gRPC มากขึ้น gRPC-web จึงเข้ากันได้ดีกับโลก API ที่ไม่มี REST
ในกรณีที่คุณพบข้อผิดพลาดในโค้ดของฉันหรือมีคำถามใดๆ โดยทั่วไป โปรดแสดงความคิดเห็นได้
จนถึงตอนนี้ ขอให้สนุกกับการเขียนโค้ด! :)