顯示具有 軟體開發 標籤的文章。 顯示所有文章
顯示具有 軟體開發 標籤的文章。 顯示所有文章

2019/01/16

以Python程式產生InfluxDB資料

我們接下來將寫個Python程式,來模擬溫度sensor產生資料並寫入InfluxDB中。然後利用這些資料,在Chronograf中設定dashboard的樣式。

要讓Python連上InfluxDB,我們需要Python-InfluxDB connection。我們可以使用pip來安裝這個connection元件:"pip install influxdb"。詳細指令介紹可參考這一頁。(此範例使用的是Python 3.x)

接下來的程式是模擬廚房、臥室、客廳三個溫度感應器,每秒鐘會回報溫度到InfluxDB。程式一開始,會先建立與InfluxDB server的連結,並建立一個 "sample"的database。我們準備了一個obj的json物件樣板。大家可以把measurement想像成表格(table)名稱。這裡有兩個columns,一個是location用來存放感應器位置,另一個是value來存放溫度。前面有提過,tag是indexed column,field是non-indexed column。由於InfluxDB是時間序列資料庫,因此每筆資料都隱含了一個time的column。

開始執行此Python程式後,就可以看到InfluxDB視窗一直有log message在跑,如果沒有error message,就表示此python程式正在餵資料到InfluxDB。




from influxdb import InfluxDBClient
import random
import time

host = "localhost" #influxdb IP
port = 8086 #influxdb port
username = "root" #influxdb username
password = "root" #influxdb password
dbname = "sample" #database name

# json object template
obj = {'measurement':'temperature', 'tags':{'location':''}, 'fields':{'value':0}}

# arrays for location names and temperatures
locations = ['kitchen', 'bedroom', 'living room']
t = [20] * len(locations)

client = InfluxDBClient(host, port, username, password) # connect influxdb
client.create_database(dbname) # create database

while True:
 for i in range(0,len(locations)):
  t[i] = round(t[i] + random.uniform(-0.5, 0.5), 2) # generate temperature

  # prepare data object
  obj['tags']['location'] = locations[i]
  obj['fields']['value'] = t[i]
  print(obj)

  # write data to influxdb
  client.write_points([obj], 'ms', dbname)

 time.sleep(1)


Python-InfluxDB connection元件的相關資料,可參考這頁的說明。

我們可以把InfluxDB client開啟,打入下列指令,來看最近三秒寫入InfluxDB的資料,確認資料確實有寫入。從這裡我們也可看到,除了location與value這兩個我們設定的欄位外,還有一個time欄位。

