Design Automation API:WebSocket API ワークフロー

ご承知にように、Forge では Forge サーバー(Forge のクラウド リソース)とのコミュニケーションに HTTP ベースの RESTful API を多用しています。ただ、進化する Design Automation API の記事でご案内したとおり、今回、Design Automation API の一部の機能で、WebSocket と呼ばれる形態のコミュニケーション手段が導入されています。

WebSocket は、Web サーバーとクライアント(主に Web ブラウザ)間で、直接、双方向通信を可能にするテクノロジです。接続確立後には、双方向でメッセージを送受信することが出来るようになります。

グラフィックに、ユーザーとWebブラウザ、Forgeアプリケーションサーバー、及びAutodeskの製品(AutoCAD、Revit、Inventor、3ds Max)との間のインタラクションを示す図。

一見すると RESTful API と同じに感じてしまいますが、クライアントからサーバーへの要求リクエストだけでなく、サーバーからクライアントへのプッシュ型通信が可能な点が大きく異なります。また、一度、確立した接続を維持することが出来るので、リアルタイム性を重視したコミュニケーションで利用される傾向があります。Forge でも、SVF2 を Forge Viewer で表示する際のロード処理や、過去にサポートしていた Live Review Extension で WebSocket が使われています。

Design Automation API の WebSocket API については、Forge ポータルの Developer’s Guide に WebSocket ページ が追加されています。ここでは、そのワークフローと例をご紹介しておきたいと思います。

まず、Developer’s Guide でも触れられている wscat を使ったテストからご紹介します。wscat は Node.js 用に用意されたパッケージ(ミドルウェア)で、ローカル リポジトリ毎ではなく、Node.js 環境にグローバルにインストール(npm install -g wscat)することで、コマンド プロンプト(Windows)やターミナル(Mac)上で WebSocket を試すことが出来るようになります。(Windows への Node.js のインストールについては、Forge の開発環境 のブログ記事で触れています。)

Design Automation API の WebSocket API を試すには、コマンド プロンプトやターミナルを起動後、まず、接続ターゲットである wss://websockets.forgedesignautomation.io次の構文で指定します。

wscat -c wss://websockets.forgedesignautomation.io

接続が完了したら、必要な JSON ペイロードを指定して、直接、WorkItem を起動することが出来るようになります。次の例では、テスト用に用意されている “Autodesk.Nop+Latest” Activity を指定して WorkItem を起動しています。

{ "action" : "post-workitem", "data" : { "activityId" : "Autodesk.Nop+Latest"}, "headers": {"Authorization": "Bearer <your access token>"}}

下記は、コマンド プロンプトで実行した様子です。特に何もしなくても、キューに入った WorkItem の終了後、(サーバーからのプッシュによって)自動的に WorkItem の終了とレポート URL を含むレスポンスが JSONで表示されるのがわかります。クラウアント側から GET workitems/:id endpoint を呼び出す必要はありません。

コマンドプロンプトでの表示画面。ユーザーディレクトリ内でのコマンド実行様子を示す。

wscat の処理をクライアント側の  JavaScript コード化したものが次のコードです。ここでは、あらかじめ、”Autodesk_Japan.Hello+dev” Activity を定義済で、便宜上、2-legged 認証を利用しています。

“Autodesk_Japan.Hello+dev” Activity 定義

{
    "id": DA4A_UQ_ID,
    "commandLine": "$(engine.path)\\accoreconsole.exe /s \"$(settings[script].path)\"",
    "parameters": {
    },
    "settings": {
        "script": {
            "value": "(print \"Hello!\")\n"
        }
    },
    "engine": DA4A_ENGINE,
    "appbundles": [],
    "description": "DA4A WebSocket Test"
}

JavaScript コード例

var CLIENT_ID = '<your client id>',
    CLIENT_SECRET = '<your client secret>',
    SOCKET;
    $(document).on("click", "[id^='doit']", function () {
        var header =
        {
            'Content-Type': 'application/x-www-form-urlencoded'
        };
        var payload =
        {
            client_id : CLIENT_ID,
            client_secret : CLIENT_SECRET,
            grant_type : 'client_credentials',
            scope : 'code:all'
        };
        var uri = 'https://developer.api.autodesk.com/authentication/v1/authenticate';
        $.ajax({
            url: uri,
            type: 'POST',
            headers: header,
            contentType: 'json',
            data: payload
        }).done(function (res) {
            console.log(res);
            var msg = JSON.stringify(
            {
                "action" : "post-workitem",
                "data" :
                {
                    "activityId" : "Autodesk_Japan.Hello+dev"
                },
                "headers":
                {                    
                    "Authorization": "Bearer " + JSON.parse(JSON.stringify(res)).access_token
                }
            });
            console.log(msg);
            SOCKET.send(msg);
            SOCKET.onmessage = function(event) {
                console.log("通知" + event.data);
                $("#message").text(event.data);
            };
            SOCKET.onerror = function(error) {
                console.log("エラー" + error.data);
                $("#message").text(error.data);
            };                
        }).fail(function (jqXHR, textStatus, errorThrown) {
            console.log("Failed : " + errorThrown);
            $("#message").text("Failed : " + errorThrown);
        });    
    });
    $(document).on("click", "[id^='connect']", function() {
        SOCKET = new WebSocket('wss://websockets.forgedesignautomation.io');
        SOCKET.onopen = function(event) {
            console.log("接続" + event.data);
            $("#message").text("接続");
        };
    });
    $(document).on("click", "[id^='disconnect']", function () {
        SOCKET.close();
        SOCKET.onclose = function() {
            console.log("切断");
            $("#message").text("切断");
        };  
    });

