在Genero FGL上也可以做出 RESTful 的 WEB Service。
先將回應WEB Request的方式拆解出來。如同之前在GAS設定章節中提到的,SERVICE與APPLICATION 的設定方式非常類似 (APPLICATION_LIST配置方式若不太清楚,可參考 [GAS] GBC上運作的Hello world!最下方的段落。)
整段 as.xcf 最下方就是配置 WEB SERVICE的 SERVICE_LIST段落。基底(parent) 基本都是 ws.default
若以 service 名稱 svms配置服務可參考下列範例:
<APPLICATION Id="svms" Parent="ws.default" Abstract="TRUE">
<EXECUTION>
<PATH>$(res.zone)/svms/server</PATH>
<MODULE>SvmsServer.42r</MODULE>
<ACCESS_CONTROL>
<ALLOW_FROM>$(res.access.control)</ALLOW_FROM>
</ACCESS_CONTROL>
<POOL>
<START>0</START>
<MIN_AVAILABLE>1</MIN_AVAILABLE>
<MAX_AVAILABLE>1</MAX_AVAILABLE>
</POOL>
</EXECUTION>
</APPLICATION>
與APPLICATION段落有差別的地方,主要在多了POOL設定。此設定可編配初始在 GAS啟動時,是否一併啟動此服務(START),最大同時在系統中可併行 (MAX_AVALIABLE)支數,最小留存在系統中(MIN_AVALIABLE)支數。
最小留存隻數的存在是為了不要讓每次 REQUEST進來時,都要『等待』程式的啟動(還要跑fglrun、連結database等等事務),應該要留下『待命用的』程式,隨時隨地等著 REQUEST做快速、即時的響應。
但是,要掂量掂量自己手上的 license。一個留存的service占用 1 runtime license。請仔細估算。別忘了還要保留給日常作業執行。
與 APPLICATION_LIST相同,在 SERVICE_LIST中也可以配置 GROUP
<GROUP Id="_default">$(res.zone.config)/xcf/gws-ws</GROUP>
<GROUP Id="demo">$(res.path.fgldir.demo.services)</GROUP>
<GROUP Id="svms">/u1/topprd4/svms</GROUP>
系統中也有預設提供的路徑,我們也可以加上自己有的路徑(svms)。這樣的配置好處仍舊是:不用每次調整個別的程式時,都要做 fastcgidispatch/httpdispatch的重啟。
在指定的路徑下面(也就是上方標示的 /u1/topprd4/svms,若用RESOURCE_ID -$(res.xx)-替代,則需對應as.xcf最上方的訊息),則可以配置自己的應用程式名稱,例如:我們配置一個名為『Vehicle.xcf』時,未來調用此服務的路徑就會是『http://server_ip/wtopprd/svms/Vehicle 』開頭 (此路徑以下的拆解就會在程式內處理)。
下方的 server.4gl基本都可視為一個公版,主要在設定接收 WEB Service的設定。
IMPORT COM #取用httpd套件
IMPORT FGL ServiceVehicle #實際進行服務處理的子程式
SCHEMA svms
MAIN
DEFINE req com.HttpServiceRequest
#設定發生錯誤前作可以等待的時間(預設:5sec)
CALL com.WebServiceEngine.SetOption("readwritetimeout",60)
#設定客?端、HTTPRequest和TCPRequest等待與server建立連線的最長時間(預設30,-1為無限)
CALL com.WebServiceEngine.SetOption("connectiontimeout",25)
#設定REST 操作中的 MIME類型的支持。有XML/JSON(預設BOTH)
CALL com.webserviceEngine.SetOption("server_restdefaultformat","json")
# Start server
CALL com.WebServiceEngine.Start() #開始等待
WHILE TRUE
TRY
LET req = com.WebServiceEngine.GetHttpServiceRequest(-1)
IF req IS NULL THEN
IF int_flag THEN
EXIT WHILE
END IF
ELSE
CALL ProcessRequest(req) #服務進線,往子程式傳遞處理
LET int_flag = FALSE
END IF
CATCH
EXIT WHILE
END TRY
END WHILE
END MAIN
這一段主程式主用途相當的固定。接下來看主程式內的 function 對照服務路徑的寫法:
FUNCTION ProcessRequest(req)
DEFINE req com.HTTPServiceRequest
DEFINE path STRING
DEFINE ind INT
LET path = req.getUrlPath() #依照接續下來的路徑區分要呼叫的子 function
#Request路徑 /List
LET ind = path.getIndexOf("/List",1)
IF ind>0 THEN
LET path = path.subString(ind,path.getLength())
CALL ServiceVehicle.ProcessListRequest(req, path) #此處意思就是 /List就呼叫這個
# 42m模組名.function名
RETURN
END IF
#上述這一項,若 http://server_ip/wtopprd/svms/下除了List還有其他路徑,
#均需要模仿此方式串接處理子程式
#其他不合路徑一律列為未定義 Undefined request
CALL req.sendTextResponse(400,NULL,"The Function Still UNSupport!")
END FUNCTION
主程式段,主要在做路徑對應處理的function,搭配提供的服務逐一配置即可。
子程式個別 function內,就是對應資料的處理。關於個別對應資料如何檢核、校正調整,並寫入database,就請參考相關章節進行編寫。
IMPORT com
IMPORT util
IMPORT FGL Helper #FGL一些額外的套件
IMPORT FGL WSHelper #FGL一些處理WS的額外套件
SCHEMA svms
PUBLIC FUNCTION ProcessListRequest(req, path)
DEFINE req com.HTTPServiceRequest
DEFINE path STRING
CASE req.getMethod() #確認要接的RESTful型態
WHEN "GET" CALL GetListRequest(req,path) #GET方法的處理
WHEN "POST" CALL PostListRequest(req,path) #POST方法的處理
OTHERWISE
CALL req.sendTextResponse(400,NULL, "暫時不支持本服務")
END CASE
END FUNCTION
承接主程式的路徑後,接下來在此子程式做的就是 ** action種類的判讀**。從上述的範例可以看到,在這段作業中只接受 REST的GET與POST,而若是DELETE或其他動作傳入,則會回傳 400。
接下來以 GET的方法為例,說明處理方式:
PRIVATE FUNCTION GetListRequest(req,path)
DEFINE req com.HTTPServiceRequest
DEFINE path STRING
DEFINE ops Helper.OperationsType
DEFINE txt STRING
DEFINE token STRING
DEFINE now DATETIME YEAR TO SECOND
DEFINE query WSHelper.WSQueryType
CALL Helper.SplitOperations(path,"/List") RETURNING ops #確認List後面跟上的路徑是否有次層
CALL req.getUrlQuery(query)
CASE ops.getLength()
WHEN 1 #Request發在本層的處理
#在這裡寫接收到資料的處理方式
LET response_string="xxxxx" #如果回傳的是資料,記得轉換為json格式的字串
#處理完成後要回傳的字串
CALL req.setResponseHeader("Content-Type","application/json") #回傳格式設為json
CALL req.setResponseHeader("Cache","no-cache") #不支持cache
CALL req.sendTextResponse(200,NULL, response_string) #處理完成後,回傳200及對應訊息
WHEN 2 #如果有兩層的 Request路徑,例如 /List/OnlyThisWeek 只找本周來的資料
# 處理 /Commmant/Asktime
IF ops[1]=="OnlyThisWeek" AND query.getLength()==1 THEN
#放處理機制,回傳資料放入 response_this_week
CALL req.sendTextResponse(200, NULL, response_this_week)
END IF
OTHERWISE CALL req.sendTextResponse(400,NULL,"暫時不提供此路徑的服務")
END CASE
END FUNCTION
透過上述程式的組裝,我們就可以完成基本的 RESTful 程式搭建。
沒想到這個偏門語言有系列文,學到了不少東西,謝謝分享。
資料是有的,大多較為零散,透過 ithome 的環境可以串接起來