跨域ajax線上python編輯器

我打算做一個線上的python編輯器,並且是跨伺服器的,不需要頁面跳轉的,還能支援檔案上傳,在這個過程中希望能學到一些新的知識。

主要功能:

在網頁上有一個輸入框,輸入python程式碼之後,可以看到這段程式碼執行的結果。

 

附加功能:

1.      伺服器上有一個專門的資料夾用來放寫的程式碼,並且支援在該資料夾中建立多個子資料夾,用來放置多個小專案,每個資料夾中可以放置執行程式碼所需的檔案。也可上傳程式碼,也可檢視程式碼,上傳檔案,不可訪問目錄外的檔案。

2.      大概和spyder類似,左邊是程式碼框,右上是目錄檔案,右下是輸出結果,每個方框可以調整大小,可以不用頁面跳轉就可以執行程式碼。

3.      程式碼顯示支援高亮功能,行號功能。

 

工具:

1.      雲虛擬主機PHP5.5

2.      雲伺服器 Ubuntu16.04

3.      CodeIgniter 3.0.6

4.      uWSGI 2.0.12-5

5.      Django 1.11.4

6.      python 2.7.12

 

使用應用的版本:

1.      jQuery3.2.1(2.2.0)

2.      jquery-ui 1.12.1

3.      uploadify 3.2.1

4.      angular-filemanager 1.5.1

5.      codemirror-5.28.0

 

需要注意的是angular-filemanager會用到bootstrap,裡面自帶的bootstrap是3.3.6版本的,但是這個版本不支援jQuery3.X,自帶的是jQuery2.2.0,但是bootstrap有新版本3.3.7,可以支援jQuery3.X。uploadify用jQ3可以,jQ2不行,但是如果不用uploadify,用jQ2也是可以的。

 

下面分別介紹在實現各個功能的過程中,逐步達到要求所使用的方法。

執行python程式部分:

1.      本域執行

2.      本域post

3.      跨域post

4.      本域json ajax post //跨域不能json ajax

5.      本域 jsonp ajax post

6.      跨域 jsonp ajax get //jsonp ajax不能post

7.      跨域 json ajax post cors //設定Access-Control-Allow-Origin頭

這個功能是最基礎的功能,因為涉及到兩個網站的通訊,所以每個部分都要分開除錯。我採用了從簡單到複雜的步驟,逐步實現了所需的功能。

1.      本域執行是在ubuntu系統中,可以根據給出的python程式碼字串,得到該程式碼執行的結果,我使用的方法是popen,先將字串存到檔案中,然後使用r = os.popen(‘python ‘ path)得到結果。由於有些程式碼是會實時在結果中輸出進度條,會有一些退格鍵,所以做了一個處理,使得退格鍵可以往前刪除字元。

2.      本域post是在Django網站中,做一個頁面可以提交程式碼,然後提交表單,返回執行結果,只需要執行程式碼後用json儲存,然後用HttpResponse(results)返回即可。

3.      跨域post是在PHP網站中,也做一個表單,但是提交的時候是POST到Django網站的頁面,然後把返回結果中的results項取出,顯示在頁面上。Django對跨域有csrf的限制,這裡先使用csrf_exempt避免跨域訊息拒絕。

