這段時間,項目涉及到移動端,這就不可避免的涉及到了跨域的問題。這是本人第一次接觸跨域,有些地方的配置是有點麻煩,致使一開始的不順。javascript
至於websocket具體是什麼意義,用途如何:請百度。html
簡單說就是創建一個基於互聯網的實時通訊。前端
在這裏整理下這些內容,方便往後回顧。java
一:介紹了WebSocket下的基於SignalR的跨域與不跨域例子jquery
二:簡單介紹了Http下的跨域問題web
若是使用原生的方法來開發WebSocket應用,仍是比較複雜的,不過好在Asp.net給咱們提供了一個框架:ajax
SignalR:json
微軟支持的運行在.NET平臺上集客戶端與服務器於一體的庫。簡單來講就是給咱們提供了服務端的類庫加上前端的JS庫。後端
首先拋開跨域的問題,先寫一個SignalR不跨域的例子:api
一、NuGet來得到咱們所須要的程序集:
SignalR:咱們的框架
二、新建個繼承於Hub的類
1 /// <summary>
2 /// 對應不跨域的方法
3 /// </summary>
4 public class ChatHub : Hub
5 {
6 public void Send(string message)
7 {
8 //接收數據,再傳輸到指定或所有的客戶端動態方法
9 Clients.All.addNewMessageToPage(message);
10 }
11 }
三、新建個Startup類:
只需添加一句話:app.MapSignalR();
具體的完整代碼在下面的Startup裏都有。
四、這樣咱們的服務端代碼就完成了,接下來就是客戶端代碼:
1 $(function() {
2
3 var chat = $.connection.chat;
5 $.connection.hub.start();
9 chat.addMessage = function(message){
11 alert(message);
13 }
14
15 $("#btn").click(function(){
16
17 chat.send("給服務端發送信息");
18
19 });
20
21 });
五、好了,如今來分析下客戶端代碼:
前面兩行就是基本的猜也能猜到的獲取實例,而後開啓這個鏈接。(至關於啓動線程)。第三行定義了一個函數,這個函數是與服務端的ChatHub類裏的動態方法addNewMessageToPage對應的回調函數。服務端一執行動態方法,對應的客戶端(這個由服務端發送時的選擇有關,這裏選擇就是All,也就是全部的客戶端)就執行對應的回調函數。
如今知道了動態方法跟回調函數是一一對應的,服務端觸發動態方法,客戶端執行回調函數。那麼動態方法何時執行呢,顯而易見,是客戶端發送請求時,也就是這裏「調用」服務端定義的Send方法時,觸發了動態方法。(有點囉嗦了,爲了保證人人看懂)。這樣一來,最後這句 chat.send("給服務端發送信息");就很容易理解了。就是請求。
2、跨域(一)
先來從NuGet獲取咱們須要跨域所需的程序集:
有了前面的基礎,對於SignalR也應該有了個基礎的印象了,下面就來說講如何用SignalR實現跨域通訊。
SignalR的實現機制與.NET WCF 或Remoting是類似的,都是使用遠程代理來實現,在具體使用上,有兩種不一樣目的的接口:PersistentConnection和Hub,其中PersistentConnection實現了長時間的輪詢,Hub用來解決實時信息的交換問題。
先來了解一下基於PersistentConnection接口的跨域。
咱們的跨域一:
一、註釋掉咱們Startup類中的惟一一行代碼:app.MapSignalR();
而後換上咱們跨域的代碼:
1 //這個參數"/echo",是咱們本身定義的一個路由,與客戶端建立SignalR的實例時對應。
2 app.Map("/echo",
3 map =>
4 {
5 map.UseCors(CorsOptions.AllowAll);
6 map.RunSignalR<EchoConnection>();
7 }
8 );
二、新建一個類,繼承於咱們的接口PersistentConnection
通訊的本質就是創建一個客戶端與服務端的一個鏈接,SignalR就是咱們幫助咱們創建這個鏈接的「中間件」
這個類中就實現了接口中定義的一系列方法,如鏈接創建,斷開鏈接,收到信息。這樣一來,咱們的chatHub類實際上就已經不起做用了,通訊的方法均可以寫在這個EchoConnection類中。
1 public class EchoConnection:PersistentConnection
2 {
3 /// <summary>
4 /// 當前鏈接數
5 /// </summary>
6 private static int _connections = 0;
7 /// <summary>
8 /// 鏈接創建時執行
9 /// </summary>
10 /// <param name="request"></param>
11 /// <param name="connectionId"></param>
12 /// <returns></returns>
13 protected override async Task OnConnected(IRequest request, string connectionId)
14 {
15 Interlocked.Increment(ref _connections);
16 await Connection.Send(connectionId, "Hi, " + connectionId + "!");
17 await Connection.Broadcast("新鏈接 " + connectionId + "開啓. 當前鏈接數: " + _connections);
18
19 }
20 /// <summary>
21 /// 鏈接關閉時執行
22 /// </summary>
23 /// <param name="request"></param>
24 /// <param name="connectionId"></param>
25 /// <returns></returns>
26 protected Task OnDisconnected(IRequest request, string connectionId)
27 {
28 Interlocked.Decrement(ref _connections);
29 return Connection.Broadcast(connectionId + " 鏈接關閉. 當前鏈接數: " + _connections);
30 }
31 /// <summary>
32 /// 服務器接收到前臺發送的消息時執行 發送請求 connection.send("信息");
33 /// </summary>
34 /// <param name="request"></param>
35 /// <param name="connectionId"></param>
36 /// <param name="data"></param>
37 /// <returns></returns>
38 protected override Task OnReceived(IRequest request, string connectionId, string data)
39 {
40 var message = connectionId + ">> " + data;
41 return Connection.Broadcast(message);
42 }
43
44 }
三、接下來就是客戶端代碼:
一樣,第一步,獲取實例,創建鏈接
var connection = $.connection("http://localhost:23013/echo");//echo就是咱們以前在Startup類中定義的路由。
connection.start();
創建鏈接後,就會觸發服務端的OnConnected事件,而後這個事件中又有觸發兩個方法:Broadcast,Send。這個就至關於咱們不跨域時本身定義的動態方法,so,咱們就應該想到客戶端應有對應的回調函數:Broadcast跟Send。可是!因爲這不是咱們自定義的,因此,名字就不是咱們想的這樣啦~~對於接收信息,咱們直接調用connection.received(function (data){})來接收,對於發送信息到服務端也不須要咱們手動來先在服務端定義方法如不跨域時在ChatHub中定義的send方法,直接connection(實例名).send()就能夠發消息到服務端。而在服務端,是由OnReceived來處理。具體的代碼一樣在本文的最後。
跨域(一)小結:
1 /// <summary>
2 /// 對應跨域的第一種方法
3 /// 一、前臺直接用connection(實例).send()就能夠發消息到服務端,用connection.received(function (data) {
4 /// $("body").append(data + "<br />");
5 /// });就能接收服務端放過來的消息
6 /// 二、後臺Connection.Send(connectionId, "消息");可發送消息給指定的客戶端
7 /// 用Connection.Broadcast("消息");可發送給全部的客戶端
8 /// 用protected override Task OnReceived(IRequest request, string connectionId, string data)
9 /// {
10 /// var message = connectionId + ">> " + data;
11 /// return Connection.Broadcast(message);
12 /// }接收客戶端發送來的消息,進行處理
13 /// </summary>
3、跨域(二)
相比PersistentConnection這種輪詢機制,基於Hub利用js動態載入執行方法纔是咱們所說的websocket技術。
Let’s start
一、有的朋友應該知道咱們的第一步都是爲前端能夠建立實例準備的了
再一次修改咱們的Startup類,以前的註釋掉:
1 app.Map("/signalr", map =>
2 {
3 map.UseCors(CorsOptions.AllowAll);
4 var hubConfiguration = new HubConfiguration
5 {
6 EnableJSONP = true//跨域的關鍵語句
7 };
8 map.RunSignalR(hubConfiguration);
9 });
10 app.MapSignalR();
上面的/signalR即咱們此次設置的路由。同樣,供客戶端匹配
一、從新創建咱們的MyHub類,跟最開始不跨域的步驟一致,新建類,繼承於Hub。而後實現接口,裏面也有鏈接、斷開鏈接、重連事件。以及咱們自定義的方法也都寫在這裏:
1 /// <summary>
2 /// 對應跨域的第二種方法
3 /// public IGroupManager Groups { get; set; } hub類中還有個組屬性,尚待開發,待看文檔,再寫demo
4 /// </summary>
5 public class MyHub : Hub
6 {
7 /// <summary>
8 /// 鏈接前執行
9 /// </summary>
10 /// <returns></returns>
11 public override System.Threading.Tasks.Task OnConnected()
12 {
13 //Clients.All.sayHello("鏈接成功");
14 Clients.Caller.sayHello("鏈接成功");//當前請求的用戶
15 return base.OnConnected();
16 }
17
18 /// <summary>
19 /// 斷開連接時執行
20 /// </summary>
21 /// <param name="stopCalled"></param>
22 /// <returns></returns>
23 public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
24 {
25 return base.OnDisconnected(stopCalled);
26 }
27
28 /// <summary>
29 /// 從新創建鏈接 如服務器重啓的時候,或者前臺獲取超時仍在等待,重連上時
30 /// </summary>
31 /// <returns></returns>
32 public override System.Threading.Tasks.Task OnReconnected()
33 {
34 return base.OnReconnected();
35 }
36
37 public void Hello(string name)//方法名稱首字母大小寫都與前臺匹配上 前臺首字母必須小寫
38 {
39 //動態方法,與前臺的回調函數名稱一致
40 Clients.All.sayHello2("第二次");
41 Clients.All.sayHello3(Context.ConnectionId);//第三個回調函數,返回連接的ConnectionId
42 }
43 }
三、咱們的客戶端
第一步:獲取實例,創建鏈接
var chat = $.connection.myHub; //獲取服務端實例 首字母小寫,不是跟服務端一致的MyHub
chat.connection.url = "http://localhost:23013/signalr";測試項目的地址
$.connection.hub.start();
第二步、接下來就是跟服務端打個招呼
Chat.server.hello(「hello」);//對應的方法中有兩個動態方法,sayHello2,sayHello3
第三步、接下來就是咱們的回調函數,對應的回調函數就能夠有兩個,與上面對應。
chat.client.sayHello2 = function(msg) {
alert(msg);
};
chat.client.sayHello3 = function(msg) {
alert(msg);
};
WebSocket小結:基於Hub接口的跨域方法的可擴展性仍是很強的,這裏有幾個本人總結的注意點(針對Hub跨域):
一、回調函數 函數名要匹配,參數能夠不匹配,後臺傳過來一個就執行一個,即若後臺同時觸發了兩個同名的sayHello 依次執行
二、必定要有回調函數,否則不算鏈接成功,能夠不調用服務器端的方法。若只是調用服務器端方法,沒寫回調函數,依然不算鏈接成功
三、在鏈接成功的狀況下,後臺先執行OnConnected事件,再執行前臺調用的某個方法
四、用回調函數來判斷是否真的鏈接成功,$.connection.hub.start().done裏直接輸出鏈接成功,是假成功。
五、因此一樣,斷開鏈接是否成功也應用回調函數來判斷,這個回調函數對應後臺代碼應在OnDisconnected事件裏
六、第五點失敗,sayGoodBye是在執行完這個事件(OnDisconnected)後才傳輸到前臺,而事件中執行完已經把連接斷開了,前臺是接收不到的。
相對於websocket協議的跨域,http應該是簡單的,網上也有不少資料。這裏就當筆者本身的筆記吧。
http下的跨域指異步的傳輸,也就是說form表單提交這種非異步方式是不存在跨域問題的。網上最多的異步跨域是jsonp方式。
Jquery跨域請求
在JavaScript中,有一個很重要的安全性限制,被稱爲「Same- Origin Policy」(同源策略)。這一策略對於JavaScript代碼可以訪問的頁面內容作了很重要的限制,
即JavaScript只能訪問與包含它的文檔或腳本 在同一域名下的內容。不一樣域名下的腳本不能互相訪問,即使是子域也不行。關於同源策略,讀者可百度更詳細的解釋,這裏再也不贅述。
可是有時候又不可避免地須要進行跨域操做,這時候「同源策略」就是一個限制了,怎麼辦呢?採用JSONP跨域GET請求是一個經常使用的解決方案,下面咱們來看一下JSONP跨域是如何實現的,
並探討下JSONP跨域的原理。
這裏提到了JSONP,那有人就問了,它同JSON有什麼區別不一樣和區別呢,接下咱們就來看看,百度百科有如下說明:
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。它基於JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一個子集。
JSON採用徹底獨立於語言的文本格式,可是也使用了相似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成爲理想的數據交換語言。
易於人閱讀和編寫,同時也易於機器解析和生成(網絡傳輸速度快)。
JSONP(JSON with Padding)是JSON的 一種「使用模式」,可用於解決主流瀏覽器的跨域數據訪問的問題。因爲同源策略,通常來講位於 server1.example.com 的網頁沒法與
不是 server1.example.com的服務器溝通,而 HTML 的<script> 元素是一個例外。利用 <script> 元素的這個開放策略,網頁能夠獲得從其餘來源動態產生的 JSON 資料,
而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並非 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。
到這裏,應該明白了,JSON是一種輕量級的數據交換格式,像xml同樣,是用來描述數據間的。JSONP是一種使用JSON數據的方式,返回的不是JSON對象,是包含JSON對象的javaScript腳本。
那JSONP是如何工做的呢,咱們知道,因爲同源策略的限制,XmlHttpRequest只容許請求當前源(域名、協議、端口)的資源。若要跨域請求出於安全性考慮是不行的,可是咱們發現,
Web頁面上調用js文件時則不受是否跨域的影響,並且擁有」src」這個屬性的標籤都擁有跨域的能力,好比<script>、<img>、<iframe>,這時候,聰明的程序猿就想到了變通的方法,
若是要進行跨域請求, 經過使用html的script標記來進行跨域請求,並在響應中返回要執行的script代碼,其中能夠直接使用JSON傳遞 javascript對象。即在跨域的服務端生成JSON數據,
而後包裝成script腳本回傳,着不就突破同源策略的限制,解決了跨域訪問的問題了麼。
下面咱們就看下怎麼實現:
前端代碼:
function CallWebServiceByJsonp() {
$("#SubEquipmentDetails").html('');
$.ajax({
type: "GET",
cache: false,
url: "http://servername/webservice/webservice.asmx/GetSingleInfo",
data: { strCparent: $("#Equipment_ID").val() },
dataType: "jsonp",
//jsonp: "callback",
jsonpCallback: "OnGetMemberSuccessByjsonp"
});
}
function OnGetMemberSuccessByjsonp(data) {
//處理data
alert(data);
}
後端的WebService代碼:
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = true)]
public void GetSingleInfo(string strCparent)
{
string ret = string.Empty;
HttpContext.Current.Response.ContentType = "application/json;charset=utf-8";
string jsonCallBackFunName = HttpContext.Current.Request.Params["callback"].ToString();
//string jsonCallBackFunName1 = HttpContext.Current.Request.QueryString["callback"].Trim();
//上面代碼必須
//中間代碼執行本身的業務操做,可返回本身的任意信息(多數據類型)
BLL.equipment eq_bll = new BLL.equipment();
List<Model.equipment> equipmentList = new List<Model.equipment>();
equipmentList = eq_bll.GetModelEquimentList(strCparent);
ret = JsonConvert.SerializeObject(equipmentList);
//下面代碼必須
HttpContext.Current.Response.Write(string.Format("{0}({1})", jsonCallBackFunName, ret));
HttpContext.Current.Response.End();
}
如上所示,前端的CallWebServiceByJsonp方法採用jQuery的ajax方法調用後端的Web服務GetSingleInfo方法,後臺的GetSingleInfo方法,
使用前端的回調方法OnGetMemberSuccessByjsonp包裝後臺的業務操做的JSON對象,返回給前端一段javascript片斷執行。巧妙的解決了跨域訪問問題。
JSONP的缺點:
JSONP不提供錯誤處理。若是動態插入的代碼正常運行,你能夠獲得返回,可是若是失敗了,那麼什麼都不會發生。
連接:http://www.cnblogs.com/JerryTian/p/4194900.html
我比較喜歡下面這種簡潔的方式:
$.ajax({ type:"GET",
url:"http://localhost:6874/Admin/Login/IsLegal", //跨域URL dataType:"json", data:{ par1:」參數1」, par2:」參數2」 }, success:function (result){ if(result.id == "123"){ $("#div1").html("驗證成功,可進行跳轉"); }else{ $("#div1").html(result.name); } }, error:function (XMLHttpRequest, textStatus,errorThrown) { alert(errorThrown); // 調用本次AJAX請求時傳遞的options參數 } });
服務端代碼:
1 public ActionResult IsLegal(string par1,string par2)
2 {
3 var str = new { id = "123", name = "joey" };
4
5 Httpcontext.Response.Appendheader("access-control-allow-origin", "*");
6
7 return json(str, jsonrequestbehavior.allowget);
8 }
或者定義一個特性標籤,這樣咱們在須要跨域的action上打上特性標籤便可。
public class CrossSiteAttribute : ActionFilterAttribute
{
private const string Origin = "Origin";
private const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
private const string originHeaderdefault = "*";
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.Add(AccessControlAllowOrigin, originHeaderdefault);
}
}
這樣就定義了一個特性標籤,[CrossSite]
使用:
[CrossSite]
public ActionResult Test()
{
return Content("跨域OK");
}
上面均是針對MVC或web api的,即對Access-Control-Allow-Origin進行了一個封裝,本質都是對配置文件進行修改。因此最直接的就是配置文件修改:
webconfig中添加:
簡單實用,同時能夠進行post傳輸。
其餘的跨域策略
一是經過Flash插件發送HTTP請求,這種方式能夠繞過瀏覽器的安全限制,但必須安裝Flash,而且跟Flash交互。不過Flash用起來麻煩,並且如今用得也愈來愈少了。
二是經過在同源域名下架設一個代理服務器來轉發,JavaScript負責把請求發送到代理服務器:
'/proxy?url=http://www.sina.com.cn'
代理服務器再把結果返回,這樣就遵照了瀏覽器的同源策略。這種方式麻煩之處在於須要服務器端額外作開發。
第三種方式稱爲JSONP,它有個限制,只能用GET請求,而且要求返回JavaScript。這種方式跨域其實是利用了瀏覽器容許跨域引用JavaScript資源:
JSONP一般以函數調用的形式返回,例如,返回JavaScript內容以下:
Foo('data')
這樣一來,咱們若是在頁面中先準備好foo()
函數,而後給頁面動態加一個<script>
節點,至關於動態讀取外域的JavaScript資源,最後就等着接收回調了。
最後是以前上文一直提到的「本文最後」
1 #region 不跨域的方法
2 //app.MapSignalR();
3 #endregion
4
5 #region 跨域的第一種方法
6 //這個參數"/echo",是咱們本身定義的一個路由,與客戶端建立SignalR的實例時對應。
7 app.Map("/echo",
8 map =>
9 {
10 map.UseCors(CorsOptions.AllowAll);
11 map.RunSignalR<EchoConnection>();
12 }
13 );
14 #endregion
15
16 #region 第一種方法前端調用代碼
17 // <script src="js/jquery-1.8.2.js"></script>
18 // <script src="js/jquery.signalR-2.2.0.min.js"></script>
19 // <script type="text/javascript">
20 // $(function () {
21 // var connection = $.connection("http://localhost:23013/echo");//對應的服務器端地址
22 // //var connection = $.connection("http://192.168.137.1/q");
23 // connection.logging = true;
24 // connection.received(function (data) {
25 // $("body").append(data + "<br />");
26 // });
27 // connection.error(function (err) {
28 // alert("存在一個錯誤. \n" +
29 // "Error: " + err.message);
30 // });
31 // connection.start().done(function () {
32 // $("#send").click(function () {
33 // connection.send($("#text").val());
34 // $("#text").val("").focus();
35 // });
36 // });
37 // });
38 //</script>
39 #endregion
40
41 #region 跨域的第二種方法
42 //app.Map("/signalr", map =>
43 //{
44 // map.UseCors(CorsOptions.AllowAll);
45 // var hubConfiguration = new HubConfiguration
46 // {
47 // EnableJSONP = true//跨域的關鍵語句
48 // };
49 // map.RunSignalR(hubConfiguration);
50 //});
51 //app.MapSignalR();
52
53 #endregion
54
55 #region 第二種方法前臺代碼
56 // <script src="js/jquery-1.8.2.js"></script>
57 // <script src="js/jquery.signalR-2.2.0.min.js"></script>
58 // <script src="http://localhost:23013/signalr/js" type="text/javascript" charset="utf-8"></script>地址是服務器端地址加上/設置的路由/js
59 // <script type="text/javascript">
60 // var chat = $.connection.myHub; //獲取服務端實例 首字母小寫,不是跟服務端一致的MyHub
61 //console.log(chat);
62 //chat.connection.url = "http://localhost:23013/signalr";測試項目的地址
63 // chat.connection.url = "http://localhost:6874/joey";
64 //一、回調函數 函數名要匹配,參數能夠不匹配,後臺傳過來一個就執行一個,即若後臺同時觸發了兩個同名的sayHello
65 //依次執行
66 //二、必定要有回調函數,否則不算鏈接成功,能夠不調用服務器端的方法。若只是調用服務器端方法,沒寫回調函數,
67 //依然不算鏈接成功
68 //三、在鏈接成功的狀況下,後臺先執行OnConnected事件,再執行前臺調用的某個方法
69 //四、用回調函數來判斷是否真的鏈接成功,$.connection.hub.start().done裏直接輸出鏈接成功,是假成功。
70 //五、因此一樣,斷開鏈接是否成功也應用回調函數來判斷,這個回調函數對應後臺代碼應在OnDisconnected事件裏
71 //六、第五點失敗,sayGoodBye是在執行完這個事件(OnDisconnected)後才傳輸到前臺,而事件中執行完已經把連接斷開了,前臺是接收不到的
72 //chat.client.sayHello = function(connectionCode) {
73 // if(connectionCode == 1)
74 // {
75 // $("#IsConnSuc").val("1");//給隱藏字段賦值,1表示鏈接成功
76 // alert("鏈接成功");
77 // }
78 // else
79 // {
80 // alert("鏈接失敗");
81 // }
82 // };
83 //-----------註釋掉的---------------------------------------------------------------------
84 // chat.client.sayGoodBye = function(connectionCode) {
85 // if(connectionCode == 1)
86 // {
87 // alert("斷開鏈接成功");
88 // }
89 // else
90 // {
91 // alert("斷開鏈接失敗");
92 // }
93 // };
94 // //創建鏈接
95 // $.connection.hub.start().done(function() {
96 // // Call the Send method on the hub.
97 // chat.server.hello("fd"); //調用服務器端定義的方法 方法名首字母小寫,後臺對應的方法首字母大小寫都能匹配上
98 // });
99 //-----------註釋掉的---------------------------------------------------------------------
100 // $(function(){
101 // $("#start").click(function(){
102 // //創建鏈接
103 // $.connection.hub.start();
104 // });
105
106 // $("#stop").click(function() {
107 // if($("#IsConnSuc").val() == "1"){
108 // //有鏈接時,才執行斷開鏈接操做
109 // $.connection.hub.stop();
110 // $("#IsConnSuc").val("0");
111 // alert("斷開鏈接成功");
112 // }
113
114 // });
115
116 // });
117
118 //</script>
119 #endregion