接著我們再用瀏覽器進入Chronograf網頁介面。(http://localhost:8888)
我們到Dashboard這頁,可以看到下面的畫面。

點選line,然後add data,接下來都可以用點選的方式來產生dashboard圖樣。我們先產生一個彙總的圖表。點選sample,再點選temperature,把三個地點都勾起來,並點選group by location,然後再把value勾起來。這個操作過程會自動幫我們產生InfluxQL。完成後點選右上角的綠色勾即可儲存。

回到Dashboard頁面,可以設定此圖表之位置與大小,並可在右上角設定更新速度與資料顯示長度。

正上方有Add a Cell to Dashboard藍色按鈕,可以加入更多圖表。大家可以去玩玩各種圖表,了解Chronograf。Chronograf的使用說明可以參考官網的文件。


InfluxDB

若想了解時間序列資料,可參考這篇簡述。
InfluxDB應該是目前佔有率最高的TSDB了,因此我們利用InfluxDB來了解TSDB的運作。

這裡是InfluxData的官網,其open source的版本中有一個TICK stack的架構,包含了四個元件:


  • Telegraf:資料收集元件
  • InfluxDB:資料儲存元件
  • Chronograf:視覺化操作介面
  • Kapacitor:資料處理引擎
這四個元件組合起來,對大部分的應用來說,應該是相當充足了。

為熟悉InfluxDB,我們透過關聯式資料庫的概念來了解一下InfluxDB的架構。你也可以透過官網的Key concepts來做更深入的了解。
  • database:這個概念跟關聯式資料庫一樣
  • measurement:相當於表格(table)
  • tag:有索引的欄位 (indexed column)
  • field:沒有索引的欄位 (non-indexed column)
先提這四個名詞。如果各位想了解更多詞彙,可以參考官網上的Glossary。

為了快速達到了解InfluxDB,我們只先專注在InfluxDB與Chronograf。首先先來安裝這兩個元件。我們以Windows為使用環境。請先到官網的下載頁面進行下載。

下載的是zip格式檔案,只要將之解壓縮即可。我們先來看influxdb.zip,解開後會有5個exe執行檔。主要的有兩個:influxd.exe是InfluxDB server,執行起來後可以看到這樣的畫面。server預設會使用port 8086來接收API指令。因此系統的安全設定上要允許使用這個port。資料庫預設的username及password都是root。

influx.exe 是client程式,我們可以利用這個程式來操作資料庫。直接執行後便可以連接到本機上資料庫。我們可以先試一個指令 "show databases" 來看看資料庫是否正常運作。如果有回應目前資料庫中databases清單,便表示系統正常運作。 (由於才剛剛安裝,應該只有 _internal 這個內部資料庫)
我們可以用exit指令來離開client程式。
InfluxDB使用的query language是InfluxQL,官網上有一頁做了與SQL的比較,大家可以參考一下。

接下來解開chronograf.zip後,有兩個exe。chronograf.exe是web server程式。執行後以瀏覽器連接 http://localhost:8888,就可以進入到視覺化界面。

我們可以先到configuration頁面檢視目前與InfluxDB server的連線狀況

到目前InfluxDB與Chronograf都完成安裝並可以正常執行。要停止這兩個server的執行,只要按ctrl-C即可。

下一篇,我們將寫個Python程式來為資料進資料庫,並在Chronograf中設定dashboard的圖表。

2019/01/14

時間序列資料與資料庫

再資料處理領域中,有一類型的資料是跟時間有密切關係的,這類的資料紀錄了某事物隨著時間變動的狀態,例如某商品的價格資訊、某廠房的環境溫溼度、某物流車的位置與速度等等。這些資料都屬於時間序列資料(Time-Series Data)。

這類型的資料有一些特質:

  • 資料是用時間排序的一組隨機變量,例如下圖,數值會隨著時間的變化而改變,因此時間是很重要的資料維度之一

  • 資料的產生可能是快速、大量且持續
    例如某個機台,每秒鐘會產生100筆資料,每筆資料約250KB,一天下來單單一個機台的資料量就高達2TB了,而且只要生產線保持運作,資料就不斷的產生
有相當多領域的資料都具有時間序列的特性,如計量經濟學、數學金融、天氣預報、地震預測、通信工程等等。尤其IoT物聯網應用蓬勃發展,時間序列資料的相關研究更會受到重視。

資料若要利用資訊系統來處理,儲存便是一個重要的課題。目前許多的應用中,會使用關聯式資料庫加上一個time-stamp欄位來解決這問題。若檢視時間序列資料的特質,我們要考量的因素有:

  • 資料是源源不斷產生,而且鮮少需要修改,因此insert的效能會是重要因素,update反而不那麼重要
  • 在一些應用中,資料的產生是快速且大量的,因此擴展性(scalability)就相當重要
  • 時間的運算常會是取回資料時常用到的功能,所以時間運算的效能很重要
對關聯式或其他資料庫來說,對上述的幾點的表現並沒有那麼亮眼,因此,有一時間序列資料庫(TSDB; Time-Series Database)這個分支,針對了時間序列資料的特質來量身打造了。目前市場上有相當多的TSDB系統,常見的可參考這篇wiki資料。目前較熱門的就以InfluxDB與Kdb+為主了。這一篇比較表也很有參考價值。

下一篇,我們將開始安裝與使用InfluxDB。

2018/10/17

Spring Boot 試作紀錄

工作上的系統,架構常用到micro-service,建立這些服務,常常都需要自己架構出需要的服務元件。常讀到Spring Boot的介紹,對它的自動化很好奇,趁著有個小空檔來試作一下。

試作目標:
  • 支援 static resources
  • 支援 servlets (HTTP request handler),收到request後,可根據client的資訊作回應
  • 嵌入Tomcat或Jetty

使用環境:Eclipse 2018-09

安裝 Spring Tools
進入Eclipse後,點選 Help | Eclipse Marketplace...
以 "spring tool" 就可找到 Spring Tools 4,點選Install即可安裝

建立 project
我們利用剛剛安裝的Spring Tools提供的工具來建立Spring Boot專案。
首先在主選單點選 File | New | Other...

選擇 Spring Boot | Spring Starter Project,然後進入Next

接下來要設定專案的相關資訊了,雖然事後都還可以調整,不過一開始就先設定好,後面可以省些事

幾個比較重要的欄位:
  • Name (專案名稱),這預設會連動到下面的Artifact ID
  • Type, Java Version, Language都使用預設,Packaging有Jar與War,看未來想打包成何種形式,可以在pom.xml中改變
  • Group (Group ID) 用來識別此專案的名稱 (可參考這裡),可以在pom.xml中改變
  • Artifact (Artiface ID)在未來產生Jar file時會用到這名稱,可以在pom.xml中改變
  • Version用來記錄版本,在未來產生Jar file時會用到,可以在pom.xml中改變
  • Description用來對這個專案作一個描述,可以在pom.xml中改變
  • Package則是建立程式時預設的package架構

接下來就要選擇專案需要的dependent libraries了,例如若專案需要用到PostgreSQL的JDBC driver,就可以直接勾選,Maven就會自動幫你下載了。
目前試作的專案只需要Web,所以就勾選Web即可。

點選Finish,Spring Tool就開始幫我們產生專案了。

Spring Tool幫我們產生了一個程式 GreetingApplication.java。這程式的內容很簡單,就只是啟動Spring Boot的應用


建立靜態網頁
靜態網頁與相關資源(js, css, etc.) 可以放在src/main/resources/static下。在此專案我們就放一個簡單的網頁。


page.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Welcome</title>
<link href="css/page.css" rel="stylesheet" type="text/css" />
</head>
<body>
<h1>Hello, welcome to static page.</h1>
</body>
</html>


page.css
h1 {
color: #ff7777;
font-style: italic;
}


建立Servlet (request handler)
接下來我們要進行動態回應的部分。我們列出需求:

  • 當使用者以POST或GET連結到 http://:8080/func時,則會由此handler來處理
  • 如果request parameter有name,我們就回覆 Hello, !,如果沒有name,則回覆 Hello, world!
  • 列出使用者的IP

我們建立的handler如下 (RequestHandler.java)
@RestController
public class RequestHandler {

    @RequestMapping(value="/func", method={RequestMethod.GET, RequestMethod.POST})
    public String handler1(@RequestParam(value="name", required=false) String name,
            HttpServletRequest request, HttpServletResponse response) {
        String user = (name==null || name.isEmpty())? "world": name;
        String host = request.getRemoteHost();
        String result = String.format("Hello, %s! You are from %s", user, host);
        return result;
    }
}


對此程式片段作一個註記:
  • @RestController:Spring採用MVC架構,註冊此程式為Controller
  • @RequestMapping:註冊此方法負責特定request的處理。value的值為URL對應的path,method則標記了要處理的方法
  • @RequestParam(value="name", required=false) String name
    Spring Boot會幫我們解析parameters,你可以將你需要的parameters都列出來。
    required預設為true,如果為false則表示此參數可能不存在於query string中
    解析出來的name值會放到字串name中
  • 因為我們需要使用者的IP,這需要從request物件中來取得,因此我們handler1的參數列加上了HttpServletRequest request,以便在程式中抓到IP
  • 雖然不會用到,我們也加上了HttpServletResponse response,若程式需要對response object作一些設定可以使用。在這個專案中是可以拿掉


執行
如果熟Eclipse的朋友就知道有很多種執行方法。這裡介紹其中一種,在project上按右鍵,Run As | Spring Boot App 就可以執行了


透過瀏覽器來看靜態網頁
以及動態內容


產生 JAR file
在project上按右鍵,Run As | Maven install就可以產生jar file了。

產生的jar file會放在專案的target目錄下。

所產生的jar file包含了專案所有需要的資源與libraries,因此只要把這個jar file放置到要執行程式的機器上,就可以執行這個服務了!
java -jar greeting-0.0.1.jar


由於這個jar file包含了必要的libraries,因此其file size大概都從15MB起跳。對大部分的運行環境應該都還能接受。

改內嵌 Jetty
Spring Boot 在 web應用上是預設內嵌Tomcat,若要改為使用Jetty,則需要修改pom.xml。現有的pom之片段是:

在這裡將Tomcat排除掉,再加入Jetty

這樣就可改用Jetty為平台了

修改port
這個系統預設是使用port 8080,若需要修改,可以直接在src/main/resources/application.properties中加上下列這行 (例如要改為port 88)
server.port = 88

2017/11/09

Python群集型態整理

常用的Python群集型態有list, tuple, set, dict,把它們的特性整理一下

ListTupleSetDict
element typeobjectobjectobjectkey-value
(object-object)
orderedYYNN
mutableYNYY
duplicableYYNN
(unique key)
example[1, 'b', 3, 3](1, 'b', 3, 3){1, 'b', 3}{'a': 1, 'b': 'hello', 'c': 'hello'}

2016/09/18

Uncaught ReferenceError: $ is not defined

I got a weird error when debugging my web page on Chrome. The Developer Tool shows a "Uncaught ReferenceError: $ is not defined". And the error is in getPageLang function.
I checked my code and grep on my whole project, I could't find  the codes in my project. It is very weird!

After hours study, I got the exception is from Skype extension of Chrome. If you disable the Skype extension, the error won't happen.

Great! It is not my problem!
And hope this can help you if you also encounter this ERROR!

2016/06/07

Socket Options

開發網路程式的朋友都應該用過socket,而socket中除了基本的方法與屬性外,還有不少options。如果觀察Socket的JavaDoc,你一定會發現有很多關於SO_XXXX、IP_XXXX、TCP_XXXX之類的屬性。這些屬性多半與通訊的連線成功或傳輸關係不是那麼大。但是,如果程式需要關注到效能,那這些屬性就會扮演重要的角色。

從Java的角度來檢視一下這些options:


IP_TOS


TOS(Type-Of-Service)的值必須介於[0..255]間。RFC 1394中則定義了下列

  • 0x00 -- normal service
  • 0x02 -- minimize monetary cost
  • 0x04 -- maximize reliability
  • 0x08 -- maximize thrughput
  • 0x10 -- minimize delay

程式可依需要將這些值做bitwise運算,來設定通訊時的等級(例如0x14是指minimize delay and maximize reliability)。
不過並不是所有的設備都會依照這個option的設定來進行傳輸,但是有需要還是可以設上。

SO_LINGER

linger是拖延的意思,這會影響Socket.close()的速度。
如果觀察TCP State Diagram,當我們做close socket的動作時,連線兩端還是會做許多複雜的動作。其中active close這端在TIME_WAIT會等待 2*MSL (Maximum Segment Lifetime)的時間,來確保送出的資料可以被另一端接收到。在RFC 793中提到了MSL是120秒。不同的OS與應用會去調整這個值,目前多數的Linux、BSD及Windows是30秒。

對於寫server-side程式的人來說,linger時間太長,你的程式雖然已經close這個socket了,但是在底層並沒有真的關掉,還在TIME_WAIT的狀態,造成了程式在短時間內沒有辦法再bind到這個port。
現在的internet速度與穩定性已經比早年好太多了,應該不需要這麼長的linger time了。如果你的程式需要較多的socket開關操作時,可以考慮把linger time設小一點。

如果把linger time設為0呢?則程式會送出設有RST flag的封包給對方,可以達到快速結束連線的效果。但是,對方的程式可能會發生 "Connection reset by peer" 的錯誤。

SO_TIMEOUT

這個時間是Socket.getInputStream().read()會被block的等待時間長度。如果read等待超過這個時間長度,則會丟出java.net.SocketTimeoutException。
SO_TIMEOUT的值須大於0,若為0,則視為無止盡。
這個值設太小,就容易常發生timeout的現象;若設太大,有時程式又會被block太久,反應速度變長。所以如何拿捏就要看應用類型而定了。

SO_SNDBUF / SO_RCVBUF

對底層的傳送/接收緩衝區大小的建議值。
這只是建議值,底層會依照所設定的值,在參照實際系統狀況來決定這兩個緩衝區的大小。
前輩建議,如果是在通訊較不好的環境,或是瞬間傳送的速度很快,那SO_SNDBUF可以設大些,這樣可以確保TCP sliding window夠大,足以容納一值無法成功送出的資料。
如果是電腦的處理速度慢,那SO_RCVBUF就要設大些,免得來不及處理,資料掉了。

SO_KEEPALIVE

如果socket建立了,但是長時間(2小時,視OS實作而定)沒有進行資料傳送,這個連線很可能會被中間的某個網路設備中止。為了避免這情形發生,如果啟用了SO_KEEPALIVE,則socket會自動發出一個keepalive probe給對方,對方也會回應回來,讓網路設備知道這個連線仍在使用中。
對有些應用或環境來說,2小時真的太長了(尤其是行動網路,有些系統商大概不會讓你站用連線這麼久),而且對Java也無法變更改時間值,所以許多應用就會自己實作ping/pong的機制來當作keep-alive。

SO_OOBINLINE

當連線有許多資料排隊要送時,如果有一緊急事件要插隊通知對方,就可以用 OOB (Out-Of-Band) Inline。啟用SO_OOBINLINE,並可透過Socket.sendUrgentData來即時送出 1-byte的資料給對方。
不過,直到Java 8,都沒有看到可以接收urgent data的方法。所以...

TCP_NODELAY

在提這項之前,要先瞭解Nagle's algorithm。簡單的說,想想一家貨運公司,如果一收到小貨物就馬上出車送貨,對公司來說整個營運效能就不高,對整個道路來說就會增加車次,容易造成路上塞車。
若是貨運公司將這些收到的小貨物累積到一定的量才出車送貨,那公司的營運效能才能提高,不會造成浪費。但是這就會造成最前面的幾件貨物要等很久才會被送出。
TCP_NODELAY就是停用Nagle's algorithm,來提高throughput。
如果所開發的系統常會傳送小資料,這個選項就該納入考慮。


以上做為工作上的紀錄,方便未來查閱。也希望對大家都有些幫助。

2016/05/05

JavaMail透過GMail送信遇到的問題

這樣的技術文件,網路上應該很多了,那些詳細的實作就不贅述
套著網路上的資訊,使用了下面的參數


結果遇到了下列的exception

javax.mail.MessagingException: Can't send command to SMTP host;
  nested exception is:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1564)
