九种 “姿势” 让你彻底解决跨域问题


原文出自:https://www.pandashen.com


同源策略

同源策略/SOP(Same origin policy)是一种约定,由 Netscape 公司 1995 年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS、CSRF 等攻击。所谓同源是指 "协议 + 域名 + 端口" 三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。


什么是跨域?

当协议、域名、端口号,有一个或多个不同时,有希望可以访问并获取数据的现象称为跨域访问,同源策略限制下 cookielocalStoragedomajaxIndexDB 都是不支持跨域的。

假设 cookie 支持了跨域,http 协议无状态,当用户访问了一个银行网站登录后,银行网站的服务器给返回了一个 sessionId,当通过当前浏览器再访问一个恶意网站,如果 cookie 支持跨域,恶意网站将获取 sessionId 并访问银行网站,出现安全性问题;IndexDB、localStorage 等数据存储在不同域的页面切换时是获取不到的;假设 dom 元素可以跨域,在自己的页面写入一个 iframe 内部嵌入的地址是 <a href="javascript:;">www.baidu.com</a>,当在百度页面登录账号密码时就可以在自己的页面获取百度的数据信息,这显然是不合理的。

这就是为什么 cookielocalStoragedomajaxIndexDB 会受到同源策略会限制,下面还有一点对跨域理解的误区:

误区:同源策略限制下,访问不到后台服务器的数据,或访问到后台服务器的数据后没有返回;
正确:同源策略限制下,可以访问到后台服务器的数据,后台服务器会正常返回数据,而被浏览器给拦截了。


实现跨域的方式

一、使用 jsonp 跨域

使用场景:当自己的项目前端资源和后端部署在不同的服务器地址上,或者其他的公司需要访问自己对外公开的接口,需要实现跨域获取数据,如百度搜索。

// 封装 jsonp 跨域请求的方法
function jsonp({ url, params, cb }) {
    return new Promise((resolve, reject) => {
        // 创建一个 script 标签帮助我们发送请求
        let script = document.createElement("script");
        let arr = [];
        params = { ...params, cb };

        // 循环构建键值对形式的参数
        for (let key in params) {
            arr.push(`${key}=${params[key]}`);
        }

        // 创建全局函数
        window[cb] = function(data) {
            resolve(data);
            // 在跨域拿到数据以后将 script 标签销毁
            document.body.removeChild(script);
        };

        // 拼接发送请求的参数并赋值到 src 属性
        script.src = `${url}?${arr.join("&")}`;
        document.body.appendChild(script);
    });
}

// 调用方法跨域请求百度搜索的接口
json({
    url: "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su",
    params: {
        wd: "jsonp"
    },
    cb: "show"
}).then(data => {
    // 打印请求回的数据
    console.log(data);
});

缺点:

  • 只能发送 get 请求 不支持 post、put、delete;
  • 不安全,容易引发 xss 攻击,别人在返回的结果中返回了下面代码。
`let script = document.createElement('script');
script.src = "http://192.168.0.57:8080/xss.js";
document.body.appendChild(script);`;

会把别人的脚本引入到自己的页面中执行,如:弹窗、广告等,甚至更危险的脚本程序。


二、使用 CORS 跨域

跨源资源共享/CORS(Cross-Origin Resource Sharing)是 W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

使用场景:多用于开发时,前端与后台在不同的 ip 地址下进行数据访问。

现在启动两个端口号不同的服务器,创建跨域条件,服务器(NodeJS)代码如下:

// 服务器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服务器2
const express = require("express");
let app = express();
app.get("/getDate", function(req, res) {
    res.end("I love you");
});
app.use(express.static(__dirname));
app.listen(4000);

由于我们的 NodeJS 服务器使用 express 框架,在我们的项目根目录下的命令行中输入下面代码进行安装:

npm install express --save

