牛奶,是給牛喝的

我今天實在太不爽了,這個錯誤其實我早就知道,但還是不小心犯了,因此我決定要好好發洩一下,同時也想提醒我身邊的朋友們,不要跟我一樣。

事情是這樣,我今天早上聽了一個很棒的演講,然後也點了一杯拿鐵,一切都很美好。不過一點都不意外的是,兩個小時後,我拉肚子了。

我知道這不是拿鐵的錯,而是那該死的牛奶。沒錯,就是專家們所謂的乳糖不耐症,我身邊很多朋友都跟我一樣,所以我相信一定有更多人也是,所以我想要跟大家聊聊這件事:

「牛奶,是給牛喝的。」

沒錯,不要再被騙了。先不談什麼專有名詞,就說一個簡單的道理:你有看過狗去喝羊的奶嗎?或是牛去喝馬的奶,或是海豚去喝鯊魚的奶,或是羚羊去喝大象的奶,或是獅子喝老虎的奶嗎?

不可能嘛!

以上所提的動物都是哺乳類動物,在正常情況下,全世界的哺乳類動物不會有A動物去喝B動物的奶,道理很簡單,不同物種的生理結構完全不同,牛有四個胃,你有嗎?

獅子老虎一輩子都不吃草也不會便秘!牛馬羊一輩子都不吃肉,也可以長出健壯的肌肉!生理結構不同,所需的營養以及攝取方式也就不同,獅子所需要的養分就是跟牛不一樣,大自然就是如此嘛!

「A動物去喝B動物的奶,這靠譜嗎?」

再來,牛長大就是吃草,獅子長大就是吃肉,大象、海豚、羚羊…等等的不用我講你都知道

可你有看過牛長大還在喝母奶嗎?有看過獅子長大還在喝母奶嗎?全世界只有一種哺乳類動物,在長大之後還沒斷奶,就是人類!

如果今天牛長大還在喝奶,那麼小牛喝牛媽媽的奶,牛媽媽喝牛奶奶的奶,那請問牛奶奶喝誰的奶?

不合邏輯嘛!

哺乳類動物,只在嬰幼兒時期哺乳,長大後就從自然界中攝取所需要的養分,全世界哺乳類皆如此。

「已經長大的我們卻還在喝奶,而且喝的是B動物的奶,這靠譜嗎?」

再回到一開始的偽命題:乳糖不耐症。當「人」去喝「牛」的奶時,當然會不適應,因為母牛的奶,演化到最後也都是適合給小牛喝,裡面有最適合小牛成長的「黃金比例」,可你不是小牛啊!我可以打賭一杯啤酒,狗如果去喝羊的奶也會發生乳糖不耐症。

如果你長期有在喝牛奶,你可能不會跟我一樣拉肚子,但這並不代表牛奶對你而言是健康的。如果一個人三餐都吃餿水,我保證他會拉肚子,但是如果他持續三五年都吃餿水,他的身體很可能就適應了。

「可是牛奶有很多鈣質耶?」

那你知道其實很多蔬菜其實鈣質更多嗎?更精確地說是可吸收的鈣質更多,像是花椰菜。牛奶有多少適合人體吸收的鈣質這件事我不知道,但是我知道他肯定有更多人體無法吸收的成分,因為它是專為小牛設計的。這些無法吸收的成分,對人體難道沒有影響?那我又怎麼會拉肚子呢?

哦對了,你知道蟑螂其實也有很多蛋白質嗎?

我也知道,人不用活得太辛苦,你可以因為乳製品美味而偶爾吃吃,但千萬不要以為多喝牛奶會「有益身體健康」,下次看到牛奶的時候仔細想想,這符合大自然的邏輯嗎?

用Windows Azure Web Application開多個WordPress網站

Windows Azure最近提供了一個不錯的服務,讓創業團隊可以3年內免費使用約價值NT6000多/月的雲端資源。興奮之餘,順手就把自己的blog搬到這上面來,一切都很容易。

