Von Azure Web PubSub unterstütztes protobuf-WebSocket-Unterprotokoll

In diesem Dokument wird das Unterprotokoll protobuf.webpubsub.azure.v1 beschrieben.

Wenn ein Client dieses Unterprotokoll verwendet, wird erwartet, dass es sich sowohl bei den ausgehenden als auch den eingehenden Dataframes um Protokollpuffernutzlasten (protobuf) handelt.

Übersicht

Das Unterprotokoll protobuf.webpubsub.azure.v1 ermöglicht es den Clients, direkt Veröffentlichen-Abonnieren-Vorgänge (Publish-Subscribe, PubSub) anstelle eines Roundtrips zum Upstreamserver durchzuführen. Die WebSocket-Verbindung mit dem Unterprotokoll protobuf.webpubsub.azure.v1 wird auch „PubSub-WebSocket-Client“ genannt.

In JavaScript können Sie beispielsweise einen PubSub-WebSocket-Client mit dem protobuf-Unterprotokoll erstellen, indem Sie Folgendes verwenden:

// PubSub WebSocket client
var pubsub = new WebSocket('wss://test.webpubsub.azure.com/client/hubs/hub1', 'protobuf.webpubsub.azure.v1');

Für einen einfachen WebSocket-Client verfügt der Server über die erforderliche Rolle für die Behandlung von Clientereignissen. Eine einfache WebSocket-Verbindung löst beim Senden von Nachrichten immer ein message-Ereignis aus, und die Verarbeitung von Nachrichten und andere Vorgänge werden immer serverseitig ausgeführt. Mithilfe des Unterprotokolls protobuf.webpubsub.azure.v1 kann ein autorisierter Client unter Verwendung von Joinanforderungen einer Gruppe beitreten und Nachrichten mit Veröffentlichungsanforderungen direkt in einer Gruppe veröffentlichen. Der Client kann mithilfe von Ereignisanforderungen Nachrichten auch an verschiedene Upstreamereignishandler weiterleiten, um das Ereignis anzupassen, zu dem die Nachricht gehört.

Hinweis

Derzeit unterstützt der Web PubSub-Dienst nur proto3.

Berechtigungen

Ein PubSub-WebSocket-Client kann nur dann auf anderen Clients veröffentlichen, wenn er dazu autorisiert ist. Die dem Client zugewiesenen roles bestimmen die Berechtigungen, die dem Client gewährt werden:

Role Berechtigung
Nicht angegeben Der Client kann Ereignisanforderungen senden.
webpubsub.joinLeaveGroup Der Client kann einer beliebigen Gruppe beitreten/diese verlassen.
webpubsub.sendToGroup Der Client kann Nachrichten in einer beliebigen Gruppe veröffentlichen.
webpubsub.joinLeaveGroup.<group> Der Client kann der Gruppe <group> beitreten/diese verlassen.
webpubsub.sendToGroup.<group> Der Client kann Nachrichten in der Gruppe <group> veröffentlichen.
webpubsub.joinLeaveGroups.<pattern> Der Client kann jeder Gruppe beitreten/verlassen, deren Namen übereinstimmen <pattern> (siehe Rollenmuster für Wildcard-Gruppen).
webpubsub.sendToGroups.<pattern> Der Client kann Nachrichten in jeder Gruppe veröffentlichen, deren Namen übereinstimmen <pattern> (siehe Rollenmuster für Wildcardgruppen).

Der Server kann dynamisch Clientberechtigungen über REST-APIs oder Server-SDKs erteilen oder widerrufen.

Hinweis

Wildcardrollen (z. B. webpubsub.sendToGroups.<pattern>) werden während der Laufzeit nicht in REST-APIs oder Server-SDKs unterstützt.

Requests

Alle Anforderungsnachrichten entsprechen dem folgenden protobuf-Format:

syntax = "proto3";

import "google/protobuf/any.proto";

