”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 如何将通讯录与手机同步?在 Go 中实现 CardDAV!

如何将通讯录与手机同步?在 Go 中实现 CardDAV!

发布于2024-11-07
浏览:674

How to synchronize your contacts with your phone? Implemeting CardDAV in Go!

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

实现 CardDAV 接口

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有多个属性,最重要的是:

  • 识别此特定联系人的路径(可以是任意的,以斜线开头)
  • ETag允许客户端快速检查是否发生任何更新(如果忘记,iOS将不会显示任何内容)
  • 需要 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
}

采用只读快捷方式

某些方法允许更新联系人。由于我不希望通过 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 服务器有点复杂,但显然值得:您的联系人将自动与您组织服务器上的数据同步!

您知道其他允许这种本机集成的很酷的协议吗?欢迎分享您的经验!

版本声明 本文转载于:https://dev.to/cmdscale/how-to-synchronize-your-contacts-with-your-phone-implemeting-carddav-in-go-9ia?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何有效地选择熊猫数据框中的列?
    如何有效地选择熊猫数据框中的列?
    在处理数据操作任务时,在Pandas DataFrames 中选择列时,选择特定列的必要条件是必要的。在Pandas中,选择列的各种选项。选项1:使用列名 如果已知列索引,请使用ILOC函数选择它们。请注意,python索引基于零。 df1 = df.iloc [:,0:2]#使用索引0和1 c...
    编程 发布于2025-05-06
  • 如何有效地转换PHP中的时区?
    如何有效地转换PHP中的时区?
    在PHP 利用dateTime对象和functions DateTime对象及其相应的功能别名为时区转换提供方便的方法。例如: //定义用户的时区 date_default_timezone_set('欧洲/伦敦'); //创建DateTime对象 $ dateTime = ne...
    编程 发布于2025-05-06
  • 在Python中如何创建动态变量?
    在Python中如何创建动态变量?
    在Python 中,动态创建变量的功能可以是一种强大的工具,尤其是在使用复杂的数据结构或算法时,Dynamic Variable Creation的动态变量创建。 Python提供了几种创造性的方法来实现这一目标。利用dictionaries 一种有效的方法是利用字典。字典允许您动态创建密钥并分...
    编程 发布于2025-05-06
  • 如何解决由于Android的内容安全策略而拒绝加载脚本... \”错误?
    如何解决由于Android的内容安全策略而拒绝加载脚本... \”错误?
    Unveiling the Mystery: Content Security Policy Directive ErrorsEncountering the enigmatic error "Refused to load the script..." when deployi...
    编程 发布于2025-05-06
  • 解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    解决MySQL插入Emoji时出现的\\"字符串值错误\\"异常
    Resolving Incorrect String Value Exception When Inserting EmojiWhen attempting to insert a string containing emoji characters into a MySQL database us...
    编程 发布于2025-05-06
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-05-06
  • JavaScript计算两个日期之间天数的方法
    JavaScript计算两个日期之间天数的方法
    How to Calculate the Difference Between Dates in JavascriptAs you attempt to determine the difference between two dates in Javascript, consider this s...
    编程 发布于2025-05-06
  • 如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    如何为PostgreSQL中的每个唯一标识符有效地检索最后一行?
    postgresql:为每个唯一标识符在postgresql中提取最后一行,您可能需要遇到与数据集合中每个不同标识的信息相关的信息。考虑以下数据:[ 1 2014-02-01 kjkj 在数据集中的每个唯一ID中检索最后一行的信息,您可以在操作员上使用Postgres的有效效率: id dat...
    编程 发布于2025-05-06
  • 为什么不使用CSS`content'属性显示图像?
    为什么不使用CSS`content'属性显示图像?
    在Firefox extemers属性为某些图像很大,&& && && &&华倍华倍[华氏华倍华氏度]很少见,却是某些浏览属性很少,尤其是特定于Firefox的某些浏览器未能在使用内容属性引用时未能显示图像的情况。这可以在提供的CSS类中看到:。googlepic { 内容:url(&#...
    编程 发布于2025-05-06
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-05-06
  • 如何使用node-mysql在单个查询中执行多个SQL语句?
    如何使用node-mysql在单个查询中执行多个SQL语句?
    Multi-Statement Query Support in Node-MySQLIn Node.js, the question arises when executing multiple SQL statements in a single query using the node-mys...
    编程 发布于2025-05-06
  • PHP未来:适应与创新
    PHP未来:适应与创新
    PHP的未来将通过适应新技术趋势和引入创新特性来实现:1)适应云计算、容器化和微服务架构,支持Docker和Kubernetes;2)引入JIT编译器和枚举类型,提升性能和数据处理效率;3)持续优化性能和推广最佳实践。 引言在编程世界中,PHP一直是网页开发的中流砥柱。作为一个从1994年就开始发展...
    编程 发布于2025-05-06
  • Java中如何使用观察者模式实现自定义事件?
    Java中如何使用观察者模式实现自定义事件?
    在Java 中创建自定义事件的自定义事件在许多编程场景中都是无关紧要的,使组件能够基于特定的触发器相互通信。本文旨在解决以下内容:问题语句我们如何在Java中实现自定义事件以促进基于特定事件的对象之间的交互,定义了管理订阅者的类界面。以下代码片段演示了如何使用观察者模式创建自定义事件: args)...
    编程 发布于2025-05-06
  • 如何高效地在一个事务中插入数据到多个MySQL表?
    如何高效地在一个事务中插入数据到多个MySQL表?
    mySQL插入到多个表中,该数据可能会产生意外的结果。虽然似乎有多个查询可以解决问题,但将从用户表的自动信息ID与配置文件表的手动用户ID相关联提出了挑战。使用Transactions和last_insert_id() 插入用户(用户名,密码)值('test','test...
    编程 发布于2025-05-06
  • 如何在Java中正确显示“ DD/MM/YYYY HH:MM:SS.SS”格式的当前日期和时间?
    如何在Java中正确显示“ DD/MM/YYYY HH:MM:SS.SS”格式的当前日期和时间?
    如何在“ dd/mm/yyyy hh:mm:mm:ss.ss”格式“ gormat 解决方案:的,请访问量很大,并应为procectiquiestate的,并在整个代码上正确格式不多: java.text.simpledateformat; 导入java.util.calendar; 导入java...
    编程 发布于2025-05-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3