通过访问 <a href="javascript:;">http://localhost:3000/index.html</a> 获取 index.html 文件并执行其中的 Ajax 请求 <a href="javascript:;">http://localhost:4000/getDate</a> 接口去获取数据,index.html 文件内容如下:

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CORS 跨域</title>
</head>
<body>
    <script>
        let xhr = new XMLHttpRequest();

        // 正常 cookie 是不允许跨域的
        document.cookie = 'name=hello';

        // cookie 想要实现跨域必须携带凭证
        xhr.withCredentials = true;

        // xhr.open('GET', 'http://localhost:4000/getDate', true);
        xhr.open('PUT', 'http://localhost:4000/getDate', true);

        // 设置名为 name 的自定义请求头
        xhr.setRequestHeader('name', 'hello');

        xhr.onreadystatechange = function () {
            if(xhr.readyState === 4) {
                if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    // 打印返回的数据
                    console.log(xhr.response);

                    // 打印后台设置的自定义头信息
                    console.log(xhr.getResponseHeader('name'));
                }
            }
        }
        xhr.send();
    </script>
</body>
</html>

上面 index.html 代码中发送请求访问不在同源的服务器 2,此时会在控制台给出错误信息,告诉我们缺少了哪些响应头,我们对应报错信息去修改访问的服务器 2 的代码,添加对应的响应头,实现 CORS 跨域。

// 服务器2
const express = require("express");
let app = express();

// 允许访问域的白名单
let whiteList = ["http://localhost:3000"];

app.use(function(req, res, next) {
    let origin = req.header.origin;
    if (whiteList.includes(origin)) {
        // 设置那个源可以访问我,参数为 * 时,允许任何人访问,但是不可以和 cookie 凭证的响应头共同使用
        res.setHeader("Access-Control-Allow-Origin", origin);
        // 想要获取 ajax 的头信息,需设置响应头
        res.setHeader("Access-Control-Allow-Headers", "name");
        // 处理复杂请求的头
        res.setHeader("Access-Control-Allow-Methods", "PUT");
        // 允许发送 cookie 凭证的响应头
        res.setHeader("Access-Control-Allow-Credentials", true);
        // 允许前端获取哪个头信息
        res.setHeader("Access-Control-Expose-Headers", "name");
        // 处理 OPTIONS 预检的存活时间,单位 s
        res.setHeader("Access-Control-Max-Age", 5);
        // 发送 PUT 请求会做一个试探性的请求 OPTIONS,其实是请求了两次,当接收的请求为 OPTIONS 时不做任何处理
        if (req.method === "OPTIONS") {
            res.end();
        }
    }
    next();
});

app.put("/getDate", function(req, res) {
    // res.setHeader('name', 'nihao'); // 设置自定义响应头信息
    res.end("I love you");
});

app.get("/getDate", function(req, res) {
    res.end("I love you");
});

app.use(express.static(__dirname));
app.listen(4000);


三、使用 postMessage 实现跨域

postMessage 是 H5 的新 API,跨文档消息传送(cross-document messaging),有时候简称为 XMD,指的是在来自不同域的页面间传递消息。

调用方式:window.postMessage(message, targetOrigin)

  • message:发送的数据
  • targetOrigin:发送的窗口的域

在对应的页面中用 message 事件接收,事件对象中有 dataoriginsource 三个重要信息

  • data:接收到的数据
  • origin:接收到数据源的域(数据来自哪个域)
  • source:接收到数据源的窗口对象(数据来自哪个窗口对象)

使用场景:不是使用 Ajax 的数据通信,更多是在两个页面之间的通信,在 A 页面中引入 B 页面,在 AB 两个页面之间通信。

与上面 CORS 类似,我们要创建跨域场景,搭建两个端口号不同的 Nodejs 服务器,后面相同方式就不多赘述了。

// 服务器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服务器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);