4.      本域json ajax post是先在PHP網站中測試一下json方式的ajax,只需要把輸入的程式碼實時輸出到頁面的另一個方框中。我使用的方法是jQuery,需要對$.ajax設定type、url,data:{code:code},dataType,成功和失敗的執行函式,使用$(‘#text’).html(msg[‘results’]);將返回的結果輸出到頁面中,在本域中做一個頁面是將POST的結果直接返回的即可。

5.      本域 jsonp ajax post是使用jsonp的方式傳送ajax資料,原因是json方式的ajax不能跨域使用,需要修改的是dataType為jsonp,增加jsonp:”callback”和jsonpCallback:” run_python”。使用jsonp,返回值需要做一個修改,不能直接返回json結果,需要在json結果外面包一層run_python([results:”abc”]),這樣就可以識別了。

6.      跨域 jsonp ajax get是因為jsonp會把傳送的POST請求自動轉化為GET請求,這裡需要把前面的url改成Django網站的url即可實現跨域請求。

7.      跨域 json ajax post cors是因為我這個需求是提交程式碼,GET請求字串長度有限制,所以必須要用POST方式傳遞,所以找到了cors。這個方式不是所有瀏覽器都支援,但是就目前來說是夠用的。它的使用方法是在Django網站返回資料時,增加Access-Control-Allow-Origin頭的設定,加入我PHP網站的域名,這樣PHP網站就能正常接收傳送過來的網頁了。方法是response = HttpResponse(results) response[“Access-Control-Allow-Origin”]=
“www.XXX.com” return response。因為我這裡傳送端使用的是json格式,所以這個方向沒有被拒絕。

 

元素縮放部分:

1.      Javascript //需要看懂後自行修改

2.      Jquery UI Resizable Widget //過載resize函式,設定handles

這個功能是類似spyder中可以調整各個視窗大小的功能,需要左右和上下的縮放。

1.      我在網上搜尋時第一個看到的是一段javascript程式碼,它支援將一個元素調整大小,滑鼠移動到一個元素周圍時,會變成對應的縮放的圖示,滑鼠移動即可改變改元素的大小。由於我需要指定某些邊緣是可以移動的,所以就分析了一下它具體是怎麼實現的,然後修改後可以在指定的邊緣移動。我使用的程式碼是“Generic Resize by Erik Arvidsson”,它實現的原理是,截獲滑鼠按下和滑鼠移動事件,有一個函式判斷滑鼠所在的支援resize的第一個div元素,還有一個函式是根據滑鼠位置和div元素判斷當前滑鼠的形狀。然後當滑鼠按下時,如果當前滑鼠是屬於縮放的形狀,就記錄下當前的div的資訊。當滑鼠移動時,需要根據它所在的位置判斷它的形狀,如果目前有需要移動的div,要根據滑鼠當前的位置和div上次所在的位置大小計算div的新的位置大小。

2.      上面的方法雖然可以實現縮放,但是寫起來比較麻煩,需要把其中的resizeMe改成支援4種(resizeRight等)。原本的程式碼必須在div內部點選才可以調整大小,如果改成在邊緣兩側都可以,有些點會有多個所在div,還要自定義優先順序。如果想完美實現也是可以的,但是由於我這個頁面是浮動頁面,都是百分比的,有些東西我不知道怎麼用js獲取,如果想獲取需要jQuery,所以我找了一個jQuery的方法。使用Jquery UI的Resizable Widget,通過對div增加一個class,就可以縮放它,可以限制它所在的區域,也可以通過設定handles控制它哪個方向可以縮放,還可以通過過載resize函式,使得左邊的方框改變大小後右邊的方框根據它的大小來調整,解決了我的問題。

 

傳送檔案部分:

1.      本域post

2.      本域ajax post

3.      跨域 ajax post ftp //未實現

4.      伺服器端post

5.      跨域ajax post //django-flashpolicies

我一開始想的是用PHP頁面把輸入的程式碼存成檔案,然後用ftp發到Django網站,後來因為不明原因上傳不了檔案,所以找了個傳送檔案的jQuery外掛,uploadify。這個不明原因很多天後覺得應該是我登入的ftp使用者可能許可權不夠,但是當時我就跳過了這個問題。

1.      本域post是PHP頁面,通過post把輸入框內容存到檔案,用的是ci框架的方法。

2.      本域ajax post是把表單提交改成按鈕繫結ajax請求。

3.      跨域 ajax post ftp是用ftp連線伺服器,上傳剛剛儲存好的檔案,這個方法應該是能用的,但是我沒有實現。

4.      伺服器端post是Django網站建立一個上傳檔案的頁面,就是一段小程式碼,可以上傳檔案到指定目錄,提取f=request.FILES.get(‘userfile’),然後 for chrunk inf.chunks(): fobj.write(chrunk)。

5.      跨域ajax post是用了一個外掛uploadify,簡單設定一下就可以ajax上傳,還有一些小功能。但是因為跨域flash,Django網站不能讀取到檔案的內容,所以需要安裝一個django-flashpolicies。

 

檔案管理部分:

1.      本域配置angular-filemanager×2

2.      跨域django-cors-headers //因為有XMLHttpRequest

雖然實現了檔案上傳功能,但是因為我想弄一個介面來管理上傳的檔案,所以又找了一個jQuery外掛angular-filemanager,恰好支援PHP和Django。PHP的後端用ftp,Django可以直接設定上傳的路徑。

1.      配置的過程還是不是很麻煩的,有些東西必須寫在指定地方,<html data-ng-app=”FileManagerApp”>,<divid=”filemanager” class=”ng-cloak”>,<angular-filemanager></angular-filemanager>。這個PHP的後端是ftp的,所以如果可以用ftp的話直接就可以跨域了。Django的直接就可以用。

2.      如果是跨域的話,Django首先需要用csrf_exempt暫時去掉csrf,但是這樣還是不夠。這裡和之前傳json的時候不一樣了,就算在HttpResponse的時候增加Access-Control-Allow-Origin欄位也不行。因為這個外掛使用的是XMLHttpRequest來傳遞訊息,所以接收方Django拒絕該訊息。所以我安裝了django-cors-headers,這個東西可以使得Django可以接收跨域的XMLHttpRequest訊息,也可以設定接收哪些地方的訊息,以及訊息的種類,然後就可以使用了。需要修改的地方時是在傳送方的引數設定里加上一列的:listUrl:
site ‘list’,site是Django網站。其中有兩個地方PHP和Django函式名不一樣,download和permissions。這個外掛的很多css方面是固定的,我暫時還沒有仔細看裡面的內容,它和我前面用的resize的外掛衝突,改變它的時候會有奇怪的表現,所以我讓這個div不可調整大小了。

 

實時高亮部分:

1.      配置codemirror

選這個是因為據說它比較有名,我用的話感覺還不錯,它支援的東西還是很多的,多語言,多風格,配置也比較簡單。

1.      var editor = CodeMirror.fromTextArea(document.getElementById(“area”), {lineNumbers: true, mode: “python”});,調整大小使用editor.setSize(“100%”,”auto”);,獲取其中的文字使用varcode = editor.getValue();,繫結後textarea是空的,不能直接獲取內容。需要匯入對應語言的.js。

 

額外的部分:

1.      我在讓兩列的內容一樣長時使用了一個jquery.ba-resize外掛,實時監控resize。因為右邊的檔案管理器高度會發生變化,就往下推了,而我又沒有辦法拿到它變化的地方。

2.      我之前限制了左右兩個區域的最小寬度,裡面的內容又是按百分比寬度,所以移動到邊上的話視覺效果不好,所以加了個長條型的按鈕可以把某頁面調整到100%寬度和恢復。本來是想弄一個摺疊的,不過jQuery-UI的摺疊外掛有縮排,Tab外掛表現更奇怪,所以以後有空再研究了,現在還挺好用的。

3.      我在做這個網站的時候,一直使用的是uWSGI直接把Django網站放上外網,隱約覺得不安全,所以接下來研究了一下nginx連線uWSGI,但是還是不太清楚怎樣可以更安全。