Overview

Namespaces

  • LeanCloud
    • Engine
    • Operation
    • Storage
    • Uploader

Classes

  • LeanCloud\ACL
  • LeanCloud\AppRouter
  • LeanCloud\Bytes
  • LeanCloud\Client
  • LeanCloud\Engine\Cloud
  • LeanCloud\Engine\LaravelEngine
  • LeanCloud\Engine\LeanEngine
  • LeanCloud\Engine\SlimEngine
  • LeanCloud\File
  • LeanCloud\GeoPoint
  • LeanCloud\LeanObject
  • LeanCloud\MIMEType
  • LeanCloud\Operation\ArrayOperation
  • LeanCloud\Operation\DeleteOperation
  • LeanCloud\Operation\IncrementOperation
  • LeanCloud\Operation\RelationOperation
  • LeanCloud\Operation\SetOperation
  • LeanCloud\Push
  • LeanCloud\Query
  • LeanCloud\Region
  • LeanCloud\Relation
  • LeanCloud\Role
  • LeanCloud\RouteCache
  • LeanCloud\SaveOption
  • LeanCloud\SMS
  • LeanCloud\Storage\CookieStorage
  • LeanCloud\Storage\SessionStorage
  • LeanCloud\Uploader\QCloudUploader
  • LeanCloud\Uploader\QiniuUploader
  • LeanCloud\Uploader\S3Uploader
  • LeanCloud\Uploader\SimpleUploader
  • LeanCloud\User

Interfaces

  • LeanCloud\Operation\IOperation
  • LeanCloud\Storage\IStorage

Exceptions

  • LeanCloud\BatchRequestError
  • LeanCloud\CloudException
  • LeanCloud\Engine\FunctionError
  • Overview
  • Namespace
  • Class
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 
<?php

namespace LeanCloud;

use LeanCloud\Region;

class AppRouter {
    const TTL_KEY               = "ttl";
    const API_SERVER_KEY        = "api_server";
    const PUSH_SERVER_KEY       = "push_server";
    const STATS_SERVER_KEY      = "stats_server";
    const ENGINE_SERVER_KEY     = "engine_server";
    const RTM_ROUTER_SERVER_KEY = "rtm_router_server";
    private static $INSTANCES;
    private $appId;
    private $region;
    private $routeCache;

    private static $DEFAULT_REGION_ROUTE = array(
        Region::US    => "us-api.leancloud.cn",
        Region::CN_E1 => "e1-api.leancloud.cn",
        Region::CN_N1 => "api.leancloud.cn"
    );

    private static $DEFAULT_REGION_RTM_ROUTE = array(
        Region::US    => "router-a0-push.leancloud.cn",
        Region::CN_E1 => "router-q0-push.leancloud.cn",
        Region::CN_N1 => "router-g0-push.leancloud.cn"
    );

    private function __construct($appId) {
        $this->appId = $appId;
        $region = getenv("LEANCLOUD_REGION");
        if (!$region) {
            $region = Region::CN;
        }
        $this->setRegion($region);
        $this->routeCache = RouteCache::create($appId);
    }

    /**
     * Get instance of AppRouter.
     */
    public static function getInstance($appId) {
        if (isset(self::$INSTANCES[$appId])) {
            return self::$INSTANCES[$appId];
        } else {
            $router = new AppRouter($appId);
            self::$INSTANCES[$appId] = $router;
            return $router;
        }
    }

    /**
     * Get app region default route host
     */
    public function getRegionDefaultRoute($server_key) {
        $this->validate_server_key($server_key);
        return $this->getDefaultRoutes()[$server_key];
    }

    /**
     * Set region
     *
     * See `LeanCloud\Region` for available regions.
     *
     * @param mixed $region
     */
    public function setRegion($region) {
        if (is_numeric($region)) {
            $this->region = $region;
        } else {
            $this->region = Region::fromName($region);
        }
    }

    /**
     * Get and return route host by server type, or null if not found.
     */
    public function getRoute($server_key) {
        $this->validate_server_key($server_key);
        $routes = $this->routeCache->read();
        if (isset($routes[$server_key])) {
            return $routes[$server_key];
        }
        $routes = $this->getRoutes();
        if (!$routes) {
            $routes = $this->getDefaultRoutes();
        }
        $this->routeCache->write($routes);
        return isset($routes[$server_key]) ? $routes[$server_key] : null;
    }

    private function getRouterUrl() {
        $url = getenv("LEANCLOUD_APP_ROUTER");
        if (!$url) {
            $url = "https://app-router.leancloud.cn/2/route?appId=";
        }
        return "{$url}{$this->appId}";
    }

    private function validate_server_key($server_key) {
        $routes = $this->getDefaultRoutes();
        if (!isset($routes[$server_key])) {
            throw new IllegalArgumentException("Invalid server key.");
        }
    }

    /**
     * Detect region by app-id
     */
    private function detectRegion() {
        if (!$this->appId) {
            return Region::CN_N1;
        }
        $parts = explode("-", $this->appId);
        if (count($parts) <= 1) {
            return Region::CN_N1;
        } else if ($parts[1] === "MdYXbMMI") {
            return Region::US;
        } else if ($parts[1] === "9Nh9j0Va") {
            return Region::CN_E1;
        } else {
            $this->region = Region::CN_N1;
        }
    }

    /**
     * Get routes remotely from app router, return array.
     */
    private function getRoutes() {
        $routes = @json_decode(file_get_contents($this->getRouterUrl()), true);
        if (isset($routes[self::TTL_KEY])) {
            return $routes;
        }
        return null;
    }

    /**
     * Fallback default routes, if app router not available.
     */
    private function getDefaultRoutes() {
        $host = self::$DEFAULT_REGION_ROUTE[$this->region];

        return array(
            self::API_SERVER_KEY        => $host,
            self::PUSH_SERVER_KEY       => $host,
            self::STATS_SERVER_KEY      => $host,
            self::ENGINE_SERVER_KEY     => $host,
            self::RTM_ROUTER_SERVER_KEY => self::$DEFAULT_REGION_RTM_ROUTE[$this->region],
            self::TTL_KEY               => 3600
        );
    }


}


/**
 * Route cache
 *
 * Ideally we should use ACPu for caching, but it can be inconvenient to
 * install, esp. on Windows[1], thus we implement a naive file based
 * cache.
 *
 * [1]: https://stackoverflow.com/a/28124144/108112
 */
class RouteCache {
    private $filename;
    private $_cache;

    private function __construct($id) {
        $this->filename = sys_get_temp_dir() . "/route_{$id}.json";
    }

    public static function create($id) {
        return new RouteCache($id);
    }

    /**
     * Serialize array and store in file, array must be json_encode safe.
     */
    public function write($array) {
        $body = json_encode($array);
        if (file_put_contents($this->filename, $body, LOCK_EX) === false) {
            error_log("WARNING: failed to write route cache ({$this->filename}), performance may be degraded.");
        } else {
            $this->_cache = $array;
        }
    }

    /**
     * Read routes either from cache or file, return json_decoded array.
     */
    public function read() {
        if ($this->_cache) {
            return $this->_cache;
        }
        $data = $this->readFile();
        if (!empty($data)) {
            $this->_cache = $data;
            return $data;
        }
        return null;
    }

    private function readFile() {
        if (file_exists($this->filename)) {
            $fp = fopen($this->filename, "rb");
            $body = null;
            if (flock($fp, LOCK_SH)) {
                $body = fread($fp, filesize($this->filename));
                flock($fp, LOCK_UN);
            }
            fclose($fp);
            if (!empty($body)) {
                $data = @json_decode($body, true);
                if (!empty($data)) {
                    return $data;
                }
            }
        }
        return null;
    }
}
API documentation generated by ApiGen