通过访问 <a href="javascript:;">http://localhost:3000/a.html</a>,在 a.html 中使用 iframe 标签引入 <a href="javascript:;">http://localhost:4000/b.html</a>,在两个窗口间传递数据。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 A</title>
</head>
<body>
    <iframe src="http://localhost:4000/b.html" id="frame" onload="load()"></iframe>
    <script>
        function load() {
            let frame = document.getElementById('frame');
            frame.contentWindow.postMessage('I love you', 'http://localhost:4000');
            window.onmessage = function (e) {
                console.log(e.data);
            }
        }
    </script>
</body>
</html>
<!-- 文件:b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 B</title>
</head>
<body>
    <script>
        window.onmessage = function (e) {
            // 打印来自页面 A 的消息
            console.log(e.data);
            // 给页面 A 发送回执
            e.source.postMessage('I love you, too', e.origin);
        }
    </script>
</body>
</html>


四、使用 window.name 实现跨域

同样是页面之间的通信,需要借助 iframe 标签,A 页面和 B 页面是同域的 <a href="javascript:;">http://localhost:3000</a>,C 页面在独立的域 <a href="javascript:;">http://localhost:4000</a>。

// 服务器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服务器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);

实现思路:在 A 页面中将 iframesrc 指向 C 页面,在 C 页面中将属性值存入 window.name 中,再把 iframesrc 换成同域的 B 页面,在当前的 iframewindow 对象中取出 name 的值,访问 <a href="javascript:;">http://localhost:3000/a.html</a>。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 A</title>
</head>
<body>
    <iframe src="http://localhost:4000/c.html" id="frame" onload="load()"></iframe>
    <script>
        // 增加一个标识,第一次触发 load 时更改地址,更改后再次触发直接取值
        let isFirst = true;
        function load() {
            let frame = document.getElementById('frame');
            if(isFirst) {
                frame.src = 'http://localhost:3000/b.html';
                isFirst = false;
            } else {
                console.log(frame.contentWindow.name);
            }
        }
    </script>
</body>
</html>
<!-- 文件:c.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 C</title>
</head>
<body>
    <script>
        window.name = 'I love you';
    </script>
</body>
</html>


五、使用 location.hash 实现跨域

window.name 跨域的情况相同,是不同域的页面间的参数传递,需要借助 iframe 标签,A 页面和 B 页面是同域的 <a href="javascript:;">http://localhost:3000</a>,C 页面是独立的域 <a href="javascript:;">http://localhost:4000</a>。

// 服务器1
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(3000);

// 服务器2
const express = require(express);
let app = express();
app.use(express.static(__dirname));
app.listen(4000);

实现思路:A 页面通过 iframe 引入 C 页面,并给 C 页面传一个 hash 值,C 页面收到 hash 值后创建 iframe 引入 B 页面,把 hash 值传给 B 页面,B 页面将自己的 hash 值放在 A 页面的 hash 值中,访问 <a href="javascript:;">http://localhost:3000/a.html</a>。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 A</title>
</head>
<body>
    <iframe src="http://localhost:4000/c.html#Iloveyou" id="frame"></iframe>
    <script>
        // 使用 hashchange 事件接收来自 B 页面设置给 A 页面的 hash 值
        window.onhashchange = function () {
            console.log(location.hash);
        }
    </script>
</body>
</html>
<!-- 文件:c.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 C</title>
</head>
<body>
    <script>
        // 打印 A 页面引入 C 页面设置的 hash 值
        console.log(location.hash);
        let iframe = document.createElement('iframe');
        iframe.src = 'http://localhost:3000/b.html#Iloveyoutoo';
        document.body.appendChild(iframe);
    </script>
</body>
</html>
<!-- 文件:b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 B</title>
</head>
<body>
    <script>
        // 将 C 页面引入 B 页面设置的 hash 值设置给 A页面
        window.parent.parent.location.hash = location.hash;
    </script>
</body>
</html>


六、使用 document.domain 实现跨域

使用场景:不是万能的跨域方式,大多使用于同一公司不同产品间获取数据,必须是一级域名和二级域名的关系,如 <a href="javascript:;">www.baidu.com</a> 与 <a href="javascript:;">video.baidu.com</a> 之间。

