實驗-用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的我判斷有:

其他的暫時不管,我只要讓CodeIgniter的index.php會動。

這裡面用到的filesystem functions有幾個:

要讓index.php會動,其實不必全部詳細實作,只要不影響功能,可以做概念驗證就好。所以如以下寫了一個QuercusModule:
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一面寫的,所以考慮不完整,別真的拿來用喔...)