同源策略(Same origin policy)是一種約定羹与,它是瀏覽器最核心也最基本的安全功能氮采,如果缺少了同源策略塔插,則瀏覽器的正常功能可能都會受到影響∨雌椋可以說Web是構(gòu)建在同源策略基礎(chǔ)之上的几缭,瀏覽器只是針對同源策略的一種實現(xiàn)。現(xiàn)在所有支持JavaScript 的瀏覽器都會使用這個策略沃呢。所謂同源是指年栓,域名,協(xié)議薄霜,端口相同某抓。當一個瀏覽器的兩個tab頁中分別打開來 百度和谷歌的頁面當瀏覽器的百度tab頁執(zhí)行一個腳本的時候會檢查這個腳本是屬于哪個頁面的纸兔,即檢查是否同源,只有和百度同源的腳本才會被執(zhí)行否副。如果非同源汉矿,那么在請求數(shù)據(jù)時,瀏覽器會在控制臺中報一個異常备禀,提示拒絕訪問负甸。
??源于同源策略,我們有時需要跨域處理痹届。
一呻待、jsonp解決方案
jsonp是利用script標簽沒有跨域限制的特性,通過在src的url的參數(shù)上附加回調(diào)函數(shù)名字队腐,然后服務(wù)器接收回調(diào)函數(shù)名字并返回一個包含數(shù)據(jù)的回調(diào)函數(shù)
function doSomething(data) {
// 對data處理
}
var script = document.createElement("script");
script.src = "http://www.b.com/b.html?callback=doSomething";
document.body.appendChild(script);
// 1.生成一個script標簽蚕捉,將其append在body上,向服務(wù)器發(fā)出請求
// 2.服務(wù)器根據(jù) callback 這個參數(shù)生成一個包含數(shù)據(jù)的函數(shù) doSomething({"a", "1"})
// 3.頁面事先已聲明doSomething函數(shù)柴淘,此時執(zhí)行 doSomething(data) 這個函數(shù)迫淹,獲得數(shù)據(jù)
示例
前端以jq封裝的jsonp為例
$.ajax({
url:'http://192.168.9.5/jsonp_test1.jsp',
dataType:"jsonp",
jsonp:"jsonpcallback",
success:function(data){
var $ul = $("<ul></ul>");
$.each(data,function(i,v){
$("<li/>").text(v["id"] + " " + v["name"]).appendTo($ul)
});
$("#res").append($ul);
}
});
服務(wù)器的處理
c#
[HttpGet]
public ActionResult getSurvey_Contact(String contact_id,string survey_id)
{
var callback = Request.QueryString["callback"]; //獲取回調(diào)函數(shù)的名字
var contactColl = MyMongoDb.CurrentDB.GetCollection("Survey_Contact");
if (contact_id == null)
contact_id = String.Empty;
var doc = contactColl.FindAs<BsonDocument>(Query.And(Query.EQ("contact_id", contact_id), Query.EQ("Survey_Id", survey_id))).FirstOrDefault();
if (doc == null)
doc = new BsonDocument();
FormateJSON(new[] { doc });
var answerColl = MyMongoDb.CurrentDB.GetCollection("Survey_Answers");
var answerDocs = answerColl.FindAs<BsonDocument>(Query.And(Query.EQ("contact_id", contact_id), Query.EQ("survey_id", survey_id))).ToArray();
FormateJSON(answerDocs);
doc.Set("answers", new BsonArray(answerDocs));
var result = new ContentResult();
result.ContentEncoding = System.Text.Encoding.UTF8;
result.ContentType = "application/json";
//Regex reg = new Regex("ObjectId\\(([a-z0-9])\\)");
if (String.IsNullOrWhiteSpace(callback))
{
result.Content = doc.ToJson();
return result;
}
else
{
result.Content = callback + "(" + doc.ToJson() + ");"; //前端用js文件格式解析結(jié)果,通過callback(data)獲取數(shù)據(jù)
return result;
}
}
//題外
jquery的$.Jsonp或者$.ajax的jsonp和ajax沒有關(guān)系为严,只是jquery封裝起來方便使用敛熬。
實際也是利用script標簽沒有跨域限制的特性實現(xiàn)的
JSONP的優(yōu)點:它不像XMLHttpRequest對象實現(xiàn)的Ajax請求那樣受到同源策略的限制;它的兼容性更好第股,在更加古老的瀏覽器中都可以運行应民,不需要XMLHttpRequest或ActiveX的支持;并且在請求完畢后可以通過調(diào)用callback的方式回傳結(jié)果夕吻。
JSONP的缺點:它只支持GET請求而不支持POST等其它類型的HTTP請求诲锹;它只支持跨域HTTP請求這種情況,不能解決不同域的兩個頁面之間如何進行JavaScript調(diào)用的問題涉馅。
二归园、window.name+iframe
//window.name的原理是利用同一個窗口在不同的頁面共用一個window.name,這個需要在a.com下建立一個代理文件c.html,使同源后a.html能獲取c.html的window.name
//需要目標服務(wù)器響應(yīng)window.name
三稚矿、window.location.hash+iframe
//需要目標服務(wù)器作處理
//b.html將數(shù)據(jù)以hash值的方式附加到c.html的url上庸诱,在c.html頁面通過location.hash獲取數(shù)據(jù)后傳到a.html(這個例子是傳到a.html的hash上,當然也可以傳到其他地方)
四晤揣、document.domain
這種方式適用于主域相同桥爽,子域不同,比如http://www.a.com和http://b.a.com
假如這兩個域名下各有a.html 和b.html,
a.html
document.domain = "a.com";
var iframe = document.createElement("iframe");
iframe.src = "http://b.a.com/b.html";
document.body.appendChild(iframe);
iframe.onload = function() {
console.log(iframe.contentWindow....); // 在這里操作b.html里的元素數(shù)據(jù)
}
b.html
document.domain = "a.com";
注意:document.domain需要設(shè)置成自身或更高一級的父域碉渡,且主域必須相同聚谁。
五、html5的 postMessage+ifrme
//這個需要目標服務(wù)器或者說是目標頁面寫一個postMessage滞诺,主要側(cè)重于前端通訊形导。
假設(shè)在a.html里嵌套個<iframe src=""http://www.b.com/b.html" rel="nofollow">http://www.b.com/b.html" frameborder="0"></iframe>,在這兩個頁面里互相通信
a.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.frames[0].postMessage("b data", "http://www.b.com/b.html");
}
b.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.parent.postMessage("a data", "http://www.a.com/a.html");
}
這樣打開a頁面就先彈出 a data,再彈出 b data
六环疼、CORS解決方案
CORS是XMLHttpRequest Level 2 里規(guī)定的一種跨域方式。在支持這個方式的瀏覽器里朵耕,javascript的寫法和不跨域的ajax寫法一模一樣炫隶,只要服務(wù)器需要設(shè)置Access-Control-Allow-Origin: *
1、apache設(shè)置header : Access-Control-Allow-Origin
//httpd.conf
找到這行
#LoadModule headers_module modules/mod_headers.so
把#注釋符去掉
LoadModule headers_module modules/mod_headers.so
目的是開啟apache頭信息自定義模塊
<Directory />
AllowOverride none
Require all denied
</Directory>
改為下面代碼
<Directory />
Require all denied
Header set Access-Control-Allow-Origin *
</Directory>
設(shè)置請求頭
2阎曹、nginx設(shè)置header : Access-Control-Allow-Origin
在nginx的conf文件中加入以下內(nèi)容:
location / {
add_header Access-Control-Allow-Origin *;
}
3伪阶、服務(wù)器設(shè)置header :Access-Control-Allow-Origin
//示例
php
//header("Access-Control-Allow-Origin:*");
//header("Access-Control-Allow-Origin:http://www.a.com");
c#
//在Web.config設(shè)置
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
</customHeaders>
</httpProtocol>
nodejs
var express = require('express');
var app = express();
//設(shè)置跨域訪問
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("X-Powered-By",' 3.2.1')
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.get('/getdata', function(req, res) {
res.send({id:req.params.id, name: req.params.password});
});
app.listen(3000);
console.log('Listening on port 3000...');
//題外
如果需要跨域設(shè)置cookie,要設(shè)置Access-Control-Allow-Credentials处嫌,例如
c#
x.Headers.Add("Access-Control-Allow-Origin", "http://localhost:8080");
x.Headers.Add("Access-Control-Allow-Credentials", "true");
//題外
cors缺點:
需要服務(wù)器配合
設(shè)置Access-Control-Allow-Origin 存在一定的風險
七栅贴、chrome插件跨域方案
//CORS Toggle
八、代理服務(wù)器解決方案
1熏迹、nignx反向代理
//搭建一個中轉(zhuǎn)nginx服務(wù)器轉(zhuǎn)發(fā)請求,如果是自己的主機就比較好操作檐薯。但是用了Nginx配置之后,webpack的hot reload會存在比較大的延遲
//nginx.conf
http {
include mime.types;
default_type application/octet-stream;
server {
listen 8080;
charset utf-8;
access_log on;
access_log logs/host.access.log ;
location / {
#8080端口號是開啟的本地服務(wù)端口號
proxy_pass http://localhost:8080;
}
location /api {
#每有一個新的代理需求注暗,就新增一個location
#反向代理坛缕,達到前后端分離開發(fā)的目的
proxy_pass http://192.168.60.225:7003;
}
}
}
/***********
關(guān)于proxy_pass
既是把請求代理到其他主機,其中 http://www.b.com/ 寫法和 http://www.b.com寫法的區(qū)別如下:
不帶/
location /html/
{
proxy_pass http://b.com:8300;
}
帶/
location /html/
{
proxy_pass http://b.com:8300/;
}
上面兩種配置捆昏,區(qū)別只在于proxy_pass轉(zhuǎn)發(fā)的路徑后是否帶 “/”赚楚。
針對情況1,如果訪問url = http://server/html/test.jsp骗卜,則被nginx代理后宠页,請求路徑會便問http://proxy_pass/html/test.jsp,將test/ 作為根路徑膨俐,請求test/路徑下的資源勇皇。
針對情況2罩句,如果訪問url = http://server/html/test.jsp焚刺,則被nginx代理后,請求路徑會變?yōu)?http://proxy_pass/test.jsp门烂,直接訪問server的根資源乳愉。
***********/
2、apache反向代理
用 apache 的 mod_proxy 模塊開啟反向代理功能來實現(xiàn):
1 修改 apache 配置文件 httpd.conf ,去掉以下兩行前面 # 號,加載反向代理模塊
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
2 在站點目錄中修改:
<VirtualHost *:80>
DocumentRoot "E:\study-environment\www"
#正向代理
#ProxyRequests On
#ProxyVia On
#反向代理時設(shè)置為Off
ProxyRequests Off
<Proxy "*">
Order deny,allow
#Deny from all
#Allow from 127.0.0.1 188.1.8.1
Allow from all
</Proxy>
#proxy setting
ProxyPass /api http://188.1.8.1/api
#ProxyPassReverse 瀏覽器的地址欄不會顯示反向代理的地址
ProxyPassReverse /api http://188.1.8.1/api
#如果路徑的名稱/api代理后沒有變化屯远,session不會丟失蔓姚,則可以不用ProxyPassReverseCookiePath
#ProxyPassReverseCookiePath /api /api
ProxyPass /log http://188.1.8.1/apilog
ProxyPassReverse /log http://188.1.8.1/apilog
ProxyPassReverseCookiePath /log /apilog
#另一種寫法
<Location / >
ProxyPass http://188.1.8.1/home connectiontimeout=5 timeout=300
ProxyPassReverse http://188.1.8.1/home
</Location>
</VirtualHost>
重啟 apache
//題外
正向代理(forward)是一個位于客戶端【用戶A】和原始服務(wù)器(origin server)【服務(wù)器B】之間的服務(wù)器【代理服務(wù)器Z】,為了從原始服務(wù)器取得內(nèi)容慨丐,用戶A向代理服務(wù)器Z發(fā)送一個請求并指定目標(服務(wù)器B)坡脐,然后代理服務(wù)器Z向服務(wù)器B轉(zhuǎn)交請求并將獲得的內(nèi)容返回給客戶端》拷遥客戶端必須要進行一些特別的設(shè)置才能使用正向代理备闲。
反向代理正好與正向代理相反晌端,對于客戶端而言代理服務(wù)器就像是原始服務(wù)器,并且客戶端不需要進行任何特別的設(shè)置恬砂∵志溃客戶端向反向代理的命名空間(name-space)中的內(nèi)容發(fā)送普通請求,接著反向代理將判斷向何處(原始服務(wù)器)轉(zhuǎn)交請求泻骤,并將獲得的內(nèi)容返回給客戶端漆羔。
簡單點說就是
正向代理:客戶向代理服務(wù)器發(fā)請求,代理服務(wù)器再把請求到指定服務(wù)器
反向代理:客戶向目標服務(wù)器發(fā)請求狱掂,代理服務(wù)器攔截請求演痒,再把請求轉(zhuǎn)發(fā)到指定的目標服務(wù)器
3、webpack+webpack-dev-server
//webpack.config.js
var path = require("path");
module.exports = {
entry: {
index: './src/index.entry.js',
authManage: './src/authManage.entry.js',
reports: './src/reports.entry.js'
},
output: {
path: path.join(__dirname, 'public'),
filename: '[name].bundle.js'
},
module: {
loaders: [
{
test: /\.jsx?$/,
loader: 'babel',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react', "stage-2"]
}
},
{ test: /\.css$/, loader: 'style!css' },
{ test: /\.(png|jpg|jpeg|gif|woff)$/, loader: 'url-loader?limit=8192' }
]
},
devServer: {
//代理
proxy: {
'/api/*': 'http://localhost:7003/'
//'/api': {
// target: 'http://localhost:7003/'
//}
}
}
};
//在cmd敲入命令運行webpack-dev-server,例如
>>webpack-dev-server --inline --hot --progress --colors --port 8082
//也可以在package.json的scripts配置命令方便啟動服務(wù)或者配置new WebpackDevServer來啟動服務(wù)
//在頁面請求本地接口即可,devServer會攔截/api/的請求
//action.js
fetch(`/api/map?exhibition=${data._id}`)
.then(res => res.json())
.then(data => {
let sourceId = data[0]._id;
return fetch(`/api/exhibitor/${sourceId}`);
})
.then(res => res.json())
.then(data => {
return dispatch(addexhibition(data)) ;
})
.catch(error => {
console.log(error);
})
4趋惨、node+express+webpack+webpack-dev-middleware+http-proxy-middleware
//webpack-dev-server是一個小型的Node.js Express服務(wù)器,它使用webpack-dev-middleware來服務(wù)于webpack的包,除此自外嫡霞,它還有一個通過Sock.js來連接到服務(wù)器的微型運行時.所以我們也可以不用webpack-dev-server,自己來搭一個
//server.js
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
var port = 7003
// 定義HTTP代理到自定義的API后端
var proxyTable = {
'/api': {
target: 'http://localhost:7003/',
changeOrigin: true,
// pathRewrite: {
// '^/api': '/api'
// }
},
'/Uploads': {
target: 'http://localhost:7003/',
changeOrigin: true
}
}
var app = express()
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false
}
})
var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(context, options)) //使用代理中間件
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
var uri = 'http://localhost:' + port
console.log('Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})
5、node+proxy-middleware
//主要針對采用https協(xié)議的服務(wù)器
var connect = require('connect');
var url = require('url');
var proxy = require('proxy-middleware');
var app = connect();
app.use('/api', proxy(url.parse('https://example.com/endpoint')));
// now requests to '/api/x/y/z' are proxied to 'https://example.com/endpoint/x/y/z'
//same as example above but also uses a short hand string only parameter
app.use('/api-string-only', proxy('https://example.com/endpoint'));
6希柿、node+cors
7诊沪、node+node-http-proxy
8、node+node-reverse-proxy
9曾撤、node+reverse-proxy
//通過pem和SNI 解決了 HTTPS 證書認證的問題