2014年6月30日 星期一

iOS 開發筆記 - 初探 Core Text 架構:paragraphs, lines, glyph runs and glyph



Core Text is an advanced, low-level technology for laying out text and handling fonts. The Core Text API, introduced in Mac OS X v10.5 and iOS 3.2, is accessible from all OS X and iOS environments.

先搞懂 String 在 iOS 系統內是如何被畫到 UIView 上頭:
CTFramesetter -> CTFrame (paragraphs) -> CTLine (lines) -> CTRun (glyph runs)
簡易的範例:

NSString *utf8String = @"0你1我2他";
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:utf8String];
CTLineRef line = CTLineCreateWithAttributedString((CFMutableAttributedStringRef) attrString);
CFIndex glyphLength = CTLineGetGlyphCount(line);
CFArrayRef runArray = CTLineGetGlyphRuns(line);
CFIndex runArrayLength = CFArrayGetCount(runArray);

for (CFIndex runIndex = 0; runIndex < runArrayLength; runIndex++) {
CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex(runArray,  runIndex);
CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++) {
CFRange thisGlyphRange = CFRangeMake(runGlyphIndex, 1);
CGGlyph glyph;
CGPoint position;
CTRunGetGlyphs(run, thisGlyphRange, &glyph);
CTRunGetPositions(run, thisGlyphRange, &position);

//CGContextShowGlyphsAtPositions(context, &glyph, &position, 1);
}
}

NSLog(@"intput: %lu, runArrayLength: %@, glyphLength:%@", [utf8String length], @(runArrayLength), @(glyphLength));


結果:

intput: 6, runArrayLength: 6, glyphLength:6

其中每一個 run 代表同一個 attributed string ,此例數字、中文交替,所以共有 6 個 run。另一個例子:

NSString *utf8String = @"0你1我我2他";
intput: 7, runArrayLength: 6, glyphLength:7


由於有兩個"我"在同一個 run 中,所以只有 6 個 run。

[Linux] 簡易備份加密 MySQL / MySQLDump 資料 @ Ubuntu 14.04

採用 mysqldump 備份 MySQL 資料,可搭配 --where " timestamp < '2014-06-01' AND timestamp >= '2014-05-01' " 等月份備份方式;使用 md5sum 驗證;使用 tar 和 openssl des3 加密:

  • $ mysqldump -u root -p myDatabase myTable --where " timestamp < '2014-06-01' AND timestamp >= '2014-05-01' " > mydatabase_mytable.2014-06.sql
  • $ find * -name "*.sql" -exec sh -c 'test -e {}.md5sum || md5sum {} > {}.md5sum' \; 
  • $ find * -name "*.sql" -exec sh -c 'tar -zcf - {} {}.md5sum | openssl des3 -salt -k MyPassword | dd of={}.tgz.des3' \;
連續動作:
  • $ find * -name "*.sql" -exec sh -c 'test -e {}.md5sum || md5sum {} > {}.md5sum' \; && md5sum -c *.md5sum > /dev/null && find * -name "*.sql" -exec sh -c 'test -e {}.tgz.des3 || tar -zcf - {} {}.md5sum | openssl des3 -salt -k MyPassword | dd of={}.tgz.des3' \;
如此一來,即可透過 crontab 定期一直加密新增的 *.sql 檔案(大概要處理一下 md5sum 的指令驗證那塊),加密後就可以隨意丟一堆雲端儲存服務了吧

2014年6月27日 星期五

[PHP] 使用 CURL 測試 Remote Resource Availability @ Ubuntu 14.04

有些程式跑在對岸,就該測測網路是否可取得啦

<?php
function checkRemoteResourceAvailability($target_url) {
        $ch = curl_init($target_url);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        $status = curl_exec($ch) !== false && curl_getinfo($ch, CURLINFO_HTTP_CODE) == 200;
        curl_close($ch);
        return $status;
}

echo checkRemoteResourceAvailability('http://tw.yahoo.com/favicon.ico') ? "ok" : "not available";

2014年6月25日 星期三