message UpstreamMessage {
    oneof message {
        SendToGroupMessage send_to_group_message = 1;
        EventMessage event_message = 5;
        JoinGroupMessage join_group_message = 6;
        LeaveGroupMessage leave_group_message = 7;
        SequenceAckMessage sequence_ack_message = 8;
        PingMessage ping_message = 9;
        StreamDataMessage stream_data_message = 13;
        StreamEndMessage stream_end_message = 14;
    }

    message SendToGroupMessage {
        string group = 1;
        optional uint64 ack_id = 2;
        MessageData data = 3;
        optional bool no_echo = 4;
        StreamStartInfo stream = 7;
    }

    message StreamStartInfo {
        string stream_id = 1;
        optional uint32 idle_timeout_ms = 2;
    }

    message EventMessage {
        string event = 1;
        MessageData data = 2;
        optional uint64 ack_id = 3;
    }
    
    message JoinGroupMessage {
        string group = 1;
        optional uint64 ack_id = 2;
    }

    message LeaveGroupMessage {
        string group = 1;
        optional uint64 ack_id = 2;
    }

    message SequenceAckMessage {
        uint64 sequence_id = 1;
    }

    message PingMessage {
    }

    message StreamDataMessage {
        string stream_id = 1;
        optional uint64 stream_sequence_id = 2;
        MessageData data = 3;
    }

    message StreamEndMessage {
        string stream_id = 1;
        optional StreamEndError error = 2;

        message StreamEndError {
            optional string message = 1;
            optional string user_error_code = 2;
        }
    }
}

message MessageData {
    oneof data {
        string text_data = 1;
        bytes binary_data = 2;
        google.protobuf.Any protobuf_data = 3;
    }
}

Gruppen beitreten

Format:

Legen Sie join_group_message.group auf den Gruppennamen fest.

  • ackId ist die Identität jeder Anforderung und sollte eindeutig sein. Der Dienst sendet eine ack-Antwortnachricht, um den Prozess über das Ergebnis der Anforderung zu informieren. Weitere Informationen finden Sie unter AckId- und Ack-Antwort.

Gruppen verlassen

Format:

Legen Sie leave_group_message.group auf den Gruppennamen fest.

  • ackId ist die Identität jeder Anforderung und sollte eindeutig sein. Der Dienst sendet eine ack-Antwortnachricht, um den Prozess über das Ergebnis der Anforderung zu informieren. Weitere Informationen finden Sie unter AckId- und Ack-Antwort.

Veröffentlichen von Nachrichten

Format:

  • ackId: Die eindeutige Identität jeder Anforderung. Der Dienst sendet eine ack-Antwortnachricht, um den Prozess über das Ergebnis der Anforderung zu informieren. Weitere Informationen finden Sie unter AckId- und Ack-Antwort.

  • dataType: Das Datenformat, das je nach protobuf in textbinary, data oder MessageData sein kann. Die empfangenden Clients können dataType verwenden, um den Inhalt ordnungsgemäß zu verarbeiten.

  • protobuf: Wenn Sie send_to_group_message.data.protobuf_data festlegen, entspricht der implizite dataTypeprotobuf. protobuf_data kann dem Nachrichtentyp Beliebig entsprechen. Alle anderen Clients erhalten protobuf-codierte Binärdateien, die vom protobuf-SDK deserialisiert werden können. Clients, die nur textbasierten Inhalt unterstützen (z. B. json.webpubsub.azure.v1 ), erhalten eine Base64-codierte Binärdatei.

  • text: Wenn Sie send_to_group_message.data.text_data festlegen, entspricht der implizite dataTypetext. text_data sollte eine Zeichenfolge sein. Alle Clients mit anderen Protokollen erhalten eine UTF-8-codierte Zeichenfolge.

  • binary: Wenn Sie send_to_group_message.data.binary_data festlegen, entspricht der implizite dataTypebinary. binary_data sollte ein Bytearray sein. Alle Clients mit anderen Protokollen erhalten eine unformatierte Binärdatei ohne protobuf-Codierung. Clients, die nur textbasierten Inhalt unterstützen (z. B. json.webpubsub.azure.v1 ), erhalten eine Base64-codierte Binärdatei.

Fall 1: Veröffentlichen von Textdaten

