2010-04-14

2010-04-14 POST Base64 encoding data to Coldfusion

這幾天遇到一個詭異的問題,程式環境是前端FLASH(AS3)後端是Coldfusion.
需求是前端AS3產生FLASH畫面ScreenShot的BitmapData透過JPGEncoder轉換之後直接將binary data POST到後端接收.
一般情況下採用 Adobe Forums: uploading bitmapData to ColdFusion 這作法是OK的.

不過在一些特殊環境之下遇到了資料傳輸被中斷問題,經過測試發現是前端POST送出的binary data在某些情況下會被Client Side某些寫的不嚴謹的防火牆誤判,
導致觸發filter產生connection reset造成數據傳輸被強制中斷.

為了解決這問題經測試後決定先用Base64編碼將binary data轉為String後再POST出去,後端接收到以後再作Base64 Decode的動作還原回JPG圖檔.這樣雖然傳輸的數據量變大了,但是原則上應該可以避免被防火牆誤判.

結果前後端改過之後測試又發現新的問題,在某些時候後端Coldfusion會噴出 500 Server Error.

ROOT CAUSE:
java.lang.IllegalArgumentException
at coldfusion.filter.FormScope.parseQueryString(FormScope.java:355)
at coldfusion.filter.FormScope.parsePostData(FormScope.java:327)
at coldfusion.filter.FormScope.fillForm(FormScope.java:277)
at coldfusion.filter.FusionContext.SymTab_initForRequest(FusionContext.java:438)
at coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:33)
at coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22)
at coldfusion.filter.RequestThrottleFilter.invoke(RequestThrottleFilter.java:126)
at coldfusion.CfmServlet.service(CfmServlet.java:175)
at coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:89)
at jrun.servlet.FilterChain.doFilter(FilterChain.java:86)
at coldfusion.monitor.event.MonitoringServletFilter.doFilter(MonitoringServletFilter.java:42)
at coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46)
at jrun.servlet.FilterChain.doFilter(FilterChain.java:94)
at jrun.servlet.FilterChain.service(FilterChain.java:101)
at jrun.servlet.ServletInvoker.invoke(ServletInvoker.java:106)
at jrun.servlet.JRunInvokerChain.invokeNext(JRunInvokerChain.java:42)
at jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:286)
at jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:543)
at jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:203)
at jrunx.scheduler.ThreadPool$DownstreamMetrics.invokeRunnable(ThreadPool.java:320)
at jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:428)
at jrunx.scheduler.ThreadPool$UpstreamMetrics.invokeRunnable(ThreadPool.java:266)
at jrunx.scheduler.WorkerThread.run(WorkerThread.java:66)


花了時間測試查找才確定原因是因為Base64的可用字元與傳輸所用的 Content-Type: application/x-www-form-urlencoded 定義的保留字有所衝突.



Base64中有用到的 / = + 在 x-www-form-urlencoded 定義中是保留字元,
像下圖的情況,當POST DATA中出現兩個連續的 // 時,Coldfusion就噴500了.





解決方式有兩種.
1.前端AS3在Base64轉換完以後再作一次URLEncode的動作,把Base64Encode之後String內的 / + = 轉成 %2F %2B %3D ,後端收到之後再做URLDecode來還原後再Base64Decode,不過這樣傳輸的DATA又變更肥了.
2.前端AS3在傳Base64Encode的STRING時,把Http Header的Content-Type改為 application/octet-stream 來避開Coldfusion對於x-www-form-urlencoded的內容格式判斷

最後決定採行第二種方式,由前端AS3修改上傳時HTTP Header的Content-Type為application/octet-stream去避免後端Coldfusion自動進行格式判斷


目前測試觀察下來Coldfusion就不會再吐500了 .