const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);

想要模拟使用 document.domain 跨域的场景需要做些小小的准备,到 <a href="javascript:;">C:\Windows\System32\drivers\etc</a> 该路径下找到 hosts 文件,在最下面创建一个一级域名和一个二级域名。

127.0.0.1          <a href="javascript:;">www.domainacross.com</a>
127.0.0.1          <a href="javascript:;">sub.domainacross.com</a>

命名是随意的,只要是符合一级域名与 二级域名的关系即可,然后访问 <a href="javascript:;">http://www.domainacross.com:3000/a.html</a>。

<!-- 文件:a.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面 A</title>
</head>
<body>
    <p>我是页面 A 的内容</p>
    <iframe src="http://sucess.domainacross.com:3000/b.html" onload="load()" id="frame"></iframe>
    <script>
        document.domain = 'domainacross.com';
        function load() {
            console.log(frame.contentWindow.message);
        }
    </script>
</body>
</html>
<!-- 文件:b.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>页面 B</title>
</head>
<body>
    <p>我是 B 页面的内容</p>
    <script>
        document.domain = 'domainacross.com';
        var message = 'Hello A';
    </script>
</body>
</html>


七、使用 WebSocket 实现跨域

WebSocket 没有跨域限制,高级 API(不兼容),想要兼容低版本浏览器,可以使用 socket.io 的库,WebSocket 与 HTTP 内部都是基于 TCP 协议,区别在于 HTTP 是单向的(单双工),WebSocket 是双向的(全双工),协议是 ws://wss:// 对应 http://https://,因为没有跨域限制,所以使用 file:// 协议也可以进行通信。

由于我们在 NodeJS 服务中使用了 WebSocket,所以需要安装对应的依赖:

npm install ws --save

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页面</title>
</head>
<body>
    <script>
        // 创建 webSocket
        let socket = new WebSocket('ws://localhost:3000');
        // 连接上触发
        socket.onopen = function () {
            socket.send('I love you');
        }
        // 收到消息触发
        socket.onmessage = function (e) {
            // 打印收到的数据
            console.log(e.data); // I love you, too
        }
    </script>
</body>
</html>
const express = require("express");
let app = express();

// 引入 webSocket
const WebSocket = require("ws");
// 创建连接,端口号与前端相对应
let wss = new WebSocket.Server({ port: 3000 });

// 监听连接
wss.on("connection", function(ws) {
    // 监听消息
    ws.on("message", function(data) {
        // 打印消息
        console.log(data); // I love you
        // 发送消息
        ws.send("I love you, too");
    });
});


八、使用 nginx 实现跨域

nginx 本身就是一个服务器,因此我们需要去 nginx 官网下载服务环境 <a>http://nginx.org/en/download.html</a>。

  • 下载后解压到一个文件夹中
  • 双击 nginx.exe 启动(此时可以通过 <a href="javascript:;">http://localhost</a> 访问 nginx 服务)
  • 在目录新建 json 文件夹
  • 进入 json 文件夹新建 data.json 文件并写入内容
  • 回到 nginx 根目录进入 conf 文件夹
  • 使用编辑器打开 nginx.conf 进行配置

data.json 文件:

{
    "name": "nginx"
}

nginx.conf 文件:

server {
    .
    .
    .
    location ~.*\.json {
        root json;
        add_header "Access-Control-Allow-Origin" "*";
    }
    .
    .
    .
}

含义:

  • ~.*\.json:代表忽略大小写,后缀名为 json 的文件;
  • root json:代表 json 文件夹;
  • add_header:代表加入跨域的响应头及允许访问的域,* 为允许任何访问。

nginx 根目录启动 cmd 命令行(windows 系统必须使用 cmd 命令行)执行下面代码重启 nginx

nginx -s reload

不跨域访问:<a href="javascript:;">http://localhost/data.json</a>

跨域访问时需要创建跨域条件代码如下:

// 服务器
const express = require("express");
let app = express();

app.use(express.static(__dirname));
app.listen(3000);

跨域访问:<a href="javascript:;">http://localhost:3000/index.html</a>

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>nginx跨域</title>
</head>
<body>
    <script>
        let xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://localhost/data.json', true);
        xhr.onreadystatechange = function () {
            if(xhr.readyState === 4) {
                if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    console.log(xhr.response);
                }
            }
        }
        xhr.send();
    </script>