但是,當我想創第二個wordpress網站的時候,問題就來了:目前我使用的windows azure subscription只允許一次開一個MySQL資料庫(Azure本身沒提供MySQL,而是透過Clear DB第三方提供),當第二個wordpress web application創建後,他會連到第一個wordpress web application一樣的資料庫,導致第二個網站出現跟第一個網站一模一樣的內容。

既然問題出在不同網站連結到同一個資料庫(精確來說是連結到同一個資料表組),解決方式也很明顯:
1. 使其連結到不同資料庫
2. 使其連結到不同資料表組

最簡單的方式當然是第二個,只要修改wp-config.php裡面的資料表前綴,使其連到不同的資料表組即可。

具體作法可以用ftp連進去修改,步驟: 每一個azure的web application都已經配好ftp了,只需要一組帳密就可以連線,如果第一次使用,你需要先設定一個FTP User:到Dashboard Reset your deployment credentials,新增一個FTP User即可。
Screen Shot 2015-10-18 at 3.49.07 PM
然後重新整理Dashboard頁面,你會看到右側Deployment/FTP User裡面多了一個使用者名稱(完整名稱為application/user name),用ftp連進去,在/site/wwwroot/裡找到wp-config,修改table_prefix,必須跟第一個wordpress application不一樣。設定完畢後,訪問這個網站的url即可。

reference

Setup a MySQL database on Ubuntu

Install mysql-server

sudo apt-get -y update; sudo apt-get -y upgrade; sudo apt-get -y install mysql-server

After installation, mysql server daemon should already running automatically.

Modify /etc/mysql/my.cnf, make

bind-address = *

Grant Privilege

Login to mysql and grant privilege for remote connecters.

mysql -u root -p 
GRANT ALL ON *.* to user@'%' IDENTIFIED BY 'password'; 
sudo service mysql restart

Then you can connect to this server with the default port 3306 (make sure you firewall is open on this port).

This is not safe but quite convenient when testing, do at your own risk.

Build a Web Client with Nodejs: wireup AngularJs + Express + coffeescript + Jade + Less

廢話

有沒有這麼一個概念:「用同一套方式開發iOS、Android以及web?」隨著javascript的日益強大以及前端MVC的迅猛發展,這個概念變得越來越可行了。

一個完整的application通常包含web端以及mobile端,而web已經可以被視為iOS, Android以外的第三個「client」了。在技術的分類裡,iOS, Android, web backend的主流開發框架都已經是MVC了,惟獨剩下web frontend尚未完全「進化」成MVC。而近年來,前端MVC的優秀框架如雨後春筍般出現,像是AngularJs, Backbone, Ember …等,讓web frontend這塊紛亂之地也逐漸被MVC給攻克佔據…

一招MVC打天下,似乎不再遙不可及。

以前也是有開發過web,但在我的戰鬥經驗裡,web frontend的經驗值始終是初心者等級。現在既然有了MVC跟咖啡,是不是應該來練一下等級。用這麼酷的方式寫web我還是第一次,這些前端的MVC中,又以Google開發的angularjs最為火紅,(因為是Google大神開發的),有道是「站在巨人的肩膀上,才能看得更遠」不是嗎?所以這兩天便學了一下AngularJs,在此做個筆記~

先說說幾個開發時主要的component:

Express

Nodejs最火紅的套件之一,建立網站必備

Jade

類似rails的haml,擁有簡潔的語法,真的很簡潔,用過就會愛上,用來編譯成html

Less

類似rails的Sass,強化版的css語法,可以有變數,巢狀結構(這超重要),用來編譯成css

Coffeescript

Javascrip雖然強大,但其語法看就噁心,寫就想吐,debug更是會令人當場身亡! Coffeescript結合了ruby跟python的優點,是我看過語法最漂亮的程式語言,不只簡潔,而且有力,會幫你避掉許多Javascript語法設計不良的小毛病(例如global scope)。就像賽亞人+地球人=超強混血,沒什麼好講的,必備。