Legen Sie send_to_group_message.group auf group und send_to_group_message.data.text_data auf "text data" fest.

  • Der protobuf-Unterprotokollclient in der Gruppe group empfängt den Binärframe und kann die DownstreamMessage zum Deserialisieren verwenden.

  • Die JSON-Unterprotokollclients in group empfangen:

    {
        "type": "message",
        "from": "group",
        "group": "group",
        "dataType" : "text",
        "data" : "text data"
    }
    
  • Die einfachen WebSocket-Clients in group empfangen die Zeichenfolge text data.

Fall 2: Veröffentlichen von protobuf-Daten

Angenommen, Sie haben eine benutzerdefinierte Nachricht empfangen:

message MyMessage {
    int32 value = 1;
}

Legen Sie send_to_group_message.group auf group und send_to_group_message.data.protobuf_data auf Any.pack(MyMessage) mit value = 1 fest.

  • Die protobuf-Unterprotokollclients in group empfangen den binären Frame und können DownstreamMessage zum Deserialisieren verwenden.

  • Der Unterprotokollclient in group empfängt:

    {
        "type": "message",
        "from": "group",
        "group": "G",
        "dataType" : "protobuf",
        "data" : "Ci90eXBlLmdvb2dsZWFwaXMuY29tL2F6dXJlLndlYnB1YnN1Yi5UZXN0TWVzc2FnZRICCAE=" // Base64-encoded bytes
    }
    

    Hinweis

    Bei den Daten handelt es sich um eine Base64-codierte, deserialisierbare protobuf-Binärdatei.

Sie können die folgende Protobuf-Definition verwenden und sie mit Any.unpack() deserialisieren:

syntax = "proto3";

message MyMessage {
    int32 value = 1;
}
  • Die einfachen WebSocket-Clients in group empfangen den binären Frame:

    # Show in hexadecimal
    0A 2F 74 79 70 65 2E 67 6F 6F 67 6C 65 61 70 69 73 2E 63 6F 6D 2F 61 7A 75 72 65 2E 77 65 62 70 75 62 73 75 62 2E 54 65 73 74 4D 65 73 73 61 67 65 12 02 08 01
    

Fall 3: Veröffentlichen von Binärdaten

Legen Sie send_to_group_message.group auf group und send_to_group_message.data.binary_data auf [1, 2, 3] fest.

  • Der protobuf-Unterprotokollclient in der Gruppe group empfängt den Binärframe und kann die DownstreamMessage zum Deserialisieren verwenden.

  • Der JSON-Unterprotokollclient in der Gruppe group empfängt Folgendes:

    {
        "type": "message",
        "from": "group",
        "group": "group",
        "dataType" : "binary",
        "data" : "AQID", // Base64-encoded [1,2,3]
    }
    

    Da der JSON-Unterprotokollclient nur textbasiertes Messaging unterstützt, ist die Binärdatei immer Base64-codiert.

  • Die einfachen WebSocket-Clients in group empfangen die binären Daten im binären Frame:

    # Show in hexadecimal
    01 02 03
    

Beginnen Sie mit dem Streamen von Nachrichten

Um einen Gruppenstrom zu starten, setze send_to_group_message.group auf die Zielgruppe und auf send_to_group_message.stream eine StreamStartInfo Nachricht. Eine Stream-Startanfrage setzt send_to_group_message.data nicht oder send_to_group_message.ack_id.

  • send_to_group_message.stream.stream_id ist der Identifikator des logischen Stroms. Es muss ein nicht-leerer String sein und unter aktiven Streams auf derselben Clientverbindung eindeutig sein. Client-Bibliotheken werden empfohlen, um einen global eindeutigen Wert zu erzeugen, wie zum Beispiel einen GUID oder UUID.
  • send_to_group_message.stream.idle_timeout_ms ist optional. Wenn angegeben, muss sie größer als 0sein. Wenn weggelassen, ist der Dienststandard Millisekunden 300000 . Der Wert ist ein Leerlauf-Timeout, nicht die gesamte Lebensdauer des Stroms. Stream-Daten senden, einen Stream am Leben halten oder den Stream beenden, bevor dieser Timeout abläuft, wenn die Anwendung den Stream offen halten muss.
  • send_to_group_message.no_echo ist optional. Wenn sie auf true gesetzt sind, werden Stream-Nachrichten nicht an dieselbe Verbindung zurückgesendet. Wenn keine Festlegung erfolgt, ist der Standardwert FALSE.

Wenn der Strom akzeptiert wird, erhält der Client eine Stream-Ack-Antwort mit expected_sequence_id gesetzt auf 1.

Senden Sie Streaming-Daten

Um Stromdaten zu senden, setzen stream_data_message.stream_idSie , stream_data_message.stream_sequence_id, und stream_data_message.data.

  • stream_data_message.stream_id identifiziert einen aktiven Stream auf derselben Client-Verbindung.
  • stream_data_message.stream_sequence_id ist eine positive uint64-Zahl. Das erste Datenfragment in einem Strom verwendet 1, und jedes folgende Datenfragment für dasselbe stream_id nimmt genau 1zu.
  • stream_data_message.data verwendet dieselben MessageData Codierungsregeln wie Publish Messages.

Um einen Stream aktiv zu halten, ohne Daten an Abonnenten zu liefern, senden stream_data_message Sie nur stream_id mit eingestelltem Konto.

Ende der Streaming-Nachrichten

Um einen Stream zu beenden, setze stream_end_message.stream_id.

Um einen Strom mit einem anwendungsdefinierten Fehler zu beenden, setzen stream_end_message.errorSie .

  • stream_end_message.error.message ist eine optionale, menschenlesbare Fehlermeldung.
  • stream_end_message.error.user_error_code ist ein optionaler, von der Anwendung definierter Fehlercode.

Wenn der Stream geschlossen wird, erhält der Publisher eine Stream geschlossen-Antwort.

Versenden von benutzerdefinierten Ereignissen

Es gibt einen impliziten dataType, der in Abhängigkeit von dem von Ihnen festgelegten protobuf entweder text, binary oder dataType sein kann. Die Empfängerclients können den dataType nutzen, um den Inhalt ordnungsgemäß zu verarbeiten.

  • protobuf: Wenn Sie event_message.data.protobuf_data festlegen, entspricht der implizite dataTypeprotobuf. Der Wert protobuf_data kann ein beliebiger unterstützter protobuf-Typ sein. Der Ereignishandler empfängt die protobuf-codierten Binärdateien, die von jedem protobuf-SDK deserialisiert werden können.

  • text: Wenn Sie event_message.data.text_data festlegen, entspricht der implizite dataTypetext. Der Wert text_data sollte eine Zeichenfolge sein. Der Ereignishandler empfängt eine UTF-8-codierte Zeichenfolge.

  • binary: Wenn Sie event_message.data.binary_data festlegen, entspricht der implizite dataTypebinary. Der Wert binary_data sollte ein Bytearray sein. Der Ereignishandler empfängt den unformatierten Binärframe.

Fall 1: Senden eines Ereignisses mit Textdaten

Setzen Sie event_message.data.text_data auf "text data".

Der Upstreamereignishandler empfängt eine Anforderung ähnlich der folgenden:

POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: text/plain
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>

text data

Content-Type für die CloudEvents-HTTP-Anforderung ist text/plain, mit dataType=text.

Fall 2: Senden eines Ereignisses mit protobuf-Daten

Angenommen, Sie haben die folgende Nachricht von einer Kundin oder einem Kunden erhalten:

message MyMessage {
    int32 value = 1;
}

Legen Sie event_message.data.protobuf_data auf any.pack(MyMessage) mit value = 1 fest.

Der Upstreamereignishandler empfängt eine Anforderung, die der folgenden ähnelt:

POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/json
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>

// Just show in hexadecimal; read it as binary
0A 2F 74 79 70 65 2E 67 6F 6F 67 6C 65 61 70 69 73 2E 63 6F 6D 2F 61 7A 75 72 65 2E 77 65 62 70 75 62 73 75 62 2E 54 65 73 74 4D 65 73 73 61 67 65 12 02 08 01

Content-Type für die CloudEvents-HTTP-Anforderung ist application/x-protobuf, mit dataType=protobuf.

