如何在SAE使用外部数据库

可能有些人认为云计算平台让人感觉扑朔迷离,把重要的数据存在平台中感觉不放心,但是SAE的平台又不支持使用外部的数据,这是个大问题。本文章可以解决这个问题。

PS顺便小小的宣扬下,SAE的mysql很稳定,数据的安全性有99.999999999999%的保障性。言归正传,怎么使用外部的数据库呢,大多数人都会想到REST,本文实现方式也算是一个模拟rest,如果你想利用sae五线千兆接入的带宽执行脚本,当重要的数据库不放在SAE上就可以。

实现

首先抛砖引入,我们众所周知,微博开放平台就是一个很好的rest的例子,但是请求的是https://api.weibo.com/2/statuses/public_timeline.json 这样的json文件,具体的实现原理我猜想是一个urlrewitre,因为不可能直接请求json文件,静态的文件肯定不能处理post的变量。那么不适用url rewrite只用php也可以做到这个效果,测试的文件代码如下:

<?php
$array = array('id'=>'1','username'=>'lazypeople');
header('Content-type: text/json');
$json = json_encode($array);
echo($json);
?>

这个文件放到浏览器执行就可以直接输出一个没有JSON后缀的json文件,原因是指定了“header(‘Content-type: text/json’);”header头,但是相同的代码在SAE的环境上却不能直接输出,原因不知道,我提出过好像也没有得到过回答。

然后在另一个脚本使用CURL抓取JSON,得到需要的数组。代码如下:

<?php
$url = "http://dsp.hfut.edu.cn/json.php";
$post_data = array (
    //可以附带POST 参数
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
//指定post数据
curl_setopt($ch, CURLOPT_POST, 1);
//添加变量
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$output = curl_exec($ch);
$output = preg_replace('/.+?({.+}).+/','$1',$output);
curl_close($ch);
$json = $output;
$json = json_decode($json,true);
var_dump($json);
?>

这样就能看到输出结果array(2) { ["id"]=> string(1) “1″ ["username"]=> string(10) “lazypeople” }大功告成一半。

以下开始介绍怎么在SAE上的使用,原理如上,实现的最终目的就是数组等在SAE的机器和你的数据库机器上直接传递,但是存在一些细节问题,例如授权问题,我采用的方式也是 username+password md5加密的方式作为token,如果token正确则返回sql脚本执行的结果,如果不对就直接拒绝请求了。

做MYSQL的rest接口就需要实现增,删,改,查。本文实现的方式都是直接POST sql语句,然后设定参数指定到底是增还是删还是改等,为了方便可以给予编号,例如指定参数param function = 1,那么在POST的脚本上就去执行get_data()[基于lazyphp],如果function ==2就去执行get_line等等等等,你可以自己定义。

你数据库机器上接收POST的脚本可以这样[例子是基于lazyphp的]

<?php
if( !defined('IN') ) die('bad request');
include_once( AROOT . 'controller'.DS.'app.class.php' );

class restController extends appController
{
        function __construct()
        {
                parent::__construct();
        }
       
        function check($hash)//验证用户是否合法
        {
            $username = '你定义的';
                $password = '你定义的';
                $md5query = $username.$password;
                $md5hash = md5($md5query);
                $posttoken = $hash;
                if ($posttoken != $md5hash) {
                    echo 'bad request!';
                        exit(0);
                }
                 
        }
       
        function query()//执行请求的语句
        {
                $hash = t(v('hash'));
                self::check($hash);
                //验证通过
                $query = t(v('sql'));
                $function = intval(v('function'));
                if ($function == 1) {
                        $data = get_data($query);
                } elseif ($function == 2) {
                        $data = get_line($query);
                }
                header('Content-type: text/json');//指定输出json
                $data = json_encode($data);
                echo($data);             
        }
}

check函数检测是否合法,query根据post的参数去执行相应的操作。

在SAE上请求的脚本可以这样:

<?php
if( !defined('IN') ) die('bad request');
include_once( AROOT . 'controller'.DS.'app.class.php' );

class getcontentController extends appController
{
        function __construct()
        {
                parent::__construct();
        }
       
        function rest($sql,$function = 1)
        {
                $url = "http://dsp.hfut.edu.cn?c=rest&a=query";
                $hash = '你定义的';//跟上面脚本的$username.$password要一致
                $hash = md5($hash);    
                $post_data = array (
                        "hash" => $hash,
                        "function"=>$function,
                        "sql" => $sql
                );
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_POST, 1);
                curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
                $output = curl_exec($ch);
                curl_close($ch);
                $output = preg_replace('/.+?({.+}).+/','$1',$output); //很重要              
                $output = json_decode($output,true);           
                return($output);
        }
       
        function content()
        {
                $aid = intval(t(v('aid')));
                if ($aid == null) {
                        header('location:index.php');
                        exit(0);
                }
                $sql = "select title from dsp_article where id='$aid'";
                $title = self::rest($sql,2);
                $data['title'] = $data['top_title'] = $title['title'];
                $sql2 = "select body from dsp_article_data where aid='$aid'";
                $content = self::rest($sql2,2);
                $data['content'] = $content['body'];
                $data['flag'] = 1;             
                render( $data,'web','content' );
        }
}

注意 $title = self::rest($sql,2);即是调用get_data()方法执行$sql,然后对执行的结果json编码返回给sae上的脚本再处理成一维或者二维的数组展示。

看完上面的例子应该感觉很简单了,例如你的项目中需要使用get_data()获取二维数组,那么你就可以使用self::rest($sql,1)完全代替。

存在的问题

还是有的,首先就是$sql语句直接POST提交,在被抓包的情况下可能会泄露表名,有一定的安全隐患,其次就是效率问题了,但是我估摸着应该和你的数据库所在的主机到SAE curl主机的速度直接相关,当然,这样执行走过了很多的中间层,但是也是迫不得已。号外,如果你想实现反向,那就不需要自己写了,http://ftqq.com/lazyrest/这里有个很好的工具。

本文实际运用的例子

  1. 问个菜鸟问题,使用sae mysql的常量怎么连接不上呢? from sae.const import ( MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASS, MYSQL_DB )else: # Make `python manage.py syncdb` works happy! MYSQL_HOST = ‘SAE_MYSQL_HOST_M’ MYSQL_PORT = ‘SAE_MYSQL_PORT’ MYSQL_USER = ‘SAE_MYSQL_USER’ MYSQL_PASS = ‘SAE_MYSQL_PASS’ MYSQL_DB = ‘SAE_MYSQL_DB’DATABASES = { ‘default’: { ‘ENGINE’: ‘django.db.backends.mysql’, ‘NAME’: SAE_MYSQL_DB, ‘USER’: SAE_MYSQL_USER, ‘PASSWORD’: SAE_MYSQL_PASS, ‘HOST’: SAE_MYSQL_HOST_M, ‘PORT’: SAE_MYSQL_PORT, }}