ホームページ  >  記事  >  バックエンド開発  >  連絡先を携帯電話と同期するにはどうすればよいですか? Go に CardDAV を実装する!

連絡先を携帯電話と同期するにはどうすればよいですか? Go に CardDAV を実装する!

DDD
DDDオリジナル
2024-09-19 22:21:191136ブラウズ

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

あなたが小さな組織やクラブの管理を手伝っており、すべてのメンバーの詳細 (名前、電話番号、電子メールなど) を保存するデータベースがあるとします。
必要なときにどこでもこの最新情報にアクセスできたら便利だと思いませんか? CardDAV を使えばそれが可能です!

CardDAV は、十分にサポートされている連絡先管理のオープン スタンダードです。 iOS の連絡先アプリにネイティブに統合されており、Android では多くのアプリが利用可能です。

サーバー側で CardDAV を実装するのは、通常とは異なる http メソッド (GET、POST の代わりに PROPFIND、REPORT...) に応答する http サーバーです。幸いなことに、作業を大幅に簡素化する Go モジュールが存在します: github.com/emersion/go-webdav。このライブラリは実装されたバックエンドを想定しており、認証後に HTTP リクエストを処理する標準の http.Handler を提供します。

認証

興味深いことに、このライブラリはユーザー認証に関するヘルプを提供していませんが、Go のコンポーザビリティのおかげで、これは問題になりません。
CardDAV は Basic 認証資格情報を使用します。資格情報がチェックされたら、それらの資格情報をコンテキストに保存できます (後で役立ちます):

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:operation not support"))

ローカルでテストする

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 サーバーの実装は少し複雑ですが、それだけの価値があることは明らかです。連絡先は、組織のサーバー上にあるデータと自動的に同期されます。

この種のネイティブ統合を可能にする他の優れたプロトコルをご存知ですか?あなたの経験をぜひ共有してください!

以上が連絡先を携帯電話と同期するにはどうすればよいですか? Go に CardDAV を実装する!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。