at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1551)
at com.sun.mail.smtp.SMTPTransport.ehlo(SMTPTransport.java:935)
at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:426)
at javax.mail.Service.connect(Service.java:310)
at javax.mail.Service.connect(Service.java:169)
at javax.mail.Service.connect(Service.java:118)
at com.ruby.vtun.entity.GeneralMail.send(GeneralMail.java:133)
at com.ruby.vtun.entity.GeneralMail.main(GeneralMail.java:161)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1884)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:276)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:270)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1439)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:878)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:814)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1016)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
at sun.security.ssl.SSLSocketImpl.writeRecord(SSLSocketImpl.java:702)
at sun.security.ssl.AppOutputStream.write(AppOutputStream.java:122)
at com.sun.mail.util.TraceOutputStream.write(TraceOutputStream.java:114)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at com.sun.mail.smtp.SMTPTransport.sendCommand(SMTPTransport.java:1562)
... 8 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:385)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:326)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:231)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:126)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1421)
... 19 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:196)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:268)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:380)
... 25 more


經過被蹂躪了一陣子,總算發現是我的防毒軟體Avast作怪!把Avast關掉,就可以正常送信了。
要解決這問題,除了關掉Avast外,可以透過更改Avast的設定來解決,因為目前是使用port 587來送,只要把587從Avast的設定中拿掉,就可以正常送信了