</body>
</html>


九、使用 http-proxy-middleware 实现跨域

NodeJS 中间件 http-proxy-middleware 实现跨域代理,原理大致与 nginx 相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中 cookie 中的域名,实现当前域的 cookie 写入,方便接口登录认证。

1、非 vue 框架的跨域(2 次跨域)

<!-- 文件:index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>proxy 跨域</title>
</head>
<body>
    <script>
        var xhr = new XMLHttpRequest();

        // 前端开关:浏览器是否读写 cookie
        xhr.withCredentials = true;

        // 访问 http-proxy-middleware 代理服务器
        xhr.open('get', 'http://www.proxy1.com:3000/login?user=admin', true);
        xhr.send();
    </script>
</body>
</html>

中间代理服务中使用了 http-proxy-middleware 中间件,因此需要提前下载:

npm install http-proxy-middleware --save-dev

// 中间代理服务器
const express = require("express");
let proxy = require("http-proxy-middleware");
let app = express();

app.use(
    "/",
    proxy({
        // 代理跨域目标接口
        target: "http://www.proxy2.com:8080",
        changeOrigin: true,

        // 修改响应头信息,实现跨域并允许带 cookie
        onProxyRes: function(proxyRes, req, res) {
            res.header("Access-Control-Allow-Origin", "http://www.proxy1.com");
            res.header("Access-Control-Allow-Credentials", "true");
        },

        // 修改响应信息中的 cookie 域名
        cookieDomainRewrite: "www.proxy1.com" // 可以为 false,表示不修改
    })
);

app.listen(3000);
// 服务器
const http = require("http");
const qs = require("querystring");

const server = http.createServer();

server.on("request", function(req, res) {
    let params = qs.parse(req.url.substring(2));

    // 向前台写 cookie
    res.writeHead(200, {
        "Set-Cookie": "l=a123456;Path=/;Domain=www.proxy2.com;HttpOnly" // HttpOnly:脚本无法读取
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen("8080");

2、vue 框架的跨域(1 次跨域)

利用 node + webpack + webpack-dev-server 代理接口跨域。在开发环境下,由于 Vue 渲染服务和接口代理服务都是 webpack-dev-server,所以页面与代理接口之间不再跨域,无须设置 Headers 跨域信息了。

// 导出服务器配置
module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.proxy2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些 https 服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为 false,表示不修改
        }],
        noInfo: true
    }
}


本篇文章在于帮助我们理解跨域,以及不同跨域方式的基本原理,在公司的项目比较多,多个域使用同一个服务器或者数据,以及在开发环境时,跨域的情况基本无法避免,一般会有各种各样形式的跨域解决方案,但其根本原理基本都在上面的跨域方式当中方式,我们可以根据开发场景不同,选择最合适的跨域解决方案。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容

  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    HeroXin阅读 835评论 0 4
  • 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实...
    他方l阅读 1,062评论 0 2
  • 这个世界就是都是一样的 没有什么是不一样的 差不多先生
    泰_3fa6阅读 142评论 1 1
  • 几年前的一次拓展培训上,老师带我们作了一个测试,把一天用在工作、锻炼、家人、生活的时间详细写出来,做成雷达图,看我...
    西苏Sisu阅读 228评论 0 1
  • 这是一段记忆。 那个飘雪的早晨,“好久没有你的信,好久没有人陪我谈心”手机铃声叫我起床,可已经养成的习惯就是...
    泽L不爱阅读 376评论 0 1