Friday, February 17, 2012

Music for My Wedding.
















Tuesday, February 7, 2012

受夠了台灣的 Costco

真的有點受夠了台灣 Costco 老是在賣些美國沒賣的美國產品了。




https://www.google.com/search?q=vitamin%20goldmax 找下去,找不到一個條目的東西,台灣 Costco 也敢拿出來賣?這些採購人員是心理只有錢是吧?

以前住美國時,我大多的日用產品都是在 costco 買的,那是因為,美國 costco 設定的營業方針是『貨品以近乎進貨成本來賣、獲利全靠會員費』,賺不賺錢,就看選貨能力來吸引會員數。

Costco@USA選貨的標準又剛好跟我的收入很相近,所以,若是要買日用品又懶得做功課時,就去 costco 買;那是我相信 costco 的選貨標準,不用讓我在買日用品時,還要一個個拿起來看產地與成份,逛的很舒服

但是台灣的 Costco 逛起來給我的感覺卻不是如此, 50%的貨品是台灣採購的,讓我逛起來像是在逛市場一樣,要玩訛虞我詐的遊戲,許多商品的外包照片跟內容跟本不符,又有許多假美國商品,逛起來要心驚膽顫,一點都沒有消費的樂趣。

Tuesday, January 3, 2012

How to Protect A WebService.

最近做了不少Restful WebService的案子,也用了不少外部的WebService API,看到許多的系統,在安全性上,有許多的漏洞,因此想寫篇文章來說說,到底要怎樣保護好你的WebService,必竟,台灣主流網站安全性已經夠糟了,不用再多幾個有問題的API來把環境弄更糟。

大家用Web API,最常碰到的保護機制是 ApiKey ,但是,請不要以為 ApiKey 有什麼保護做用,HTTP Request的東西都是明碼,你傳什麼,網路上流過的機器都可以看的一清二楚,假設你做的 client 端程式是用 ApiKey 來保護 client <-> server 間的通訊的話,要破的人,只要在 client 所在的區網內,安裝 packet sniffer 就可以拿到 client 內包的 ApiKey 了。

ApiKey通常是WebService Provider拿來統計流量用的,不要以為他有什麼安全機制存在。

再來,有些 Web API 要求你要登入,才可以使用他們的 API ,如 chargify or GMail,這些服務所使用的機制是HTTP Basic Authentication ;但是非常不幸的是,HTTP Basic Authentication只不過變形的ApiKey,Basic Auth的運作方式是把 username:password 用 base64 編碼後,放在 HTTP Request Header 當成密碼傳到 server 端,因此,一樣可以被 sniffer 直接偷出來用。

當然,你會問,我們不是有用 HTTPS 來把封包加密了嗎?然而,除非你把 certificate 預載在 client 端,要不然 client <-> server 第一次的通訊是危險的,會受到 Man in the middle attack ,直接從中間把雙方收送的 certificate 換掉。


較安全的做法是,使用HTTPS Client Authentication,在包 client 就把 client 端該用的 server-signed certificate 包進 client 端裡面;這樣一來,少了透過 internet 交換 certificate 的機制,能夠確保 certificate 不會被偷換掉。

另外,使用 Client Authentication 的好處是,只有 server 端認可的 client 可以使用 API ,縱始是第三方拿到了username, password, apikey,還是沒有辦法使用你的服務。

至於有那些大廠,用了不安全的方式來保護他們的 web service api 呢? unblock-us 這邊有幾個名單


unblock-us 是怎麼破解幾大影音網站的 geo-location check 的呢?

一般的解決方案是用 VPN,但是影音串流不適合透過 VPN 來做 proxy。

就我的猜測 unblock-us 等用的解決方案應該是這樣,在 DNS 那層把 server 端的 ip 反解成他們自己的機器,當 client 端送 request 給 http://api.poorguy.com, unblock-us 會先檢查這個 call 的用途,如果是做 geo-location 反解的,就送個假的回應給 client 端,如果是其他的方式,就轉送到正常的機器。