[Linux] Apache/GeoIP/mod_geoip: 依據 Client IP Country 回傳指定 Server Location @ Ubuntu 14.04、Apache 2.4.7

$ apt-cache show libapache2-mod-geoip
Package: libapache2-mod-geoip
Priority: optional
Section: universe/web
Installed-Size: 86
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Prach Pongpanich <prachpub@gmail.com>
Architecture: amd64
Version: 1.2.8-2
Depends: apache2-api-20120211, libc6 (>= 2.3.4), libgeoip1
Filename: pool/universe/liba/libapache2-mod-geoip/libapache2-mod-geoip_1.2.8-2_amd64.deb
Size: 18748
MD5sum: fc16528f6d8acabaf8d40c70fe47b1b2
SHA1: 457df303c09556297a8b1f6887fe7a901d8bd063
SHA256: 26eabfe728014a506186f2856a3149f00e7bfcb6db9226dbfe13976ab3b4d584
Description-en: GeoIP support for apache2
 This is an apache2 module for finding the country that a web request
 originated from. It uses the GeoIP library and database to perform
 the lookup. The module allows manipulation of client requests from within
 Apache based on the country of origin.
 .
 This module only works on Apache 2 servers.
Description-md5: e4085008663af571952df21045e8534a
Homepage: http://www.maxmind.com/app/mod_geoip
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Origin: Ubuntu

$ sudo apt-get install libapache2-mod-geoip

$ ls -la /usr/share/GeoIP/
total 3852
-rw-r--r--   1 root root  827301 Apr  7 14:20 GeoIP.dat
-rw-r--r--   1 root root 3105495 Apr  7 14:21 GeoIPv6.dat


基本上安裝完就等同啟動 mod_geoip ,可以在 /etc/apache2/mods-enabled 看到 geoip.conf, geoip.load 蹤影。

編輯 Apache configure file:

<IfModule mod_geoip.c>
        GeoIPEnable On
        <IfModule mod_rewrite.c>
                RewriteEngine On
                RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^CN$
                RewriteRule ^(.*)$ http://example.com.cn$1 [L]
        </IfModule>
</IfModule>


如此一來,當 Client IP 判斷是 CN 時,透過 Web Server 導向 CN 區的機器。

使用這招的目的是...大陸架設服務時,需要"備案",在備案還沒通過時,使用 domainname 連線過去的機器會被 ban 掉,這時候可以先稍微用已備案的或是 IP 來頂替了。

當備案成功後,就可以大方地使用 GeoDNS 的解法,讓使用者不必先連到指定機器再轉址。

最後,如果要留一些 debug 模式,可以多加上 hostname 的判斷,限定特定 hostname 才會依照使用者 IP 位置轉址。

<IfModule mod_geoip.c>
        GeoIPEnable On
        <IfModule mod_rewrite.c>
                RewriteEngine On
                RewriteCond %{HTTP_HOST} .
                RewriteCond %{HTTP_HOST} ^www\.example\.com$
                RewriteCond %{ENV:GEOIP_COUNTRY_CODE} ^CN$
                RewriteRule ^(.*)$ http://www.example.com.cn$1 [L]
        </IfModule>
</IfModule>


參考資料:

AWS 筆記 - 透過 AWS CLI 取得指定 ELB 下的所有機器的 Public IP @ Ubuntu 14.04

基於 deploying 需求,希望能夠得知指定 ELB 下的機器列表,再搭配 ssh keyfile 進行動作,以此降低人工部分,所以就需要用用 aws cli 啦,不然完全從 web ui 大多都能滿足一些操作手法。

關鍵兩個 aws cli 用法:
  • $ aws elb describe-load-balancers --load-balancer-name YourELBName
  • $ aws ec2 describe-instances --instance-ids YourEC2InstanceID
相關 AIM 權限:
  • elasticloadbalancing:DescribeLoadBalancers
  • ec2:DescribeInstances
使用 PHP 連續動作:

<?php
define("AWS_ELB_NAME", "YourELBName");