Die Daten sind eine gültige Protobuf-Binärdatei. Sie können proto und any.unpack() wie folgt für die Deserialisierung verwenden:

syntax = "proto3";

message MyMessage {
    int32 value = 1;
}

Fall 3: Senden eines Ereignisses mit Binärdaten

Setzen Sie send_to_group_message.binary_data auf [1, 2, 3].

Der Upstreamereignishandler empfängt eine Anforderung ähnlich der folgenden:

POST /upstream HTTP/1.1
Host: xxxxxx
WebHook-Request-Origin: xxx.webpubsub.azure.com
Content-Type: application/octet-stream
Content-Length: nnnn
ce-specversion: 1.0
ce-type: azure.webpubsub.user.<event_name>
ce-source: /client/{connectionId}
ce-id: {eventId}
ce-time: 2021-01-01T00:00:00Z
ce-signature: sha256={connection-id-hash-primary},sha256={connection-id-hash-secondary}
ce-userId: {userId}
ce-connectionId: {connectionId}
ce-hub: {hub_name}
ce-eventName: <event_name>

// Just show in hexadecimal; you need to read it as binary
01 02 03 

Für dataType=binary ist der Content-Type für die CloudEvents-HTTP-Anforderung application/octet-stream. Der WebSocket-Frame kann entweder im text-Format für Textnachrichtframes oder UTF-8-codierten Binärdateien für binary-Nachrichtframes vorliegen.

Der Dienst lehnt den Client ab, wenn die Nachricht nicht dem vorgeschriebenen Format entspricht.

Ping

Der Client kann PingMessage an den Dienst senden, um den Web PubSub-Dienst zu aktivieren, damit die Aktivität des Clients erkannt werden kann.

Antworten

Alle Antwortnachrichten entsprechen dem folgenden protobuf-Format:

message DownstreamMessage {
    oneof message {
        AckMessage ack_message = 1;
        DataMessage data_message = 2;
        SystemMessage system_message = 3;
        PongMessage pong_message = 4;
        StreamAckMessage stream_ack_message = 6;
        StreamNackMessage stream_nack_message = 7;
        StreamClosedMessage stream_closed_message = 8;
    }
    
    message AckMessage {
        uint64 ack_id = 1;
        bool success = 2;
        optional ErrorMessage error = 3;
    
        message ErrorMessage {
            string name = 1;
            string message = 2;
        }
    }

    message DataMessage {
        string from = 1;
        optional string group = 2;
        MessageData data = 3;
        StreamInfo stream = 6;
    }

    message SystemMessage {
        oneof message {
            ConnectedMessage connected_message = 1;
            DisconnectedMessage disconnected_message = 2;
        }
    
        message ConnectedMessage {
            string connection_id = 1;
            string user_id = 2;
        }

        message DisconnectedMessage {
            string reason = 2;
        }
    }

    message PongMessage {
    }

    message StreamAckMessage {
        string stream_id = 1;
        uint64 expected_sequence_id = 2;
    }

    message StreamNackMessage {
        string stream_id = 1;
        string name = 2;
        string message = 3;
        uint64 expected_sequence_id = 4;
    }

    message StreamClosedMessage {
        string stream_id = 1;
        optional StreamClosedError error = 2;

        message StreamClosedError {
            string name = 1;
            string message = 2;
        }
    }
}

message StreamInfo {
    string stream_id = 1;
    uint64 stream_sequence_id = 2;
    optional bool end_of_stream = 3;
    optional StreamError error = 4;

    message StreamError {
        string name = 1;
        string message = 2;
        string user_error_code = 3;
    }
}

Vom Client empfangene Nachrichten können , message, , system, pong, streamAck, streamNack, oder streamClosedseinack.

Ack-Antwort

Wenn die Anforderung ackId enthält, gibt der Dienst eine Ack-Antwort für diese Anforderung zurück. Die Clientimplementierung sollte diesen Ack-Mechanismus verarbeiten, einschließlich:

  • Warten auf die Ack-Antwort für einen asyncawait-Vorgang
  • Timeoutüberprüfung, wenn eine Ack-Antwort innerhalb eines bestimmten Zeitraums nicht empfangen wird

