برنامه نویسی

چگونه مخاطبین خود را با گوشی خود همگام سازی کنیم؟ پیاده سازی 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 کمی دخیل است، اما به وضوح ارزش آن را دارد: مخاطبین شما به طور خودکار با داده هایی که در سرور سازمان خود دارید همگام می شوند!

آیا پروتکل های جالب دیگری را می شناسید که این نوع ادغام بومی را امکان پذیر می کند؟ با خیال راحت تجربیات خود را به اشتراک بگذارید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا