Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save willwhui/fd511cee75153bc73d51f451f3d9e0a2 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
Author

VPS OS版本(cat /etc/issue):Ubuntu 14.04.5 LTS

@willwhui
Copy link
Copy Markdown
Author

初始资源:

top
top - 11:58:39 up  1:44,  2 users,  load average: 0.00, 0.00, 0.00
Tasks:  85 total,   1 running,  84 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:    602640 total,   322972 used,   279668 free,    11444 buffers
KiB Swap:        0 total,        0 used,        0 free.   229200 cached Mem
 df -h
Filesystem      Size  Used Avail Use% Mounted on
udev            283M  4.0K  283M   1% /dev
tmpfs            59M  284K   59M   1% /run
/dev/sda1       9.9G  1.2G  8.2G  13% /
none            4.0K     0  4.0K   0% /sys/fs/cgroup
none            5.0M     0  5.0M   0% /run/lock
none            295M     0  295M   0% /run/shm
none            100M     0  100M   0% /run/user

@willwhui
Copy link
Copy Markdown
Author

willwhui commented Jul 1, 2017

运行之前的https程序
需要安装OpenSSL:
sudo apt-get update
sudo apt-get install python3-openssl

需要安装pip3:
sudo apt-get install python3-pip
需要安装flask:
sudo pip3 install flask

需要安装git:
sudo apt-get install git
并从github上获取之前写好的代码

@willwhui
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Author

willwhui commented Jul 4, 2017

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

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

@willwhui
Copy link
Copy Markdown
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
Copy Markdown
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