在 HTTP 请求 header 中发送认证信息的方法
WebSocket 接口的认证信息(API 密钥)不仅可以在 s 命令中以 authorization={API_KEY} 的形式设置,也可以写入 WebSocket 连接时的 HTTP 请求 header 中。这里将说明该方法。
方法一:使用 Authorization header 的方法
在 WebSocket 连接开始时的 HTTP 请求 header 中,按如下方式写入用于认证的自定义 header。
Authorization: Bearer {API_KEY}
自定义 header 的指定方法因所使用的客户端库和执行环境而异。
例如,要将WebSocket 接口中介绍的 Python 示例代码改写为这种形式,需要如下添加 header 参数。同时,删除 s 命令中有关认证信息的描述。
ws = websocket.WebSocketApp('wss://acp-api.amivoice.com/v1/',
header=["Authorization: Bearer {API_KEY}"],
on_open=on_open,
on_message=on_message,
on_close=on_close)
方法二:使用 Sec-WebSocket-Protocol header 的方法
即使无法使用方法一的 Authorization header,AmiVoice API 也可以使用子协议协商 header 来发送认证信息。
在这种情况下,按如下方式写入 HTTP 请求 header。
Sec-WebSocket-Protocol: wrp, {API_KEY}
例如,要将WebSocket 接口中介绍的 Python 示例代码改写为这种形式,需要如下添加指定子协议的参数。同时,删除 s 命令中有关认证信息的描述。
ws = websocket.WebSocketApp('wss://acp-api.amivoice.com/v1/',
subprotocols=["wrp", "{API_KEY}"],
on_open=on_open,
on_message=on_message,
on_close=on_close)
使用 Sec-WebSocket-Protocol header 的方法,即使在浏览器 JavaScript 等无法直接指定 Authorization header 的环境中也可以使用。但是,请注意,如果使用此方法从浏览器直接连接,API 密钥将可以从客户端代码或执行环境中被引用。为了避免将 API 密钥分发给最终用户,请通过代理服务器等服务器端连接到 AmiVoice API。
认证信息的优先级
如果使用多种方法写入了认证信息,实际认证时将按以下优先级使用。
s命令中的authorization={API_KEY}Authorization: Bearer {API_KEY}header (方法一)Sec-WebSocket-Protocol: wrp, {API_KEY}header (方法二)
如果要使用写入 HTTP 请求 header 中的认证信息,请不要在 s 命令中写入认证信息(authorization)。
在代理服务器上管理 API 密钥
出于安全考虑,如果不想将 API 密钥分发给客户端设备,可以考虑这样一种构成:在代理服务器等服务器端组件上管理 API 密钥,从该服务器端组件连接到 AmiVoice API,而客户端设备则连接到该服务器。
但是,对于 WebSocket 接口,如果采用在 WebSocket 会话建立后通过 s 命令发送 API 密钥的方法,那么在代理服务器仅中继 WebSocket 的构成中,将无法在中途附加 API 密钥,从而必须让客户端设备持有 API 密钥。
在这种情况下可以利用的,正是这里说明的认证信息发送方法。当代理服务器端建立到 AmiVoice API 的 WebSocket 连接时,通过在连接时的 HTTP 请求 header 中设置 API 密钥,即可实现不向最终用户的客户端设备分发 API 密钥的构成。
此外,即使应用程序处于浏览器 JavaScript 等无法直接指定 Authorization header 的环境中,只要由代理服务器进行到 AmiVoice API 的 WebSocket 连接,也可以使用方法一。
示例代码
下面展示了将 WebSocket 接口的示例代码改写为使用方法一的形式。改写的部分已高亮显示。
import time
import websocket
import json
import threading
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(threadName)s %(message)s")
server = 'wss://acp-api.amivoice.com/v1/'
header = ["Authorization: Bearer {API_KEY}"]
filename = 'test.wav'
codec = "16K"
audio_block_size = 16000
grammar_file_names = "-a-general"
options = {
"profileId" : "",
"profileWords" : "",
"keepFillerToken": "",
"resultUpdatedInterval" : "1000",
}
def on_open(ws):
logger.info("open")
def start(*args):
command = "s {} {}".format(codec, grammar_file_names)
for k, v in options.items():
if v != "":
if k == 'profileWords':
v = '"' + v.replace('"', '""') + '"'
command += f" {k}={v}"
logger.info(f"send> {command}")
ws.send(command)
threading.Thread(target=start).start()
def on_message(ws, message):
event = message[0]
content = message[2:].rstrip()
logger.info(f"message: {event} {content}")
if event == 's':
if content == "can't connect to recognizer server":
logger.error(content)
return
def send_audio(*args):
with open(filename, mode='rb') as file:
buf = file.read(audio_block_size)
while buf:
logger.debug("send> p [..({} bytes)..]".format(len(buf)))
ws.send(b'p' + buf,
opcode=websocket.ABNF.OPCODE_BINARY)
buf = file.read(audio_block_size)
time.sleep(0.5)
logger.info("send> e")
ws.send('e')
threading.Thread(target=send_audio).start()
elif event == 'G':
pass
elif event == 'S':
starttime = int(content)
elif event == 'E':
endtime = int(content)
elif event == 'C':
pass
elif event == 'U':
raw = json.loads(content) if content else ''
elif event == 'A' or event == 'R':
raw = json.loads(content) if content else ''
elif event == 'e':
logger.info("close>")
ws.close()
def on_close(ws):
logger.info("close")
logger.info("open> {}".format(server))
ws = websocket.WebSocketApp(server,
header=header,
on_open=on_open,
on_message=on_message,
on_close=on_close)
ws.run_forever()