Powerbuilder inet.PostURL用法
目前PB的開發我都是把它當成一個 Remote Client UI,
所以實際上它沒有連接資料庫,而完全是透過Coldfusion的服務頁面完成資料操作,
因此很多時候需要傳遞大量資料時,簡單的 inet.GetURL 函數就變得不好用了,
因此特地研究了inet.PostURL的用法,它的用法在Help上有兩個範例(我有修改過):
第一種是簡單的呼叫一個頁面,並取得頁面回傳的HTML內容:
(請注意 linet 和 n_internetresult 都是必須先建立的物件與類別,可參考這裡)
上面這種方式並沒有傳遞參數,其實效果如同:
不過上述方式實際在執行時是有點問題,那就是Cache,如果你已經執行過一次這個方式,其回傳內容會被Cache在PBVM內,當下次再執行時,它並不會重新發出請求,反而直接從記憶體Cache中的內容重新取出並回傳。
解決的方式可以在要傳遞的內容不要放空值,隨便填一個東西,只是這樣子做真的有點多此一舉,不如直接使用 GetURL 還來得直覺些:
第二種方式帶有傳遞參數
其實上面這種效果也同等於下面的方式
但若要傳遞檔案就要使用HTML表單的 HTTP multipart/form-data
方式來處理會比較妥當,因此在PB Help說明不甚清楚的情況下就必須研究一下
HTTP的相關協定了。
在HTTP的協定中有訂製 multipart/form-data 傳輸的方式為 rfc1867協定
這個協定的基本結構如下:
可以看到淺藍色部份就是它的 Header,粉色部份就是 Arguments
由於 PostURL 的 Header 與 Arguments 分開傳輸,所以淺藍色與粉色的部份就必須分開處理。
下面展示一個上傳圖片的 Script:
注意:展示程式碼沒有檢查機制所以正常情況下該有的檢查還是要做,比如檔案開啟失敗等等,雖然你可以用PowerBuilder Exception來處理,但實際上不太建議。
附上 PowerBuilder 發送 http 請求的基本 Header 給大家參考:
所以實際上它沒有連接資料庫,而完全是透過Coldfusion的服務頁面完成資料操作,
因此很多時候需要傳遞大量資料時,簡單的 inet.GetURL 函數就變得不好用了,
因此特地研究了inet.PostURL的用法,它的用法在Help上有兩個範例(我有修改過):
第一種是簡單的呼叫一個頁面,並取得頁面回傳的HTML內容:
(請注意 linet 和 n_internetresult 都是必須先建立的物件與類別,可參考這裡)
Blob lblb_args
String ls_headers , ls_url
Long ll_length
n_internetresult lir_html
inet linet
linet = Create inet
lir_html = CREATE n_internetresult
ls_url = "http://www.mydoamin.com/testpage.cfm"
lblb_args = blob("")
ll_length = Len(lblb_args)
ls_headers = "Content-Length: " + String(ll_length) + "~n~n"
linet.PostURL (ls_url, lblb_args, ls_headers, 8080, lir_html)
destroy lir_html
destroy linet
上面這種方式並沒有傳遞參數,其實效果如同:
linet.GetURL("http://www.mydoamin.com:8080/testpage.cfm",lir_html)
不過上述方式實際在執行時是有點問題,那就是Cache,如果你已經執行過一次這個方式,其回傳內容會被Cache在PBVM內,當下次再執行時,它並不會重新發出請求,反而直接從記憶體Cache中的內容重新取出並回傳。
解決的方式可以在要傳遞的內容不要放空值,隨便填一個東西,只是這樣子做真的有點多此一舉,不如直接使用 GetURL 還來得直覺些:
lblb_args = blob(String(today()))
ll_length = Len(lblb_args)
第二種方式帶有傳遞參數
Blob lblb_args
String ls_headers , ls_url , ls_args
Long ll_length
n_internetresult lir_html
inet linet
linet = Create inet
lir_html = CREATE n_internetresult
ls_url = "http://www.mydoamin.com/testpage.cfm"
ls_args = "user=John&pwd=123456"
lblb_args = blob(ls_args)
ll_length = Len(lblb_args)
ls_headers = "Content-Type: application/x-www-form-urlencoded~n" + &
"Content-Length: " + String(ll_length) + "~n~n"
linet.PostURL (ls_url, lblb_args, ls_headers, 8080, lir_html)
destroy lir_html
destroy linet
其實上面這種效果也同等於下面的方式
linet.GetURL("http://www.mydoamin.com:8080/testpage.cfm?user=John&pwd=123456",lir_html)
但若要傳遞檔案就要使用HTML表單的 HTTP multipart/form-data
方式來處理會比較妥當,因此在PB Help說明不甚清楚的情況下就必須研究一下
HTTP的相關協定了。
在HTTP的協定中有訂製 multipart/form-data 傳輸的方式為 rfc1867協定
這個協定的基本結構如下:
Content-Type: multipart/form-data; boundary=****20110908123520****~r~n
Content-Length: 299~r~n
~r~nContent-Length: 299~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="user"~r~n
~r~n
John~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="pwd"~r~n
~r~n
123456~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="txtfile"; filename="我的訂單.txt"~r~n
Content-Type: text/plain~r~n
~r~n
...我的訂單.txt的內容...~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="imgfile"; filename="我的照片.jpg"~r~n
Content-Type: image/jpeg~r~n
Content-Transfer-Encoding: binary~r~n
~r~n
...我的照片.jpg的內容...~r~n
--****20110908123520****--~r~n
Content-Disposition: form-data; name="user"~r~n
~r~n
John~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="pwd"~r~n
~r~n
123456~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="txtfile"; filename="我的訂單.txt"~r~n
Content-Type: text/plain~r~n
~r~n
...我的訂單.txt的內容...~r~n
--****20110908123520****~r~n
Content-Disposition: form-data; name="imgfile"; filename="我的照片.jpg"~r~n
Content-Type: image/jpeg~r~n
Content-Transfer-Encoding: binary~r~n
~r~n
...我的照片.jpg的內容...~r~n
--****20110908123520****--~r~n
可以看到淺藍色部份就是它的 Header,粉色部份就是 Arguments
- 在 Header 的地方 Content-Type:設定為 multipart/form-data;
- boundary 是用來定義分隔線的樣式,其內容可以自訂,一般而言都是亂數產生的文字,盡量避開會與內容發生相同的可能性。
- Content-Length: 是 Arguments (粉色部份全部)內容的總長度。
- Header 與 Arguments 中間由一個換行分隔(白色部份)
- 粉色部份則是 Arguments的內容,而裡面每一個 argument 都由一個 boundary 分開,
- 每一個作為分隔的 boundary 前面會再加上 --
- argument內容都會先定義其來源變數名稱:Content-Disposition: form-data; name="欄位名"
- 然後會加個換行表示此欄位對應值的資料開始,直到遇到下一個 boundary 結束
- 如果 argument 內容值不是單純的文字資料,則會視情況加上 Content-Type 或是 Content-Transfer-Encoding
- 最後,整個 Arguments 的結束由 boundary 前後各加上 -- 作為結束符號
由於 PostURL 的 Header 與 Arguments 分開傳輸,所以淺藍色與粉色的部份就必須分開處理。
下面展示一個上傳圖片的 Script:
Blob lblb_args , lblb_pic
String ls_headers , ls_url , ls_args , ls_bndy
Long ll_length
n_internetresult lir_html
inet linet
integer li_file
//讀取圖片
li_file = FileOpen("C:\myPic.jpg",StreamMode!)
FileReadEx(li_file,lblb_pic)
FileClose(li_file)
//建立網路物件
linet = Create inet
lir_html = CREATE n_internetresult
ls_url = "http://www.mydoamin.com/uploadpic.cfm"
//建立分隔字串
ls_bndy = "****" + String(cpu()) + "****"
//先產生其它argument資料
ls_args = "--" + ls_bndy + "~r~n" + &
"Content-Disposition: form-data; name=~"user~"~r~n~r~n" + &
"John~r~n" + &
"--" + ls_bndy + "~r~n" + &
"Content-Disposition: form-data; name=~"pwd~"~r~n~r~n" + &
"123456~r~n" + &
"--" + ls_bndy + "~r~n" + &
"Content-Disposition: form-data; name=~"pic~"; filename=~"MyPic.jpg~"~r~n" +&
"Content-Type: image/jpeg~r~n" + &
"Content-Transfer-Encoding: binary~r~n~r~n"
//轉成binary後加上圖片資料與結束符號
lblb_args = blob(ls_args) + lblb_pic + blob("~r~n--" + ls_bndy + "--~r~n")
//計算Argumments長度
ll_length = Len(lblb_args)
ls_headers = "Content-Type: multipart/form-data; boundary=" + ls_bndy + "~r~n" + &
"Content-Length: " + String(ll_length) + "~r~n~r~n"
//Post方式上傳檔案
linet.PostURL (ls_url, lblb_args, ls_headers, 8080, lir_html)
destroy lir_html
destroy linet
注意:展示程式碼沒有檢查機制所以正常情況下該有的檢查還是要做,比如檔案開啟失敗等等,雖然你可以用PowerBuilder Exception來處理,但實際上不太建議。
附上 PowerBuilder 發送 http 請求的基本 Header 給大家參考:
POST /test/hello.html HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Content-Length: 16
User-Agent: PowerBuilder
Host: 192.168.0.155
Cache-Control: no-cache
Cookie: CFID=7835; CFTOKEN=15357209
2.0.1.1./.9./.9.
留言
我使用pb post 的方式取到回傳的session id , 但是想要繼續用這個session id 傳一些參數, 但是還到瓶頸, 不知道要如何處理, 可否賜教
for example-url...http://localhost/WebSite/WebService.asmx/calc?x=3&y=6...OK
(this page had gotten the resault that I wish)
but
in PB
...
ls_url = "http://localhost/WebSite/WebService.asmx/calc"
ls_args = "x=3&y=6"
...
inet.posturl(...)
PB can not correctly pass two parameter value x,y into this WebService(calc).
Why? was something missing?
please,Can you help me to ptch this issue?
my WebService(function calc(int x, int y)) http://localhost/WebSite/WebService.asmx/calc?x=3&y=6
the web.comfig had already added httpget,httppost,httpsoap protocol.
PB and WebService are both on a server.
PB inet.geturl to call WebService by httpget...tested OK,
But inet.posturl to call WS by httppost protocol....tested NG,
the error message is "lost parameter x value"
why? Are something wrong?
Can you help me to debug this issue?
Hi, As far as i know , It's different retrieve arguments from asp.net (.asmx) when use GET/POST method , because arguments will be store in different array .
// GET method
String data1 = this.Context.Request.QueryString["x"]
// POST method
String data1 = this.Context.Form["x"]
Hi , can you show your server script of how get the pass arguments ?
>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Blob lblb_args
String ls_headers , ls_url , ls_args
Long ll_length
nv_internetresult lir_html
inet linet
integer li_rtn
linet = Create inet
lir_html = CREATE nv_internetresult
ls_url = "http://localhost/website2/WebService.asmx/calc"
ls_args = "y=7&x=5"
lblb_args = blob(ls_args)
ll_length = Len(lblb_args)
ls_headers = "Content-Type: application/x-www-form-urlencoded~n" + &
"Content-Length: " + String(ll_length) + "~n~n"
li_rtn=linet.PostURL (ls_url, lblb_args, ls_headers, 80, lir_html)
messagebox(string(li_rtn),lir_html.is_text )
//....internetdata function is_textstring(blob)
destroy lir_html
destroy linet
Your PB script seems to be no problem, May the problem occur in the server script ?
我需要從PB的應用程式裡去判斷另一個網頁應用程式是否已經登入.
請教二個問題:
1.PB9 + IE瀏覽器, 採用Get_URL(), 但似乎有PBVM cache問題, 一定要退出PB應用程式後再登入才得以正確取得資訊. 請問有何手法? Post_URL()則無法抓到已登入狀態資訊.
2.當網頁應用程式採Chrome或Friefox瀏覽器登入, Get_URL()則完全無作用?
以上, 感謝先!
Charles
////Get_URL程式碼
integer li_rc
inet linet_main
n_cst_internet luo_data // as defined above
linet_main = CREATE inet
luo_data = CREATE n_cst_internet
SetPointer(HourGlass!)
erLogin.aspx?employeeid=00004725")
li_rc = linet_main.GetURL("http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725", luo_data)
SetPointer(Arrow!)
IF li_rc = 1 THEN
MessageBox("Data from Real's HowTo1", luo_data.is_data)
ELSE
MessageBox("Data from Real's HowTo2", "Oops rc:" + string(li_rc))
END IF
DESTROY luo_data
DESTROY linet_main
////Post_URL程式碼
Blob lblb_args
String ls_headers , ls_url
Long ll_length
n_cst_internet lir_html
inet linet
linet = Create inet
lir_html = CREATE n_cst_internet
ls_url = "http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725"
lblb_args = blob(string(f_sysdate()))
ll_length = Len(lblb_args)
ls_headers = "Content-Length: " + String(ll_length) + "~n~n"
linet.PostURL (ls_url, lblb_args, ls_headers, 80, lir_html)
MessageBox("Data from Real's HowTo1", lir_html.is_data)
destroy lir_html
destroy linet
PB的getURL我有發現它好像是基於IE底層的元件,所以有可能受到IE的設定影響,可以嘗試2種方式去解決:
1.IE->設定->網際網路選項->一般->瀏覽歷程記錄(設定)->檢查儲存的畫面是否有新版本->(選擇)每次造訪網頁時。
2.在GetURL的參數放入假的變動參數,例如:
http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725&dummy=052015221
其中dummy的值是隨機產生,借此破壞inet元件在本機上的cache條件,當然server通常不會處理自己不接受的參數。
3.先瞭解http的架構下所有Client和Server是Request/Response(離線式)不是Connection(連線式)型式,在server上無法直覺的取得各個請求的來源是否原自同一支程式?因此Server的判讀上一般都會採用IP或是Browser的token,或是COOKIE傳遞的值,或是傳遞的帳號來記錄連線狀態。
4.PB與各瀏覽器一樣都是Client,在WEB架構下除非Server會回傳給Client目前的登入帳號數量,否則Client無法判斷其他程式是否登入,因此從您的程式碼上我無法得知您如何去判斷『其他的網頁應用程式是否登入』?
所以,要判斷其他的網頁應用程式是否登入應該要從Server解決,例如您只希望只有同一支程式可以登入一個帳號,那您在傳遞參數時除了employeeid之外還要有一個ApplicationID或稱為Application token,而這個ApplicationID必須依照每部電腦特徵去產生(例如:電腦名稱、IP等等),然後Server上要對這個ApplicationID與employeeid去組合記錄,並且拒絕同一個employeeid和不同ApplicationID的請求,並返回拒絕訊息。
當你的應用程式關閉前必須進行『Logout』通知Server解除employeeid和ApplicationID的配對關係。
您可以從我的文章後面看到 PowerBuilder 發送 http 請求的基本 Header
其實是很簡單的,其中user-agent只有PowerBuilder而已,如果你用FF或是chrome或是IE,你會發現它是像這樣:
Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13
而我的Server能夠認出由哪個瀏覽器來的部分是下面這個meta:
Cookie: CFID=7835; CFTOKEN=15357209
因為我用的Server是Coldfusion,它會要求瀏覽端記錄這組Cookie已便認出是哪個瀏覽器來的,由於每種Server會發出的要求不太一樣所以這段僅能參考。
因此,若您使用GetURL便無法再增加Meta的數量,所以只能用傳遞參數去變化了。
比較大的問題是powerbuilder的inet元件不支援cookie的設定,由於session id的使用必須加在cookie裡面,如果一定要用到,網路上也有人建議使用webbrowser control元件,把webbrowser control崁入程式,隱藏,然後利用它的Method發送/接收請求,這樣它本身就可以記錄session id 到cookie裡面了。
我們的ERP系統是PB9開發, 需要與簽核系統密切整合. 於ERP裡的某些表單作業裡, 擬佈下Hyper-link, 讓user可以直接click開啟簽核系統去瀏覽該單據的簽核履歷網頁(前題是該user已經在同一台電腦登入在簽核系統裡-即認證過); 然而, 當click觸發時, 若該user並未登入在簽核系統裡, 基於安全考量, 系統是不允許開發簽核履歷頁面的. "http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725" 此URL即在判斷00004725帳號是否已登入? 若user的瀏覽器已登入在簽核系統, 上述URL會傳回"Y"; 反之, 則會提示user必須先登入簽核系統. 上述URL在任何Browser上測試均成功, 但在PB作業裡則有前PO文的種狀況.
故, 針對您第1點之回覆, 似乎並無相關性, 且應只適用於IE? 經實測結果為NG. 而採用dummy變數li_rc = linet_main.GetURL("http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725&dummy=" + string(f_sysdate()), luo_data),此作法亦告失敗.
誠如您所言, 若Get_URL真基於IE底層的元件, 那真的就沒法子了, 畢竟限制user的瀏覽器是不智之舉!
感謝您如此神速的回覆.
Charles
容我再詳細說明一下我想實的功能:
我們的ERP是以PB9所開發, ERP表單作業會發起簽核流程, 並由.Net的綱頁簽核系統接手後續的簽核作業. 故我們需要於ERP的表單作業裡, 透過click url即可開啟該表單於簽核系統的最新或歷史簽核紀錄, 而非再客製一pb window/datawindow來呈現簽核紀錄.
"http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725"此URL即是在判斷該電腦, 帳號00004725是否已登入簽核系統? 若是, return 'Y', 因已認證過, 故ERP即可開啟簽核紀錄頁面; 若否, 則會開啟簽核系統的登入頁面. 上述作法, 很明顯地是基於資安考量.
針對您回覆的第一點並IE設定, 似乎與我所遭遇的問題點關係不大, 經實測後, 並無任何幫助.
至於加上dummy參數的作法, 結果也依舊.
或許, 誠如您所言, 或許INET元件是基於ie所開發, 故無法套用於Chrome及FF等其他browser. 由於我們不可能限制user只能使用IE, 故只能再尋求其他手法了.
總之, 感謝您的分享.
Charles
您有測試過是否為ASPX的Cache造成的,而不是瀏覽器的問題?
這個以前我也做過,但我不會再用URL去認證使用者ID。
我的做法是當程式(使用者端)正常登入後,會記錄到一個LOGIN記錄,併產生一組隨機編號做為認證ID,此記錄檔會記錄使用者ID、電腦IP、登入時間、最後存取時間等,並將認證ID返回給程式做為URL查詢的代替身分。
1.程式會定時更新存取時間,保持ID有效性。
2.所有從程式發出URL請求一律帶此認證ID做為識別。
3.當URL查詢要限定查尋來源的電腦時,只要從ASPX取得存取電腦的IP,去比對認證ID的登錄電腦IP即可進行限制。
4.當URL查詢時,也會去檢查該認證ID『最後存取時間』,如果在一定時間內有保持更新,表示程式還在使用中,屬於有效,反之代表程式已關閉,則此認證ID屬於失效。
5.當查詢條件需要使用者ID,只需利用認證ID反查記錄檔可得之。
6.如果URL只是返回查詢結果(畫面),則只需利用RUN外部程式呼叫iexplorer.exe yoururlstring方式,毋須辛苦自己處理HTML的內容。
7.看到您使用li_rc = linet_main.GetURL("http://10.190.2.122/Workflow/WhetherLogin.aspx?employeeid=00004725&dummy=" + string(f_sysdate()), luo_data),比較好奇的是f_sysdate()只有到日期嗎?如果是的話,那同一日的查詢條件亦是『沒變化』啊...我還比較建議Rand的方式耶
如果要用外部瀏覽器submit複雜的查詢條件,我會在程式路徑下設計一個要submit用的HTML表單,這個表單的method="POST" target="_self",然後HTML尾部寫上javascript自動去submit表單的程式碼。
畢竟HTML是文字檔,所以input條件/值就用PB去處理填好後,再以外部瀏覽器呼叫就可以完成複雜的POST查詢動作囉。
這個方法提供給您參考