等 client 端把這些處理做完後,要做影音串流的 rstp://stream.poorguy.com 又是傳回正常的 ip ,所以這樣用到的流量最小。

這樣等於是直接的違反人家的授權去 hack API,這種服務可以活的很久嗎??我不清楚

不過從這一點也可以看到,各大廠做 operation 的能力優劣,像我的前雇主 rhapsody就有利用 client authentication 在各式各樣的 client 端上。所以讓我想在台灣用都沒得用 :(


公商服務時間:

小弟的公司,目前有時間接設計 WebService API 的案子,若是有合作機會的,請與我連絡

Thursday, December 8, 2011

Lucene On Android

嘗試性的把 Lucene 放到 Android 上面來跑,結果不是太理想,但仍是一些心得分享出來,省去後人嘗試的時間。

Lucene 要跑在 Android 上,第一個碰上的問題是,如何把 index files 傳到手機上去,在 Lucene 中,對 index 的讀取,是以目錄為單位的,所以說,無法把 index files 放在 apk 中直接讀取,一定要存放在 device or external storage 上,才能夠使用;或者是自己弄個虛擬目錄出來,不過,這會耗用過多的計憶體空間。

我是選用把 index 放在 'src/res/raw' 底下,讓他變成 apk 的一部份,省去在網路上找個空間來放置 index 的問題,當要更新 index 時,就重編個 apk 叫使用者更新就好。

在放 lucene index 時,如果你想用 compound format 的話,可以用底下的指令,把多個 index files 包裹成單一檔案 .cfs

// run REPL with 'scala -cp luke-3.4.0_1-all.jar'

import org.apache.lucene.store.FSDirectory
import org.apache.lucene.index._
import org.apache.lucene.analysis.standard._

val source = FSDirectory.open(new java.io.File("source"))
val dest = FSDirectory.open(new java.io.File("dest"))

// open source index
val reader = IndexReader.open(source)

// create writer for compound index.
val analyzer = new StandardAnalyzer(org.apache.lucene.util.Version.LUCENE_34)
val writer = new org.apache.lucene.index.IndexWriter(dir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED)

// force writer always use compound index format.
writer.getMergePolicy.asInstanceOf[LogByteSizeMergePolicy].setNoCFSRatio(1.0)


// add source index to dest index.
writer.addIndexes(reader)
writer.optimized
writer.close

reader.close

接著,是把產生的 segment, segments_1, _0.cfs 拷到 src/res/raw 底下,讓這些檔案變成 .apk 的一部份。


接著,是要在第一次執行時,把這些 index 從 apk 中覆製到 SD 卡上或是機子上,這邊,我寫了個小工具來做這件事

import android.content.Context
import android.os.Environment
import android.util.Log

import com.bluetangstudio.android.disastermap.TaipeiDisasterApp.LogTag

import org.apache.commons.io.FileUtils
import org.apache.lucene.store.{FSDirectory, Directory}

import scala.collection.JavaConversions._
import java.io.File

/**
 *  Helper class that search for lucene index directories on the device. The search order is
 *  external storage first then local storage. If lucene index does not exist on device, this
 *  class will copy the index from the apk to the device storage.
 *
 * @param context  the application context
 * @param path     the root folder name of the index directory to use and to look for.
 * @param source   the source of index resource to copy if index does not exist on the device.
 *                 format: Seq[(filename, resourceId)]
 */
case class LuceneOpenHelper(context: Context, path: String, source: Seq[Tuple2[String, Int]]) {

    /**
     * create or open an Directory.
     */
    def open(): Option[Directory] = {
        val candidates = Seq(externalFolder, internalFolder).flatten

        // find the folder with index in it.
        val folder = candidates.filter(f => f.exists() && f.list().length > 0).headOption
        val withIndex = folder.orElse(
            candidates.find(
                f => {
                    // ensure folder is available.
                    f.exists() || f.mkdirs() match {
                        // folder is not accessible
                        case false => false

                        case _ => {
                            Log.d(LogTag, "Duplicating index from apk to %s...".format(f))
                            source.foreach(
                                s => {
                                    val is = context.getResources.openRawResource(s._2)
                                    try {
                                        FileUtils.copyInputStreamToFile(is, new File(f, s._1))
                                    } finally {
                                        is.close()
                                    }
                                }
                            )
                            true
                        }
                    }
                }
            )
        )

        return withIndex.map(FSDirectory.open(_))
    }

    private def externalFolder: Option[File] = {
        Environment.getExternalStorageState match {
            case Environment.MEDIA_MOUNTED => Option(context.getExternalFilesDir(path))
            case _ => None
        }
    }

    private def internalFolder: Option[File] = {
        return Option(new File(context.getFilesDir, path))
    }

}

最後,是在 *App 上加上這段

object MyApp {
    private val INDEX_DIRECTORY = "idx"

    private val INDEX_FILES = Seq(
        ("_0.cfs", R.raw.idx_0), 
        ("segments", R.raw.segments), 
        ("segments_1", R.raw.segments_1)
    )
}
class MyApp extends android.app.Application {

    import MyApp._

    private var _luceneSearcher: Option[IndexSearcher] = None

    def luceneSearcher: Option[IndexSearcher] = {
        if (_luceneSearcher.isEmpty) {
            Log.d(LogTag, "Initializing new IndexSearcher...")
            _luceneSearcher = LuceneOpenHelper(this, INDEX_DIRECTORY, INDEX_FILES).open().map(new IndexSearcher(_))
        }
        _luceneSearcher
    }
   
    override def onLowMemory() {
        _luceneSearcher.foreach(s => s.close())
        _luceneSearcher = None
    }
}

這樣一來,就能在 Android 上面跑 lucene-core 了.

Thursday, November 24, 2011

Analytics: Hadoop V.S. DBMS

最近兩周,我開始參加 GTUG 的活動,在活動間,有人問起,為什麼我公司選用DBMS(MySQL)來做資料分析,而不用 Hadoop。在這邊,我把跟公司的DB怪才同事的討論結果,稍為簡單敘述一下。

首先,要先理解到的一點是,在 MapReduce 及 Hadoop 出現之前,Data Mining的工具及技術,已經在DBMS上建構了有30年了,相關的技術也發產的很成熟且有許多專書論述。

ETL(Extract, Transform, Load)是這個領域的關鍵字,用 ETL 去找下去,會找到許多的書籍講怎麼做資料分析
  • Extract: 如何在雜亂的原始資料中,找出有用的訊號,並把這些訊號歸檔
    例如:把 Apache 的 access.log 轉換成一對對的 (timestamp, session)
  • Transform: 如何把整理好的訊號,轉換成更好用的中介查詢表格
    例如:把前面的 (timestamp, session) 轉換成以小時為單位的存取次數 (time-slice, count)
  • Load: 把整理好的資料,存到資料庫中。
透過這個轉換過的Fact Table,我們可以把很昂貴的『每日訪問總數查尋』從
# pseudo code 應該是錯的,不過你知道我在表達什麼就好了 :P

SELECT COUNT(UNIQUE session) FROM http_access
  GROUP BY DATE_FORMAT(timestamp,'%Y %m %d')
變成
SELECT SUM(count) FROM access_counts
  GROUP BY DATE_FORMAT(time_slice,'%Y %m %d')

假設,你的原始資料有一整年這麼多,總共有四千萬筆(40M),那麼,前者就是把四千萬筆的資料讀出來,然後過濾加總,而後者,由於資料匯入時已先處理過了,所以要處理的只有24 * 365筆資料;而這兩者間所花費的時間差易,會隨著查尋次數而產生重大差異。 

試問,若是每次你老闆跟你要資料時,你要跟他說,要等半小時才可以,還是說等我五分鐘? 至於前面的半小時是怎麼算出來的呢?

由於DB運算,瓶頸多是卡在硬碟存取上,在前面的例子中,40M筆資料,假設一筆是1K bytes,那麼,總資料量是 40GB ,從硬碟循序讀取 40GB 出來,所花的時間是 40GB / 40MB/s(硬碟速度) = 17分鐘

MapReduce & DBMS


MapReduce是把資料分拆運算,然後再加總的技術;但是,MapReduce的概念對 RDBMS 並不是什麼新奇的事,在這份IBM的文件中,便清楚的敘述到,DBMS會對 SQL 指令做編譯,DBMS內部就會依資料型態、INDEX檔、CPU Core的數量等,去做最佳化,這些,是Hadoop不會幫你做的。

既然DBMS那麼好那麼,為什麼會有 MapReduce 等技術的出現呢,那是因為,DBMS可以幫你scale up,但是最終,還是會碰上硬體的極限,那麼,這時只有 scale out ,才能解決這問題。 但是 scale out 的程式難度,總是比 scale up 困難,而且,一個不好的分析模式,並不會因為跑的快10倍,就變得比較好;因此, ETL 的概念,在使用 MapReduce 時仍是可以應用的,


講到這邊,什麼時候該用 Hadoop ,什麼時候該選用 DBMS ,應該很清楚了
  1. 首先,就是要先看你要找的分析訊號是什麼,定好分析的步驟及查詢表格
  2. 再來,看你的資料量會有多大,做些簡單的計算,看看是不是能在合理的時間內跑完
  3. 最後,看你這些報表倒底是有多常被取用,若是,這是內部的報表,只要一個月能出一次就好,那麼,Hadoop可能就大才小用;若是,你是要做產品,像Google Analytics做到即時大量分析,那麼你可能就需要用到Hadoop

    過去我在 Medio 服務時,公司有幫美國幾大電信商分析 App 的購買行為,由於,這些是每個月才需產生一次的報表,而且,資料的訊號有待我們的 data expert 去尋找,因此,我們的資料專家是把資料倒到 MicroStrategy 再去下指令分析的,並沒有用到 Hadoop

Tuesday, November 22, 2011

Source code of my Scala on Android Project.

把前面幾回講的用Scala寫的Android程式,放上 bitbucket 了,有興趣的可以自己去下載來看。

https://bitbucket.org/bluetang/android-taipei-disaster

Wednesday, November 16, 2011

My experience with Scala on Android

用Scala開發Android,是個滿有趣的經驗,大致上來因為我切入的時間點較晚,所以大部份的問題已經被前人所解決,就用 https://github.com/jberkel/android-plugin 把專案用 sbt 開好後,就可以開始寫 android.

我用的 IDE 是 intellij 11 EAP,用 sbt-idea 把 idea project 設好後,把預設的 asset pa th 從 .idea_module 改成 src/main ,就可以開始開發了。


在開發上,除了前一回碰上的proguard問題外,我還碰上另一個比較嚴重的問題-不能在Scala裡寫 AsyncTask

這問題跟SI-3622 SI-3494有關,看來是在 Scala 2.8解掉的問題,2.9又跑回來了,我這邊看到的狀況是

override protected def doInBackground(params: Params*): Result

會被Scala compiler專換成
public Result doInBackground(Seq params)
    public Result doInBackground(Params[] params)


而底下的code,則是scala compiler會吐出 overides nothing.
override protected def doInBackground(params: Array[Params]): Result

不管怎樣,都跟Android要求的protected Result doInBackground(Params[] params)不同,所以在runtime時會跑出NoSuchMethodError.

解決方法是在 java 裡寫個 bridge interface ,幫 scala compiler 搞不定的東西,在這個 interface 裡定意好

public abstract class SAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {

    protected abstract Result doInBackground(Seq<Params> params);

    @Override
    protected Result doInBackground(Params... paramses) {
        return doInBackground(JavaConversions.asScalaBuffer(Arrays.asList(paramses)));
    }
}