LiveReload

寫web最常做的事就是不停的reload reload re-re-re-re-reload頁面,re到手都快抽筋了,這個小工具可以讓生命更美好一點。它會在程式存檔的當下自動reload瀏覽器頁面(神奇吧),

AngularJs

前端主角,不解釋。

Notifier

Nodejs啟動時給一個桌面小提示,可含圖示跟聲音,不須切換視窗就能知道server啟動成功或失敗。 一樣是讓生命更美好的小工具

node-dev

一旦存檔就自動重啟nodejs server,搭配Notifier服用考試都100分。

bower

第三方js與css的套件管理程式,像是rails的gem,iOS的pod。意外嗎?一開始我也覺得意外,but… welcome frontend!

前面幾個component的setup在這篇文章有不錯的介紹,我就不打算贅述了,畢竟有好的輪子就不用再重新發明了是吧。(我決定繼承那篇文章然後結束這回合。)


檔案結構

一圖解千文

 /></p>
<h5>/api</h5>
<p>放跟api server相關的程式。為什麼有api server? 因為我想把web搞得像client,就像mobile一樣ㄎㄎ</p>
<h5>/bower_components</h5>
<p>存放用bower這個前端套件管理程式所安裝的第三方套件,brow會自動創建此資料夾</p>
<h5>/node_modules</h5>
<p>存放用npm這個後端套件管理程式所安裝的第三方套件,npm會自動創建此資料夾</p>
<h5>/routes</h5>
<p>routing的規則,由於是thin server,所以只做很簡單的routing,大部份的routing會在AnglarJs裡做,在AngularJs尚未建立之前,還是需要由server供給一些基本的啟動檔案</p>
<h5>/views</h5>
<p>jade檔的家</p>
<h5>/client</h5>
<p>frontend端相關的檔案,AngularJs及Less的東西會分別存放在coffee/與less/裡,最後會compile成js與css,分別放在/public/js與/public/css資料夾裡,程式每次修改都要重來一次。(不要覺得很恐怖,這些過程都會全自動)</p>
<h3>package.json</h3>
<p>新增一個package.json檔,npm預設會找到這個檔案並安裝其內容,<code>代表安裝最新版本

{
  "name": "theApp",
  "version": "0.0.1",
  "description": "...",
  "author": "tpy",
  "devDependencies": {
    "express": "3.2.6",
    "request": "~2.27.0",
    "jade": "*",
    "underscore": "*",
    "routes":"*",
    "coffee-script":"*",
    "node-notifier":"*",
    "less-middleware":"*",
    "connect-coffee-script":"*",
    "livereload":"*"
  }
}

然後

npm install

另外幾個比較特別的要安裝在系統比較方便,以便當指令用

sudo npm install node-dev bower -g 

node-dev

安裝完node-dev後新增一個檔案.node-dev.json,內容如下

{
  "coffee": "coffee-script/register",
  "ls": "LiveScript",
  "clear": true
}

這讓node-dev可以看懂coffee,不需要手動compile成js,寫咖啡必備

app.coffee

...

lessDir = __dirname + '/client/less'
coffeeDir = __dirname + '/client/coffee'
publicDir = __dirname + '/client/public'
bowerDir = __dirname + '/bower_components'

lessMiddleware = require 'less-middleware'
app.configure ()->
    app.set 'port', port
    app.set 'views', __dirname + '/views'
    app.set 'view engine', 'jade'
    app.use express.favicon()
    app.use express.logger('dev')
    app.use express.bodyParser()
    app.use lessMiddleware lessDir, 
        dest: publicDir,
        force: true, # development only, force re-compile in each request
        preprocess: 
            path: (pathname, req) ->
                return pathname.replace '/css/', '/'
        # without preprocess: get http://domain/css/app.css will look up lessDir/css/app.less
        # with preprocess: get http://domain/css/app.css will look up lessDir/app.less

    app.use require('connect-coffee-script')
        src: coffeeDir,
        dest: publicDir+'/js',
        prefix : '/js'
        force: true   # development only
        # without prefix: get http://domain/js/item.js will look up coffeeDir/js/item.coffee
        # with prefix: get http://domain/js/item.js will look up coffeeDir/item.coffee

    app.use express.static(publicDir)
    app.use('/bower_components',  express.static(__dirname + '/bower_components'));

