Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save willwhui/fd511cee75153bc73d51f451f3d9e0a2 to your computer and use it in GitHub Desktop.
Save willwhui/fd511cee75153bc73d51f451f3d9e0a2 to your computer and use it in GitHub Desktop.
搭建api.ai webhook(在朋友google cloud搭建的vps上使用python+flask+jinja)
搭建api.ai webhook(在朋友google cloud搭建的vps上使用python+flask+jinja)
@willwhui
Copy link
Author

willwhui commented Jul 1, 2017

运行网站(使用openssl默认的证书)

进入网站代码目录,运行python3 app.py
chrome提示网站有风险,正常的,因为不是自己专属的证书。
忽略提示,可以显示网站。

但是可能因为证书是假的,所以api.ai返回错误:
"status": {
"code": 206,
"errorType": "partial_content",
"errorDetails": "Webhook call failed. Error: Webhook response was empty."
},
因为服务器返回json的函数都没有被调用到

@willwhui
Copy link
Author

willwhui commented Jul 1, 2017

设置域名解析(将域名和ip绑定)

概念:
主域名:mydomain.com
可解析的主机名:主机名.主域名
主机名为空时:可解析的主机名 = mydomain.com。但是设置解析记录时,不能不填主机名,于是大家用“@”来代替“空”
主机名不为空时(假设是host1):可解析的主机名 = host1.mydomain.com

ip:运行代码的计算机的地址(域名解析的结果)
TTL:刷新时间

设置域名解析是指,按照国际组织的约定,设置A记录。
在godaddy.com的域名设置中,可以更改A记录:

Type	Name	Value			TTL
A	@	xxx.xxx.xxx.xxx		600 seconds
A	host1	yyy.yyy.yyy.yyy		600 seconds

这样设置之后:
用户ping mydomain.com 就会得到ip地址 xxx.xxx.xxx.xxx
用户ping host1.mydomain.com 就会得到ip地址 yyy.yyy.yyy.yyy
当然,xxx.xxx.xxx.xxx 可以和yyy.yyy.yyy.yyy完全相同

由此可见,如果在xxx.xxx.xxx.xxx上部署了web服务,希望用户输入mydomain.com,www.mydomain.com都可以访问到这个服务
可以这样做:

Type	Name	Value			TTL
A	@	xxx.xxx.xxx.xxx		600 seconds
A	www	xxx.xxx.xxx.xxx		600 seconds

也可以利用CNAME来完成:

Type	Name	Value			TTL
A	@	xxx.xxx.xxx.xxx		600 seconds
A	www	@			1 hr

可以理解为“希望主机www.mydowmain.com等同于主机mydomail.com”

CNAME的好处在于:
如果你在xxx.xxx.xxx.xxx这个ip对应的计算机上设定了多个服务,当你改变你的ip地址的时候,只需要改变第一条记录对应的ip地址就可以了。

@willwhui
Copy link
Author

willwhui commented Jul 2, 2017

安装nginx

为什么要使用nginx等服务,原因在这里
通过这里的说明,简单配置完成nginx+uwsgi+flask
基本原理:
nginx负责监听80端口的http请求,并将所有请求以socket数据包形式转发到127.0.0.1:xxxx端口
127.0.0.1端口是由uwsgi负责监听的,flask作为它的一个组件,受其调用

注意:
通过软连接方式"ln -s"的方式,将代码目录中的nginx的config文件连接到 /etc/nginx/sites-enabled/ 或者/etc/nginx/conf.d/ 中,需要参照这里的说明,理解"ln -s"的含义。正确的写法是:
sudo ln -s linked-file link-to-path

如果cat link-to-path/linked-file能够成功输出文件内容,说明对了。
注释掉location / { ... }中的
try_files $uri $uri/ =404;
表示任何以"domain-name/"开头的地址请求都是允许的

uwsgi的问题

按照上面的方法安装uwsgi并运行之后,忘了出于某种原因,uwsgi进入后台运行之后,通过如下命令终止uwsgi
killall -s INT /usr/local/bin/uwsgi
当然,前提是ps aux | grep uwsgi 中显示的uwsgi路径是 /usr/local/bin/uwsgi

可以写一个restart-uwsgi.sh,每次需要重启的时候执行一下:

ps aux | grep uwsgi
killall -s INT /usr/local/bin/uwsgi
sleep 2
ps aux | grep uwsgi
setsid uwsgi ryansreader-uwsgi.ini &
sleep 1
ps aux | grep uwsgi

杀死和启动uwsgi都需要一点时间,利用sleep来让输出的结果好看点。
利用setsid设置它的父进程为别人,避免当前控制台退出后进程终止。
利用&设置为后台执行,避免阻塞当前控制台。不过执行之后,控制台还能看到用户请求是uwsgi输出的结果,为什么呢?以后再研究。不过也好,正好需要看结果。

这里对uwsgi的用法做了一些说明。
uwsgi官网文档并没有明确提到这些信息,也许是他们认为这是显而易见的?

@willwhui
Copy link
Author

willwhui commented Jul 3, 2017

从Let's Encrypt获取并安装自己的ssl证书

按照Let‘s Encrypt官方的教程,通过certbot来创建。
操作倒也方便。

不知为何没有自动配置成功
按照这里的说明手动搞一下:

Certificate Files
After obtaining the cert, you will have the following PEM-encoded files:
cert.pem: Your domain's certificate
chain.pem: The Let's Encrypt chain certificate
fullchain.pem: cert.pem and chain.pem combined
privkey.pem: Your certificate's private key

Certbot creates symbolic links to the most recent certificate files in the 
/etc/letsencrypt/live/your_domain_name directory. 
Because the links will always point to the most recent certificate files, 
this is the path that you should use to refer to your certificate files.

因此手动修改nginx的https配置

ssl_certificate /etc/letsencrypt/live/mypals.today/fullchain.pem #注意不能用cert.pem,它不包含完整信息(公钥?),会导致SSL被python, api.ai判定为不可信,从而导致webhook不可用
ssl_certificate_key /etc/letsencrypt/live/mypals.today/privkey.pem

其他方法(没试过):
pythonprogramming.net提到的教程

@willwhui
Copy link
Author

willwhui commented Jul 3, 2017

webhook错误206排除

绑定域名,设定了ssl之后,api.ai依然返回错误:
"status": {
"code": 206,
"errorType": "partial_content",
"errorDetails": "Webhook call failed. Error: Webhook response was empty."
},
服务器返回json的函数依然没有被调用到。

尝试在python3命令行中进行如下操作:

>>> import requests
>>> r = requests.get('https://mydomainname/webhook')

失败:requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:600)

>>> import requests
>>> r = requests.get('http://mydomainname/webhook')
>>> print(r)
<Response [200]>

成功。

说明ssl有问题。

经过一番查找,发现这里(stackoverflow)的答案:
在nginx的配置中,必须使用“fullchan.pem”
ssl_certificate /etc/letsencrypt/live/mydomainname/fullchain.pem;

重启nginx之后,在python命令行中运行requests.get成功。

@willwhui
Copy link
Author

willwhui commented Jul 4, 2017

根据api.ai Action返回不同的结果

这里有简单示例
根据action来判断应该执行何种动作

@willwhui
Copy link
Author

willwhui commented Jul 4, 2017

让Google Home播放mp3文件

参见stackoverflow

var msg = `
  <speak>
    Tone one
    <audio src="https://examaple.com/wav/Dtmf-1.wav"></audio>
    Tone two
    <audio src="https://example.com/wav16/Dtmf-2.wav"></audio>
    Foghorn
    <audio src="https://example.com/mp3/foghorn.mp3"></audio>
    Done
  </speak>
  `;

  var reply = {
    speech: msg,
    data:{
      google:{
        "expect_user_response": true,
        "is_ssml": true
      }
    }
  };

  res.send( reply );

以上js代码的关键点在于:

  • api.ai的webhook返回字段的“speech"部分用msg来填充
  • 增加data字段
  • msg要符合google home要求的"SSML"规范
    参见google的文档:Format of response from the webhook
    根据SSML规范,speak字段中可以返回文本和mp3文件链接的混合物。

@willwhui
Copy link
Author

willwhui commented Jul 5, 2017

在flask中使用静态文件返回指向mp3的json

参见这里
只需要在template同级目录建立static目录就可以直接输入完整网址访问此目录下的所有文件

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment