چگونه مخاطبین خود را با گوشی خود همگام سازی کنیم؟ پیاده سازی CardDAV در Go!

Summarize this content to 400 words in Persian Lang
فرض کنید به مدیریت یک سازمان یا باشگاه کوچک کمک می کنید و یک پایگاه داده دارید که تمام جزئیات اعضا (نام، تلفن، ایمیل…) را ذخیره می کند.آیا خوب نیست که به این اطلاعات به روز در هر جایی که نیاز دارید دسترسی داشته باشید؟ خوب، با CardDAV شما می توانید!
CardDAV یک استاندارد باز با پشتیبانی خوب برای مدیریت تماس است. این یک ادغام بومی در برنامه مخاطبین iOS و بسیاری از برنامه های موجود برای اندروید است.
در سمت سرور، پیادهسازی CardDAV یک سرور http است که به روشهای غیرمعمول http پاسخ میدهد (PROPFIND، REPORT به جای GET، POST…). خوشبختانه یک ماژول Go وجود دارد که کار را بسیار ساده می کند: github.com/emersion/go-webdav. این کتابخانه انتظار دارد که اجرا شود Backend و یک استاندارد ارائه می کند http.Handler که باید درخواست های HTTP را پس از احراز هویت ارائه دهد.
احراز هویت
جالب است که کتابخانه هیچ کمکی در مورد احراز هویت کاربر ارائه نمیکند، با این حال به لطف قابلیت ترکیببندی Go، این مشکلی نیست. CardDAV از اعتبارنامه های Basic Auth استفاده می کند. هنگامی که اعتبارنامه ها بررسی شدند، می توانیم آن اعتبارنامه ها را در زمینه ذخیره کنیم (بعدا مفید خواهد بود):
package main
import (
“context”
“net/http”
“github.com/emersion/go-webdav/carddav”
)
type (
ctxKey struct{}
ctxValue struct {
username string
}
)
func NewCardDAVHandler() http.Handler {
actualHandler := carddav.Handler{
Backend: &ownBackend{},
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
// check username and password: adjust the logic to your system (do NOT store passwords in plaintext)
if !ok || username != “admin” || password != “s3cr3t” {
// abort the request handling on failure
w.Header().Add(“WWW-Authenticate”, `Basic realm=”Please authenticate”, charset=”UTF-8″`)
http.Error(w, “HTTP Basic auth is required”, http.StatusUnauthorized)
return
}
// user is authenticated: store this info in the context
ctx := context.WithValue(r.Context(), ctxKey{}, ctxValue{username})
// delegate the work to the CardDAV handle
actualHandler.ServeHTTP(w, r.WithContext(ctx))
})
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پیاده سازی رابط CardDAV
را ownBackend ساختار باید اجرا شود carddav.Backend رابطی که خیلی نازک نیست، اما همچنان قابل مدیریت است.
را CurrentUserPrincipal و AddressBookHomeSetPath باید URL ها را ارائه کند (با یک اسلش شروع و پایان می یابد). معمولاً خواهد بود username/contacts. اینجاست که باید نام کاربری را از زمینه استخراج کنید (که تنها آرگومان موجود است):
func currentUsername(ctx context.Context) (string, error) {
if v, ok := ctx.Value(ctxKey{}).(ctxValue); ok {
return v.username, nil
}
return “”, errors.New(“not authenticated”)
}
type ownBackend struct{}
// must begin and end with a slash
func (b *ownBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
username, err := currentUsername(ctx)
return “https://dev.to/” + url.PathEscape(username) + “https://dev.to/”, err
}
// must begin and end with a slash as well
func (b *ownBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) {
principal, err := b.CurrentUserPrincipal(ctx)
return principal + “contacts/”, err
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پس از آن سرگرمی می تواند آغاز شود: شما باید آن را پیاده سازی کنید AddressBook، GetAddressObject و ListAddressObjects روش ها
AddressBook یک ساختار ساده را برمی گرداند، جایی که path باید با شروع شود AddressBookHomeSetPath بالا (و با اسلش خاتمه دهید)
GetAddressObject و ListAddressObjects باید مسیر فعلی را بررسی کنید (برای اطمینان از اینکه کاربر تأیید شده فعلی می تواند به آن مخاطبین دسترسی داشته باشد) و سپس مخاطبین را به عنوان AddressObject.
AddressObject
را AddressObject دارای چندین ویژگی است که مهمتر از همه:
را path برای شناسایی این تماس خاص (می تواند دلخواه باشد، با یک اسلش شروع کنید)
را ETag به مشتری اجازه می دهد تا به سرعت بررسی کند که آیا به روز رسانی رخ داده است (اگر آن را فراموش کنید، iOS چیزی را نشان نمی دهد)
را Card که انتظار دارد الف VCard
را VCard اطلاعات تماس واقعی را نشان می دهد و احتمالاً باید بسته به نحوه ذخیره مخاطبین خود تطبیق داده شود. در مورد من اینطور تمام شد:
func utf8Field(v string) *vcard.Field {
return &vcard.Field{
Value: v,
Params: vcard.Params{
“CHARSET”: []string{“UTF-8″},
},
}
}
func vcardFromUser(u graphqlient.User) vcard.Card {
c := vcard.Card{}
c.Set(vcard.FieldFormattedName, utf8Field(u.Firstname+” “+u.Lastname))
c.SetName(&vcard.Name{
Field: utf8Field(“”),
FamilyName: u.Lastname,
GivenName: u.Firstname,
})
c.SetRevision(u.UpdatedAt)
c.SetValue(vcard.FieldUID, u.Extid)
c.Set(vcard.FieldOrganization, utf8Field(u.Unit))
// addFields sorts the key to ensure a stable order
addFields := func(fieldName string, values map[string]string) {
for _, k := range slices.Sorted(maps.Keys(values)) {
v := values[k]
c.Add(fieldName, &vcard.Field{
Value: v,
Params: vcard.Params{
vcard.ParamType: []string{k + “;CHARSET=UTF-8”}, // hacky but prevent maps ordering issues
// “CHARSET”: []string{“UTF-8”},
},
})
}
}
addFields(vcard.FieldEmail, u.Emails)
addFields(vcard.FieldTelephone, u.Phones)
vcard.ToV4(c)
return c
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
استفاده از میانبر Readonly
برخی از روش ها امکان به روز رسانی مخاطب را فراهم می کنند. از آنجایی که نمی خواهم لیست اعضای من از طریق CardDAV به روز شود، خطای 403 را به Put و Delete روش ها: return webdav.NewHTTPError(http.StatusForbidden, errors.New(“carddav: operation not supported”))
تست محلی
iOS به سرور CardDAV نیاز دارد تا از طریق https ارائه شود. شما می توانید گواهی های خود امضا شده را به صورت محلی با استفاده از آن تولید کنید openssl (تعویض 192.168.XXX.XXX با آدرس IP شما) باید به آن وارد شود http.ListenAndServeTLS(addr, “localhost.crt”, “localhost.key”, NewCardDAVHandler())
openssl req -new -subj “/C=US/ST=Utah/CN=192.168.XXX.XXX” -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr
openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
پس از آن، باید بتوانید با افزودن یک “حساب تماس CardDAV” که به آدرس IP و پورت خود اشاره می کند، به صورت محلی آزمایش کنید.
نتیجه گیری
پیاده سازی یک سرور CardDAV در Go کمی دخیل است، اما به وضوح ارزش آن را دارد: مخاطبین شما به طور خودکار با داده هایی که در سرور سازمان خود دارید همگام می شوند!
آیا پروتکل های جالب دیگری را می شناسید که این نوع ادغام بومی را امکان پذیر می کند؟ با خیال راحت تجربیات خود را به اشتراک بگذارید!
فرض کنید به مدیریت یک سازمان یا باشگاه کوچک کمک می کنید و یک پایگاه داده دارید که تمام جزئیات اعضا (نام، تلفن، ایمیل…) را ذخیره می کند.
آیا خوب نیست که به این اطلاعات به روز در هر جایی که نیاز دارید دسترسی داشته باشید؟ خوب، با CardDAV شما می توانید!
CardDAV یک استاندارد باز با پشتیبانی خوب برای مدیریت تماس است. این یک ادغام بومی در برنامه مخاطبین iOS و بسیاری از برنامه های موجود برای اندروید است.
در سمت سرور، پیادهسازی CardDAV یک سرور http است که به روشهای غیرمعمول http پاسخ میدهد (PROPFIND، REPORT به جای GET، POST…). خوشبختانه یک ماژول Go وجود دارد که کار را بسیار ساده می کند: github.com/emersion/go-webdav. این کتابخانه انتظار دارد که اجرا شود Backend
و یک استاندارد ارائه می کند http.Handler
که باید درخواست های HTTP را پس از احراز هویت ارائه دهد.
احراز هویت
جالب است که کتابخانه هیچ کمکی در مورد احراز هویت کاربر ارائه نمیکند، با این حال به لطف قابلیت ترکیببندی Go، این مشکلی نیست.
CardDAV از اعتبارنامه های Basic Auth استفاده می کند. هنگامی که اعتبارنامه ها بررسی شدند، می توانیم آن اعتبارنامه ها را در زمینه ذخیره کنیم (بعدا مفید خواهد بود):
package main
import (
"context"
"net/http"
"github.com/emersion/go-webdav/carddav"
)
type (
ctxKey struct{}
ctxValue struct {
username string
}
)
func NewCardDAVHandler() http.Handler {
actualHandler := carddav.Handler{
Backend: &ownBackend{},
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
// check username and password: adjust the logic to your system (do NOT store passwords in plaintext)
if !ok || username != "admin" || password != "s3cr3t" {
// abort the request handling on failure
w.Header().Add("WWW-Authenticate", `Basic realm="Please authenticate", charset="UTF-8"`)
http.Error(w, "HTTP Basic auth is required", http.StatusUnauthorized)
return
}
// user is authenticated: store this info in the context
ctx := context.WithValue(r.Context(), ctxKey{}, ctxValue{username})
// delegate the work to the CardDAV handle
actualHandler.ServeHTTP(w, r.WithContext(ctx))
})
}
پیاده سازی رابط CardDAV
را ownBackend
ساختار باید اجرا شود carddav.Backend
رابطی که خیلی نازک نیست، اما همچنان قابل مدیریت است.
را CurrentUserPrincipal
و AddressBookHomeSetPath
باید URL ها را ارائه کند (با یک اسلش شروع و پایان می یابد). معمولاً خواهد بود username/contacts
. اینجاست که باید نام کاربری را از زمینه استخراج کنید (که تنها آرگومان موجود است):
func currentUsername(ctx context.Context) (string, error) {
if v, ok := ctx.Value(ctxKey{}).(ctxValue); ok {
return v.username, nil
}
return "", errors.New("not authenticated")
}
type ownBackend struct{}
// must begin and end with a slash
func (b *ownBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
username, err := currentUsername(ctx)
return "https://dev.to/" + url.PathEscape(username) + "https://dev.to/", err
}
// must begin and end with a slash as well
func (b *ownBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) {
principal, err := b.CurrentUserPrincipal(ctx)
return principal + "contacts/", err
}
پس از آن سرگرمی می تواند آغاز شود: شما باید آن را پیاده سازی کنید AddressBook
، GetAddressObject
و ListAddressObjects
روش ها
AddressBook
یک ساختار ساده را برمی گرداند، جایی که path
باید با شروع شود AddressBookHomeSetPath
بالا (و با اسلش خاتمه دهید)
GetAddressObject
و ListAddressObjects
باید مسیر فعلی را بررسی کنید (برای اطمینان از اینکه کاربر تأیید شده فعلی می تواند به آن مخاطبین دسترسی داشته باشد) و سپس مخاطبین را به عنوان AddressObject
.
AddressObject
را AddressObject
دارای چندین ویژگی است که مهمتر از همه:
- را
path
برای شناسایی این تماس خاص (می تواند دلخواه باشد، با یک اسلش شروع کنید) - را
ETag
به مشتری اجازه می دهد تا به سرعت بررسی کند که آیا به روز رسانی رخ داده است (اگر آن را فراموش کنید، iOS چیزی را نشان نمی دهد) - را
Card
که انتظار دارد الفVCard
را VCard
اطلاعات تماس واقعی را نشان می دهد و احتمالاً باید بسته به نحوه ذخیره مخاطبین خود تطبیق داده شود. در مورد من اینطور تمام شد:
func utf8Field(v string) *vcard.Field {
return &vcard.Field{
Value: v,
Params: vcard.Params{
"CHARSET": []string{"UTF-8"},
},
}
}
func vcardFromUser(u graphqlient.User) vcard.Card {
c := vcard.Card{}
c.Set(vcard.FieldFormattedName, utf8Field(u.Firstname+" "+u.Lastname))
c.SetName(&vcard.Name{
Field: utf8Field(""),
FamilyName: u.Lastname,
GivenName: u.Firstname,
})
c.SetRevision(u.UpdatedAt)
c.SetValue(vcard.FieldUID, u.Extid)
c.Set(vcard.FieldOrganization, utf8Field(u.Unit))
// addFields sorts the key to ensure a stable order
addFields := func(fieldName string, values map[string]string) {
for _, k := range slices.Sorted(maps.Keys(values)) {
v := values[k]
c.Add(fieldName, &vcard.Field{
Value: v,
Params: vcard.Params{
vcard.ParamType: []string{k + ";CHARSET=UTF-8"}, // hacky but prevent maps ordering issues
// "CHARSET": []string{"UTF-8"},
},
})
}
}
addFields(vcard.FieldEmail, u.Emails)
addFields(vcard.FieldTelephone, u.Phones)
vcard.ToV4(c)
return c
}
استفاده از میانبر Readonly
برخی از روش ها امکان به روز رسانی مخاطب را فراهم می کنند. از آنجایی که نمی خواهم لیست اعضای من از طریق CardDAV به روز شود، خطای 403 را به Put
و Delete
روش ها: return webdav.NewHTTPError(http.StatusForbidden, errors.New("carddav: operation not supported"))
تست محلی
iOS به سرور CardDAV نیاز دارد تا از طریق https ارائه شود. شما می توانید گواهی های خود امضا شده را به صورت محلی با استفاده از آن تولید کنید openssl
(تعویض 192.168.XXX.XXX
با آدرس IP شما) باید به آن وارد شود http.ListenAndServeTLS(addr, "localhost.crt", "localhost.key", NewCardDAVHandler())
openssl req -new -subj "/C=US/ST=Utah/CN=192.168.XXX.XXX" -newkey rsa:2048 -nodes -keyout localhost.key -out localhost.csr
openssl x509 -req -days 365 -in localhost.csr -signkey localhost.key -out localhost.crt
پس از آن، باید بتوانید با افزودن یک “حساب تماس CardDAV” که به آدرس IP و پورت خود اشاره می کند، به صورت محلی آزمایش کنید.
نتیجه گیری
پیاده سازی یک سرور CardDAV در Go کمی دخیل است، اما به وضوح ارزش آن را دارد: مخاطبین شما به طور خودکار با داده هایی که در سرور سازمان خود دارید همگام می شوند!
آیا پروتکل های جالب دیگری را می شناسید که این نوع ادغام بومی را امکان پذیر می کند؟ با خیال راحت تجربیات خود را به اشتراک بگذارید!