HTML 定義

このブログサイトで HTML 記述がページ記述に誤認識されてしまうので、下記 <> を意図的に全角にしています。ご注意ください。

<html>
<head>
    <title>Test</title>
    <meta charset="utf-8">
    <link type="text/css" rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css">
    <link type="text/css" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
    <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="index.js"></script>
    <style>
    </style>
</head>
<body>
    <div style="padding:20px">
        <button id="connect" class="btn btn-outline-dark">WebSocket 接続</button>
        <button id="doit" class="btn btn-outline-dark">WorkItem 実行</button>
        <button id="disconnect" class="btn btn-outline-dark">WebSocket 切断</button>
        <p></p>
        <label id="message" style="width:90%"></label>
    </div>
</body>
</html>

上記 HTML ページを Web ブラウザで開いて左からボタンをクリックしていくと、次のように遷移します。wscat と同じく、プッシュ通信で WorkItem の終了のレスポンス JSONで表示されるのがわかります。最後の部分では、デベロッパ^ ルール(F12 キー)のコンソールに表示したレポート URL から、WorkItem の処理レポートをダウンロードして表示しています。

WebSocket接続、WorkItem実行、WebSocket切断のボタンが表示されたインターフェース

クライアント側のコードのみで 2-legged の Access Token を取得するには、デベロッパキー(Client Id と Client Secret)を保持ことが必要になってしまうため、セキュリティ上好ましくありません。そこで、Design Automation API で WebSocket API を使用する際には、Implicit Grant も含め、3-legged で得た Access Token を取得する方法も用意されています。

ただ、code:all Scope を持つ Access Token をクライアント側で持つ点には変わりありません。この Access Token で WebSocket を使って WorkItem を起動する際、使用する JSON ペイロードを書き換えられてしまう懸念が残ります。ここでは、Das.WorkitemSigner ツールを使用して公開キーと秘密キーのペアを生成、使用する Activity Id にデジタル署名して WorkItem を呼び出しす方法が提供されています。

手順は次のとおりです。

①.Das.WorkitemSigner ツールをダウンロード

②.Das.WorkitemSigner ツールで公開キーと秘密キーのペアを生成

Das.WorkItemSigner.exe generate mykey.json

③.Das.WorkItemSigner ツールで公開キーを JSON ファイルにエクスポート

Das.WorkitemSigner.exe export mykey.json mypublickey.json

④.PATCH forgeapps id endpoint で公開キーをアップロード

JSON形式のデータを送信するためのインターフェース画面。公衆鍵の情報が表示されている部分が強調表示されている。

⑤.Das.WorkitemSigner ツールで Activity Id のデジタル署名を生成

Das.WorkitemSigner.exe sign mykey.json Autodesk_Japan.Hello+dev

⑥.WorkItem 呼び出し時に Signatures スコープで署名された Activity Id を使用

        var msg = JSON.stringify(
            {
                “action”: “post-workitem”,
                “data”:
                {
                    “activityId”: “Autodesk_Japan.Hello+dev”,
                    “signatures”: {
                        “activityId”: “⑤ で返されるデジタル署名”
                    }
                },
                “headers”:
                {
                    “Authorization”: “Bearer ” + JSON.parse(JSON.stringify(res)).access_token
                }
            });
        SOCKET.send(msg);
Command prompt showing commands for generating and signing keys using the Das.WorkItemSigner tool.

なお、3-legged の Access Token を使って、Signatures スコープの指定なしで WorkItem を呼び出すと、次のエラーが返されます。

{"action":"error","data":"{\"Signatures.ActivityId\":[\"Argument must be specified when using 3-legged oauth token. (Parameter 'Signatures.ActivityId')\"]}"}

また、WorkItem 呼び出し以外は 3-legged 認証で得た Access Token は使用出来ません。Activity や AppBundle の登録は、従来通り、2-legged 認証の Access Token  とともに RESTful API の各 endpoint 呼び出しが必要です。

By Toshiaki Isezaki

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading