通用版父頁面跨域訪問iframe裡面的內容解決方案,jupyter+tornado跨域通訊解決方案
1、問題:獲取jupyter裡面的內容
現在已經將jupyter放進iframe裡面,現在的問題描述為:在父頁面獲取iframe裡面的內容,iframe裡面就是jupyter,這裡的父頁面是自己搭建的tornado伺服器開啟的頁面,網址為127.0.0.1:9000,iframe裡面的jupyter用的網址是127.0.0.1:8888,所以涉及到了跨域請求。
1.1、通用版解決跨域問題(通用的不限於tornado),可以用於其他任何場景
父頁面獲取iframe裡面內容的解決方案如下:
實驗如下:開兩個tornado伺服器,一個埠9000,一個埠9001
現在在9000頁面要放一個iframe指向9001的一個頁面,再在9000父頁面列印9001iframe
目標父頁面127.0.0.1:9000訪問自己iframe src=127.0.0.1:9001裡面的內容
解決這個問題的方案的思路:
1、在127.0.0.1:9000頁面使用js程式碼動態增加一個iframe其src是與127.0.0.1:9001同級目錄下的一個頁面127.0.0.1:9001/execB.html
2、然後先去9001建立好這個頁面execB.html配置好url,保證能訪問到。
3、在127.0.0.1:9001/execB.html中,去做想做的js操作(如呼叫127.0.0.1:9001中的一個js函式,或者改變背景色等都在這個頁面進行)
原理解釋:9000頁面現在有兩個iframe,一個訪問9001,一個是動態增加的訪問9001/execB.html,9000載入完後返回9001的<body><html>….</html></body>標籤,此時,9001/execB.html也返回了,但是9001/execB.html裡面有操作9001的DOM的js程式碼如:parent.window.frames[“myframe9000”].document.getElementById(“test”).innerHTML,這樣就相當於9001/execB.html再操作9001裡面的DOM,就能夠操作成功
具體程式碼如下:
127.0.0.1:9000
html程式碼:
js程式碼:
window.onload=function (ev) { function exec_iframe(){ if(typeof(exec_obj)=='undefined'){ exec_obj = document.createElement('iframe'); exec_obj.name = 'tmp_frame'; exec_obj.src = 'http://127.0.0.1:9001/execB'; exec_obj.style.display = 'none'; document.body.appendChild(exec_obj);//動態建立一個iframe }else{ exec_obj.src = 'http://127.0.0.1:9001/execB?' + Math.random(); } } exec_iframe() }
127.0.0.1 :9001
html程式碼(目標id=notebook裡面的內容被獲取,被顯示,顏色更改):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
test9001
<div id="notebook">
<p>這個頁面放到iframe裡面,目標獲取這裡的內容就算成功</p>
</div>
</body>
</html>
127.0.0.1:9001/execB.html(真正做取值的js頁面,被動態新增到9000頁面iframe)
js程式碼(去操作同一個域名下的127.0.0.1:9001的內容獲取id=notebook裡面的內容):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> </body> </html> <script src="http://lib.baomitu.com/jquery/3.3.1/jquery.js"></script> <script> //jquery操作方式,變個顏色 // $('#notebook', window.parent.myframe.document).css("backgroundColor","red") //js的操作方式,變個顏色 parent.window.frames["myframe"].document.getElementById("notebook").style.backgroundColor = "green"; var temp = parent.window.frames["myframe"].document.getElementById("notebook").innerHRML;//取值 console.log(temp) </script>
至此,跨域訪問成功~
2、目標:自己的tornado伺服器的一個父頁面(127.0.0.1:9000/test)要取得iframe裡面(127.0.0.1:8888/notebooks/test2.ipynb)的jupyter伺服器的值。
發現其實jupyter自身還有一層保護,通過閱讀文件發現,還要去設定jupyter被引用的域名是誰。
(同時可以閱讀原始碼:notebook/base/handlers.py 從62行開始定義了content_security_policy函式的操作,有相同描述,下文有解釋。)
如果不新增上面的這串程式碼,在自己寫的父頁面裡寫
<iframe id="myframe" name="myframe" src="http://127.0.0.1:8888/notebooks/test2.ipynb" frameborder="1" style="" height="400" width=“720"></iframe>
將會導致下圖1這裡都打不開,將會顯示圖2這種東西,就是self指向是不對的,不允許其他的頁面的iframe指向自己
圖1
圖2
那麼添加了下面這串之後一切顯示正常。但是現在的需求是獲取iframe的值,然後好在tornado服務端存入資料庫,好跟蹤記錄學生的學習情況。
使用1種所說的解決跨域的方案。在jupyter目錄下建立execB.html,讓這個execB.html去操作jupyter裡面的DOM獲取裡面的元素。本來是可以實現的。結果jupyter內部的content_security_policy定義的策略,讓execB.html也成了允許範圍之外的人
在notebook/base/handlers.py裡面說的很清楚,如下圖:
就是說,如果settings定義了headers就用它的,那我們現在是定義了的(就是上面讓定義的在這裡:~.jupyter/jupyter_notebook_config.py,如過不定義自己的tornado伺服器的頁面裡的iframe顯示不了,但是這裡必須顯示出來,不然沒意義)但是現在定義了,因為execB.html也是一個動態建立的iframe,它又顯示不了。現在這種跨域取值的方式因為jupyter自身安全保護的機制取不了值。只有換一種思路~~~~
3、jupyter取值通過ajax jsonp跨域將資料傳給tornado伺服器
我現在想在jupyter(隨意開啟一個jupyter notebook http://127.0.0.1:8888/notebooks/test2.ipynb)中獲取了值(學生通過jupyter列印的返回結果)之後,通過更改jupyter原始碼,在原始碼中新增ajax方式,在jupyter中將獲取的結果傳給我自己的tornado服務端,然後就可以美滋滋的將學生操作的結果值存資料庫了。
需求:將jupyter notebook中列印的結果傳到自己的tornado服務端,在tornado服務端寫sql語句將jupyter notebook前端頁面傳遞過來的資料儲存,方便顯示到其他地方(二次開發jupyter notebook,將其結果作為一種使用jupyter的學生的操作反饋顯示在網站後臺)
3.1、jupyter頁面採用修改原始碼的方式
在jupyter原始碼根目錄下找到notebook/templates/notebook.html新增如下程式碼window.onload=function () {
//var temp_result = $("#notebook div[class ~='selected'] .output_wrapper .output .output_text pre").text())
var temp_result = $("#notebook .output_wrapper .output .output_text pre").text()
console.log(temp_result)
}
3.2、新增ajax程式碼使用jsonp的方式做跨域請求
notebook/templates/notebook.html中新增ajax請求的程式碼
<script>
window.onload=function () {
//temp.push($("#notebook div[class ~='selected'] .output_wrapper .output .output_text pre").text())
var temp_result = $("#notebook .output_wrapper .output .output_text pre").text()
//console.log(temp_result)
$.ajax({
url: 'http://127.0.0.1:9000/ajaxjupyter',//http://127.0.0.1:9000/ajaxjupyter?callbackfun=jsonpCallback&temp_result=temp_result
type: 'get',
dataType: 'jsonp',
jsonp:"callbackfun",//傳遞給請求處理程式或頁面的,用以獲得jsonp回撥函式名的引數名(預設為:callback)
jsonpCallback:"jsonpCallback",//自定義的jsonp回撥函式名稱,預設為jQuery自動生成的隨機函式名,如果定義了就success:function(){}就失效
data: {
temp_result:temp_result
},
});
}
function jsonpCallback(data) {
console.log(data)
}
</script>
3.3、tornado服務端需要做接收和處理返回
class AjaxjupyterHandler(HomeHandler):
def get(self):
temp_result = self.get_argument("temp_result", "")
callbackfun = self.get_argument("callbackfun", "") #接受callback的名字callbackfun=jsonpCallback
res = dict()
res["status"] = 0
res["info"] = "新增成功"
res["temp_result"] = temp_result
self.set_header("content-type", "application/json")
data = json.dumps(res)