假設您協助管理小型組織或俱樂部,並擁有一個儲存所有會員詳細資料(姓名、電話、電子郵件...)的資料庫。
在您需要的任何地方都可以存取這些最新資訊不是很好嗎?好吧,有了 CardDAV,你就可以!
CardDAV 是一個經過良好支援的聯絡人管理開放標準;它在 iOS 聯絡人應用程式和許多適用於 Android 的應用程式中具有本機整合。
伺服器端,實作 CardDAV 是一個 http 伺服器,它會回應不尋常的 http 方法(PROPFIND、REPORT 而不是 GET、POST...)。幸運的是,有一個 Go 模組可以大大簡化工作:github.com/emersion/go-webdav。該庫需要一個已實現的後端,並提供一個標準的 http.Handler,該處理程序應在身份驗證後為 HTTP 請求提供服務。
有趣的是,該庫不提供有關用戶身份驗證的任何幫助,但是由於 Go 可組合性,這不是問題。
CardDAV 使用基本驗證憑證。檢查憑證後,我們可以將這些憑證保存在上下文中(稍後將有用):
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)) }) }
ownBackend結構必須實作carddav.Backend接口,該接口不是很薄,但仍然可以管理。
CurrentUserPrincipal 和 AddressBookHomeSetPath 必須提供 URL(以斜線開頭和結尾)。通常是使用者名稱/聯絡人。這是您需要從上下文中提取使用者名稱的地方(這是唯一可用的參數):
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 "/" url.PathEscape(username) "/", 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 傳回一個簡單的結構,其中路徑應以上面的 AddressBookHomeSetPath 開頭(並以斜線結尾)
GetAddressObject 和 ListAddressObjects 必須檢查目前路徑(以確保目前經過驗證的使用者可以存取這些聯絡人),然後將聯絡人作為 AddressObject 傳回。
AddressObject有多個屬性,最重要的是:
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 }
某些方法允許更新聯絡人。由於我不希望透過 CardDAV 更新我的成員列表,因此我向 Put 和 Delete 方法返回 403 錯誤: return webdav.NewHTTPError(http.StatusForbidden,errors.New("carddav:不支援操作"))
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
之後,您應該能夠透過新增指向您自己的 IP 位址和連接埠的「CardDAV 聯絡帳戶」來在本地進行實驗。
在 Go 中實作 CardDAV 伺服器有點複雜,但顯然值得:您的聯絡人將自動與您組織伺服器上的資料同步!
您知道其他允許這種本機整合的很酷的協議嗎?歡迎分享您的經驗!
免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。
Copyright© 2022 湘ICP备2022001581号-3