實驗-用Google App Engine跑CodeIgniter
Tue, 12 May 2009 20:19:02 +0800怕標題太聳動,所以先聲明一下,我只是想讓CodeIgniter根目錄下的index.php會動。(怎麼在Google App Engine跑php,請參考前篇用Google App Engine跑php ,至於為何挑CodeIgniter?因為他架構比較簡單,似乎比較容易改...)
Google App Engine有一個限制,就是無法寫入檔案系統,這對於許多framework及php有很大殺傷力。尤其是cache機制,需要可以動態產生、更新cache,這樣在Google App Engine跑php就有很多問題。例如想要跑CodeIgniter,就會出現一堆錯誤訊息,主要來自無法寫入檔案。
Quercus有一個很方便的地方,就是可以自己寫QuercusModule,QuercusModule的公有方法就會成為php函數。使用自訂的函數,配合Google App Engine的datastore服務,就有可能把datastore當作檔案系統來使用。
先trace一下CodeIgniter,看看哪裡需要改動。看了一下原始碼,有幾個程式跟IO有關,其中會影響到index.php的我判斷有:
- 'system/codeigniter/Common.php'裡面的is_really_writable函數
- 'system/libraries/Output.php',這個是cache控制的主要程式。
- 'system/libraries/Log.php',log機制也有可能會寫入檔案系統
這裡面用到的filesystem functions有幾個:
- fopen
- fread
- fwrite
- fclose
- chmod
- file_exists
- flock
- is_dir
- is_writable
- unlink
package tw.idv.fillano; import com.caucho.quercus.module.AbstractQuercusModule; import com.google.appengine.api.datastore.DatastoreService; import com.google.appengine.api.datastore.DatastoreServiceFactory; import com.google.appengine.api.datastore.EntityNotFoundException; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.KeyFactory; import com.google.appengine.api.datastore.Blob; public class GAEFileModule extends AbstractQuercusModule { public Entity GAEfopen(String filePath, String mode) { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); try { Entity en = ds.get(KeyFactory.createKey("file", filePath)); return en; } catch(EntityNotFoundException e) { Entity en = new Entity("file", filePath); return en; } } public boolean GAEfclose(Entity fd) { return true; } public String GAEfread(Entity fd, int length) { Blob b = (Blob)fd.getProperty("fileData"); byte[] sb = b.getBytes(); byte[] rb = new byte[length]; for(int i=0; i<length; i++) { rb[i] = sb[i]; } return new String(rb); } public int GAEfwrite(Entity fd, byte[] data, int length) { fd.setProperty("fileData", new Blob(data)); DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); ds.put(fd); return data.length; } public boolean GAEflock(GAEFileData fd, int operation) { return true; } public boolean GAEunlink(String filePath) { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); ds.delete(KeyFactory.createKey("file", filePath)); return false; } public boolean GAEis_dir(String pathName) { return false; } public boolean GAEfile_exists(String filePath) { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); try { @SuppressWarnings("unused") Entity en = ds.get(KeyFactory.createKey("file", filePath)); return true; } catch(EntityNotFoundException e) { return false; } } public int GAEfilesize(String filePath) { DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); try { Entity en = ds.get(KeyFactory.createKey("file", filePath)); Blob b = (Blob)en.getProperty("fileData"); return b.getBytes().length; } catch(EntityNotFoundException e) { return 0; } } public boolean GAEis_writable(String fileName) { return true; } public boolean GAEchmod(String fileName, int mode) { return true; } }
接下來把上述三個php程式裡面用到的這些函數,改成自己寫的GAE字首的函數,例如fopen->GAEfopen,然後跑一跑看看:
不過即使是這樣,距離真正可以在Google App Engine上跑CodeIgniter還差的遠,而且自己寫的這些替代方法也很簡陋。有空的話,再來試試讓CodeIgniter的ActiveRecord相關方法可以存取DataStore(不過不知道是否可行),但是還是要用JDO來定義資料就是了。
另外,其實使用datastore當作filesystem代價似乎有點昂貴,也許直接用一個in memory的cache比較好,有機會再試試。
(PS. java的程式是我一面看javadoc一面寫的,所以考慮不完整,別真的拿來用喔...)