2016/02/03

GCM SERVICE_NOT_AVAILABLE

今天又遇到怪事了
在修改一支Android APP,這支APP有用到GCM來做push notification,但是修改的點只有在UI的部份
修改完放到Samsung Galaxy Mega(4.2.2)一跑就當,以前沒發生過這問題
查了一下,是跑到GoogleCloudMessaging的register()就當,這段程式應該是很標準的寫法

if (gcm == null) {
    gcm = GoogleCloudMessaging.getInstance(parent);
}
String regId = gcm.register(Global.GCM_SENDER_ID);


丟出來的exception是SERVICE_NOT_AVAILABLE
java.io.IOException: SERVICE_NOT_AVAILABLE
at com.google.android.gms.gcm.GoogleCloudMessaging.register(Unknown Source)
at com.ruby.switch2go.manager.GCMHelper$GCMAsyncTask.doInBackground(GCMHelper.java:76)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:856)

把程式放到ASUS Nexus 7(6.0.1)上跑,耶!一樣的程式,卻可以正常執行喔。所以應該不是程式的問題,那...難道是Android版本的問題?

再找來另一支6.0的手機,嗶~~~,還是SERVICE_NOT_AVAILABLE!
看來應該不是Android版本問題了
這幾台的Google Play Service都是8.4.89版的

