您的位置:首页 > 编程语言 > PHP开发

ThinkPhp5下使用restful风格路由导致跨域put请求失效解决办法

2017-07-29 10:06 639 查看
今天写代码的时候,碰到ajax发起put请求失败的问题,碰到这个情况的时候时候我也一脸懵逼,不过还好,费了不少功夫终于解决。

问题

通过官方资源控制器生成的restful控制器,在路由后浏览器进行ajax跨域put请求,导致请求失败。get和post请求正常,postman测试put请求正常。

分析

这个问题相信很多人也碰到,在官方的github上面有人提到过这个问题,但没有具体的解决方法。先看下代码把。tp版本是最新版本5.0.10

使用的官方资源控制器生成的restful控制器

php think make:controller app\index\controller\user


在Route.php下路由

use think\Route;
Route::resource('user', 'index/User');


生成后的user类:

<?php

namespace app\index\controller;

use think\Controller;
use think\Request;

class User extends Controller
{
public function index()
{
// 默认get
echo 'index';
}

public function create()
{
//
echo 'create';
}

public function save(Request $request)
{
//
echo 'save()';
}

public function read($id)
{
//
echo 'read';
}

public function edit($id)
{
//
echo 'edit';
}

public function update(Request $request, $id)
{
//
echo 'put - update()';
}

public function delete($id)
{
//
echo 'delete';
}
}


上面输出是为了api请求时有返回值,方便调试

ajax:

$.ajax({
url: 'http://localhost:9096/think/public/index.php/user/2',
type: 'put',
dataType: 'text',
success: function (data) {
console.log(data);
}
});


第一次请求:



报了一个404错误。控制台输出:XMLHttpRequest cannot load…..。

ajax跨域导致的错误,解决方式:

1、使用jsonp方式

2、服务器准许跨域

使用jsonp的方式调用的话路由会当做一个get请求 然后调用read()方法。可以去修改路由

这里使用第二种:服务器准许跨域:

在index.php头部添加响应信息:

header("Access-Control-Allow-Origin: * ");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE");


这个时候post请求已经是正常了,但是put请求还是报错,并提示无法跨域



这个时候注意到了一点,ajax是发起的put请求。为什么浏览器提交的是OPTIONS请求?这点很奇怪,第一次碰到。百度options请求后大概是这么个意思

浏览器在发起复杂请求(如跨域)之前会先发起一个options的请求来嗅探服务器是否支持改请求或方法等

有复杂请求就有简单请求:

如get、post,就属于简单请求。

那么,我们就有一个大胆的猜想:浏览器在发起options请求嗅探的时候,被tp检测到,并没有检测到这个路由。而user也不是具体的方法,从而导致返回404。浏览器在接收到是404后就不会在发起put请求。

结果测试,这个猜想是正确的。查看框架的Route.php源码。是把options请求当做所有请求中的一种,并没有做单独处理。(这话怎么都感觉有点不对,不要纠结这里)



解决办法

1、修改框架源码或路由规则

框架的Route.php
---
// REST路由操作方法定义
private static $rest = [
'index'  => ['get', '', 'index'],
'create' => ['get', '/create', 'create'],
'edit'   => ['get', '/:id/edit', 'edit'],
'read'   => ['get', '/:id', 'read'],
'save'   => ['post', '', 'save'],
// 修改为options方法
'update' => ['options', '/:id/put', 'update'],
'delete' => ['options', '/:id/delete', 'delete'],
];


这个方法也行的通,但是要修改框架源码了,不甘心,难道就没有不修改框架源码,又简单的方法了吗?经过摸索后还真有。看下面这种方法。

2、单独处理options请求

浏览器发起options请求嗅探后被框架返回404,那我们就单独处理这个请求,使他能正常返回;

在index.php头部添加下面代码:

// 准许跨域请求。
header("Access-Control-Allow-Origin: * "); header("Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE");
/**
* 浏览器第一次在处理复杂请求的时候会先发起OPTIONS请求。路由在处理请求的时候会导致PUT请求失败。
* 在检测到option请求的时候就停止继续执行
*/
if($_SERVER['REQUEST_METHOD'] == 'OPTIONS'){
exit;
}


额?这么简单? 就这么简单,我们主要让上面的请求头能够正常返回就行。浏览器只有正常返回的情况下才会发出put请求。



有两次请求,第一次请求时嗅探是否准许,第二次才是我们的put请求。

但是这个会造成2次请求,能不能在优化下,变成一次请求。

3、使用请求伪装

通过查阅手册发现可以设置请求伪装的方式达到同样的效果。文档地址

在config.php里设置

'var_method               => '_method',


然后ajax代码:

$.ajax({
url: 'http://localhost:9096/think/public/index.php/user/2',
type: 'post',
data:{
_method:'PUT'
},
dataType: 'text',
success: function (data) {
console.log(data);
}
});




嗯,达到了预期的效果。和第二种方法相比,各有特点。

自学注定不是一帆风顺的,多看文档,多思考,多搜索!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