2021-10-16

《Thinking in Java》 的時代眼淚

最近接了一個 Java 家教,目前在上 OO 概念的部份。因為本來就不是靠唸書學會 OO 的(而且也沒學得多好),加上閒來無事,所以拿起塵封已久的《Thinking in Java》中文版(2002 初版)來看一下有沒有啥遺漏的。

等等… 「(p.252) 利用 imports 來改變行為」?這是什麼鬼?書上是這樣寫的:

Java 並不具備 C 的「條件編譯(conditional compilation)」功能。此功能讓你得以切換開關,令程式產生不同行為,無須更動程式碼。Java 拿掉這個功能的原因,可能是因為此功能在 C 語言中多半被用來解決跨平台問題,也就是根據不同的平台編譯程式碼中的不同部份。由於 Java 本身的設計可自動跨越不同平台,所以應該不需要此功能。

不過條件編譯仍具有其他實用價值。除錯便是常用的用途之一:在開發過程開啟除錯功能,在出貨產品中關閉除錯功能。Allen Holub(www.holub.com)提出了以 packages 來模擬條件編譯的想法。根據這一想法,他在 Java 裡頭產生原本在 C 語言中極為有用的 assertion 機制。

基本上就是寫兩個叫做 Assert 的 class、擺在不同 package 下,要出貨的時候把 Assert 的 import 改成空殼 Assert 的路徑。

真的是時代的眼淚阿… [遠目]

其實中文版出版的同一年,Java 1.4 也出了,其中一個重大的改進就是導入 assert

這裡還有個不可靠的印象,就是當年有些人認為:「 assert 導入的太晚,現在用(誕生於 1997 年,正逐漸統治世界 XD) JUnit 就好了」

時至今日,JUnit 勉強算是有用一點,assert 則是從來沒用過,只有在追 GXT 的 source code 有看到過。至於「利用 imports 來改變行為」這事… 連我這種不碰 Spring 的咖小都知道應該要套個 DI 什麼的,而不是去動 import 這種鬼想法…

當然,現在回頭用吐槽的姿態看二十年前的概念實在沒什麼太大的意義,那時本來就還在盤古開天,連 design pattern 都還是浪頭上的顯學,殺個豬公也不是什麼太奇怪的事情,就只能說是時代的眼淚。

BTW… Allen Holub 的網站還在… 不知道當事人還記不記得這段黑歷史… wwwww

2021-08-20

Google Sheet API v3 升 v4

之前用這個網址格式取得 JSON 格式 Google Sheet 資料,一直相安無事:

https://spreadsheets.google.com/feeds/list/SHEET_ID/TAB_INDEX/public/values?alt=json

然後大概從 2021/8/15 開始,就有機率讀取不成功(對,有時候會成功 🙈),有的時候是炸 CORS 問題、有的時候是炸 404。

狗了一下的結果,應該就是 Google Sheet API v3 在 2021/8/2 停用所導致的。至於 v3 版跟應該還會繼續活著的 GData 有什麼關係、為什麼是「有機率掛掉」就… 不研究… [蓋牌][翻白眼]

經過幾天的摸索,如果只是單純要取得 JSON,那麼會遇到下面這幾件事情。

首先要去 Google Cloud Platform 搞一個 API 金鑰(以下簡稱 API_KEY)。如果之前沒有用過可能還要先開一個(看起來毫無意義的)專案然後才能產生… 😱

有了 API 金鑰之後,接下來是把網址改成這個格式:

https://sheets.googleapis.com/v4/spreadsheets/SHEET_ID/values/RANGE&key=API_KEY

這邊不再是用「第幾個工作表」(或是那個根本不知道怎麼來的 id 值)的方式,而是指定 RANGE。完整版的 RANGE 大概是「FOO!A1:D100」的長相,這表示要求「FOO」這個工作表的 A1~D100 這個 cell 範圍的資料。

實測之後(找不到文件… 🙈)發現允許下列幾種變形:

  • FOO:可以省略 cell 範圍。如此會回傳整個 FOO 的資料
  • A1:D100:可以省略工作表名稱。如此會回傳第一個工作表的指定 cell 範圍的資料

回傳的 JSON 也跟以前不一樣,v4 版變得很純:

{
  "range": "'FOO'!A1:D100",
  "majorDimension": "ROWS",
  "values": [
    ["ColumnA", "欄位B", "ColumnC", "欄位D"],
    ["第一筆A", "第一筆B", "第一筆C", "第一筆D"],
    [],
    ["", "第三筆B"]
}
  • v3 會把 sheet 的第一個 row 的值當成 JSON 的 key 值;v4 純粹就是給你一個二維字串陣列。(反正都能指定範圍了)
  • v3 遇到第一個空白 row 就會視為後面沒有資料停止輸出;v4 會在指定範圍內確定是否以下空白才停止輸出,其中夾雜的空白行會給一個空陣列。
  • 如果該 row 第 n 個 column 之後都沒有值,則該 row 對應的陣列長度只到 n - 1。1~n - 1 當中如果有 column 沒有值,會給空字串。

一樣是實測結果(還是找不到文件… 🙈),下列狀況會導致 server 給不是 200 的 HTTP status code:

  • 400:range 無法正常解析
  • 403:API_KEY 值不正確
  • 404:SHEET_ID 值不正確

最後,用 v4 版,sheet 可以不用做「發布到網路」這一個動作。

2021-05-15

Spring Data REST 初步心得

測試環境:

  • OpenJDK 14
  • Spring Boot 2.2.4

注意

這是在「REST 只有基本概念、Spring 碰不到一個月而且沒認真讀過文件」的情況下,在混亂的實做中得到的混亂廢話

因為 Spring 幾乎沒碰過,所以從頭 (明明就是鞋子) 開始講起。

Spring Boot 真的讚,pom.xml 加幾個 dependency,弄個 @SpringBootApplication 的 main class,就連 Tomcat(包含自動 reload)都幫你啟動完畢。瞬間把 Eclipse 那個越來越難用的 Servers view 給關了省的佔畫面(不過萬一要同時多個 webapp 運作可能還是… 算了到時候再說 XD)

@RestController 大概也是簡化的極限了,預設情況下連 return 值轉 JSON 變成 response 內容都自動幫你處理好,找不到可以抱怨的點(因為也沒寫過幾個… [逃])

Spring Data JPA 稍微棘手一點,自動掃 @Entity class 一直沒成功,然後又想搞可以自訂檔案位置(而不是設定檔寫死)的 H2,所以稍微花了一點時間。不過已經比想像中的還要簡單很多了。

撇開一些個人龜毛的部份,從 Spring 程度幾乎等於 0 的初學者(但是會基本 Maven、有用過 Hibernate)、到弄出一個 REST controller 來對 DB 做 CRUD,大概就是 10hr 以內的光景。當然,這裡說的是 POC 等級的程度。 是說我寫出來的東西好像也都只有 POC 等級

至於 Spring Data REST 嘛… 一句話就可以結案:「@Repository class 再加一個 @RepositoryRestResource(path = "foo")

立馬 /foo 就可以處理 GET / PUT… 了,就是這麼簡單~ 就是這麼神奇~

好,server side 收工!

才沒有這麼沒好… [眼神死]

第一個會炸到的問題是:「GET 的 response 要怎麼 unmarshal?」

不是直接把 List<Foo> 轉成 JSON 嗎?我也這麼希望,但就不是,而是長這樣(HATEOAS / HAL):

{
  "_embedded" : {
    "foos" : [ {
      "name" : "FooName",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/foo/2"
        },
        "location" : {
          "href" : "http://localhost:8080/foo/2"
        }
      }
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/foo"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/foo"
    }
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

真正的資料是在 ._embedded.foos,這還好,不算啥大事,而且有分頁功能好棒棒。問題是裡頭的 Foo 結構沒有掛 @Id 的 field。雖然這個去狗一下可以找到解決辦法,但終究得花上一些功夫才有可能轉回 Foo instance… (沒有實際測試,好像還得設法讓 gwt-jackson 忽略 "_links" 才能正常運作… )

這也許是用 GWT 的人才會有的困擾,畢竟 GWT 的賣點之一就是 server / client 可以使用同一份 code。如果 client 用純 JS 或許就沒問題,反正本來 就是假的 OOP 就沒有 code 可以用,要抓 JSON 中的值也可以用字串隨便指到想要的位置…

目前的作法是直接跳過 @RepositoryRestResource 提供的 URI,自己另外寫一個吐單純 JSON 的 REST controller… 💃

第二個炸點就更麻煩了:「不會處理 @ManyToOne(其他沒測)的 field」。

假設前頭的 Foo 是這樣:

@Entity
public class Foo {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
	private String name;
	
	@ManyToOne
	private Wtf wtf;
}

GET 得到的 JSON 不會出現 wtf、POST 也會跳過這個 field 不處理,直接塞 null。

這樣設計有沒有道理?應該有,至少 GET 的傳輸量就可以少很多(都說 many to one 了嘛)。但是完全連轉都沒轉是不是合理?為什麼不像 ORM 時 wtf 會轉換成 WTF_ID column 那樣做 JSON marshal?(還是說有神秘的設定檔可以改這行為?)(沒有試著找過,因為連關鍵字都不知道怎麼下 XD)

總之,沒用到就沒事,只要有用到 ManyToOne 的話,Spring Data REST 大概就是爛給你看。如果是剛出生沒多久的 project 或許還改得動,不然自己手寫 REST controller 可能還比較實在… 😱

沒有意義的結論

  • Spring 真的強,強到「完全不敢想像背後程式是怎麼寫」的那種強
  • 為什麼 HATEOAS / HAL 會設計成這樣子 (之前接觸 FHIR 的時候好像也是這付德性,原本還以為是 FHIR 亂搞…)
  • 目前看起來 GWT 這邊沒有 library 可以對付 HATEOAS / HAL,不知道 JS 那邊… 算了,我不想知道
  • Spring Data REST (至少)還有 validate 等功能要 try… 😭

最後就是… 嗯… 我真的很懷念 GWT RPC。要是有時程壓力,我一定馬上回頭用 GWT RPC… [遠目]

2021-02-19

升版後 GetValueProvider 導致 GXT Grid 炸 NPE

經過緩慢且冗長的演進之後,開發環境終於逐步變成 Java 8 + GWT 2.8 + GXT 4.0…

然後昨天手賤去升級 Eclipse,結果重啟不能、抱怨 Java 8 不給用、得換到 Java 11(印象中)以後。把 JAVA_HOME 切到 OpenJDK 14.0.1 之後,換 GWT 罷工,理由是 GWT 2.8 只能用 Java 8。為了都使用同一個 JDK 的 無聊 理由,所以 GWT 就乾脆也升到 2.8.2,也就是 Java 14 + GWT 2.8.2 + GXT 4.0。

前面是前情提要,接下來終於進入到主角了:RQC

RQC (14e5fb7) 還停留在 Java 7 + GWT 2.7 + GXT 3.1 的狀態,而且那時 GoogleSheetToolkit 也還沒抽出去。所以得先處理開發環境升級…

然後就爆炸了… <囧>

目前確認的一個詭異炸點(也許還有其他的 😱 )是 SheetIdGrid.javagenColumnModel(),只要用到 GetValueProvider 就會在 attach 的時候炸 NPE(實際上是炸 AttachDetachException)。關鍵點是 GetValueProvider.getPath() 預設回傳 null,只要不用 GetValueProvider 或是 override getPath() 隨便回傳個空字串都行。

帳面上看起來似乎對症下藥了,但其實整件事很謎:

  • GetValueProvider 已經用 N 年、有 N 個 Grid 在用這玩意
  • 在另外一個 project(LATE)完全無法重現這個問題。
    • 這兩個 project 的差別就只有 RQC 原本是 Java 7 + GWT 2.7 + GXT 3.1,而 LATE 原本是 Java 8 + GWT 2.8 + GXT 4.0,其餘 pom.xml / gwt.xml 都(幾乎)一樣。

目前實在沒什麼想法 (X你X的,都無法重現了還能怎樣) ,只能就先留個紀錄備考。當然改變 GetValueProvider.getPath() 的回傳值是一個萬用解,不過那等到其他 project 也炸了再來考慮吧… 😭

2020-04-23

GoDaddy 無厘頭請款事件

我沒有作電話錄音,以現在的標準來說可能算空口無憑。只能說我會對以下的陳述負所有的法律責任。

TL;DR

  • 在沒有登入的情況下,GoDaddy 莫名產生購物內容並嘗試請款(因為信用卡驗證碼錯誤而請款失敗),GoDaddy 目前無法解釋這個狀況。
  • 若在 GoDaddy 綁定的信用卡,最好開啟刷卡消費通知,以防上述事件發生並請款成功。

事發經過

我在 GoDaddy 有購買兩個 domain,有開啟綁定信用卡自動續訂,上一次自動扣款日是 2019 年 8 月,信用卡應該都是同一張沒有因為過期換過。

2020/4/23 19:3x,手機收到信用卡銀行的兩封簡訊,有一筆 1923 元的請款授權失敗。

20:0x,看到手機簡訊,打電話到信用卡銀行,銀行方說有五次授權失敗的紀錄,請款方是 GoDaddy。

20:3x 上 GoDaddy 的網站查看訂單紀錄,依然是兩個 domain。但是發現購物車內有五筆預計購買的 domain(都是 c 開頭的 .com),估算金額是 18xx 元。

打電話到 GoDaddy 台北客服(02-77039087),開始了漫長的對話與等待… 概要如下:

  • 我最近的登入紀錄依序是 2020/4/23 20:3x 兩筆、再往前就是 2019 年年底
  • GoDaddy 有信用卡嘗試請款失敗的紀錄,但是客服說只有紀錄日期,沒有紀錄時間(喵的,誰相信系統會這樣設計),但是他們願意相信我所提供授權失敗時間。

綜合以上兩點,GoDaddy 無法解釋是誰在進行購物結帳的動作,因為請款時間是 19:3x,在請款時間之前的登入紀錄得回溯到 2019 年底。

然後,**GoDaddy 甚至也無法解釋為什麼會請款失敗。**按照信用卡銀行的說法,都是驗證碼錯誤所以授權失敗。但是按照 GoDaddy 客服人員的說法,因為有設定續訂、有給信用卡資訊(含驗證碼),理論上購物車在結帳請款時會沿用、也就是說不會再要求輸入一次驗證碼,所以理論上會請款成功才對… (至於 GoDaddy 操作流程上是不是真的不需要再次輸入驗證碼,我就沒有自己嘗試了)

無法總結的總結

GoDaddy 承認我所回報的狀況,但是他們目前無法提出任何解釋。唯一能給的建議就是要我換掉各種密碼、加上二階段認證(如果真的是密碼被 try 那事情還簡單一點,問題就不是帳號被駭阿)。客服只能承諾說查明真相會發個 email 告訴我,至於需要多久就不會保證;如果後續我要追問進度,只要告知客服人員我的客戶編號、以及我在哪個時間打過電話就可以。

所以呢… 我覺得還是趕快把信用卡的請款簡訊通知打開,然後換到 Google Domains 比較實在…

附錄

簡單 Google 了一下,發現近期也有人( https://noter.tw/ )遇到類似案例,一併 memo 於此:

2020-02-17

Service Worker 之存貓得狗

最近突然開始在測 Service Worker,理所當然拿教學文件上的那個「第一次看到狗、第二次以後看到貓」的範例自己跑跑看。

為了後面的劇情需要,這裡先講解一下這個範例在幹啥。它是在示範 Service Worker 攔截 request 的能力,程式刻意設計成在第二次(以後)瀏覽,明明網頁要顯示的依然是 dog.svg 這個 resource,但實際上 Service Worker 給的卻是 cat.svg,所以使用者看到的會是貓。

好的,我複製貼上了程式碼、直接抓範例網站的貓狗圖…

為什麼跑出來的完全都是狗狗狗狗狗…

原本還要測一些其他的東西,所以是自己開個 localhost server 試,理所當然沒有 https(我也不會掛… [死]),為了預防萬一所以還丟上 GitHub Pages 試試,一樣還是狗狗狗狗狗… (備註:其實與 https 無關,都可以運作)

◢▆▅▄▃ 崩╰(〒皿〒)╯潰 ▃▄▅▆◣

鬼打牆到已經放棄治療,正準備關機睡覺的時候,無意間發現… 幹,為什麼兩個圖檔開出來都是狗?那不管程式有沒有 bug、Service Worker 有沒有運作都馬一定看到狗阿… [死]

原本以為是自己手賤粗心存檔存錯,打算再抓一次圖檔測試看看,如果成功就自己去跪主機板,結果沒想到…

只要是在範例網站上用「另存圖片」的方式,就會遇到「存貓得狗」的結果

這邏輯感覺很詭異,不過仔細想想可能還是有理可循?以「在新分頁開啟圖片」的結果來說,就會看到 url 依然是 dog.svg 但畫面還是貓,這表示 Service Worker 在不同 tab 一樣會運作。至於「另存圖檔」、「複製圖片」這兩個行為應該是單純拿 url 作 curl 之類的動作,不會觸發 Service Worker,所以依然會得到真正的 dog.svg

(謎之聲:事後諸葛都碼很簡單)

於是我的「範例程式跑不動」歷史又增添了一筆。雖然說 browser 行為的確出乎意料,但是在鬼打牆的時候(明明都有把 catches.match('cat.svg') 印出來看,url 的確是 cat.svg)也沒懷疑到圖檔上頭還是缺失。 於是寫完這篇檢討報告之後來跪個滑鼠墊以作懲處

2020-02-09

Maven Central Repo 強制使用 HTTPS

今天炸了 project 突然無法 build 的問題,mvn 跳了三行 download pom 檔之後就宣告 fail。拿 URL 到 browser 上頭確認是不是 server 掛了,結果得到一個 501,然後給了一句:「More information at https://links.sonatype.com/central/501-https-required 」。

簡單地說,就是從 2020.01.15 開始,兩大預設 central repo 都強制要求要用 https 連線,不然就賞你 501。

好,知道是知道了,但是不知道要去哪裡改,至少 conf/settings.xml 看不出所以然、我也不知道別招了… (艸

狗了一下發現(還)沒啥災情,想到我還是在用 n 年前的 3.2.1 版,於是抓了最新的 3.6.3 版,四海昇平。

幹,那就這樣吧… [逃]

2019-08-03

GWT 的 pom.xml 設定哏

測試環境

  • JDK 1.8(但是 maven.compiler.* 是給 1.7)
  • Maven 3.2.1
  • GWT 2.7.0
  • GXT 3.1.0
  • mojo Maven Plugin for GWT 2.7.0
  • Tomcat7 Maven Plugin 2.2

JDK 8 + GWT 2.8 失敗

想把 GWT 升到 2.8 已經很久了,發神經的時候 try 一下通常都炸一堆,就回到懶惰狀態,所以一直就擺著… (艸

前陣子終於初步搞定 jgit,但是它指定要吃 JDK 1.8+,可是 GWT 2.7 只停在 JDK 1.7,這就有升的動力…

然後就被 GXT 擊落了,原因不明,總之 GXT 3.x 只支援到 JDK 1.7

還懶得搞定 Sencha 的 Maven Repo 設定(而且誰知道升到 4.x 會不會有新炸點…),只能繼續維持現狀。

gwt-user 的 scope

因為逐漸擺脫 GWT-RPC,加上 Eclipse 裡頭跑 Tomcat 常常會因為偵測到檔案變動(雖然變動的是 GWT client code)而 restart server(但有些 project 還不會,不明所以… 😱),所以後來也會回頭用 Tomcat Maven Plugin 來啟動 web server。

有一天要啟動時突然炸了 exception,癥結點應該是:

gwt-user-2.7.0.jar) - jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending class: javax/servlet/Servlet.class

雖然不懂為啥之前都沒炸,不過狗了一下發現好像也不是只有我炸。解決辦法就是把 gwt-user 的 dependency scope 設定為 provided解法出處 還順便說了一下設定 provided 的好處。

本來也都相安無事的,直到要作 mvn install 的時候炸了:

GWT Module com.google.web.bindery.requestfactory.RequestFactory not found in project sources or resources.

這真的是莫名其妙,因為 SDM 一直都跑得好好的,為啥 SDM 沒吭過半聲?而且我還沒(直接)用 RequestFactory 咧… 拿掉 provided 的設定就又沒事了。

Tomcat 不能跑事小,mvn install 會錯誤就大條了。所以還是拔掉 provided 的設定。

Tomcat 不能用,那就來試試 jetty 吧。由於 JDK 只能停在 JDK 1.7,所以回頭找了 jetty 8 的版本(註:jetty 9 似乎要 JDK 1.11 才能跑… =="):

<plugin>
	<groupId>org.mortbay.jetty</groupId>
	<artifactId>jetty-maven-plugin</artifactId>
	<version>8.1.16.v20140903</version>
</plugin>

除了啟動後的 URL 少了 app 名稱,直接從根目錄起算,其他都沒啥問題(或是還沒炸到 XD)。

2019-06-29

初步測試 tbroyer 版 plugin

TL;DR

目前找不到從 mojo 跳到 tbroyer 的理由,甚至有可能想跳也跳不過去… 囧rz

測試環境

  • JDK 1.8(但是 maven.compiler.* 是給 1.7)
  • Maven 3.2.1
  • GWT 2.7.0
  • GXT 3.1.0
  • guava-gwt 19.0
  • tbroyer Maven Plugin for GWT 1.0-rc-10

這兩天(終於)試了一下 tbroyer 版的 GWT Maven plugin,結果還蠻慘烈的。

首先是 GF 改成 tbroyer 版(據說 GMD 前陣子也改了,想跟風),把 pom.xmlpackage 改成 gwt-libmvn install 好像也沒啥用、source code 沒有跟著包進去,不確定到底發生了啥事情。因為只是個 library project,於是決定跳過,專心搞 app project。

拿了一個發展緩慢的 project 來試試看(aka 在改版之前是能正常 build 的),沒想到 SDM 開不起來,一直炸 NPE…

從官方文件上看不出異常,只好去他的 integration tests 跟 GMD demo project 裡頭找答案。盲目地一個一個把東西加上去之後,終於發現是 plugin.configuration 裡頭得設定 moduleName(也許 moduleShortName 也要?沒有詳測),即使 skipModule 已經給 true 了… =="

現在回頭檢討,其實官方文件 Usage 第一步就有說要設定(GWT)module,只是跟第四步的 generate-module 搞混了… [被毆飛]。這也產生第一個吐點:「mojo 版不用設定 GWT module」

事情還沒完,目前看起來 tbroyer 版在 generate-resources phase 只有作(個人認為不痛不癢)gwt.xml 的 generator。所以原本 mojo 版會幫忙生的 RpcServiceAsync 現在也沒了(看 integration tests 裡頭有 GWT RPC 的範例也是直接給 code),以此類推恐怕 I18N 也得自己搞了?

好,沒關係,即使我這個 GWT RPC 鐵粉也在考慮是不是該拋棄(尤其前陣子試 gwt-jackson 成功、而 AsyncCallback.onFailure() 從來沒處理過 XD),I18N 也不知道有沒有那個命去處理到,大不了在 JISS 裡頭也再搞個 code generator 嘛!這部份無視跳過!

結果還是繼續炸 error,而且這下真的死透了:

  • guava(FutureCountDownLatch)用到 java.lang.InterruptedException 但是沒有 source code
    • 而且見鬼了,拿來測試的 project 裡頭只有因為 GF 宣告而 inherit com.google.common.base.Base,所以應該沒 inherit 到 Concurrent 的東西…
  • GXT 的 XmlReader.XmlSplittable 有 abstract method 沒實作?

立馬切回去 mojo 版,還是可以正常開啟 SDM… WTF?一個 GWT compiler 各自表述?去狗了一下 GXT 有沒有災情… 沒找到,倒是發現 GXT 4.x 文件給的 archetypes 依然還是在用 mojo 版…

這下也懶得再去找看看是不是改個設定 or 版本就能解決,直接宣告放棄。

結論

整體看起來還是 mojo 版比較實在,tbroyer 版看起來比較像是一個理想崇高但是缺乏廣泛使用回饋的產物?

也許哪一天徹底跟 GWT RPC 以及 GXT 說掰掰再來考慮?是說那時我能理解 tbroyer 版的優點嗎? Orz

2019-06-04

JSON 日期碎碎念

最近(終於)在測 gwt-jackson,負責在 server side 噴 JSON 的是 Gson,然後測到日期(java.util.Date)的時候炸了一輪,所以來碎念一下留個紀念… XD

一開始以為是 gwt-jackson 炸掉,畢竟 GSON 資格老關係好(?),GWT 現在還有多少人在用都是個問題 lol。但是狗了一圈沒發現啥災情,事情開始有點詭異…

那找「gson date」試試看… WTF?GSON 預設的日期格式會隨平台不同而變?這就別提什麼 W3C 之類的標準了,根本亂搞一通嘛… =="

不過至少有解,就是可以用 new GsonBuilder().setDateFormat() 來設定 format 然後用這個 builder 來建立 Gson instance。

那麼,什麼是 JSON 標準定義的日期格式呢?根據這個 stackoverflow 的說法,JSON 根本沒做出定義… =="

好吧,至少 JS / W3C 貌似有,就是 ISO-8601

因為懶得查,所以直接狗「java dateformat 8601」,我的搜尋結果第一條是 stackoverflow,不過那是要把字串還原回 Date,不太對題。第二條跟第三條(居然)是簡體中文,都是對岸的 CSDN。

先說第三條,文章裡頭給的 pattern 是 yyyy-MM-dd'T'HH:mm:ss.SSS'Z',hmmm… 看起來跟前面 stackoverflow 附的範例一樣,丟下去跑也能正常 parse,太好了可以收工了… 才怪… 得到時間不對,整整多了 8 小時。WTF?時區問題?

算啦算啦… 去測第二條吧,直接拿文章內說可行的 yyyy-MM-dd'T'HH:mm:ss:SSSZZ 來試試看… 連 parse 都過不了?WTF?等等,為什麼反覆測試下發現有時候會 parse 過、而且值也正確?但有時候卻又 parse 不過?幹這是七月半提前報到嗎?

鬼打牆了 n 分鐘之後,終於發現… 測試會過的時候是測完第三條之後把 Z 前後的單引號拔掉,而測試不過的時候是直接複製第二條的文字…

對,X 他 X 的第二個給的 pattern 根本有問題,按照 ISO-8601 的規格,在 sec 跟 ms 中間應該是「.」而不是「:」。肉眼不仔細比對根本很難發現… Orz

到了這個時候,真的是被嚇到了,乖乖回頭看文件。在 Java API 的定義當中,「X」跟「Z」都表示時區,「X」是 ISO-8601 格式、「Z」是 RFC-822 格式(不過目前看不出差異)。ISO-8601 要求日期與時間之間以一個寫死的大寫「T」連結,寫成 Java 的 pattern 就要前後加上單引號。所以第三條 pattern 尾巴的「Z」是寫死不變的。那,為什麼 parse 會過呢?

ISO-8601 當中規定,如果時區剛好是 UTC(零時區)就顯示 Z,所以得到的是合法的字串,但是除非人在 UTC 不然解出來的實際 Date 就會有時差問題… Orz

心得與結論:

  • 即使有名有號的 library 還是有可能存在奇妙的行為。即使到現在我還是無法替 Gson 在日期上的作法想出任何合理的理由…
    • 寫這篇文章的時候認真找了一下,原來 Gson 有開過 issue-281 還 close 了,整個看起來有點莫名而且最後路人們還是在用 GsonBuilder 這招? WTF?
  • google 出來的結果未必可信,即使排名在很前面。stackoverflow 上也是。像這個解答還有 62 個推,底下也只有一個人指出這是不對的答案。
  • 雖然說官方文件也不是不會出錯,但相比來路不明的 blog,機率還是低的多(雖然啃起來也難過很多)

2019-04-05

GF TextUtil debug 雜記

自從在前前公司接觸 GXT、接著把 Chart 的底層 DrawComponent 給翻了一遍之後,就對這玩意很感興趣,之後陸陸續續以 DrawComponent 為基礎搞了一些東西出來。去年終於搞了一個 GF 版的 TextButton 出來。TextButton重點 惡搞之處在於文字的字體會隨著整體大小而自動調整。好不好用很難說,自己是頗為得意啦… 囧>,因為算是集大成之作:

  • 驗證了 GF Layer 機制的可用性
  • 處理 TextSprite 在視覺上的 y 軸位移問題(雖然沒有相對正統地用後來搞出來的 FontMatrics 來校正 XD)
  • 大幅解決效率問題(因為發現有 Sprite.redraw() 而不用每次都搞 DrawComponent.redraw()

不過實務上陸陸續續有炸出一些問題,在某些狀況下初始的字體並沒有變成正確的大小。但是因為一直都能 workaround 掉,所以沒提煉 SSCCE、自然也沒深究

2015-11-25

Git repo 之間的 sync 操作步驟

這篇文章是 GitHub 作為 repo host,不過下列操作都沒有 depend on GitHub。或著說,就是不希望用 GitHub 的功能,所以才要有這些步驟,不然 pull request 應該可以解決大部分的狀況。

2015-11-17

試論述 Listbox 的 check 與 Image id 的關係

ZK 版本:6.5.3

故事是這樣開始的:畫面上有一個設定 checkmark=true 的 Listbox。每個 item 除了自動生出來的 checkbox 之外就是 Image,src 是用 data URI(所以基本上沒有 loading 時間的問題),也有設定 height 讓 Listbox 不會長得太可怕。也因此,使用者希望點圖片可以看到夠大的圖、但是又不希望出現 scroll bar。

所以當使用者點圖片的時候,就用 Executions.createComponents() 製造出一個 Window,然後設法讓圖片等比例縮小到長或寬撐滿整個 Window。Window 的程式碼長這樣(VM 就不附了):

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ScaleImageViewModel')" 
    closable="true" title="Image" width="90%" height="90%">

    <vlayout vflex="1" hflex="1">
        <div id="cave" vflex="1" hflex="1" style="overflow:auto;">
            <image id="image" src="@load(vm.image)" />
        </div>
        <hlayout hflex="1">
            <button label="Fit Window" w:onClick="resetRatio()" />
            <slider id="scale" hflex="1" w:onScroll="changeRatio()"/>
            <button label="Pre." onClick="@command('prev')" disabled="@load(vm.start)"/>
            <button label="Next" onClick="@command('next')" disabled="@load(vm.end)"/>
        </hlayout>
    </vlayout>
    <script defer="true"><![CDATA[
    zk.Widget.$("$cave").onAfterSize = function() {
        this.$super("onAfterSize");
        resize();
    }

    scaleWgt = zk.Widget.$("$scale");
    caveElmt = $("$cave")[0];
    imageElmt = $("$image")[0];
    imageOriginWidth = imageElmt.width;
    imageOriginHeight = imageElmt.height;

    resetRatio();
    ]]></script>
</window>

<script><![CDATA[
    var scaleWgt;
    var caveElmt;
    var imageElmt; 
    var imageOriginWidth;
    var imageOriginHeight;
    var ratio;

    function resetRatio() {
        var wRatio = caveElmt.offsetWidth / imageOriginWidth;
        var hRatio = caveElmt.offsetHeight / imageOriginHeight;
        setRatio(
            Math.min(
                (wRatio > 1) ? 1 : wRatio,  //不能比 100% 還大,所以最多壓成 1
                (hRatio > 1) ? 1 : hRatio
            )
        );
        scaleWgt.setCurpos(ratio * 100);
    }

    function resize() {
        imageElmt.width = imageOriginWidth * ratio;
        imageElmt.height = imageOriginHeight * ratio;
        console.log("[after] " + imageElmt.width + "x" + imageElmt.height);
    }

    function setRatio(value) {
        ratio = Math.min(1, value); //不能比 100% 還大,所以最多壓成 1
        resize();
    }

    //其實應該要可以直接 call setRatio(),只是 ZK 我實在...
    function changeRatio() {
        setRatio(scaleWgt.getCurpos() / 100);
    }
]]></script>

我想盡量讓事情都只停在 client side 就處理完成,是說我也不確定調整圖片大小這件事情在 server side 能不能順利解。總之,程式碼可能不夠精煉,而且有些莫名其妙地方是靠 trial and error 得到的可行解、完全不明所以…… 只能說我對 ZK 完全沒有愛,能在零零落落毫無章法的 ZK 文間當中湊出這些,我都覺得減壽三天了…… Zzz

好的,前情提要終於講完了,要開始進入正題了。用實際資料測試的時候發現,有些圖片就是無法改變大小(slider 也沒反應)。原本以為是圖片的問題,但是拿 data URI 的值直接塞 browser 能得到正確的 size……

在經過一番折騰之後,終於發現規律:

Listbox 有一個(以上)item 勾選,就會無法改變大小

既然確定是 ZK 搞出來的問題,那要懷疑的東西就少了一點。又一輪的 trial and error 後,終於發現:Image 的 id 不能是 “image”,不然 $("$iamge") 不知道會 select 到哪個 element 去。

是的,只要換個 id,一切統統都沒事了(為了保險起見,我試過 fullImagescaleImage,不保證其他值不會有問題 Zzz)。

WTF?這到底什麼鬼東西?為什麼 Listbox 有沒有 item 被勾選會影響到 $("$image") 的結果?那個 node 還是在 Window 裡頭(不是說 Window 有自己的 id space?)?而為什麼 ZUL 設定的 id 值對應到 JS / DOM 當中居然不是 unique 的?以後開發人員要自己讓 id 值 unique 嗎(不然有 bug 連要從哪裡開始懷疑起都不知道?)

雖然平常就有在 blame ZK,但是沒遇到過這麼令人傻眼的,這件事情足足讓我笑了十分鐘… lol

教練我想寫 GWT…… [淚目]


後來要修正其他功能的 bug,所以重寫了一次,關鍵部份還是一樣,只是程式碼變得清爽一點,茲更新如下:

<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('ScaleImageViewModel')" 
    closable="true" title="Image" width="90%" height="90%">

    <vlayout vflex="1" hflex="1">
        <div id="cave" vflex="1" hflex="1" style="overflow:auto;">
            <image id="scaleImage" src="@load(vm.image)">
                <attribute w:name="setSrc">
                function (src) {
                    this.$setSrc(src);
                    resetRatio();
                }
                </attribute>
            </image>
        </div>
        <hlayout hflex="1">
            <button label="Fit Window" w:onClick="resetRatio()" />
            <slider id="scale" hflex="1" w:onScroll="changeRatio()"/>
            <button label="Pre." onClick="@command('prev')" disabled="@load(vm.start)"/>
            <button label="Next" onClick="@command('next')" disabled="@load(vm.end)"/>
        </hlayout>
    </vlayout>
    <script defer="true">
    //第一次 Image.setSrc() 不會真正 resetRatio(),所以得在 defer 的 script 指定來一次
    resetRatio();
    </script>
</window>

<script><![CDATA[
function resetRatio() {
    var imageElmt = $("$scaleImage")[0];

    if (!imageElmt) { return; }

    var tempImage = new Image();
    tempImage.src = imageElmt.src;

    var caveElmt = $("$cave")[0];
    var wRatio = caveElmt.offsetWidth / tempImage.width;
    var hRatio = caveElmt.offsetHeight / tempImage.height;
    setRatio(
        Math.min(
            (wRatio > 1) ? 1 : wRatio,  //不能比 100% 還大,所以最多壓成 1
            (hRatio > 1) ? 1 : hRatio
        )
    );
    zk.Widget.$("$scale").setCurpos(ratio * 100);
}

function resize() {
    var imageElmt = $("$scaleImage")[0];
    var tempImage = new Image();
    tempImage.src = imageElmt.src;
    imageElmt.width = tempImage.width * ratio;
    imageElmt.height = tempImage.height * ratio;
}

/** value 的值域理論上是 [0,1] **/
function setRatio(value) {
    ratio = Math.min(1, value); //不能比 100% 還大,所以最多壓成 1
    resize();
}

//其實應該要可以直接 call setRatio(),只是 ZK 我實在無法... Orz
function changeRatio() {
    setRatio(zk.Widget.$("$scale").getCurpos() / 100);
}
]]></script>

2015-04-23

C / C ++ 程式設計師使用 JS 的四個階段

原文網址:http://games.greggman.com/game/the-4-stages-of-a-cc-programmer-using-javascript/

第一階段:三年前

這是什麼愚蠢的 script 語言啦?我才沒有用過咧… 但是它是一個 script 語言、然後 script 語言就是爛。除了作 form 驗證之外根本沒啥鳥用。誰在乎阿?我永遠不會用它… 除非我的個人網站打算防止 submit 兩次之類的事情。

第二階段:現在

唉… 這個 project 必須用 JS 作一些東西。幹他媽的爛語言,全域變數是什麼鬼?大括號不能定義變數 scope 又是哪招?好吧,至少我還找到一些愚蠢的方法可以弄出 class 跟繼承行為。

第三階段:三~五年間

哇靠… 如果到處都用 closure、而且用對方法,JS 實在超有趣的你都不知道。非同步的功能直接可以用、所有 API 都是這樣運作的。我可以把一個東西放到螢幕上而不需要 14 個 library、也不用搞了一個禮拜才找到取得一個 window 並顯示出來的方法。我不用搞清楚如何在 7 個不同的平台上用 17 個語言顯示字型。我可以下載圖片、用 canvas 跟 WebGL 畫出東西、播放聲音、存取攝影機跟麥克風… 都花不了什麼功夫。只要 refresh 馬上可以得到回應。外觀設計就丟給設計師,我不用寫一卡車的程式碼;要展示東西給別人看也只要丟個連結給他就好。這實在是太棒啦~

第四階段:五年後

我要重新回去寫一些 C / C++ 的程式。靠北阿… 沒辦法 compile 了?為什麼 IDE 沒辦法再載入這個 project 了?應該是有人改了格式?馬的咧… 為什麼不用 libglfobar link(譯註:原文就是這個字,意義不明 @_@)?搞什麼阿… 寫一個泛用的 callback 要 200 行 meta-template 程式碼?幹他媽的為什麼要搞這麼複雜?阿?你想看看發生啥事情喔?抱歉,我沒辦法 compile 成 Mac 版給你耶… 他媽的 C / C++ 去死吧……

2014-11-29

評即時開票網站

選舉日的下午剛好沒事,所以找點事情作,於是就想來分析比較一下各個即時開票網站的 UI / UX / whatever… [逃]

選了六個網站,分別是:

前面是電視媒體的網站、後面是平面媒體的網站,至於其他先後順序完全無意義。

觀察時間從 16:2x~18:2x,環境為 Win7 + Chrome 39(沒有任何 plugin),螢幕解析度是 1920 * 1080。基本上只評論輸入網址後看到的東西,沒有參雜其他操作。

TVBS

  • 首頁顯示範圍:台北市長
  • 顯示台灣地圖:無
  • 手動更新按鈕:有
  • 最後更新時間:有
  • 得票比率的文字或圖表:無
  • 資料來源:沒有明確列出

提供的資訊量很少,完全沒打算提供其他圖表輔助。layout 簡單到差點以為看的是放大過後的手機版(說不定還真的是 XD)。

三立

  • 首頁顯示範圍:台北市長
  • 顯示台灣地圖:無
  • 手動更新按鈕:無
  • 最後更新時間:無,但有顯示幾秒後刷新。
  • 得票比率的文字或圖表:無
  • 資料來源:三立、TVBS、民視、中視、中選會

提供「xx 秒後刷新」的資訊遠比顯示「最後更新時間」來得好(根本沒列出最後更新時間的網站,去死吧 [指])。提供的資訊貌似很多,但只是列出不同資料來源的開票數,實在沒太大意義。

layout 中規中矩,不太能吐槽、但是也找不出可以稱讚的部份。

東森

  • 首頁顯示範圍:六都、每區最多顯示三位候選人得票數
  • 顯示台灣地圖:無
  • 手動更新按鈕:有
  • 最後更新時間:應該沒有
  • 得票比率的文字或圖表:無
  • 資料來源:應該是多家,挑某一家的資料顯示

網頁技術大概是六個網站當中最落後的,PHP 不意外 [喂]。懶得用一些工具檢測,純粹肉眼觀察可能不太準確,但大抵上「資料更新」是整個頁面重新 refresh(WTF),然後 server 似乎又不夠強壯、static resource 可能也沒塞 cache 相關 header(WTF),常常 CSS 沒 load 進來導致頁面裸奔、圖檔出不來。甚至看到好幾次某一塊區域資料出不來還出現很 raw 也很 low 的 database error 訊息。

layout 也一無可取之處,藍綠色底圖固定在最上頭、高度居然只有大概 800px 左右,以下全部留白。真不知道到底是哪家網頁公司可以在這年頭做出這樣子的作品而且東森居然還讓他驗收過……

自由時報

  • 首頁顯示範圍:基隆、台北、台中、彰化、雲林、嘉義、澎湖。每區均顯示全部候選人得票數
  • 顯示台灣地圖:有
    • 移到各縣市會跳出 tooltip 顯示該區全部縣市長候選人的得票數
    • 點選後列出八個資料來源的得票數、轄內行政區地圖
  • 手動更新按鈕:無
  • 最後更新時間:有,而且是各區各自獨立的更新時間
  • 得票比率的文字或圖表:無
  • 資料來源:七都資料來源不盡相同,例如台北市用 TVBS、台中市用中天。

整體看起來挑不出什麼毛病,畫面乾淨、運作正常、layout 簡單但是一目了然…… 都在正常水準之上。唯一的問題就是為什麼要列那七個縣市?既不是六都、好像也不是激烈戰區,完全看不出選擇依據…… [抓頭]

聯合報

  • 首頁顯示範圍:直轄市、縣市候選人全部列出
  • 顯示台灣地圖:有
    • 移到各縣市會跳出 tooltip 顯示該區全部縣市長候選人的得票數
    • 無法點選
    • 地圖可以放大縮小,但是沒啥變化。
  • 手動更新按鈕:無
  • 最後更新時間:無
  • 得票比率的文字或圖表:長條圖
  • 資料來源:沒有明確列出

聯合報最大的敗筆就是那張地圖。有一堆無謂的功能(DnD、縮放)沒有給予對應的效果,然後操作反應緩慢、縮放之後 DnD 還有可能自己跳回去 default 的縮放 level…… WTF

聯合報的亮點在於他有提供長條圖、還幫忙算出 / 畫出「預估當選門檻」的灰色虛線,讓開票不是只有數字 / 比率跳動,還能讓人體會到「還剩多遠」的目標感。

中國時報

  • 首頁顯示範圍:六都目前最高票的候選人
  • 顯示台灣地圖:有
    • 移到各縣市會跳出 tooltip,顯示領先的候選人以及政黨得票率
    • 點選後列出前三名候選人得票長條圖、政黨得票圓餅圖、轄內行政區地圖
  • 手動更新按鈕:無
  • 最後更新時間:無
  • 得票比率的文字或圖表:文字顯示得票率
  • 資料來源:沒有明確列出

唯一徹底以政黨為呈現角度的網站、也是唯一用即時開票資訊在地圖上顯示領先政黨顏色的網站。是好是壞很難講,也許對於台灣民眾來講反而直覺有效?另外只顯示領先者的得票數、得票比率,我覺得不是很好的設計。

資料更新似乎也是整頁 refresh(有 blink、relayout 的現象),但呈現效果沒有像東森那樣悲劇就是了。

其他

自由時報跟中國時報的地圖都是用 SVG 作的(聯合報懶得看),我們終於徹底脫離 Flash 了嗎? XD

2014-06-01

[新手止難] 為什麼有些東西要弄得那麼複雜?

昨日驚見一文〈「互相之臉」所謂何用〉 [註1],
討論文章數目,為數月來爪哇連線版鮮見現象。
拜讀過各家之言後,回顧提問者之疑……

此時夜半人靜、身困馬桶不得其門而出
沉吟許久,上下交迫間,腦中浮現劣思緒緒
顧不得少年得痔之慮,返回書桌振鍵急敲,以求一吐為快
乃成於此篇陋文,敗筆處處
還望各方高人雅士撥冗指點一二,以正方家


敝痞在資訊領域打混唬爛多年
整理出〈資訊三大唬爛金句〉
其中首要綱領,為挪用錢鍾書所言:

道不虛明,有為而發  

或用俗語釋之

殺頭生意有人作  
賠本生意沒人幹  

雖凡天下學問,約莫均可套用此理
但惟資訊領域,可全然貫穿通透

只因此門學問均屬人為打造,鮮少摻雜自然現象
間或參雜不確定性,也可用機率模型解釋
又因效率為首,一切循規蹈矩最為便利
是謂「資訊領域少例外,規格讀通各處行」

所以,當吾人遇一理論或一設計,艱澀難懂
正當唸之無味棄之被當,國罵聲不絕與耳時
不妨憶此金句,自問之:

懶惰乃人類之美德  
若發明者不夠懶惰,安可流傳至今?

抱此想法續讀,不僅情緒平復、易於心領神會
倘使發現更懶惰之法,圖林獎豈非垂手可得?


今吾人已瞭解其所敘述之架構
但僅見「超頭」處處 [註2]
再三盤算依然覺得:此乃蝕本生意,作不得! 作不得!
莫非敝痞之虎爛金句有誤? 非也…

在此,逆舉老子之說,搭配程式設計為例:

小國寡民,使有什佰之器而不用,使民重死而不遠徒。  
雖有舟輿,無所乘之;雖有甲兵,無所陳之;使民復結繩而用之。  
甘其食,美其服,安其居,樂其俗。  
鄰國相望,雞犬之聲相聞,民至老死不相往來。

將「國」類比至「程式規模」
將「民」類比至「程式設計師」
將「舟輿」、「甲兵」類比至各式軟體架構

若是程式規模小彼此之間關係簡單
又或是無須與其他程式設計師往來
吾人可任意而為之,無所窒礙
此時亦難窺得舟輿甲兵之利,也就無從了解軟體架構之妙

禪宗分南北,北宗主漸修,南宗主頓悟
若漸修之途不可得,何妨留至來日機到緣成,自然頓悟?

敝痞此說,並非低漸修而貴頓悟
漸修之途不可省,則來日方能有頓悟之緣
即便汝輩天縱英名,亦不能自恃而於此處偷懶
以免淪於「重新再造輪」之窘境,慎之! 慎之!

[註1] 「互相之臉」亦有人美譯為「臉間」
其原文為 interface,常見地球語稱之「介面」 \囧/

[註2] 「超頭」其原文為 overhead \囧/


原文寫於 2006.04.15 PTT Java 版。

嗯… 那個時候國文比較好 [笑]

[新手止難] 寫程式就像寫作文 \囧/

各位 Java 版的版友大家好, 我的唬濫分析師痞子猴

痞子今天來這裡幹什麼?
教大家怎麼唬濫新手嘛…

痞子有沒有說,寫程式就像寫作文
你一定要把題目搞清楚
不然馮京變馬涼,就算有痞子那樣唬濫的功力
寫出來的程式還是錯嘛
神 仙 難 救 白 目 客

……..

國文老師有沒有說
寫作文要有起承轉合、要立論嚴謹、要思路清晰
痞子有沒有說,程式要定義好 Input 跟 Output
整個流程都能用火星文講給任何一個火星人聽得懂
你有聽痞子的話嗎? 沒有嘛…
自己都還沒想清楚整個 Input,Output 跟流程
就跑到板上問要怎麼寫、問要用什麼語法、問要用什麼類別
結果大家都以為你是作業文,把你這篇文章作空
你要痞子怎麼辦? 痞子也不知道怎麼辦… [丟滑鼠]

……..

國文老師有沒有說,字跡要端正、成語要用對、要避免錯別字
痞子有沒有說,縮排排版要確實、基礎語法要搞清楚、要避免不良變數名稱
你有聽痞子的話嗎? 沒有嘛…
結果程式碼迴圈區段搞錯,語法 & 邏輯錯誤一堆,程式碼難讀難懂
搞得作多也不是,作空也不成
你要痞子怎麼辦? 痞子也不知道要怎麼辦… [丟鼠墊]

……..

國文老師有沒有說,立論的問題點要各個擊破
痞子有沒有說,要把功能切細再切細,然後一小個一小個分開解決
你有聽痞子的話嗎? 沒有嘛…
重頭到尾都混亂不清 bug 一堆,結果大盤走勢不明朗
你要痞子怎麼辦? 痞子也不知道該怎麼辦… [丟鍵盤]

……..

好的痞子教你用 Java,壞的痞子要你用 .net
結果你不好好學 Java,現在要寫案子了,才發現被 M$ 套牢
你要痞子怎麼辦? 痞子也不知道怎麼辦… [丟螞蟻書]

……..

好! 我們進一段廣告
為了慶祝痞子順利 po 完這篇文章
廣告期間徵召各類 Java 駐版技術達人
有優待!

進廣告


原文寫於 2006.02.23 PTT Java 版,語氣是模仿當時一位股票名嘴的風格。

2014-04-15

Google Cloud Platform 亞太區產品發表會雜記

閒著沒事,看到 Google 辦的這個產品發表會,就去了(反正不用錢)。簡單 murmur 幾句:

發表會內容:

  1. 產品發表會實在沒什麼技術內容,即使 Google 辦的也一樣。大多數著重在「Google Cloud Platform 可以作出什麼」、甚至還不到「Google Cloud Platform 是什麼」的程度。扣掉大家都會掰的「雲的好處」還有「Google 超厲害的基礎建設(這是事實)」的部份,基本上沒剩什麼了。

  2. Google Computer Engine(以下簡稱 GCE)是 IaaS,不像 GAE 是個包好的開發環境(PaaS)。在 GCE 上頭要跑 C++ 還是要幹啥壞事(應該)都可以。以 World Wide Maze 為例,WebSocket Server 就是架在 GCE 上,而不是用 GAE 的 Channel API。sudoku solver 的影像處理部份是使用 OpenCV,這段運算也是用 GCE 跑的。

2014-02-05

GWT 與 HttpClient 的版本衝突

標題好難下,不過說穿了其實根本也沒什麼。 [死]

故事是這樣的:在一個既有的 GWT project 中想在 server side 用 HttpClient 抓 web API 的東西,抓了 4.2.3 版回來,想說先寫個 application 來測,結果一直炸 java.lang.NoSuchMethodError,明明我只跑最簡單的範例阿阿阿阿混蛋:

public static void main(String[] args) throws Exception {
    System.out.println(
        Request.Get("http://localhost:8080/BSS.html")
        .execute().returnContent()
    );
}

狗了一下好像沒人有我這樣的問題,該不會只是因為我沒用 Maven 吧 [胡思亂想ing]。於是先弄個乾淨的 Java project……. 沒問題;那在 GWT project 下用 JSP / Servlet 執行 HttpClient 碼…… 也沒問題 [死]。

停下來想了一下,該不會 GWT 本來就有用 HttpClient?GWT RPC 底下用 HttpClient 好像完全合情合理,只是 HttpClient 的版本不同結果就哭哭了?結果還果真如此,上面那個例子中找不到的 setConfig()HttpRequestBase,是用 gwt-div.jar 裡頭的版本。

基本上在不想搞懂 classloader 以及 Eclipse 設定之類 blahblah 的討人厭東西,這個 issue 就到這邊為止了,反正不用要 application 就沒事了 [毆飛]

只是我常常遇到官方 example 跑不起來,這到底是怎樣的人品阿阿阿阿 [淚奔]

2014-01-07

GAE 的 static file 不會觸發 filter?

一直以來 JSP Filter 用得好好的,結果前幾天一直被炸, index.html 始終不會觸發 Filter.doFilter(); 但是只要換成 JSP 或是 Servlet 就沒事,正常得很。

本來懷疑是 GPE 的問題,結果丟上 GAE 也是一樣的情況。 後來重開乾淨的純 GWT project 跟純 GAE project 比對, 發現只要有用 GAE SDK 的 project 就會有這個現象, 看起來就不像是 bug 了 XD

胡亂繞了一下,終於找到癥結點了。 首先是 GAE 文件講到 web.xml 時說:

Note: Static files, files that are served verbatim to users such as images, CSS or JavaScript, are handled separately from paths mentioned in the deployment descriptor. A request for a URL path that matches a path to a file in the WAR that’s considered a static file will serve the file, regardless of servlet and filter mappings in the deployment descriptor. You can exclude files from those treated as static files using the appengine-web.xml file.

搭配 appengine-web.xml 的這段一起看:

These are known as static files because they do not change, and can benefit from web servers dedicated just to static content. App Engine serves static files from dedicated servers and caches that are separate from the application servers.

[…]

By default, all files in the WAR are treated as both static files and resource files, except for JSP files, which are compiled into servlet classes and mapped to URL paths, and files in the WEB-INF/ directory, which are never served as static files and always available to the app as resource files.

簡單地說,就是除了 JSP 跟 WEB-INF 底下的東西,其他預設都會當成 static file。 而 static file 因為都不會變、所以可以作一些特別處理來優化效能。 所以 index.html 始終不觸發 doFilter(),也就完全合情合理了。 於是只要照著把 index.html 排除在外,就會觸發 doFilter() 了:

<static-files>
    <exclude path="index.html" />
</static-files>

把人家刻意優化的東西當成 bug,實在太丟臉了。 所以這篇貼在 PT2Club 就算了,不要拿到 DontCare Blog 上丟人現眼 [遮臉]

(謎之聲:干有差? [指])