$elb_servers_public_ip = array();
$elb_info = @json_decode(@shell_exec("aws elb describe-load-balancers --load-balancer-name ".AWS_ELB_NAME), true);
if(isset($elb_info['LoadBalancerDescriptions'][0]['Instances']))
{
foreach( $elb_info['LoadBalancerDescriptions'][0]['Instances'] as $ec2_instance )
{
foreach( $ec2_instance as $ec2_id )
{
$ec2_info = @json_decode(@shell_exec("aws ec2 describe-instances --instance-ids $ec2_id 2>/dev/null"), true);
if(isset($ec2_info['Reservations'][0]['Instances'][0]['PublicIpAddress']))
array_push($elb_servers_public_ip, $ec2_info['Reservations'][0]['Instances'][0]['PublicIpAddress']);
}
}
}
print_r($elb_servers_public_ip);


其他心得:

雖然 aws ec2 describe-instances 可以一次查詢多筆,但是不知為何 aws elb describe-load-balancers 回傳的 instances 卻也有可能出現不存在的機器,這時透過 aws ec2 describe-instances 查詢多筆資料時,就會回傳機器不存在的訊息,得不到想要的資料,只好改成迴圈一筆一筆地問。

2014年6月24日 星期二

[Linux] 開機啟動時,執行 rsync 任務 @ Ubuntu 14.04

原先在 /etc/rc.local 執行,但不知為何 rsync 任務一直未被執行,且開機成功後,用 root 權限或其他角色測試執行 /etc/rc.local 卻又一切正常。有一說法是一開始可能網路未初始化,但很妙地我在 /etc/rc.local 執行 git pull 又很正常。

暫時還搞不懂出錯的地方,外加機器重開機太耗時間了 XD 先拿 crontab @reboot 的方式來頂替了。

# m h dom mon dow command
@reboot sudo /path/rsync_job.sh &


有這個需求是在 AWS 使用 Auto Scaling 時,機器會因為條件式而被關閉、重開新的,為了確保新開的機器上頭的程式碼仍夠是最新版,所以分別試用 git pull 跟 rsync 來更新。

2014年6月20日 星期五

AWS 筆記 - Amazon Route 53 GeoDNS 用法 (Routing Policy: Latency)



想要讓不同 client 查詢統一個 Domain 時,回應離使用者近的 Data Center 的機器嗎?恰好 AWS 有提供這個功能,用法:
  • 將各個 Data Center 的機器,透過 A Record 指定一個 Domain Name
  • 將使用者真正查詢對象(DomainName)設定多筆 CName Record 對應,在新增時,選擇 Latency 並挑選區域即可,例如服務亞洲可以挑日本(ap-northeast-1), 其他地方挑美國(us-west-1)等,更多區域資訊請查詢:AWS Regions and Endpoints

接著,測試時,可以透過 nslookup 來指定要查詢的 DNS Server 來模擬不同區域的查詢結果,如 Google 8.8.8.8 和最近阿里雲對外公佈的新服務 ALiDNS 235.5.5.5,就可以看到因為地域的不同產生的變化。

連續動作:
  1. 新增 A Record: www-geo-jp.changyy.org/1.1.1.1
  2. 新增 A Record: www-geo-us.changyy.org/2.2.2.2
  3. 新增 CName Record: www-geo.changyy.org/www-geo-jp.changyy.org/Latency/ap-northest-1
  4. 新增 CName Record: www-geo.changyy.org/www-geo-us.changyy.org/Latency/us-west-1
查詢:

$ nslookup www-geo.changyy.org 223.5.5.5
Server:         223.5.5.5
Address:        223.5.5.5#53

Non-authoritative answer:
www-geo.changyy.org       canonical name = www-geo-jp.changyy.org.
Name:   www-geo-jp.changyy.org
Address: 1.1.1.1

$ nslookup www-geo.changyy.org 8.8.8.8
Server:         8.8.8.8
Address:        8.8.8.8#53

Non-authoritative answer:
www-geo.changyy.org       canonical name = www-geo-us.changyy.org.
Name:   www-geo-us.changyy.org
Address: 2.2.2.2


別忘了,這種功能是需要額外收費的。

Amazon Route 53 Pricing
  • Standard Queries
    • $0.500 per million queries – first 1 Billion queries / month
    • $0.250 per million queries – over 1 Billion queries / month
  • Latency Based Routing Queries
    • $0.750 per million queries – first 1 Billion queries / month
    • $0.375 per million queries – over 1 Billion queries / month

AWS 筆記 - 從 Godaddy 搬到 AWS Route 53



AWS Route 53 有很多收費機制,但為了 GeoDNS 的使用,開始來搬遷。原先購買的 Domain name 是在 Godaddy 上購買以維護的,整個匯出流程還滿容易的。

  1. AWS Route 53: Create Hosted Zone

  2. AWS Route 53: Import Zone File

  3. Godaddy: Export DNS Setttings

  4. AWS Route 53: Import Zone File 貼上需要的,如 A系列、CNAME系列、MX系列等

  5. Godaddy: Set NameServer,改用 AWS Route 53 提供的清單



最後,可以用 dig 去追蹤看看,是不是真的改用 AWS Route 53 提供的 NS 囉

$ dig +trace example.com

2014年6月11日 星期三

[OSX] 關閉 Android Transfer AutoRun / Android File Transfer Agent @ Mac OSX 10.9.3

安裝 Android Transfer 後,可以讓 OSX 跟 Android 手機傳資料,但是,每次手機想透過 USB 線充電時,反而會使 OSX 彈跳出視窗,並且觀察充電情況,容易出現中斷等現象。

解法:
  1. 透過 Activity Monitor 把 Android File Transfer Agent 結束
  2. 把 Android File Transfer Agent 重新命名或刪掉

    $ mv /Applications/Android\ File\ Transfer.app/Contents/Resources/Android\ File\ Transfer\ Agent.app/ /Applications/Android\ File\ Transfer.app/Contents/Resources/Android\ File\ Transfer\ Agent.app.disable/

    $ mv ~/Library/Application\ Support/Google/Android\ File\ Transfer/Android\ File\ Transfer\ Agent.app/ ~/Library/Application\ Support/Google/Android\ File\ Transfer/Android\ File\ Transfer\ Agent.app.dislabe/
未來要傳輸檔案時,在手動去開啟 Android File Transfer 即可。

2014年6月10日 星期二

[Linux] 使用 Openssl 做簡易的(DES3)加密、解密 @ Ubuntu 14.04

準備資料:

$ ls
my.data


加密:

$ dd if=my.data | openssl des3 -salt -k helloworld  | dd of=my.data.des3

解密:

$ dd if=my.data.des3 | openssl des3 -d -k helloworld | dd of=my.data

如此一來,就可以搭配 tar 的方式,把檔案先 tar 起來,在走 openssl 加密:

$ tar -zcvf - src | openssl des3 -salt -k helloworld | dd of=data.tar.gz.des3

解密: $ dd if=data.tar.gz.des3 | openssl des3 -d -k helloworld | tar -xzvf -

[Linux] 使用 find -exec 之 Commands 與 Pipe 用法

原先打算:

$ find -name "*.sql" -exec tar -zcf - {} | dd of={}.tgz \;
dd: find: missing argument to `-exec'
unrecognized operand `;'
Try `dd --help' for more information.


改用 sh 來包:

$ find -name "*.sql" -exec sh -c 'tar -zcf - {} | dd of={}.tgz' \;
40984+1 records in
40984+1 records out
20984096 bytes (21 MB) copied, 3.51398 s, 6.0 MB/s

[Linux] Apache Error Log: configuration error: couldn't perform authentication. AuthType not set!: / @ Ubuntu 12.04, Ubuntu 14.04

最近部署環境時,想要偷懶共用一個 apache 設定檔,由於 Ubuntu 14.04 預設是安裝 apache 2.4.7 而 Ubuntu 12.04 預設是安裝 Apache 2.2.2。

忘記是 apache 哪版開始,預設必須加上:

Order allow,deny
Allow from all



Require all granted

才能正常使用,然而這兩段剛好一個是 apache 2.2,另一個是 apache 2.4 使用,並且不相容,所幸,還有個解法...那就是 Satisfy Any 啦

<Directory /path/www>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride None

        Order allow,deny
        allow from all

        Require all granted

        Satisfy Any
</Directory>

2014年6月6日 星期五

[Linux] PHPMyAdmin - Table 'phpmyadmin.pma_table_uiprefs' doesn't exist @ Ubuntu 14.04

主因是將某處 DB 搬遷後,統一用一個 PHPMyAdmin 管理多個 DB Server ,因此出現的問題。

解法:

$ locate create_tables.sql.
/usr/share/doc/phpmyadmin/examples/create_tables.sql.gz
$ cp /usr/share/doc/phpmyadmin/examples/create_tables.sql.gz /tmp
$ cd /tmp && gunzip /tmp/create_tables.sql.gz

$ mysql -h xxxx -u root -p < create_tables.sql
$ sudo vim /etc/phpmyadmin/config.inc.php
更新 'pma_bookmark' to 'pma__bookmark' 等一系列項目,如:
$cfg['Servers'][$i]['bookmarktable'] = 'pma__bookmark';
$cfg['Servers'][$i]['relation'] = 'pma__relation';
$cfg['Servers'][$i]['table_info'] = 'pma__table_info';
$cfg['Servers'][$i]['table_coords'] = 'pma__table_coords';
$cfg['Servers'][$i]['pdf_pages'] = 'pma__pdf_pages';
$cfg['Servers'][$i]['column_info'] = 'pma__column_info';
$cfg['Servers'][$i]['history'] = 'pma__history';
$cfg['Servers'][$i]['table_uiprefs'] = 'pma__table_uiprefs';
$cfg['Servers'][$i]['designer_coords'] = 'pma__designer_coords';
$cfg['Servers'][$i]['tracking'] = 'pma__tracking';
$cfg['Servers'][$i]['userconfig'] = 'pma__userconfig';
$cfg['Servers'][$i]['recent'] = 'pma__recent';

[Linux] 使用 nc 測試 TCP Port 是否正常 @ Ubuntu 12.04

最近在構想一個架構,在遠方 Data Center 架一個 MySQL Server ,但又在本地端架一個備份機制。當連不到遠端時,改連本地機器,所以,就需要偵測遠方 MySQL Server 的狀態。

於是乎...就是用 nc 指令來達成:

$ nc -z -w 3 remote_server 3306 && echo "OK"

其中 -w timeout 機制。

2014年6月5日 星期四

iOS 開發筆記 - DNS Lookup、Host name to IP Address

最近想把玩一下 socket ,過程中需要把 domain name 轉成 ip ,筆記一下:

#include <netdb.h>
#include <arpa/inet.h>

- (NSString*)hostnameToIPAddress:(NSString*)hostname
{
    struct hostent *remoteHostEnt = gethostbyname([hostname UTF8String]);
    struct in_addr *remoteInAddr = (struct in_addr *) remoteHostEnt->h_addr_list[0];
    return [NSString stringWithUTF8String:inet_ntoa(*remoteInAddr)];
}

NSLog(@"IP: %@", [self hostnameToIPAddress:@"tw.yahoo.com"]);
NSLog(@"IP: %@", [self hostnameToIPAddress:@"tw.yahoo.com"]);
NSLog(@"IP: %@", [self hostnameToIPAddress:@"tw.yahoo.com"]);


若看到查到結果不一樣,主因是 DNS 提供的分流服務。

2014年6月4日 星期三

iOS 開發筆記 - 初學 Swift 與 Storyboard 互動方式,以 UITableViewController 和 prepareForSegue 為例



WWDC 2014 讓 Swift 出世見人,想學習的可先看 Apple 官網 Swift 文件:The Swift Programming Language,大概只要看完 A Swift Tour 即可有基本功力,看完心得:簡潔有力。接著,就可以看看 Using Swift with Cocoa and Objective-C 這份了,也是此次筆記項目。

首先下載 Xcode 6 beta 下來用,新增 Project 時,選用 Swift 程式語言,這時的程式架構就是 Swift 語法,與 Objective-C 大同小異,初次看還滿清爽的。

AppDelegate:

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
                         
    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(application: UIApplication) {
        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }

}


ViewController:

import UIKit

class ViewController: UIViewController {
                         
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}


接著,則是使用 Storyboard,承襲之前用 Xcode 開發的模式,新增元件後,可以拖拉到對應的程式碼(此例為 ViewController),就可以填寫一些 Code 了。

例如 UIButton 行為:

    @IBOutlet var button : UIButton
 
    @IBAction func myClick(sender : AnyObject) {
        println("Click")
        button.setTitle("change", forState: UIControlState.Normal);
    }


例如 UITableViewController :

import UIKit

class MyTableViewController: UITableViewController {
    var dataList = String[]()
 
    override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
        return 1
    }
 
    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
 
    override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
     
        var cell:UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
     
        cell.text = dataList[indexPath.row]
     
        return cell
    }
 
}


以及 prepareForSegue 的用法:

    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        if segue.identifier.compare("showTable") == 0 {
            if segue.destinationViewController.isKindOfClass(MyTableViewController) {
                // Type 'AnyObject!' cannot be implicitly downcast to 'UIViewController'
                // var vc:UIViewController = segue.destinationViewController

                var tvc:MyTableViewController = segue.destinationViewController as MyTableViewController
             
                tvc.dataList.append("Hello")
                tvc.dataList.append("World")
            }
        }
    }


以上的小練習,就可以用 Swift 跟 Storyboard 切換 ViewController 等行為,而 UITableViewController 也稍微用了一下。

最後一提,目前使用 Swift 開發的不習慣之處:AutoComplete ! 沒了這招就真的等於砍掉重練了,試用的結果,不是按了 "." 之後會彈跳出來,而是要打 "->" 才會出現 Orz 我想應該還有不少待釐清的用法。

2014年6月3日 星期二

[Linux] Allow from localhost 失效 @ Ubuntu 14.04 / Apache 2.4.7

[Linux] Allow from localhost 失效 @ Ubuntu 14.04 / Apache 2.4.7

週末把某檯機器更新後,發現原先寫的 apache.conf 失效,主要是 Allow from localhost 這段:

<Directory /data/path>
AuthType Basic
AuthName "Password Required"
AuthUserFile /path/auth_pass

Order allow,deny
Allow from localhost
# Allow from DeviceIP


Satisfy any
</Directory>


直到我加上 DeviceIP 後才行。

$ wget -O /dev/null http://localhost/service/api
Resolving localhost (localhost)... 127.0.0.1
Connecting to localhost (localhost)|127.0.0.1|:443... connected.
WARNING: no certificate subject alternative name matches
        requested host name ‘localhost’.
HTTP request sent, awaiting response... 401 Unauthorized

Username/Password Authentication Failed.


追了一下,發現是 iptables 的設定,把這條拿掉就行了:

iptables --table nat --append POSTROUTING --jump MASQUERADE

此筆記是用來記錄,iptables 的影響 Orz

2014年6月1日 星期日

Apple TV 3 以及觀看大陸頻道的原理



前陣子在 PCHOME 特價時下標了 XD 其實觀望許久,甚至在露天也時時觀望著,總之,家裡就又多了一台 Apple Device 了 :P

這陣子一直都只使用 AirPlay 的方式,記得曾聽別人說可以直接觀看大陸影音網站,就隨意找了一下,使用之後,大概猜測的 Hack 原理:
  • 原先 Apple TV 有提供"預告片"的機制,既然這是個動態內容,猜大概是要透過 API 到遠端 Server 要資料,因此,透過這個管道替換掉 "預告片" 的內容
  • 透過 DNS 的方式,讓 API 查詢對象從 Apple Server 換到私人機器
然而,在 2013 年中之後,Apple TV 稍作修正,查詢的 API 都改走 HTTPS 方式,導致透過 DNS 更換 API 查詢對象失敗,但解法仍有:
  • 更換 Apple TV 內,對 Apple Server 的憑證內容,以此又破壞掉 HTTPS 保護
這場路一直走下去,只要硬體沒有被保護,那應該就可以一直玩下去了,此次 Apple TV 硬體方面,其實是透過 Apple OSX App Store 的官方工具修改的,如憑證資訊。

此外,在台灣若要觀看大陸內影音網站,則必須透過 Proxy 的機制,而影音網站大多的保護方式在於"取得影片播放清單m3u8"的那個步驟做 IP 管制,而非 video binary data 的取得,所以,網路上就有一些解法讓 Chrome/Firefox 就可以觀看到大陸影音。



測試流程:
  1. 更改 DNS (180.153.225.136)
  2. 更改 Apple TV HTTPS 憑證
    1. 下載 appletv-fix.mobileconfig
    2. 使用 Apple Configurator 使用全域 HTTP 代理伺服器 (http://yo.uku.im/proxy.pac)
  3. 當 Apple TV 開機好後,透過 MicroUSB 線連接
  4. 透過 Apple Configurator 將設定新增至 Apple TV
如此一來,點選"預告片"時,就可以看到對岸客製化的服務:


最後一提,因為上述流程改變了 Apple HTTPS 憑證資訊(其實 Proxy server 也能做到一些),因此,建議不要再登入 iTunes Store了,畢竟已經無法保證你是否是跟真正 Apple Service 連結,還是跟大陸的私人機器溝通,會有帳密安全性問題。

相關資料:

[NodeJS] 透過 Wget + HTTP Proxy Server 架構進行 Content 分析

前陣子研究抓取資料時,發現 web crawler 搭配 proxy 時,可以在 proxy 這層進行資料的處理、分析,就一直思考到底要用 python 還是 node.js 測試,最後,實在是 node.js 方便許多,就...衝啦。

在進行之前,小提一下 Proxy Server 本身就有 Internet Content Adaptation Protocol (ICAP) 機制,其實只需要架一個 Squid 在寫一些 ICAP 即可達到類似的效果,有興趣可參考:[Python] 使用 PyICAP 淺玩 ICAP 與 Squid (以 Response Modification / RESPMOD 為例) @ Ubuntu 12.04

對於沒開發過 node.js 的我,就先參考 node-http-proxy 的目錄結構建立起來一個 open source:node-content-filter-proxy

這兩天抽空把玩的心得:
  • 支援 HTTP Requests
  • 尚未處理 HTTPS Requests

抽取 <a href="...">...</a> 用法:


$ cd node-content-filter-proxy/examples && npm install
$ clear;node extract-hmtl-a-href.js
1 Jun 10:20:22 - Service Running at 3128 Port


透過 wget proxy usage:

$ http_proxy="http://localhost:3128" https_proxy="http://localhost:3128" wget -qO -  http://www.google.com/
<a href="http://www.google.com.sg/imghp?hl=en&tab=wi">Images<a/>
<a href="http://maps.google.com.sg/maps?hl=en&tab=wl">Maps<a/>
<a href="https://play.google.com/?hl=en&tab=w8">Play<a/>
...


簡單的說,想要透過既有的 crawler (wget) 去下載資料,但是,資料中我只對 hyperlink 感到興趣,所以透過 proxy server 更新成只回傳 hyperlink 就好,維持 hyperlink 結構可以讓 wget --recursive 抓資料。

如此一來,原先 crawer = wget 架構,轉形成 crawler = wget + proxy server + content analysis 模式,可以專心擺重在內容分析,不必從頭刻一隻類似 wget 的程式。

最後一提,在 extract-hmtl-a-href.js 範例中,採用的不是透過 regular expression 去分析 link ,而是接近一隻 Javascript Rendering Engine (cheerio) ,所以未來可以做的事更多了 :) 至於效率部分倒還好,畢竟 crawler 抓太快會被 ban 掉,此外,分析 content 時,也能設計複雜更高的 seed list (回吐  hyperlink 時),能避開 DOS 現象(例如限定某個 domain 、網站一天只抓2000筆等)。