Die Clientimplementierung sollte immer zuerst überprüfen, ob der success-Status true oder false entspricht. Wenn der success-Status false entspricht, kann der Client Fehlerdetails aus der error-Eigenschaft lesen.

Reaktion auf die Nachricht

Clients können Nachrichten empfangen, die von einer Gruppe veröffentlicht wurden, der der Client beigetreten ist. Alternativ können sie Nachrichten von der Serververwaltungsrolle empfangen, wenn der Server Nachrichten an einen bestimmten Client oder bestimmte Benutzer*innen sendet.

In den folgenden Szenarios erhalten Sie immer eine DownstreamMessage.DataMessage-Nachricht:

  • Wenn die Nachricht von einer Gruppe stammt, wird für fromgroup angegeben. Wenn die Nachricht vom Server stammt, wird für fromserver angegeben.
  • Wenn die Nachricht von einer Gruppe stammt, ist group der Gruppenname.

Der dataType von Sender*innen verursacht, dass eine der folgenden Nachrichten gesendet wird:

  • Wenn der dataTypetext lautet, verwenden Sie message_response_message.data.text_data.
  • Wenn der dataTypebinary lautet, verwenden Sie message_response_message.data.binary_data.
  • Wenn der dataTypeprotobuf lautet, verwenden Sie message_response_message.data.protobuf_data.
  • Wenn der dataTypejson lautet, verwenden Sie message_response_message.data.text_data, und der Inhalt ist eine serialisierte JSON-Zeichenfolge.

Wenn eine Gruppennachricht zu einem Strom gehört, DownstreamMessage.DataMessage.stream wird gesetzt.

  • stream.stream_id ist die logische Stromidentifikator.
  • stream.stream_sequence_id ist die Sequenznummer der Nachricht im Strom.
  • stream.end_of_stream ist optional. Wenn auf truegesetzt ist, ist die Nachricht die Endnachricht des Stroms.
  • stream.error optional ist und nur dann vorhanden, wenn der Stream mit einem Fehler endet. user_error_code nur für UserErrorvorhanden ist.

Stream-Ack-Reaktion

Der Dienst sendet die Bestätigung der angenommenen DownstreamMessage.StreamAckMessage Stromdaten und die nächste erwartete Stream-Sequenz-ID.

  • stream_id ist die logische Stromidentifikator.
  • expected_sequence_id ist die nächste Stream-Sequenz-ID, die der Dienst erwartet.

Stream-Nack-Reaktion

Der Dienst meldet DownstreamMessage.StreamNackMessage einen wiederholbaren Stream-Fehler.

  • stream_id ist die logische Stromidentifikator.
  • expected_sequence_id ist die nächste Stream-Sequenz-ID, die der Dienst erwartet.
  • name kann InvalidSequenceId oder TransientError sein.
  • message enthält Fehlerdetails.

Stream geschlossene Antwort

Der Dienst sendet, DownstreamMessage.StreamClosedMessage wenn der Stream auf der Publisherseite geschlossen ist.

  • stream_id ist die logische Stromidentifikator.
  • error wird weggelassen, wenn der Strom normal geschlossen wird.
  • error.namekann , Forbidden, , BadRequest, InternalServerError, oder IdleTimeoutseinStreamNotFound.
  • error.message enthält Fehlerdetails.

Systemantwort

Der Web PubSub-Dienst kann auch systembezogene Antworten an den Client senden.

Verbunden

Wenn der Client eine Verbindung mit dem Dienst herstellt, erhalten Sie eine DownstreamMessage.SystemMessage.ConnectedMessage-Nachricht.

Getrennt

Wenn der Server die Verbindung schließt oder der Dienst den Client ablehnt, erhalten Sie eine DownstreamMessage.SystemMessage.DisconnectedMessage-Nachricht.

Pong-Antwort

Der Web PubSub-Dienst sendet PongMessage an den Client, wenn er PingMessage vom Client empfängt.

Nächste Schritte

Erstellen Sie mithilfe dieser Ressourcen Ihre eigene Anwendung: