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

今天写代码的时候,碰到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);
        }
    });

这里写图片描述

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


自学注定不是一帆风顺的,多看文档,多思考,多搜索!

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值