سیستمهای توزیعشده مدرن به اجرای دقیق قرارداد نیاز دارند. در حالی که REST جایگاه خود را دارد، JSON-RPC جایگزینی سبک و کارآمد برای ارتباط بین سرویسها، بهویژه در معماریهای میکروسرویس ارائه میدهد. با این حال، تماسهای خام RPC بدون اعتبارسنجی میتوانند خطرناک باشند. این پست بررسی میکند که چگونه میتوان یک سرور JSON-RPC مقاوم و با عملکرد بالا در Go ساخت و اعتبارسنجی دقیق طرحواره JSON را برای تضمین یکپارچگی و امنیت دادهها ادغام کرد.
چرا JSON-RPC و اعتبارسنجی طرحواره؟
JSON-RPC یک پروتکل فراخوانی رویه از راه دور (RPC) بدون حالت و سبک است که شبیه به JSON-RPC 1.0 میباشد، اما فاقد اشیاء تو در تو است. این پروتکل از روش POST استفاده میکند که به مشتری اجازه میدهد پارامترهای کدگذاریشده به صورت JSON را در بدنه ارسال کند. مزیت اصلی آن سادگی و کارایی است. با این حال، عیب رویکرد «هر چیزی مجاز است» در بدنه درخواستها، احتمال وجود دادههای نادرست است که میتواند باعث خطاهای زمان اجرا (panic) یا خطاهای منطقی شود.
ادغام اعتبارسنجی طرحواره JSON مانند یک نوار حفاظتی عمل میکند. با اعتبارسنجی باریکه JSON ورودی در برابر یک طرحواره از پیش تعریفشده قبل از پردازش، اطمینان حاصل میکنیم که:
- فیلدهای ضروری وجود داشته باشند.
- انواع دادهها با انتظارات مطابقت داشته باشند.
- محدودیتها (مانند طول رشته یا محدوده اعداد) اعمال شوند.
انتخاب کتابخانههای مناسب
برای این پیادهسازی، ما از gorilla/rpc برای چارچوب RPC استفاده خواهیم کرد، زیرا پایدار و بهطور گستردهای مورد استفاده قرار میگیرد. برای اعتبارسنجی، go-playground/validator استاندارد صنعتی است. با این حال، برای انطباق دقیق با طرحواره JSON (RFC 7159)، ما از github.com/xeipuuv/gojsonschema یا یک جایگزین مدرن مانند github.com/santhosh-tekuri/jsonschema استفاده خواهیم کرد. برای عملکرد بالا، از بسته jsonschema بهره میبریم که طرحوارهها را برای اعتبارسنجی سریع کامپایل میکند.
پیادهسازی سرور
بیایید سروری بسازیم که یک درخواست ایجاد کاربر را بپذیرد. ما یک طرحواره JSON تعریف میکنیم که ساختار دادههای کاربر را دیکته میکند.
اول، مطمئن شوید که وابستگیهای لازم را دارید:
go get github.com/gorilla/rpc/v2
go get github.com/gorilla/rpc/v2/json2
go get github.com/santhosh-tekuri/jsonschema/v3
حال، بیایید پردازشگر (Handler) را تعریف کنیم. نکته کلیدی برای عملکرد بالا در اینجا، کامپایل طرحواره JSON یکبار در زمان راهاندازی است، نه کامپایل آن در هر درخواست.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"github.com/gorilla/rpc/v2"
"github.com/gorilla/rpc/v2/json2"
"github.com/santhosh-tekuri/jsonschema/v3"
)
// UserRequest نمایانگر پارامترهای JSON-RPC ورودی است
type UserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
// طرحواره JSON برای اعتبارسنجی
const userSchema = `{
"type": "object",
"properties": {
"name": {"type": "string", "minLength": 1},
"email": {"type": "string", "format": "email"},
"age": {"type": "integer", "minimum": 0, "maximum": 120}
},
"required": ["name", "email", "age"],
"additionalProperties": false
}`
func main() {
// کامپایل طرحواره یکبار برای عملکرد بهتر
compiler := jsonschema.NewCompiler()
if err := compiler.AddResource("schema.json", strings.NewReader(userSchema)); err != nil {
log.Fatal(err)
}
schema, err := compiler.Compile("schema.json")
if err != nil {
log.Fatal(err)
}
// ایجاد سرور RPC
server := rpc.NewServer()
server.RegisterCodec(json2.NewCodec(), "application/json")
server.RegisterService(new(UserService), "")
// تعریف سرویس
type UserService struct{}
// پردازشگر روش
userService := &UserService{}
// بستهبندی پردازشگر سفارشی برای اعتبارسنجی
http.Handle("/rpc", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var params json.RawMessage
// در یک سناریوی واقعی، پوشه JSON-RPC را تجزیه کنید تا پارامترها استخراج شوند
// این یک مثال سادهشده است که بر منطق اعتبارسنجی تمرکز دارد
// فرض کنید باریکه JSON خام در بدنه وجود دارد
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// اعتبارسنجی در برابر طرحواره
var loadedSchema jsonschema.Loader
loadedSchema = jsonschema.NewGoLoader(map[string]interface{}{}) // بارگذاری سادهشده
// نکته: در محیط تولید، برای نگاشت آسانتر ساختارهای Go یا اعتبارسنجی دقیق JSON از gojsonschema استفاده کنید
// در اینجا ما مفهوم اعمال طرحواره را نشان میدهیم
service := new(UserService)
server.ServeHTTP(w, r)
}))
log.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil)
}
بهترین شیوهها برای عملکرد
برای حداکثر کردن نرخ انتقال، بهینهسازیهای زیر را در نظر بگیرید:
- کامپایل طرحوارهها یکبار: کامپایل طرحواره JSON میتواند از نظر پردازشی سنگین باشد. همیشه طرحوارهها را در طول راهاندازی سرور کامپایل کنید.
- از json.RawMessage استفاده کنید: هنگام تجزیه درخواستهای RPC، از
json.RawMessageاستفاده کنید تا از سریالسازی و deserialize کردن غیرضروری قبل از اعتبارسنجی جلوگیری شود. - استخر اتصال (Connection Pooling): اگر مشتریان RPC شما سرویسهای داخلی هستند، از استخر اتصال HTTP/2 برای کاهش هزینههای دستتکانی (handshake) استفاده کنید.
- استخر بافرها: برای سیستمهای با نرخ انتقال بسیار بالا، در نظر بگیرید استفاده از
sync.Poolبرای بافرهای درخواست/پاسخ تا فشار جمعآوری زباله (GC) کاهش یابد.
نتیجهگیری
ساخت یک سرور JSON-RPC در Go ساده است، اما افزودن اعتبارسنجی طرحواره JSON آن را از یک پروژه آزمایشی به یک سرویس درجه تولید ارتقا میدهد. با اعتبارسنجی ورودیها در مرزها، از منطق کسبوکار خود در برابر وضعیتهای نامعتبر محافظت کرده و بار شناختی پردازشگرهای خود را کاهش میدهید. با توجه دقیق به کامپایل طرحواره و مدیریت حافظه، میتوان هم سختگیری و هم عملکرد بالا را به دست آورد.