...

新版的express已經抽離less的模組,所以需要額外require 'less-middleware'。less-middleware能讓你coding時寫less,並讓client端自動收到css。由於我們的程式存放目錄跟user request的url不同,所以要額外做一些設定。我們這邊用lessDir作為less的來源資料夾,並用publicDir作為他compile成css後目的資料夾。在預設的情況下,當request http://domain/css/app.css時,less-middleware會去找lessDir/css/app.less,但很顯然我們的目錄結構並非如此,所以我們用他的preprocess功能,把/css濾掉,這樣才會找到lessDir/app.less,然後他也很聰明的是,將app.less`` compile完之後會直接放到回目的資料夾裏的正確置,也就是publicDir/css“`。

同理connect-coffee-script也是。它能讓你coding時寫coffee並讓client自動收到js。他的檔案結構跟user request的路徑也不一樣,所以一樣要做額外設定。他們的原理大同小異,但設定機制有點不同,如有興趣可以去github看官方的說明。

最後宣布publicDir就是可以直接存取的靜態檔案,無需經過routing規則,也就是

app.use express.static(publicDir)

另外附帶一提,為了讓bower安裝的套件能更方便地被存取,再新增一個靜態檔案存取規則:

app.use '/bower_components', express.static(__dirname + '/bower_components')

當請求http://domain/bower_components/時,會對應到bowerDir/

為什麼要把事情搞得複雜?

這是一個好問題,如果把coffee/js,less/css都放在同一個地方事情會簡單許多,我通常也不喜歡事情變得複雜,但是在coding的時候維持一個好的檔案目錄結構是一件很重要的事,這會讓程式碼易於被理解,我認為目錄結構應該跟request url獨立開來。

全部通通放在public然後再用express去擋coffee跟less的請求也是個做法,但這樣"public"資料夾的存取規則就有了例外,而我討厭例外。

接著套入notifier, development only

server = app.listen port, ()->
    console.log 'Web Server Started at port: '+ port
    notifier.notify 
        'title': 'Web Server Start',
        'message': 'Port: ' + port, 
        icon: './ok.png',
        sound: true

再套入livereload, development only

livereload = require('livereload').createServer
    exts: ['jade', 'less', 'coffee']
livereload.watch(coffeeDir);
livereload.watch(lessDir);
livereload.watch(__dirname+'/views');

目前整個app.coffee看起來像這樣:

express = require 'express'
path = require 'path'
notifier = require 'node-notifier'
lessMiddleware = require 'less-middleware'

port = 8888
app = express()
lessDir = __dirname + '/client/less'
coffeeDir = __dirname + '/client/coffee'
publicDir = __dirname + '/client/public'
bowerDir = __dirname + '/bower_components'

app.configure ()->
    app.set 'port', port
    app.set 'views', __dirname + '/views'
    app.set 'view engine', 'jade'
    app.use express.favicon()
    app.use express.logger('dev')
    app.use express.bodyParser()
    app.use lessMiddleware lessDir, 
        dest: publicDir,
        force: true,
        preprocess: 
            path: (pathname, req) ->
                return pathname.replace '/css/', '/'

    app.use require('connect-coffee-script')
        src: coffeeDir,
        dest: publicDir+'/js',
        prefix : '/js'
        force: true


    app.use express.static(publicDir)
    app.use '/bower_components',  express.static(bowerDir)


app.configure 'development', () ->
    app.use express.errorHandler()


server = app.listen port, ()->
    console.log 'Web Server Started at port: '+ port
    notifier.notify 
        'title': 'Web Server Start',
        'message': 'Port: ' + port, 
        icon: './ok.png',
        sound: true

# LiveReload
livereload = require('livereload').createServer
    exts: ['jade', 'less', 'coffee']
livereload.watch(coffeeDir);
livereload.watch(lessDir);
livereload.watch(__dirname+'/views');

啟動server

node-dev app.coffee

ng-app

新增一個layout.jade至/views

doctype
html(ng-app='theApp')
    head
        meta(charset='utf8')
        base(href='/')
        title HellpApp
        link(rel='stylesheet' href='/css/app.css')
        script(src='//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.js')

    body
        block body

在html標籤宣告ng-app,讓AngularJs的作用範圍等同整個document頁面。至於必要的angular.js從google的cdn拿就可以了XD。

再新增一個item.jade至/views

extends layout
block body
    .item
    script(src='/js/config.js')

目前就只有一個空的div(class="item")。注意到jade的的layout跟view是可以分開的,這把layout.jade從view中抽出,讓相依性進一步降低,rails也有類似的機制。

最後在末端載入config.js,這裡面會定義整個ng-app module,新增一個/client/coffee/config.coffee

angular.module 'theApp',[]

這一行基本上宣告了一個名為theApp的module,後面搭配一個空陣列表示沒有用到其他額外的module。這邊的module可以理解成一個容器,將來許多angular的元件像是component, filter, service, …等都必須放置在這個容器裡,基本上就是一個top level的容器,之後一些必要的設定也會在此進行,所以我把檔名命名成config,也有很多人會把檔名命名成app,up to you。

ng-controller

新增一個/client/coffee/controllers.coffee

app = angular.module 'theApp' #注意後面沒有array參數
class ItemController
    constructor: ($scope)->
        item = {title:'cup', photos:['1.jpg','2.jpg']}
        item.photos = (@getS3ImageUrl(photo) for photo in item.photos)
        $scope.item = item
    getS3ImageUrl: (imageName) ->
        url = 'http://images.theApp.com/'+ imageName

app.controller 'ItemController', ItemController

首先

app = angular.module 'theApp'

把剛剛在config.coffee定義好的theApp module拿出來,而

app.controller 'ItemController', ItemController

則把這個ItemController放進容器裡。

constructor裡有一個$scope參數,這是angular內建的service,angular會在instantiate物件時自動把所需的內建service餵進去。所有$開頭的變數都是angular的內建service。這個$scope代表的是一個DOM標籤的scope,例如

&lt;div ng-controller='ItemController'>
    ...
&lt;/div>

這個$scope就代表整個div,然後$scope.item = ...就表示我們在這個div的範圍裡定義了一個名為item的angular變數。這邊有完整的service列表。

然後

(@getS3ImageUrl(photo) for photo in item.photos)

用@getS3ImageUrl對item.photos做一點加工,並回傳一個新的array賦值回item.photos。這是coffee從python借來的list comprehension,酷吧! 其中@getS3ImageUrl的意思是this.getS3ImageUrl。

最後還有一個地方要修改,angular用關鍵字比對去認出那些內建的service像是$scope,但是在minify js檔案的時候變數名稱很可能都會被更動,如此便導致angular找不到關鍵字,解決這個問題的方式,就是我們同時把這個關鍵字也宣告成字串,這樣minify時就不會去動到它了。

app.controller 'ItemController', ItemController

改成

app.controller 'ItemController', ['$scope',ItemController]

現在我們有了item,可以在頁面中呈現資訊了,修改/views/item.jade

extends layout
block body
    .error(ng-hide="item")
    .item(ng-show="item" ng-controller='ItemController')
        .title {{item.title}}
        img(ng-repeat="photoUrl in item.photos" ng-src="{{photoUrl}}" style="margin: auto;")
    script(src='/js/config.js')
    script(src='/js/controllers.js')

首先link到controller.js,ItemController才會有定義,我們已經在ItemController裡定義了$scope.item,所以這邊就可以透過兩個大括號去access它{{item}}

這邊我們看到幾個用法

ng-hide

顧名思義,item存在時就這個tag就hide

ng-show

item存在時就這個tag就不hide,很ng-hide組合搭配很好用,能夠輕易地控制頁面內容,無需手動jQuery操作DOM

ng-repeat

這個ng-repeat一樣是用了跟coffee極度類似的list comprehension表示式,咖啡懂了這個就懂了。順帶一提ng-repeat跟ng-src不一定要在同一個tag,例如也可以這樣

ul(ng-repeat="photoUrl in item.photos")
    li img(ng-src="photoUrl")

Apple Push Notification Service (APNS): How your server push notifications to your apps?

Say, you have your own server, you want to push notifications to your apps, how to do that? Well, basically it should be easy because Apple do that for you by Apple Push Notification Service (APNS). But it’s not quite easy enough because the authentication process is cumbersome.

It’s all about authentication

Your Server -> APNS -> Your iOS apps

Under your provisioning profile, you have an App ID, if you want to enable the Push Notification, you need to create a certificate for it.

This is the way you do:

  • Create public/private key.
  • Create certificate signing request (CSR)
  • Upload the CSR to Apple to create certificate
  • Download the certificate
  • Convert the certificate to cert.pem
  • Convert private key to key.pem
  • Feed key.pem and cert.pem to your server
  • Get device token and upload it to your server
  • Start push notification in your server

CSR vs Certificate:

CSR is a request that you tell Apple you wan to get a certificate. You signed on it (with private key) first, submit to Apple and after Apple agrees your request, he generates a certificate and signed on it and give it to you.

Basically, a certificate is just like a paper that signed with you and Apple. It is a registration of your private key and your private key somehow represent yourself. It’s all about an identity of YOU.

Convert the certificate to cert.pem

In keychain access, find the certificate named “Apple Development iOS Push Server: appID", export (cmd+shift+E) to dev_cert.p12.

openssl pkcs12 -clcerts -nokeys -in dev_cert.p12 -out dev_cert.pem

Convert private key to key.pem

In keychain access, export the private key associated with the certificate to dev_key.p12.

openssl pkcs12 -nocerts -out dev_key.pem -in dev_key.p12

And make it unencrypted:

openssl rsa -in dev_key.pem -out dev_key.unencrypted.pem

The Client Side

The client side is relatively easy: just tell iOS you want to register remote notification and implement some delegate functions.

[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert| UIRemoteNotificationTypeBadge |  UIRemoteNotificationTypeSound)];

This method will show up an alertView asking the user to agree with your push notification request, if the user disagree, you’ll get no tokens.

# pragma mark - Remote Notification Delegate
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    NSString *key = [[[userInfo objectForKey:@"aps"] objectForKey:@"alert"] objectForKey:@"loc-key"];
    NSString *soundFile = [[userInfo objectForKey:@"aps"] objectForKey:@"sound"];
    DLog(@"didReceiveRemoteNotification: %@",key);
}

// Once successfully received the token, you should send the token to your sever and stores it. Here is just a demo.
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
    NSString* tokenString = [[[NSString stringWithFormat:@"%@",deviceToken] stringByReplacingOccurrencesOfString:@" " withString:@""]substringWithRange:NSMakeRange(1, 64)];
    DLog(@"%@",tokenString);
}

-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{

}

A very nice ref: iOS用戶端的APNS服務簡介與實現

And some nice refs that are helpful to understand the concept:

Fastest: Nginx + WordPress

Fastest Approach to Build a Nginx + WordPress

Recently, I found this nice repo, which is a linux script helping you build a website based on nginx/php/mysql/wordpress (or non-wordpress). It’s awesome, quick and easy. To see how awesome it is:

wget -qO ee rt.cx/ee && sudo bash ee     # install easyengine
ee site create example.com --wp          # Install required packages & setup WordPress on example.com