RestController.class.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. namespace Think\Controller;
  12. use Think\App;
  13. use Think\Controller;
  14. /**
  15. * ThinkPHP REST控制器类
  16. */
  17. class RestController extends Controller
  18. {
  19. // 当前请求类型
  20. protected $_method = '';
  21. // 当前请求的资源类型
  22. protected $_type = '';
  23. // REST允许的请求类型列表
  24. protected $allowMethod = array('get', 'post', 'put', 'delete');
  25. // REST默认请求类型
  26. protected $defaultMethod = 'get';
  27. // REST允许请求的资源类型列表
  28. protected $allowType = array('html', 'xml', 'json', 'rss');
  29. // 默认的资源类型
  30. protected $defaultType = 'html';
  31. // REST允许输出的资源类型列表
  32. protected $allowOutputType = array(
  33. 'xml' => 'application/xml',
  34. 'json' => 'application/json',
  35. 'html' => 'text/html',
  36. );
  37. /**
  38. * 架构函数
  39. * @access public
  40. */
  41. public function __construct()
  42. {
  43. // 资源类型检测
  44. if ('' == __EXT__) {
  45. // 自动检测资源类型
  46. $this->_type = $this->getAcceptType();
  47. } elseif (!in_array(__EXT__, $this->allowType)) {
  48. // 资源类型非法 则用默认资源类型访问
  49. $this->_type = $this->defaultType;
  50. } else {
  51. $this->_type = __EXT__;
  52. }
  53. // 请求方式检测
  54. $method = strtolower(REQUEST_METHOD);
  55. if (!in_array($method, $this->allowMethod)) {
  56. // 请求方式非法 则用默认请求方法
  57. $method = $this->defaultMethod;
  58. }
  59. $this->_method = $method;
  60. parent::__construct();
  61. }
  62. /**
  63. * 魔术方法 有不存在的操作的时候执行
  64. * @access public
  65. * @param string $method 方法名
  66. * @param array $args 参数
  67. * @return mixed
  68. */
  69. public function __call($method, $args)
  70. {
  71. if (0 === strcasecmp($method, ACTION_NAME . C('ACTION_SUFFIX'))) {
  72. if (method_exists($this, $method . '_' . $this->_method . '_' . $this->_type)) {
  73. // RESTFul方法支持
  74. $fun = $method . '_' . $this->_method . '_' . $this->_type;
  75. App::invokeAction($this, $fun);
  76. } elseif ($this->_method == $this->defaultMethod && method_exists($this, $method . '_' . $this->_type)) {
  77. $fun = $method . '_' . $this->_type;
  78. App::invokeAction($this, $fun);
  79. } elseif ($this->_type == $this->defaultType && method_exists($this, $method . '_' . $this->_method)) {
  80. $fun = $method . '_' . $this->_method;
  81. App::invokeAction($this, $fun);
  82. } elseif (method_exists($this, '_empty')) {
  83. // 如果定义了_empty操作 则调用
  84. $this->_empty($method, $args);
  85. } elseif (file_exists_case($this->view->parseTemplate())) {
  86. // 检查是否存在默认模版 如果有直接输出模版
  87. $this->display();
  88. } else {
  89. E(L('_ERROR_ACTION_') . ':' . ACTION_NAME);
  90. }
  91. }
  92. }
  93. /**
  94. * 获取当前请求的Accept头信息
  95. * @return string
  96. */
  97. protected function getAcceptType()
  98. {
  99. $type = array(
  100. 'html' => 'text/html,application/xhtml+xml,*/*',
  101. 'xml' => 'application/xml,text/xml,application/x-xml',
  102. 'json' => 'application/json,text/x-json,application/jsonrequest,text/json',
  103. 'js' => 'text/javascript,application/javascript,application/x-javascript',
  104. 'css' => 'text/css',
  105. 'rss' => 'application/rss+xml',
  106. 'yaml' => 'application/x-yaml,text/yaml',
  107. 'atom' => 'application/atom+xml',
  108. 'pdf' => 'application/pdf',
  109. 'text' => 'text/plain',
  110. 'png' => 'image/png',
  111. 'jpg' => 'image/jpg,image/jpeg,image/pjpeg',
  112. 'gif' => 'image/gif',
  113. 'csv' => 'text/csv',
  114. );
  115. foreach ($type as $key => $val) {
  116. $array = explode(',', $val);
  117. foreach ($array as $k => $v) {
  118. if (array_key_exists('HTTP_ACCEPT', $_SERVER)) {
  119. if (stristr($_SERVER['HTTP_ACCEPT'], $v)) {
  120. return $key;
  121. }
  122. }
  123. }
  124. }
  125. return false;
  126. }
  127. // 发送Http状态信息
  128. protected function sendHttpStatus($code)
  129. {
  130. static $_status = array(
  131. // Informational 1xx
  132. 100 => 'Continue',
  133. 101 => 'Switching Protocols',
  134. // Success 2xx
  135. 200 => 'OK',
  136. 201 => 'Created',
  137. 202 => 'Accepted',
  138. 203 => 'Non-Authoritative Information',
  139. 204 => 'No Content',
  140. 205 => 'Reset Content',
  141. 206 => 'Partial Content',
  142. // Redirection 3xx
  143. 300 => 'Multiple Choices',
  144. 301 => 'Moved Permanently',
  145. 302 => 'Moved Temporarily ', // 1.1
  146. 303 => 'See Other',
  147. 304 => 'Not Modified',
  148. 305 => 'Use Proxy',
  149. // 306 is deprecated but reserved
  150. 307 => 'Temporary Redirect',
  151. // Client Error 4xx
  152. 400 => 'Bad Request',
  153. 401 => 'Unauthorized',
  154. 402 => 'Payment Required',
  155. 403 => 'Forbidden',
  156. 404 => 'Not Found',
  157. 405 => 'Method Not Allowed',
  158. 406 => 'Not Acceptable',
  159. 407 => 'Proxy Authentication Required',
  160. 408 => 'Request Timeout',
  161. 409 => 'Conflict',
  162. 410 => 'Gone',
  163. 411 => 'Length Required',
  164. 412 => 'Precondition Failed',
  165. 413 => 'Request Entity Too Large',
  166. 414 => 'Request-URI Too Long',
  167. 415 => 'Unsupported Media Type',
  168. 416 => 'Requested Range Not Satisfiable',
  169. 417 => 'Expectation Failed',
  170. // Server Error 5xx
  171. 500 => 'Internal Server Error',
  172. 501 => 'Not Implemented',
  173. 502 => 'Bad Gateway',
  174. 503 => 'Service Unavailable',
  175. 504 => 'Gateway Timeout',
  176. 505 => 'HTTP Version Not Supported',
  177. 509 => 'Bandwidth Limit Exceeded',
  178. );
  179. if (isset($_status[$code])) {
  180. header('HTTP/1.1 ' . $code . ' ' . $_status[$code]);
  181. // 确保FastCGI模式下正常
  182. header('Status:' . $code . ' ' . $_status[$code]);
  183. }
  184. }
  185. /**
  186. * 编码数据
  187. * @access protected
  188. * @param mixed $data 要返回的数据
  189. * @param String $type 返回类型 JSON XML
  190. * @return string
  191. */
  192. protected function encodeData($data, $type = '')
  193. {
  194. if (empty($data)) {
  195. return '';
  196. }
  197. if ('json' == $type) {
  198. // 返回JSON数据格式到客户端 包含状态信息
  199. if (version_compare(PHP_VERSION, '5.4.0', '<')) {
  200. $this->arrayRecursive($data, 'urlencode', true);
  201. $data = urldecode(json_encode($data));
  202. } else {
  203. $data = json_encode($data, JSON_UNESCAPED_UNICODE);
  204. }
  205. } elseif ('xml' == $type) {
  206. // 返回xml格式数据
  207. $data = xml_encode($data);
  208. } elseif ('php' == $type) {
  209. $data = serialize($data);
  210. } // 默认直接输出
  211. $this->setContentType($type);
  212. //header('Content-Length: ' . strlen($data));
  213. return $data;
  214. }
  215. /**************************************************************
  216. *
  217. * 使用特定function对数组中所有元素做处理
  218. * @param string|array &$array 要处理的字符串或者数组
  219. * @param string $function 要执行的函数
  220. * @return boolean $apply_to_keys_also 是否也应用到key上
  221. * @access protected
  222. *
  223. *************************************************************/
  224. protected function arrayRecursive(&$array, $function, $apply_to_keys_also = false)
  225. {
  226. static $recursive_counter = 0;
  227. if (++$recursive_counter > 1000) {
  228. die('possible deep recursion attack');
  229. }
  230. foreach ($array as $key => $value) {
  231. if (is_array($value)) {
  232. $this->arrayRecursive($array[$key], $function, $apply_to_keys_also);
  233. } elseif (is_string($value)) {
  234. $array[$key] = $function($value);
  235. }
  236. if ($apply_to_keys_also && is_string($key)) {
  237. $new_key = $function($key);
  238. if ($new_key != $key) {
  239. $array[$new_key] = $array[$key];
  240. unset($array[$key]);
  241. }
  242. }
  243. }
  244. $recursive_counter--;
  245. }
  246. /**
  247. * 设置页面输出的CONTENT_TYPE和编码
  248. * @access public
  249. * @param string $type content_type 类型对应的扩展名
  250. * @param string $charset 页面输出编码
  251. * @return void
  252. */
  253. public function setContentType($type, $charset = '')
  254. {
  255. if (headers_sent()) {
  256. return;
  257. }
  258. if (empty($charset)) {
  259. $charset = C('DEFAULT_CHARSET');
  260. }
  261. $type = strtolower($type);
  262. if (isset($this->allowOutputType[$type])) //过滤content_type
  263. {
  264. header('Content-Type: ' . $this->allowOutputType[$type] . '; charset=' . $charset);
  265. }
  266. }
  267. /**
  268. * 输出返回数据
  269. * @access protected
  270. * @param mixed $data 要返回的数据
  271. * @param String $type 返回类型 JSON XML
  272. * @param integer $code HTTP状态
  273. * @return void
  274. */
  275. protected function response($data, $type = '', $code = 200)
  276. {
  277. $this->sendHttpStatus($code);
  278. exit($this->encodeData($data, strtolower($type)));
  279. }
  280. }