stay hungry stay foolish

post跨域请求

通常情况下,如果服务器不设置跨域头,页面是获取不到非同域下的post请求数据。利用form+iframe,通过父子页面的数据传递可以实现post跨越请求。

有两种方式可以实现父子页面的数据传递。针对支持HTML5的浏览器可以使用postMessage来实现,这种方式支持不同主域页面之间的数据传递。而另外一种是使用回调函数来实现,这种方式只支持同主域(子域名可能不同)页面之间的数据传递。

回调函数方式

示例:

<!DOCTYPE html>
<html>
<head>
    <title>post跨越请求</title>
    <meta charset="utf-8">
</head>
<body>
    <button onclick="doPost('testFn');">发送请求</button>
    <script type="text/javascript">
        document.domain="test.com";
        function doPost(callbackName) {
            var obj = {
                callbackName: callbackName
            }
            //解决ie7以下设置name无效的bug
            var iframe = document.all ? document.createElement('<iframe name="myFrame"></iframe>') : null;
            if(!iframe) {
                iframe = document.createElement('iframe');
                iframe.name = 'myFrame';
            }
            var form = document.createElement('form');
            iframe.style.display = 'none';
            form.style.display = 'none';
            form.target = 'myFrame';
            form.action = 'http://a.test.com:8080/api/1';
            form.method = 'post';
            for(var key in obj) {
                var input  = document.createElement('input');
                input.name = key;
                input.value = obj[key];
                form.appendChild(input);
            }
            document.body.appendChild(iframe);
            document.body.appendChild(form);
            form.submit();
            window[callbackName] = function(data) {
                alert(data);
                iframe.parentNode.removeChild(iframe);
                form.parentNode.removeChild(form);
            }
        }
    </script>
</body>
</html>

server.js

const express = require('express');
const fs = require('fs');
const mime = require('mime');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(bodyParser.json());//数据JSON类型
app.use(bodyParser.urlencoded({ extended: false }));//解析post请求数据

app.post('/api/1',function(req,res){  
    res.send(`<script>document.domain="test.com";window.parent.${req.body.callbackName}('服务器数据')</script>`);
});

app.post('/api/2',function(req,res){  
    res.send(`<script>window.parent.postMessage('服务器数据','*')</script>`);
});

app.get('/*', function(req,res){
    sendFile(req, res);
});

app.listen(8080);
console.log('listen 8080')

function sendFile(req,res){
        var realPath = __dirname+req.url;
        var exist = fs.existsSync(realPath);
        if(exist){
            var file = fs.readFileSync(realPath);
            res.writeHead(200, {
                'Content-Type': mime.getType(path.basename(realPath)),
            });
            res.end(file);
            console.log('send static file:'+realPath);
        } else {
            res.writeHead(404);
        }
}

示例代码

注意:

  • 服务器X-Frame-Options不能设置成DENY
  • 父页面和子页面的主域必须相同(上面父子页面的主域为test.com),并且父子页面都必须显示的设置domain为主域,即使父页面本身就是主域名

postMessage方式

实例:

<!DOCTYPE html>
<html>

<head>
    <title>post跨越请求</title>
    <meta charset="utf-8">
</head>

<body>
    <button onclick="doPost();">发送请求</button>
    <script type="text/javascript">
    function doPost() {
        var iframe = document.createElement('iframe');
        var form = document.createElement('form');
        iframe.name = 'myFrame';
        iframe.style.display = 'none';
        form.style.display = 'none';
        form.target = 'myFrame';
        form.action = 'http://a.test.com:8080/api/2';
        form.method = 'post';
        document.body.appendChild(iframe);
        document.body.appendChild(form);
        form.submit();
        window.onmessage = function(event) {
            alert(event.data);
            iframe.parentNode.removeChild(iframe);
            form.parentNode.removeChild(form);
        }
    }
    </script>
</body>

</html>