Some pits encountered when front-end AJAX requests cross-domain

Keywords: PHP JSON angular Session

From: https://icewing.cc/post/about-cross-origin.html


In the past two days, we have to study the cross-domain problem because we need to use angular's $http service to access data and the interface is under another domain name. Here's a summary of some of the problems I have encountered in the past two days.

Access-Control-Allow-Origin Problem

The first problem encountered across domains is the Access-Control-Allow-Origin error, Chrome error Response To preflight request does't pass access control check: No'Access-Control-Allow-Origin'header is present on the requested resource. That is, the domain name currently being requested is not on the white list of the server. What should we do?

Of course, the easiest way is to add an Access-Control-Allow-Origin response header to the content returned by the visited server. The value is * or the domain name of the current website. It is convenient to use *. But it is easy to be misused by other websites, which is somewhat unsafe; if you set up the domain name of the current website, you can only set up one. My solution is to set up a white list of permitted domain names to determine whether the refer address of the current request is in the white list, and if so, set this address to Access-Control-Allow-Origin. Otherwise, this response header will not be set.

Following is the collated code (the actual white list is written in the configuration file):

/**
 * API extend
 *
 * Class ApiTrait
 */
trait ApiTrait
{
    /**
     * Setting a whitelist of domain names that allow cross-domain access
     */
    protected $_ALLOWED_ORIGINS = [
        'test.icewingcc.com'
    ];


    /**
     * Generate and display a JSON string in a specific format with specified parameters
     *
     * @param int|array $status The status code, if an array, is the complete output JSON array
     * @param array     $data
     * @param string    $message
     */
    protected function render_json($status = 200, $data = [], $message = '')
    {

        /*
         * Determine cross-domain requests and set response headers
         */
        $cross_origin = $this->_parse_cross_origin_domain();

        if($cross_origin){
            @header("Access-Control-Allow-Origin: {$cross_origin}");
        }


        /*
         * Output formatted content
         */
        echo json_encode([
            'status'  => $status,
            'data'    => $data,
            'message' => $message
        ]);
    }

    /**
     * Resolve cross-domain access if access to the source domain name is in a predefined permissible list in config.inc.php,
     * Returns the full cross-domain allowed domain name, otherwise FALSE will be returned.
     *
     * @return bool|string
     */
    private function _parse_cross_origin_domain()
    {
        $refer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';

        $refer = strtolower($refer);

        /*
         * Return false directly without a source address
         */
        if(! $refer){
            return FALSE;
        }

        /*
         * Resolve the reference address and take out the host part
         */
        $refer_parts = parse_url($refer);

        if(! $refer_parts){
            return FALSE;
        }

        $host = isset($refer_parts['host']) ? $refer_parts['host'] : '';
        $scheme = isset($refer_parts['scheme']) ? $refer_parts['scheme'] : 'http';

        if(! $host){
            return FALSE;
        }

        /*
         * Check whether the reference address is in the pre-configured list of permissible cross-domain domain domain names, and if not, return FALSE
         */
        if(in_array($host, $this->_ALLOWED_ORIGINS)){

            return ($scheme ? : 'http') . '://' . $host;

        }

        return $host;

    }
}

Access-Control-Allow-Headers Problem

With the above code, the first step across domains has been achieved. GET requests are all right. However, when POST requests are needed to send data, something goes wrong again. Chrome error Request header field Content-Type is not allowed by Access-Control-Allow-Headers in preflight response. Check the following information, which roughly means that the Content-Type field content in the request header is not allowed in Access-Control-Allow-Headers.

This is simple, just add this content to Access-Control-Allow-Headers, and by the way add other commonly used headers.

    @header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Connection, User-Agent, Cookie');

Find it out.

cookie problem

The POST form sending problem was solved when users logged in, and then another problem came up: the system interacted with the back end through cookies, and each request across the domain was independent, and different cookies were generated. The cookies stored the session id information of PHP, and naturally could not smoothly interact with the back end.

This process seems troublesome, let alone the process. The final solution is to add a header in PHP and set it up in JS:

    @header('Access-Control-Allow-Credentials: true');

Credentials should also be set in JS. Here is the angular code. jQuery is similar:

$http({
    // The parameters are...

    withCredentials: true
});

This solves the problem of cross-domain time cookie s.

OPTIONS request

These problems have been solved, basically cross-domain has been settled, but a closer look at Chrome's Network logs reveals that some requests will appear twice: the first is OPTIONS request mode. The second time is the normal POST. What is this OPTIONS for?

After checking some data and testing it, we found that OPTIONS is equivalent to getting the following header before formally requesting the interface. Naturally, those headers we set up earlier. If the server returns the correct header in this OPTIONS request, then the real request will be executed later; otherwise, the request will be rejected. And throw a mistake.

If the OPTIONS request is just to get the header, then give it an empty return, without any actual operation.

/*
 * Determine the OPTIONS request if the mode of request is
 * OPTIONS ,Output header returns directly
 */
if(isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS'){
    $this->render_json([]);
    exit();
}

Complete code

Next, paste the revised complete PHP code, JS will not paste, just add a parameter. For reference only:

/**
 * API extend
 *
 * Class ApiTrait
 */
trait ApiTrait
{
    /**
     * Setting a whitelist of domain names that allow cross-domain access
     */
    protected $_ALLOWED_ORIGINS = [
        'test.icewingcc.com'
    ];


    /**
     * Generate and display a JSON string in a specific format with specified parameters
     *
     * @param int|array $status The status code, if an array, is the complete output JSON array
     * @param array     $data
     * @param string    $message
     */
    protected function render_json($status = 200, $data = [], $message = '')
    {

        /*
         * Determine cross-domain requests and set response headers
         */
        $cross_origin = $this->_parse_cross_origin_domain();

        if($cross_origin){
            @header("Access-Control-Allow-Origin: {$cross_origin}");
        }


        @header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Connection, User-Agent, Cookie');
        @header('Access-Control-Allow-Credentials: true');

        @header('Content-type: application/json');
        @header("Cache-Control: no-cache, must-revalidate");


        /*
         * Output formatted content
         */
        echo json_encode([
            'status'  => $status,
            'data'    => $data,
            'message' => $message
        ]);
    }

    /**
     * Resolve cross-domain access if access to the source domain name is in a predefined permissible list in config.inc.php,
     * Returns the full cross-domain allowed domain name, otherwise FALSE will be returned.
     *
     * @return bool|string
     */
    private function _parse_cross_origin_domain()
    {
        $refer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';

        $refer = strtolower($refer);

        /*
         * Return false directly without a source address
         */
        if(! $refer){
            return FALSE;
        }

        /*
         * Resolve the reference address and take out the host part
         */
        $refer_parts = parse_url($refer);

        if(! $refer_parts){
            return FALSE;
        }

        $host = isset($refer_parts['host']) ? $refer_parts['host'] : '';
        $scheme = isset($refer_parts['scheme']) ? $refer_parts['scheme'] : 'http';

        if(! $host){
            return FALSE;
        }

        /*
         * Check whether the reference address is in the pre-configured list of permissible cross-domain domain domain names, and if not, return FALSE
         */
        if(in_array($host, $this->_ALLOWED_ORIGINS)){

            return ($scheme ? : 'http') . '://' . $host;

        }

        return $host;

    }
}



/**
 * Basic API access classes
 *
 * Class BaseApiControl
 */
 abstract class BaseApiControl
 {

    use ApiTrait;

    protected function __construct()
    {
        /*
         * Determine the OPTIONS request if the mode of request is
         * OPTIONS ,Output header returns directly
         */
        if(isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'OPTIONS'){
            $this->render_json([]);
            exit();
        }

    }


    // ...

 }

Up to now, the interface has worked well, and this article will be updated when new pits are found. (ps. Although this is a front-end shared article, it uses a lot of PHP to solve problems...)

Links to this article: https://icewing.cc/post/about-cross-origin.html

-- EOF --

Author Jerry Bendy  Published in 2016-03-24 15:39:01, added in classification 2016 lower And was added ". Front endPHP Cross-domain "Label Finally revised to 2017-03-12 08:43:33

Posted by xbase on Wed, 17 Jul 2019 13:35:14 -0700