概要
- LINE Clova から Custom Extension (スキル) 起動時に送信される HTTP リクエストを調べる
- Ruby + Heroku による調査用 Web サーバ (Extension サーバー) を立てて HTTP リクエストの内容を調べる
Clova とは
LINE が提供しているスマートスピーカー。
ClovaはLINEが開発したAIアシスタントです。
話しかけるだけでLINEの送受信や家電の操作など、あなたの様々なリクエストに応えます。
Clova の HTTP リクエスト
Clova は CEK (Clova Extensions Kit) を介して HTTPS リクエストを Extension サーバーに送信している。
CEKの概要 - Clova Developer Center β
CEKは、Clova Extension(以下、Extension)を開発および配布する際に必要なツールとインターフェースを提供するプラットフォームです。ClovaプラットフォームとExtension間のデータの送受信をサポートします。Extensionは、音楽、ショッピングなどの外部のサービス(サードパーティサービス)、または家庭のIoTデバイスの制御など、Clovaの機能を拡張して、ユーザーに様々な体験を提供するWebアプリケーションです。
CEK APIのリファレンス - Clova Developer Center β
・HTTP/1.1バージョンでHTTP通信し、POSTメソッドを使用します。
・Hostとリクエストパスは、Extensionの開発者があらかじめ定義したURIに設定されます。
・リクエストボディのデータはJSON形式で、UTF-8エンコーディングを使用します。
・SignatureCEKフィールドとRSA公開鍵を使用して、Clovaから送信されたリクエストかどうかを検証することができます。
Extension サーバーとは
Clova から送信される HTTPS リクエストを受信して応答を返す Web サーバ。
Custom Extensionを作成する - Clova Developer Center β
Clova Developer Centerに登録するExtensionサーバーです。このサーバーは、Clovaがユーザーの音声入力を解析した結果や、デフォルトで提供されるインテントを渡された際に、そのインテントを処理して適切な応答を返す必要があります。
今回の調査対象
- Clova Friends (画面なしモデル)
- Clova Desk (画面ありモデル)
- Clova Developer Center テストツール (Web ブラウザ上で使用できるテストツール)
Clova Friends とは
内蔵バッテリーを搭載している Clova。
画面は付いていない。
Clova Friends | LINE Clova公式サイト
コンパクトなサイズで、見ためもPOPな、スマートスピーカーです。
バッテリーを内蔵しているので、音楽再生、LINE通話、占いなどの便利な機能を、お出かけ先などでご利用いただくことができます。
Clova Desk とは
内蔵バッテリー、画面、赤外線送受信機を搭載している Clova。
天気やレシピ、歌詞も画面表示でもっと楽しめる
7インチの画面で、子供と一緒の画面を見ながら、お気に入りの曲を聴いたり、料理を楽しむことができます。
IRと赤外線リモコンを搭載。リモコンを探すことなく、テレビやエアコンを操作することができます。
Clova Developer Center テストツールとは
実際に Custom Extension (スキル) を配布する前にテストすることができるツール。
Extensionをテストする - Clova Developer Center β
テスト画面では、次の2種類のテストを実行できます。
・対話モデルテストモード:任意のインテントのサンプル発話を入力して、インテントやスロットの解析結果やExtensionへのリクエストメッセージを確認できます。
・シナリオテストモード:LaunchRequestからSessionEndedRequestまでの一連のシナリオをテストできます。
HTTP リクエストを受ける調査用 Web サーバ (Extension サーバー)
- HTTP リクエスト確認用 Ruby スクリプト を Heroku に設置する
- Clova 実機等で Custom Extension (スキル) を起動して、その際に送信される HTTP リクエストを Web サーバ (Extension サーバー) で受信してログに出力する
HTTP リクエスト確認用 Ruby スクリプト
require 'socket'
# 標準出力を同期モードに設定
$stdout.sync = true
# 接続を受け付けるポート番号を決定
# 環境変数 PORT が設定されているならそれを設定
port = 8000
port = ENV['PORT'].to_i if ENV['PORT']
# サーバー接続をオープン
server = TCPServer.open(port)
# HTTP リクエストを待ち続ける
loop do
begin
# TCPSocket オブジェクトを取得
socket = server.accept
# 受け付けた日時を出力
puts "[info]#{Time.new}"
# HTTP リクエスト開始行を出力
if not req_start_line = socket.gets
puts '[info]req_start_line is nil'
next
end
puts "#{req_start_line}"
# HTTP リクエストヘッダーを1行ずつ出力
while req_header = socket.gets.chomp
puts "#{req_header}"
break if req_header == '' # ヘッダー終了
# Content-Length ヘッダーがあれば値を変数にセット
h = req_header.split(':')
content_length = h[1].strip.to_i if h[0].strip.downcase == 'content-length'
end
# Content-Length がある場合はボディを出力
if content_length != nil
puts socket.read(content_length)
end
# HTTP レスポンスを返す
# 本文データ
body = <<-'__EOS__'
{
"version": "1.0",
"sessionAttributes": {},
"response": {
"outputSpeech": {
"type": "SimpleSpeech",
"values": {
"type": "PlainText",
"lang": "ja",
"value": "こんにちは、クローバ🍀"
}
},
"card": {},
"directives": [],
"shouldEndSession": true
}
}
__EOS__
# ステータス行
socket.write "HTTP/1.1 200 OK\r\n"
# ヘッダー
socket.write "Server: #{RUBY_DESCRIPTION}\r\n"
socket.write "Content-Type: application/json\r\n"
socket.write "Content-Length: #{body.bytesize}\r\n"
socket.write "Connection: close\r\n"
# 空行
socket.write "\r\n"
# 本文
socket.write body
rescue => e
puts e.full_message
ensure
# HTTP 接続を閉じる
puts '[info]close this socket'
socket.close
end
end
server.close
調査結果
留意点
- ユーザー毎等で一意になるような値などは文字「X」による伏せ字に置き換えておく
- Heroku 経由なので一部の HTTP ヘッダ等が加工・追加されている可能性がある
- Signaturecek ヘッダ: Clova から送信されたかどうかを検証するためのヘッダ
- X-B3 ではじまるヘッダ: LINE 側でつけていると思われるヘッダ (分散トレーシングシステム Zipkin で使われるヘッダ)
- Heroku のリバースプロキシサーバ Vegur がつけていると思われるヘッダ: X-Request-Id, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port, Via, Connect-Time, X-Request-Start, Total-Route-Time
Clova Friends (ディスプレイ無しモデル) の HTTP リクエスト
HTTP リクエスト全体
POST /hello-clova/ HTTP/1.1
Host: example.herokuapp.com
Connection: close
User-Agent: Go-http-client/1.1
Content-Type: application/json; charset=utf-8
Signaturecek: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X-B3-Flags: 0
X-B3-Parentspanid: XXXXXXXXXXXXXXXX
X-B3-Sampled: 1
X-B3-Spanid: XXXXXXXXXXXXXXXX
X-B3-Traceid: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X-Clova-Experiments: 1=A&10=A&2=B&3=A&4=B&5=A&6=C&7=B&8=A&9=B
X-Clova-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Accept-Encoding: gzip
X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Via: 1.1 vegur
Connect-Time: 0
X-Request-Start: 1580853072763
Total-Route-Time: 0
Content-Length: 744
{"version":"1.0","session":{"new":true,"sessionAttributes":{},"sessionId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}},"context":{"System":{"application":{"applicationId":"info.maigo.lab.helloclova"},"device":{"deviceId":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","display":{"size":"none","contentLayer":{"width":0,"height":0}}},"user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}}},"request":{"type":"LaunchRequest","requestId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","timestamp":"2020-02-04T21:51:12Z","locale":"ja-JP","extensionId":"info.maigo.lab.helloclova","intent":{"intent":"","name":"","slots":{}},"event":{"namespace":"","name":"","payload":null}}}
HTTP メッセージボディの JSON を整形したもの
{
"version": "1.0",
"session": {
"new": true,
"sessionAttributes": {},
"sessionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"user": {
"userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
},
"context": {
"System": {
"application": {
"applicationId": "info.maigo.lab.helloclova"
},
"device": {
"deviceId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"display": {
"size": "none",
"contentLayer": {
"width": 0,
"height": 0
}
}
},
"user": {
"userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
},
"request": {
"type": "LaunchRequest",
"requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"timestamp": "2020-02-04T21:51:12Z",
"locale": "ja-JP",
"extensionId": "info.maigo.lab.helloclova",
"intent": {
"intent": "",
"name": "",
"slots": {}
},
"event": {
"namespace": "",
"name": "",
"payload": null
}
}
}
Clova Desk (ディスプレイ付きモデル) の HTTP リクエスト
HTTP リクエスト全体
POST /hello-clova/ HTTP/1.1
Host: example.herokuapp.com
Connection: close
User-Agent: Go-http-client/1.1
Content-Type: application/json; charset=utf-8
Signaturecek: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X-B3-Flags: 0
X-B3-Parentspanid: XXXXXXXXXXXXXXXX
X-B3-Sampled: 1
X-B3-Spanid: XXXXXXXXXXXXXXXX
X-B3-Traceid: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
X-Clova-Experiments: 1=A&10=A&2=B&3=A&4=B&5=A&6=C&7=B&8=A&9=B
X-Clova-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Accept-Encoding: gzip
X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Via: 1.1 vegur
Connect-Time: 0
X-Request-Start: 1580853042398
Total-Route-Time: 0
Content-Length: 787
{"version":"1.0","session":{"new":true,"sessionAttributes":{},"sessionId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}},"context":{"System":{"application":{"applicationId":"info.maigo.lab.helloclova"},"device":{"deviceId":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","display":{"size":"custom","dpi":160,"orientation":"landscape","contentLayer":{"width":1024,"height":552}}},"user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}}},"request":{"type":"LaunchRequest","requestId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","timestamp":"2020-02-04T21:50:41Z","locale":"ja-JP","extensionId":"info.maigo.lab.helloclova","intent":{"intent":"","name":"","slots":{}},"event":{"namespace":"","name":"","payload":null}}}
HTTP メッセージボディの JSON を整形したもの
{
"version": "1.0",
"session": {
"new": true,
"sessionAttributes": {},
"sessionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"user": {
"userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
},
"context": {
"System": {
"application": {
"applicationId": "info.maigo.lab.helloclova"
},
"device": {
"deviceId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"display": {
"size": "custom",
"dpi": 160,
"orientation": "landscape",
"contentLayer": {
"width": 1024,
"height": 552
}
}
},
"user": {
"userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
}
},
"request": {
"type": "LaunchRequest",
"requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"timestamp": "2020-02-04T21:50:41Z",
"locale": "ja-JP",
"extensionId": "info.maigo.lab.helloclova",
"intent": {
"intent": "",
"name": "",
"slots": {}
},
"event": {
"namespace": "",
"name": "",
"payload": null
}
}
}
Clova Developer Center テストツール の HTTP リクエスト
HTTP リクエスト全体
POST /hello-clova/ HTTP/1.1
Host: example.herokuapp.com
Connection: close
Signaturecek: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/json;charset=UTF-8
User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_202)
Accept-Encoding: gzip,deflate
X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
X-Forwarded-Port: 443
Via: 1.1 vegur
Connect-Time: 0
X-Request-Start: 1580853979149
Total-Route-Time: 0
Content-Length: 594
{"version":"1.0","session":{"sessionId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","sessionAttributes":{},"user":{"userId":"XXXXXXXXXXXXXXXXXXXXXX","accessToken":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"},"new":true},"context":{"System":{"application":{"applicationId":"info.maigo.lab.helloclova"},"user":{"userId":"XXXXXXXXXXXXXXXXXXXXXX","accessToken":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"},"device":{"deviceId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","display":{"size":"l100","orientation":"landscape","dpi":96,"contentLayer":{"width":640,"height":360}}}}},"request":{"type":"LaunchRequest"}}
HTTP メッセージボディの JSON を整形したもの
{
"version": "1.0",
"session": {
"sessionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"sessionAttributes": {},
"user": {
"userId": "XXXXXXXXXXXXXXXXXXXXXX",
"accessToken": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
},
"new": true
},
"context": {
"System": {
"application": {
"applicationId": "info.maigo.lab.helloclova"
},
"user": {
"userId": "XXXXXXXXXXXXXXXXXXXXXX",
"accessToken": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
},
"device": {
"deviceId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"display": {
"size": "l100",
"orientation": "landscape",
"dpi": 96,
"contentLayer": {
"width": 640,
"height": 360
}
}
}
}
},
"request": {
"type": "LaunchRequest"
}
}