再回到Samsung GALAXY Mega,用另一個類似但sender ID不同的程式來跑,卻又正常了

看了這篇Google Cloud Messaging is Extremely Unreliable for Push Notifications,手機端能檢查的都看過,好像也無解。

那~~~問題在那裡?真的是Google server-side的問題嗎?
是不是不同的phone token與sender ID會分派到不同的server去處理,而負責DUT的server剛好這時候有問題了?

果然,時間是最好的解藥。經過兩個多小時的等待,一切都恢復正常了。看來,是GCM server-side有問題了。




2015/06/05

Blank window issue on Safari Mobile and a quick solution

I am working on an embedded project. The device provides simple Web HTTP interface to interact with this device. It works fine on all browsers but Safari Mobile on iOS 8.3.

Surfing on Google, there are some problems on Safari Mobile (article 1, article 2, article 3, article 4, article 5). They sound like not same as what I am facing to. I monitor the packets captured by Wireshark, and found that Safari Mobile didn't load all resources(css, js, ttf, etc.) completely causes this problem. The Safari Mobile communicates with Web server in HTTP pipeline, while the others (Chrome, Firefox, Opera, IE, Safari Desktop, etc.) don't. (The others will reuse the connection, but won't pipeline them). That is the major difference!

In theory, the HTTP pipeline can speed up requests by reducing the number of round trip times. But there are some articles(articles 6, article 7, article 8) comment on this point. This is not what I want to discuss in this page. Let's back to my problem.

The HTTP pipeline raises lots of problem. Some developers submit tickets to Apple, but there is no plan to solve the problem. I try to find

I found an easy workaround for this issue at last. This problem can be solved by setting the connection field of the HTTP response header. I force the connection field to be "close" if the browser is Safari, and it works! In Java Servlet, you can set it as
response.setHeader("Connection", "close");

(or you can set the header field in Filter), or in PHP
header("Connection: close");

or you can set the field in Web server setting.

In Safari Mobile, it will close the connection when receive the response, and won't send further requests thru this connection. That is, no more HTTP pipeline from Safari Mobile. I am not sure is this the best solution or not, but it really solve my problem.

2015/05/04

iOS 7/8 Safari空白畫面

如果你的網站在Desktop版的Safari是正常,可是在iOS 7/8上的Safari卻會出現空白畫面,一直loading不停,那你可能遇到Safari的bug了。可參見: http://tech.vg.no/2013/10/02/ios7-bug-shows-white-page-when-getting-304-not-modified-from-server/ 

這問題來自當要用Safari連上一個網址,這個網址你之前曾要連,但是因為網路、使用者中斷或是其他問題,造成某些resources(html, javascript, css, 圖檔等等)並未完成下載,但是Safari卻當成已完成下載。當使用者要重新連這網址時,在reload時會問server從上個時間點後有沒有改變過(雖然上次沒有完成下載,但是Safari卻當成已下載過),而server此時可能回覆304 code表示沒有改變過。因此Safari就會從cache來取得這個resources,但是在cache裡的resources本來就不完整,造成Safari在呈現或呼叫webkit javascript engine執行時便發生錯誤,出現空白畫面或一直在下載中。 

對此問題有興趣,可以用"ios safari 304"為keywords在google上搜尋,可以找到不少討論此議題的文章。 

目前Apple似乎還未將這問題列為bug,所以可能還沒有修復的時間表。至於網路上有些人有提出些workarounds,主要都是將server-side改為不回應304,或是針對Safari不回應304,讓瀏覽器對所有的resources都一定重新取得,而不是使用cache裡的。不過這對使用iOS 7/8的iPhone/iPad來說,將會耗掉更多的電力與頻寬(如果不是吃到飽就要小心了),但對網站經營者來說,為了達到Apple常掛在嘴上的「使用者體驗」,似乎也沒什麼其他的解法了。

2015/04/28

在HTML element要處理left-click與right-click

被指派一個任務,頁面上的一個元件,當right-click與left-click時,要有不同的反應,IE 8也要能兼容,且必須在mouse up時才觸發。
如果是mouse down就比較好處理,若mouse up,這個mouse event會bubble up,無法用stopPropagation()來停止,所以就不能直接bind在mouseup上了

最後用了幾個方法組合起來,來達成這個需求,HTML的tag如下,bind onclick來處理left-click;bind oncontextmenu來處理right-click:
<a href="#" onclick="return handler(event)" oncontextmenu="return handler(event)">CLICK</a>
JavaScript的function則如下
  function handler(e, id) {
    var clickType = 1; //1:left-click; 3:right-click
    if (e.which) {
      clickType = e.which;
    } else if (e.type) {  // for IE 8
      if (e.type=="click") {
        clickType = 1;
      } else if (e.type=="contextmenu") {
        clickType = 3;
      } else {
        clickType = e.button+10;
      }
    }
    alert("click type: "+clickType);
    return false;
  }
在Chrome 42, FireFox 37, Opera 28, IE 11/8上測試都OK

2014/12/25

Android Studio 筆記

最近Google releases正式版的Android Studio,並宣稱此工具將成為official IDE for Android。那Android未來還會繼續維護與發展ADT嗎?從Android用official IDE這字眼,並在官網上鼓勵大家轉到Android Studio的態度,個人覺得以Eclipse為基礎的ADT可能會走向尾聲了(參考這一篇)。

因為Android Studio的專案結構與觀念與ADT有蠻大的差別,所以一定要來瞭解一下

讀了一些網頁,把一些重要的記錄下來

Android Studio Overview
官方的資料一定是不可或缺的
我覺得這裡的workflow章節很重要,可讓我們可以瞭解一下這工具的使用概念
這裡稍作紀錄一下,Android Studio開啟是以專案(project)為單位(Eclipse則是workspace)
每個project可以包含一至多個modules(至少要有一個application module)。Module是個容器,用來存放與組織所需要的source codes與resources。Module又分為四種:
  • Application Module:可用來產出.apk檔來安裝至Android設備
  • Library Module:如其名稱,用來存放可被其他projects或modules共用的source codes與resources,以達到reuse之目的
  • Test Module:用來包含可對application projects進行測試的codes。其中講到"are built into test applications that run on a device",這等有時間可以來多瞭解一下
  • App Engine Module:可以與Google App Engine(GAE)結合,來整合device與雲端成為一體。這一塊對應該是做IoT的朋友可以多留意的

Android Studio vs. Eclipse: What You Need To Know
如果已經用慣Eclipse的朋友,可以先看看這篇,瞭解這兩者的異同
另外這一篇也可以參考一下

Android Tutorial for Beginners: Part 1
Android Tutorial for Beginners: Part 2
Android Tutorial for Beginners: Part 3
就如標題,這是針對正式版(v.1.0)所修改過的入門文,不是用之前beta版,有正確的畫面截圖,在學習時應該會減少很多混淆與誤解

Gardle
Android Studio並未採用Ant做為其build tool,而是選用了Gardle。因此若想把Android Studio玩好,把Gardle讀通也是必要的。
也可以參考Android官網上的Gradle Plugin User Guide


2014/12/12

GCMRegistrar置換為GoogleCloudMessaging的問題

GCMRegistrar已經宣告為deprecated,官方建議大家應改用GoogleCloudMessaging,並提供範例供大家參考


不過依照這份文件來實作時,當執行到 GooglePlayServicesUtil.isGooglePlayServicesAvailable (context); 時,會遇到  "The Google Play services resources were not found. Check your project configuration to ensure that the resources are included." 的錯誤訊息。

主要的問題在文件中沒有提到要在AndroidManifest.xml的application宣告中,加上
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version"></meta-data>
然後在 /res/values/versions.xml 裡,加上
<integer name="google_play_services_version">6111000</integer>
至於這個version number應該填入什麼值?可以從 <sdk>/extras/google/google_play_services/libproject/google-play-services_lib/res/values/version.xml 中獲得。

另外一種說法,不應該把version number寫死,不過這個方法我還沒有試成功 (Eclipse)

參考文件

2014/11/26

真的不應該依賴software stack嗎?

在這篇文章中The full stack developer is dead, long live the un-stack developer,提出了un-stack developer的理念

不過現在的開發環境與應用平台,以及software reuse的概念下
不用software stack,什麼都自己動手做,在講求效率的今天,已經是接近天方夜譚了

也有許多開發者並沒有身處於start from zero的機會
不過開發者應該知道,一位好的開發者
必須要有從零開始的能力,必須要有設計架構的能力
並且努力為自己培養這樣的能力
因為有這樣的能力,才能讓自己有start-up的技能、觀念與胸懷

2014/10/30

GCM推播訊息(notification)延遲問題

最近在開發Android APP,使用到GCM(Google Cloud Message),發現一個問題:
我們的server已經送出notification,可是Android phone/table有時很快(兩三秒)就收到推播訊息,有時則等超過10分鐘才收到。

雖然Google宣稱notification不保證即時送達,也不保證可以送達,不過我們還是希望在免費的服務下,能夠儘可能的即時送達
對有些應用而言,超過5分鐘就真的很久了

工程師個性使然,總是想找出是我們的server+APP有問題,還是其他因素造成。因為有時是即時就能收到,照理說應該不是server或APP的問題
找了andQlimax的po文,覺得他的測試與推論是合理的,也符合我所觀察到的狀況

手機/平板與GCM server間是透過TCP connection來進行通訊。當手機/平板連上網路時,Android就與GCM server建立連線。為了維持連線,手機/平板會定期送出heartbeat訊號到GCM server。andQlimax觀察到如果是使用3G/4G連線,則每28分鐘會送一次heartbeat;若是用WiFi則是15分鐘。(為什麼間距那麼大?這除了要考慮手機平板的耗電外,還有GCM server的負擔等等)

andQlimax認為一般電信服務商(3G/4G)或WiFi router(無線分享器...),就資源分配與使用效率等考量,通常不會無限維持這麼多的TCP connection(因為可以建立的connection數是有限的),因此這些設備(3G/4G:基地台; WiFi:無線分享器/HotSpot)會定期(例如每5分鐘)檢視那些connection是idle的(沒有傳送資料),就將這個connection關掉。

因為Android的heartbeat間距很長,所以手機平板與GCM server間的TCP connection會被關掉,因此當有訊息時,GCM server便無法透過原來建立的connection將訊息送到手機平板,必須等到手機端時間到要發heartbeat時,才會重新建立連線。
所以當connection還沒有被關掉,或是heartbeat週期很快又到了,手機平板就能很快的接收到推播訊息;若是connection被關了,那就得等到下一次送heartbeat時才能收到notification了。

andQlimax在很多論壇與管道都呼籲Google/Android應該修改heartbeat的傳送週期。不過看來到目前Google都尚未採納。

如果,真的有mission critical的應用,也許應考慮是不是用long-polling request等機制來作,而不依賴GCM。

2014/08/15

如何讓Android的WebView不會跳出browser來呈現網頁

在開發一個Android APP時,有一個需求是必須將網頁開在APP裡。

嗯...這不難,Android SDK有提供WebView元件可以用。用這段code就搞定了

WebView web = (WebView) findViewById(R.id.webView1);
web.getSettings().setJavaScriptEnabled(true);
web.loadUrl(url);

如果有這樣簡單,就不會寫這一篇了。
這樣的寫法當然可以將網頁開在WebView裡。不過,一遇到post form, redirect或其他的情況,就發現會跳出APP,用browser去顯現後面的網頁了。

做一下功課,瞭解了幾件事
  1. WebView如果沒有設定WebViewClient,那預設WebView會要Activity Manager來提供適當程式來處理這個URL。這也就是為什麼會跳出browser來顯現網頁
  2. 如果WebView有設定WebViewClient,這時會執行這WevViewClient的shouldOverrideUrlLoading method。這方法如果傳回true則表示APP會自行處理這個URL,否則WebView會接著出面來接手處理這個URL
瞭解了這段後,我們就須把剛剛的code改一下

WebView web = (WebView) findViewById(R.id.webView1);
web.getSettings().setJavaScriptEnabled(true);
web.setWebViewClient(new InsideWebViewClient());
web.loadUrl(url);

然後也要加個class
class InsideWebViewClient extends WebViewClient {
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        view.loadUrl(url);
        return true;
    }
}

這樣,就可以達到需求了


2014/06/27

一個在Chrome裡不斷觸發onload的問題

昨天接獲回報,原來一個Chrome瀏覽正常的網頁(內含SVG),不知為何在Chrome(35.0)中會一直閃爍。

看了一下,這閃爍是因為onload event一直被觸發,而且不僅是Chrome,連Opera(22.0)也有此狀況。這對難兄難弟同時發難,讓我想起了這則新聞 (Google將捨棄WebKit改用新的Blink瀏覽器引擎),很有可能是Blink這個JavaScript engine的onload event handler行為與其他engine不同,造成onload一直被觸發。

修改就不在svg tag中加onload=""了,改用JavaScript的方式,在程式的最後加上
<script type="text/ecmascript">
 window.onload = function(evt) {
   load(evt); } 
</script>


寫網頁應用的朋友可以注意一下這個問題,看看你的應用在這兩個browsers會不會有問題。

2014/05/28

怪,IE8顯示的資料少了

今天遇到一件怪事,面對同樣的資料來源,IE8的呈現出來的資料筆數竟然與IE11, Firefox, Chrome不同

環境
這是個web-based的系統,browser以AJAX,透過XMLHttpRequest向server取得資料後,再以JavaScript呈現在頁面

問題
以現有的設定應該會顯示出3筆資料,在Firefox, Chrome與IE11都正確顯示,唯有IE8只顯示出一筆資料。

解決過程
用Chrome的development tool來看,responseText拿到的資料是
192.168.10.120/1/1##192.168.10.57/2/1##192.168.10.63/1/5
(我們是用"##"來做分隔號)
但在IE8中,responseText卻是
192.168.10.120/1/1## 
所以,問題就不是在javascript把資料呈現在頁面這段,而是資料來源了。而這就有點詭異了,因為在同一個時間點,這設定是不會變動的,IE8理應拿到的是跟其他browser相同的資料才對。難道IE8有問題?
直接用IE8連結資料源的網址,咦~~,拿到的資料是3筆呀。所以IE8從server拿的資料應該是正確的。那問題是在IE8的XMLHttpRequest把我的資料暗槓了嗎?應該不會才對,其他相同作法的頁面都沒事呀。

在百思不得其解時,突然想起用Wireshark來抓抓兩端間的封包看看,果然有了重大發現。server傳回來的responseText在Wireshark看到的是
192.168.10.120/1/1##\000192.168.10.57/2/1##\000192.168.10.63/1/5

在每個## (分隔號)的後面,都多了\000,也許就是因為這個,IE8的javascript engine解譯為octal null character,對null-terminated string來說,這就是字串的結束,所以把後面的都忽略了;而其他browser的javascript engine則是忽略掉這個null character,所以才有這樣的差別。

看來問題是在server-side的程式了。
查找了一下server程式,果然發現該程式(C language)在把分隔號##寫入response chunk,給的length竟然是3,難怪會多出那個null character。改正後就沒問題了。

結案!