假设您帮助管理一个小型组织或俱乐部,并拥有一个存储所有会员详细信息(姓名、电话、电子邮件...)的数据库。
在您需要的任何地方都可以访问这些最新信息不是很好吗?好吧,有了 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