Showing posts with label webapp. Show all posts
Showing posts with label webapp. Show all posts

Tuesday, August 09, 2022

Apache 的 name-based virtual host

在 Apache 上面 name-based virtual host 是指: 利用 Host: 這個 HTTP Header (標頭) 決定要用哪個網站組態設定來回應來自瀏覽器的請求。

Name-based Virtual Host Support 這篇文章看起來,現在 Apache 建議無論如何就設定 <VirtualHost> 就對了。

搜尋 request 要用哪個 <VirtualHost> 處理時,會先看 request 所連線的目標 IP:Port 組合,先把有 match 到的部份最精確 (exact match) 的設定挑出來,當有多個設定相符時,會進去看 ServerName 來比對。

Host header 比對 ServerName 時,雖然 Host header 可以攜帶 Port,但是比對時會被忽略,只會使用瀏覽器連線時的目標 Port 進行比對。

Wednesday, December 11, 2019

SCSS

SCSS (Sassy CSS) 是加上了 Sass 語法強化過後的 CSS 樣式表,如果只有 Sass 的話,主要的差異是使用縮排區分定義區塊 (CSS 使用大括號 { }),並且使用換行作為樣式分段 (CSS 使用分號 ;)。

可以在所有地方使用的語法:

變數宣告

$var: value 的型式,值的部份可以是數值,也可以是算式。

要注意,在 CSS 中也有變數 (variables; 或稱 custom properties),為 -- 開頭。

流程控制
  • @if
  • @each
  • @for
  • @while
編譯時期指引

產生錯誤或是偵錯訊息的編譯時期指引: @error (編譯會終止), @warn, @debug

只可以用在最上層的樣式定義內的敘述: @use, @import, @mixin, @function

模組 (@use)的語法目前只有 DartSass 支援,利用這個語法可以處理名稱空間汙染的問題。

舊有的 @import 語法,因為有名稱空間汙染的問題,看起來是計劃慢慢汰除掉。

Parent Selector (&) 用來巢狀的樣式定義中,在內層定義參考上一層的樣式符號名稱,。

Placeholder Selector (%...) 是給樣式定義放置一個暫時的名字,編譯後會把有 @extend 的 Selector 代換進去。

在樣式定義中,可以利用 nest block 集中定義前綴一樣的樣式屬性 (properties):

.info-page {
  margin: auto {
    bottom: 10px;
    top: 2px;
  }
}

編譯後會變成:

.info-page {
  margin: auto;
  margin-bottom: 10px;
  margin-top: 2px;
}

@function 用來定義產生樣式屬性值 (property value) 的邏輯,@mixin...@include 則是用來產生樣式定義。

Interpolation (#{ }) 可以用來將值嵌入 Selector 規則裡,或是 Property 的 Key 或 Value 中。當嵌入 Property Value 時,通常是用來產生一個字串,或是連結的值。

使用 @mixin 或是 placeholder (%) 的時機要看語意,一般來說 Placeholder 會比較著重於 Selector 間的繼承關係,比如 %error-base.error-low, .error-high 的關係;而 @mixin 就比較是單純的功能性的,像是 @mixin container-5-cols 之類的。

這部份在官方文件的 Extends or Mixins? 有些說明:

Extends and mixins are both ways of encapsulating and re-using styles in Sass, which naturally raises the question of when to use which one. Mixins are obviously necessary when you need to configure the styles using arguments, but what if they’re just a chunk of styles?

As a rule of thumb, extends are the best option when you’re expressing a relationship between semantic classes (or other semantic selectors). Because an element with class .error--serious is an error, it makes sense for it to extend .error. But for non-semantic collections of styles, writing a mixin can avoid cascade headaches and make it easier to configure down the line.

Saturday, May 25, 2019

CSRF / XSRF

CSRF (Cross-Site Request Forgery; 也會簡寫為 XSRF) 攻擊的防禦要另外在伺服器上做,稍微筆記一下原理。

這個攻擊的名稱除了常見的 CSRF / XSRF 之外,也可能被稱為: Sea Surf, Session Riding, Hostile Linking, One-click Attack 等。

簡介

這個攻擊主要是欺騙使用者去觸發「預期之外」的操作 (unwanted actions)。

攻擊目標在於「狀態的改變」,因為伺服器的回復攻擊者不太容易取得,因此偷竊資訊不是主要目標。

可能的攻擊目標類似: 盜買或盜賣、轉帳、更改帳號中的備援電子郵件位址、新增或變更紀錄等。

攻擊時可能會混用一些社交工程的手法,欺騙使用者開啟特製的網頁。

原理

由於瀏覽器在對網站發送 request 時,會附帶上代表使用者身份的資訊,如: session cookies, IP address 等,因此攻擊者有機會製作一個連結或是按鈕,讓使用者誤擊,達到以該使用者身份觸發特定網站上的操作。

一般來說攻擊者不太容易取得操作後的結果,因此主要會是針對「狀態的變更」作為攻擊目標,而非竊取資訊。

搭配 XSS 的話,攻擊強度會倍增,使用者更容易點擊到含有攻擊標籤或程式的頁面,也更容易繞過安全機制。這樣的安全缺失稱為 "Stored CSRF Flaws"。

無效的防禦方式

使用祕密的 cookie

基本上所有 cookies 都會傳,所以這個方式沒什麼功能。

主流的瀏覽器有個 SameSiteSet-Cookie 標記,可以指引 cookies 只在來源是該網站時帶入 request 上,但不應該只依賴這個機制。

只接受 POST request

有很多方法可以騙使用者觸發 POST request 查詢。

多階段的交易

攻擊者還是有可能完成攻擊。

URL 改寫

這個防禦方式指的是將部份認証用資訊編在 URL 裡面,這樣攻擊者不容易預期操作的 URL 位址,來達到防禦的目的。

利用這個方式可以一定程度迴避 CSRF 攻擊,但將認証資訊嵌在網址中不是好的安全實作方法,並不建議完全依賴這個策略。

HTTPS

完全沒差。

防禦注意事項

需完全防禦 XSS

XSS 將會使得絕大多數的 CSRF 防禦失效,因為攻擊者將可以使用 XMLHttpRequest 來取得安全機制中的 token 值。

要保護的資源

一般來說,如果遵照 RFC2616 9.1.1 的建議: 「GET 與 HEAD 為 safe-method,不應該造成 side-effect。」,那麼主要要保護的資源會是 <form> 標籤或 XHR/AJAX 使用的端點。

主要防禦手段

Token-based 的防禦

在 request 中嵌入覆核用的 token 值,可以嵌在:

  • <form> 的隱藏欄位中 (form POST),或
  • HTTP 的標頭中 (XHR),或
  • URL 中 (GET; 非常不建議)。

不是很建議將 token 嵌在 URL 中,因為很可能會透過瀏覽器的歷史紀錄或是 Refer 而洩漏,反而提供攻擊者機會。

使用 Token-based 防禦有以下注意事項:

  • 使用夠強的 Encryption 或 HMAC 函數,建議使用 AES256-GCM (encryption) 或 SHA256/512 (HMAC) 作為加密與簽章函數。
  • 需妥善實作 Key rotation 與 Token life-time 的維護。

常見的實作方式:

Synchronizer Token Pattern

將 token 存在 session 中,每次操作後就產生新的 token 值。

這個技巧原本是用來避免重複送出表單,或是防禦重送攻擊。配合 <form> 實作時要注意如果使用者按下上一頁,嵌在 <form> 的 token 可能已經無效,需做相關處理。

Encryption-based Token Pattern

將 User ID、時戳、一次性識別碼 (nonce)等資訊使用金鑰加密,作為 token 值。

伺服端在處理後續的操作時在解碼並驗證時戳是否仍有效,透過時戳與 nonce 可以一定程度防禦重送攻擊。

HMAC Based Token Pattern

這個方式類似 Encryption-based token pattern 的作法,但使用 HMAC 取代加密。

投入雜湊函式的值除了 Encryption-based token pattern 中的值之外,建議增加一個每次都不一樣的值,比如說 operation-type 之類的,以避免重送攻擊。相同的目的在 Encryption-based token pattern 中,使用 nonce 來達到增加隨機性的目的。

登入頁面的防護建議特別處理,避免使用者默默地被登入為攻擊者的帳號,以達到竊取使用者線上行為的目的。

Wednesday, February 13, 2019

AngularDart

Dart 針對網頁應用的網站在 https://webdev.dartlang.org/ 上,目前主推是基於 Angular Dart 框架的開發。

也是有其他框架或是更基礎的作法,不過指引的網頁放在很邊緣的地方,簡單來說就是: Angular Dart, Polymer Dart, Low-level HTML 這些選項。

Get Started 大概就是介紹一些工具:

  • DartPad 可以直接在瀏覽器中試驗 Dart 程式碼。
  • 下載與安裝 Dart SDK 到本機端。
  • 下載與安裝 IDE (整合開發環境) 到本機端,官網是推 JetBrains 的 WebStorm, 不過我用 CE 版的 IntelliJ IDEA 加上 Dart Plug-In 也算是還過的去。
  • 介紹怎麼用 IDE 開測試網頁,背後其實就是 pub serve 這樣。自己直接下指令好像會比較順,有碰到幾次透過 IDE 啟動但跑得不太順。

文件層次結構有點怪,總之在 Angular Dart > Guide > Overview 下,說明了整個文件的結構 XD 大概就是:

Guide
說明基礎觀念。
Tutorial
一步一步建立一個應用程式。
Advanced
進階主題。

目前還不確定先看 Guide 好還是先跑 Tutorial 好,簡單看過感覺好像都可以。


Architecture Overview 介紹了整個概念、架構、主要的物件類別。大概是下面這些組成:

Template
使用 Angular 擴充過的 HTML 撰寫樣板。
Component
撰寫 Component 類別來使用 Template。
Service
應用程式的邏輯放在 Service 類別裡。
Module
Service 與 Component 封裝在 Module 裡面。

程式 (網頁) 打開是一個稱為 bootstrap the root module 的過程,會把 Angular 載入,然後使用者就可以跟介面互動,互動的事件會驅動 Angular 讓介面做出對應的變化。

AngularDart 的 module 跟 Dart 的 module 或 package 是差不多的概念,總之是一個編譯的單元。

除了最基本的 root module 之外,比較大的程式會分出數個 feature module 來將程式模組化。於最單純的情境下,在 root module 裡面就只有一個 component,稱為 root component,通常會命名為 AppComponent。

Component 可以控制一小塊稱為 view 的畫面,控制邏輯定義在一個類別裡面。在 Component 中會儲存有部份的應用程式的數據,主要是要支援 view 的顯示,比如說: 在 view 中顯示了人員列表,那麼 component 中可能就會有 List<Person> 物件實體。在 Component 中,常會關聯有一個 Service 物件的實體,用來提供數據資料,這部份直接看文件裡的範例可能會比較清楚。另外 component 有生命週期,可以藉由實作對應的介面方法來對生命週期中的事件做出反應: Lifecycle Hooks

Template 基本上就是加上 Angular 樣板語法 (Template Syntax) 的 HTML,除了標準的 HTML tag 與 Angular Template Syntax 之外,也可以使用自定義的 Angular 元件標籤。

Metadata 用來描述 Dart 類別是什麼樣的 Angular 物件類別,或者說是把用 Dart 類別實作的邏輯接入 AngularDart 框架的方法,基本上就是用 Dart 的 @annotation 達成。比如說: 透過將 @Component 加到一個類別上,就可以告訴 Angular 框架這個類別是一個 Angular Component 類別。在 @Component 上,還會描述諸如 template 路徑 (templateUrl)、找尋對應標籤用的 CSS selector (selector) 等資訊。除了 @Component 之外,還有 @Injectable, @Input, @Output 等 annotation 可以使用。

透過 @Component 就可以把 template 跟 component 邏輯繫結在一起,所以架構圖上在 template 與 component 之間,用不一樣的圖例畫了 metadata 在中間。

Data binding (資料繫結) 可以讓資料在 Component 與 DOM 之間被推送或是拉取的動作由框架代勞,而不用自己手動寫一堆程式碼來做更新 UI 或是從 UI 取值這些讓人煩躁的事情。要使用 data binding 基本上就是加註到 template 上,描述資料怎麼從 component 拉取出來或推送進去。有 4 種 data binding 的語法:

{{hero.name}}

這個語法稱為 Interpolation,功能是把資料值從 component 取出,轉換成文字後嵌入 (interpolate) 到 DOM 裡面。

<li>{{hero.name}}</li>

這是一個從 component 到 DOM 的單向 binding,主要是處理文字的資料。

[hero]

這個語法稱為 Property binding,功能是把資料值從 component 取出,賦值到所嵌入的 child component 的屬性上。

像下面這個例子,就是從目前的 component (在此例是 HeroListComponent) 中,取出 selectedHero 屬性的值,賦值 (assign) 到被嵌入的 child component (在此例是 HeroDetailComponent) 中的 hero 屬性中。

<hero-detail [hero]="selectedHero"></hero-detail>

文件上的圖是說這是一個從 component 到 DOM 的單向 binding,不過因為資料會先被另一個 component (child component) 接收到,再視接收的 component 的處理邏輯反應到 DOM 上,感覺上也可以說是 component 到 component 的單向 binding。

這個語法可以用來傳遞物件實體,這點跟 interpolation 語法不太一樣。

(click)

這個語法稱為 Event binding,功能是在使用者事件發生的時候呼叫指定的方法。

<li (click)="selectHero(hero)"></li>

這是一個從 DOM 到 component 的單向 binding。

[(ngModel)]

這個語法稱為 Two-way binding,功能是雙向的在 DOM 或 component 以及 component 之間雙向的更新資料值。

<input [(ngModel)]="hero.name">

語法 [(x)]="p" 會假設使用了這個語法的 child component 上有屬性 x 以及會在事件發生時產生新的屬性值的事件方法 xChange,在初始化的時候會拿關聯的屬性 p 來設定 x 的值,並會在 xChange 產出新的值的時候更新關聯的屬性 p 的內容。屬性 p 的變動初步測試起來也可以反應到 x 上,不過之前在 Polymer Dart 上被搞到快不行,之後可能還是要留意。

一般的 HTML element 並不支援 x-xChange 的協定,因此有 ngModel 這個 directive 來包裝傳統的 HTML element,讓他們可以使用 two-way binding。

在 Angular 中是依據 Directive 的指引從 template 的 DOM 結構產生 (render) 出最後的 DOM 結構,一個 Directive 就是一個有 @Directive annotation 的類別。

技術上來說 Component 也是一個 Directive,不過因為 Component 算是整個 Angular 運作的中心,因此在架構上會拉出來說明。

從行為上來看,Directive 有分成 structural directiveattribute directive 兩種,差別就是轉換的前後 DOM 結構是不是一樣。

Structural directive 在標記上要有一個星號 (*) 在標記的名稱前面,像是 *ngIf 這樣。實際上是會先產生一個 <template></template> 標籤,把被標記的標籤放進去,然後再把 <template> 丟進 directive 的 class 中處理。

在一個標籤中不可以有兩個以上的 structural directive,因為 structural directive 會變更 DOM 結構,變更動作的先後會對結果有影響,因此透過禁止有兩個以上的 structural directive 來迴避這個順序的問題。

常見的 structural directive 有: ngIf, ngFor, ngSwitch

Attribute directive 主要是用來變更一個元素或節點的外觀或行為。因為設定上看起來很像 DOM 節點的 attribute,所以稱為 attribute directive。

常見的 attribute directive 有: ngModel, ngStyle, ngClass

Service 基本上就是一個封裝好作業邏輯的類別實體,裡面就是實作應用程式邏輯、資料下載、資料驗證、訊息交換、紀錄等等,在 Component 裡面就是單純的呼叫 Service 提供的方法,並操作 view 做出回應。

對 Angular 來說,Service 並不是特別的組件,Angular 本身不會去看到或是識別出 Service 組件。如果要把邏輯通通實作在 Component 中也不是不行,但是這樣從程式的結構來看就是比較不理想。

AngularDart 實作了 Dependency Injection 機制來協助管理 Service 實體,讓 Service 的物件實體的產生跟 Component 的邏輯抽離。

使用 Angular 的 Dependency Injection (DI) 框架,大致上就是:

  1. 要被當成 Service 組件注入的類別要加註 @Injectable() (注意: 是產生一個 Injectable 物件實體來做 annotation)
  2. 在層次上開始使用到 Service 的 @Component annotation 上,給定 providers 參數來描述所要導入的 dependency 物件。
  3. 要用到 Service 組件的 Component 中,在建構子中指定所要使用的 Service 組件的型別。

依照 Hierarchical Dependency Injectors 的說法,在 AngularDart 框架裡頭會維護一個跟 DOM tree 結構大致上一致的 Injector tree 來進行 dependency resolve 的作業。在建立 Component 物件實體時,Injector 會沿著 Injector tree 往上去找尋適當的 Service 物件實體,當找不到的時候,就會依照 @Component 上 providers 參數所設定的 Provider 組態來建立 Service 物件實體。

Dart 套件工具

Dart SDK 裡面包了一個稱為 pub 的套件管理工具程式,說明網頁的標題是 Pub Package and Asset Manager

一個含有 pubspec.yaml 設定檔的資料夾就是一個 Dart 的 package,裡面原則上有個 lib/ 資料夾,詳情可以參考: Create Library Packages

在 pubspec 裡面,主要是設定名稱、相依套件 (dependency)、轉換器 (transformer)。

相依套件可以設定名稱,也可以設定成由 git 取得: Pub Dependencies

如果要自建套件發佈站: https://github.com/dart-lang/pub-dartlang-dart

開發時期才需要的套件,比如單元測試用的套件,可以列在 dev_dependencies 中。這主要是針對作為函式庫的套件,這樣僅僅使用到函式庫的話,就不用連測試用的套件也得安裝。

設定相依性的時候,版本規則比較特殊的是 Caret Syntax。意思是依照 semantic versioning 取相容的版本,舉例來說: ^5.2.3 會等同於 >=5.2.3 <6.0.0。關於相依性解析時,版本編號的解釋,可以參考: Pub Versioning Philosophy

整個建置的部份在各個平台間的整合與一致化似乎還不是很完整,目前 pub 指令的說明,包含 Pub Package and Asset ManagerPub Commands 裡面是說 pub buildpub serve 是專門針對 web 開發的:

Two additional commands (pub build and pub serve) are specific to web development.

這兩個子命令的文件也是丟在 Dart for the Web 那邊,但是 pub build 裡面用到的 transformer 又是在 pub 本身的文件裡頭介紹的 XDDD 可能整理的還不是很好,未來可能還會有變化。

一些名詞解釋: Glossary of Pub Terms -

Lockfile

一個檔案名稱為 pubspec.lock 的檔案,裡面會記載所安裝的套件的版本,會透過 pub get, pub upgrade, pub downgrade 等套件管理指令進行更新修改。

Application package

整個套件是作為應用程式使用,使用到的 dependencies 版本通常會是透過 Lockfile 鎖定在特定的版本上,在 pubspec.yaml 裡的版本規則反而是會用很隨意的方式設定,所以會建議 Lockfile 簽入版本控制中。

Library package

套件是作為函式庫使用為主,相依的 dependencies 版本應該儘量指定寬一點。

Asset

基本上就是所有影響到最後要佈署的產出的所有東西,包含原始碼、樣式表、圖檔等等。大概可以粗略的這樣分類:

  • pub buildpub serve來說:

    • Source asset: 在儲存媒體上的檔案,會被 pub 讀進去的,也就是原始的檔案。
    • Generated asset: 丟給 client 或是寫出到儲存媒體的檔案,會被 pub 輸出,也就是最後的結果產出。
  • transformer 的角度來看:

    • Input asset: 輸入的內容,可能是剛從 source asset 讀入,或是前一級的 transformer 的輸出。
    • Output asset: 輸出的內容,可能會被作為 generated asset 輸出,或是作為下一級 transformer 的輸入。

如果要自己寫 Transformer:

實作 Transformer 時所使用的套件是: barback

基本上要實作 Transformer 就是提供 .apply() (或是 .apply() 如果是實作 AggregateTransformer 的話,參數的型別不太一樣) 方法,要轉換的輸入會由外部用 Transform 物件傳入,轉換後把輸出用 Asset 物件包裝出來加到 Transform 物件中。

專案的資料夾 layout 可以參考: Pub Package Layout Conventions

不要加入版控的資料夾或檔案,可以參考 What Not to Commit (除了 Dart 的檔案外,裡面還有提到一些 IDE 所產生的檔案):

  • .packages: 由 pub 管理的所相依套件檔案。
  • doc/api: 在 doc/ 下的 api/ 資料夾是 dartdoc 產生出來的。
  • pubspec.lock: 看著辦,建議是 application package 的話要簽入版控。

Angular with TypeScript

現在 Angular 有自己的 CLI 工具了,可以處理建立專案、產生元件、測試、檢驗等等瑣事。

ng new [PROJECT_FOLDER_NAME]
建立新專案。
ng serve
啟動測試伺服器。
ng help [COMMAND]
顯示說明。

產生的專案預設會有所有東西,包含簡單的測試,還會建立 Git repo 並自動做 import commit 出來,有選項可以關掉。

建新專案時可以順帶給定一些參數,調整後面產生的 component 等程式的預設組態,比如下面是幾個常用的:

ng new [PROJECT_NAME] --prefix=[SELECTOR_PREFIX (預設值: app)] --style=[STYLE_EXT (預設值: css)] --routing

打定主意不用內建的測試框架 (或是根本不打算測試的話 wwwwww) 的話,還可以加上 --skip-tests 來略過產生測試的程式碼。

在自動產生的程式碼中,進入點是 src/main.ts,在這邊會載入 src/app/app.module.ts 裡的定義,然後接著就會啟動 src/app/app.component.ts 裡的 AppComponent 作為 root component 顯示。

更新框架套件只要 ng update [LIBRARY_NAME] 即可,如果加上 --all 則會將因為相依性帶進來的開發套件一起更新。

Getting started: Project file review 中,有說明專案目錄下的檔案用途。

產生 Component 使用 ng generate component [COMPONENT_NAME] 指令,會在 src/app/ 下面產生指定名稱的資料夾與相關程式檔案,並更新 src/app/app.module.ts 檔案。

使用 ng generate 產生 service 或其他 module 時,要額外使用 --app= 選項,否則不會更新 src/app/app.module.ts 檔案,要手動將新產生的 Module 增加進去。

看起來目前還沒有針對 component library 的 generate 指令,如果要做 component library 可能要參考 Angular Material 的作法。

使用 ngModel 前,必須先在 app.module.ts 中 由 NgModel 修飾過的 AppModule 類別引入 FormsModule 才可以使用。事實上所有元件都必須在 NgModel 中引入,才可以使用。

在 Angular 中,使用了 RxJSObservable 實作來處理非同步的操作。

通常會用上 Observable 的多會是遠端的資源,因此還會有一個屬性儲存當前可用的實體,在 Angular 的慣例中 (convention) 會把存放 Observable 屬性名稱的尾端加上一個錢號 ($) 以作為區別儲存實際值的屬性。

Routing 的部份,慣例上要增加一個特殊的 AppRoutingModule 來處理在 view 之間切換的事情。使用下面的指令新增:

ng generate module app-routing --flat --module=app

產生在 view 之間切換的連結時,要使用 RouterLink directive 來讓 <a> 的 href 屬性可以根據給 RouterModule 的參數來產生適當的連結。

要使用 HTTP End-point 的話,已經包裝好了 HttpClient 模組可以使用。跟其他模組一樣,要在 AppModule 中匯入後就可以使用。

架構上,最基礎的 Angular 建構元素是 NgModule,用來表達 Component 等 Angular 應用程式組件。每個 Angular 應用程式至少會有一個 root module 來 bootstrap 整個應用程式,引入其他的 feature module 來完成應用程式的功能。

Component 會決定 view 的定義,提供給 Angular 框架來依照所定義的邏輯與當下取得的資料狀況來選用與調整。在每個 Angular 應用程式中,會有至少一個 root component 來作為最基底的組件。

Component 中使用 Service 所提供的與 view 無直接關係的相關功能,提供服務的組件作為 dependencyinject 到 Component 中。

Component 與 Service 都是一般的 class,但是透過 decorator 把相關的模組類型資訊等各項後設資訊加到 class 上。

  • 對於 Component 來說,所附加的主要是 template 資訊,也就是內含 directive 與 binding markup 的 HTML 內容。

    透過對類別加上 @Component() 裝飾子來定義出 Component 模組,關聯樣板的相關屬性是 selector, templateUrl, styleUrls 等,與 AngularDart 相同。

  • 對於 Service 來說,所附加的主要是讓 Angular 進行 Dependency Injection 所需要的資訊。

    將類別標示為 Service 所使用的是 @Injectable() 裝飾子,沒有特別的參數。

一般來說,應用程式的 root module 會稱為 AppModule

@NgModule() 中主要的參數:

declarations

在這個 NgModule 下開發的 Components, Directives, Pipes 要加到這個陣列裡面,在 NgModule FAQs: What classes should I add to the declarations array? 裡頭有大略講到用法。

exports

要讓其他 NgModule 使用的組件,也是陣列型式。

imports

要導入這個 NgModule 中使用的其他 NgModule,比如在樣板要使用 NgModel 的話就會需要 FormModule 模組。

providers

要使用到的服務,在這邊導入會變成全域的。也可以在 Component 層級進行導入來限制 scope 的範圍,可以參考 Hierarchical Dependency Injectors 的說明。

bootstrap

指定 root component,只有 root module 需要定義這個屬性。

可以在 Component 中利用 Life-cycle hook 定義 Angular 在建立、更新、摧毀等各階段要執行的邏輯,比如常見的 ngOnInit() 即為一例。

@Component() 中,常用的參數有:

selector

用來獲取這個元件在 DOM 中所在位置的 CSS selector 規則,當依規則找到節點後,會將元件安插在該節點上。

templateUrl

樣板的相對位址,樣板基本上就是 HTML 扣除 <script /> 等敏感標籤後加上樣板語法樣板語法主要是加在標籤的屬性部份。

providers

這個元件所需要使用的 service 類別所構成的陣列,將會由 Angular 透過 Dependency Injection 往建構子注入。

如果要在程式裡頭定義 template 的話,可以使用 ECMAScript 2015 backticks (`) 語法,功能與 Python 的 """ ... """ 語法大致相同。

所產生的 view 會由 Angular 自動的依據樣板定義的邏輯更新,更新的時機稱為 change detection cycle,多為與 view 相關的同步作業完成時,比如鍵盤事件、計時器完成、HTTP 傳輸完成等。

輸出值到 view 上會透過 template expression 來達成,在 template expression 中只可以參考到 Component 上的元素以及部份不會產生 side effect 的 JavaScript 運算子,不可以使用全域空間像是 window.location 或是 Math.max() 等元素。

可以在 template 中參考同樣板中的其他物件,首先用井號 (#) 加上 template reference variable 標記 (如: <input #heroInput>) 接著就可以在 Interpolation 中使用 (如: {{heroInput.value}})。

要特別注意,因為 template reference variable 對實體的繫結只會在初始化樣板時發生一次,而且 scope 是整個樣板,是不可以重複的,也就是不能在 *ngFor 中使用。如果要搭配 *ngFor 使用的話,可能要考慮做一個獨立的元件吧!

要對事件反應的話,是透過 template statement 搭配 event binding 來達成。在 template statement 中,大多是直接呼叫 Component 上的方法,或是簡單的 assignment 敘述。在定義 event binding 時,會多一個 $event 變數可以參考,代表 DOM event 物件。

HTML attribute 與 DOM property 不同,可以將 HTML attribute 視為初始化 DOM property 的設定,一旦 DOM 初始化完成後,就是透過 DOM property 來改變顯示或是互動的反應。在 template 中,操作的對象是 DOM property 的值。要注意,HTML attribute 與 DOM property 並不一定是 1-1 對應,此外,即使名稱相同,反應可能會不同。

Binding syntax: An overview 中,可以看到各種繫結語法的作用對象與範例。

Angular 區分 Component 與 Service 主要是為了:

  • 讓 Component 專注於朔造使用者體驗上,理想上 Component 只要提供 Data-binding 必要的屬性與方法,作為 View 與 Model (Application Logic) 中間的介接角色。
  • 不應在 Component 中處理有關資料擷取、輸入驗證、紀錄等邏輯,而應將這些邏輯抽到 Service 中,如此可以在不同的場合使用相同的 Service 以提升應用程式在不同需要下的可適應性。
Injector

整個 Angular 的 DI 機制的主要機制實作,應用程式不需要自己建立 Injector 實體,框架會在 Bootstrap 階段進行準備。

Container

Injector 會為每個所建立的 dependency 實體維護一個 Container 來儲存相關的資料,並在可能的時候儘量的再利用所建立的 dependency 實體。

Provider

Provider 是指要給 Injector 用來建立 dependency instance 的邏輯,對 Service 來說會是定義 Service 的類別。

如果在 root module 加 provider 的話,那麼會建立一個給全域使用的 instance。

如果在 Component 中加 provider 的話,每個 component instance 都會有一份獨立的 instance 給該 component instance 以及其下的 component instance 使用。

在 Angular 中有預先建置了一些表單的機制,像是雙向資料繫節 (two-way data binding), 變更追蹤 (change tracking), 表單資料驗證 (validation), 錯誤處理 (error handling) 等框架。

極端來看,最基本的取資料的方式就是用事件繫結 (event binding) 來取得使用者的輸入,比如 <input (keyup)="handle_keyup($event)"> 這樣,然後再實作處理鍵盤輸入事件的方法來從事件物件中取出使用者的動作。這麼做的缺點就是使用者看到的部份 (HTML) 跟程式的邏輯 (TypeScript) 綁的太緊密,當視覺部份有調整時,邏輯就必須跟著調整。

一個稍微好一點的作法是利用 template reference variable 來取得輸入值: <input #box (keyup)="handle_input(#box.value)">,這樣在邏輯端就不用非常知道前端事件來源的細節。

<form> 上加上 template reference variable 繫結的 ngForm directive 並在各 input control 加上名為 name 的 property 的話,就可以利用這個 reference variable 在樣板上或是利用 @View() 去存取 form 內的各個 input control 物件。

在 input control 使用 ngModel directive 除了可以用在雙向資料繫結外,也可以讓 Angular 自動基於輸入的狀態加上 class name: Track control state and validity with ngModel

要跨越 HTML element 存取 Angular 附加到 input control 的資訊的話,可以利用將 template reference variable 繫結到 ngModel 來達成:

<input [(ngModel)]="model.name" name="name" #name="ngModel">
<div [hidden]="name.valid || name.pristine">...

利用 @ViewChild 可以在 TypeScript 中存取到 HTML 裡面定義的元件參考,原生元件要用 ElementRef 型別。

另外就是 ViewChild 獲取元件參考的時機,利用 static 參數來控制,原則上就是如果要在 OnInit 取用的話,就會需要設為 true,但是該元件就不能使用 *ngIf, *ngFor 之類的樣板指令,詳情參考 Static Query Migration Guide

Thursday, June 15, 2017

Dart 基本語言元素

算是一些筆記,雖然 Dart 可以應用的場合挺多的,不過我目前主要目標還是放在瀏覽器上,工作上還是能夠自己寫一些前端比較方便,希望可以找到一個好用的前端語言。


總之,先從基本開始: 在 Get Started 裡頭介紹了 DartPad 可以在瀏覽器裡測試程式碼。

DartPad 的說明文件好像是放在 Github 上的 Wiki 裡,比如關於嵌入網頁的說明: Embedding Guide

然後有分平台:

  • webdev: 應該就是環繞在 Dart2JS 的應用程式開發,最後執行環境是瀏覽器。
  • Flutter: Mobile App 的開發,不能使用 dart:mirrors 以及 dart:html 函式庫。後者不意外,前者應該是因為 iOS 平台沒有辦法嵌 DartVM 的關係。感覺主要就是用語言的部份,不知道執行時期環境跟其他平台有共用多少。
  • DartVM: 跑在 DartVM 上,主要是 Server-side 或是 Console 介面的程式。

Sample Code 有一些常見語言邏輯片段的程式,比如: 變數宣告、邏輯判斷、迴圈、類別宣告、類別繼承、非同步操作、例外... 等等。

Language Tour 介紹了主要的語言元素,包含基本概念、關鍵字、內建型別、函數、運算子、類別、封裝、還有各種 Dart 特有的特性。

幾個我覺得比較特殊的:

  • 所有東西都是物件,繼承自 Object 類別。
  • 函數可以在頂層名稱空間 (就像 JavaScript 或是 C/C++ 那樣,跟 Java 不同) 的,也可以是類別的方法,也可以有 closure (nested or local function)
  • 沒有 public, protected, private 之類的存取控制關鍵字,封裝是以函式庫 (Library) 為單位封裝,以底線 (_) 開頭的東西只能在函式庫內使用。
  • 語言中的 expression 跟 statement 會有不同的意義,後續看文件要特別注意。

變數在明確初始化的場合下,會預設的被初始化為 null 值,即便是數值 (num, int) 也一樣。 (因為所有東西都是物件)

變數可以不明確寫出型別,不過原則上建議明確寫出變數或參數的型別,但 lambda 之類很短的程式片段,因為夠短,語言引擎應該可以正確的進行型別推理,所以建議基於可讀性而把型別標註 (Type annotation) 省略掉。

值僅在編譯時期決定的常數應該用 const 關鍵字宣告,值的繫結只會發生在編譯時期。如果是指定值後不會再變更的則使用 final 宣告,值的繫結發生在執行時期,且只會發生一次。

數值型別有 intdouble 兩種,都是原則上為 64 位元的有號數,分別表示整數與浮點數,這兩個數值型別的上層類別是 num 型別。 Dart 本身沒有規定儲存 int 型別的位元數,但是會受到平台的限制,因此原則上是 64 位元的。

數學操作有些定義在數值類別上,比如 .abs(), .ceil(), .floor(), .round() 方法,或是 int 的 << 跟 ~ 等運算子。有些是定義在 dart:math 裡,比如 sqrt() 或是 cos() 之類的。

字串可以用雙引號或單引號,不論是雙引號或單引號,脫逸字元 (如: \n) 都是有效的。另外也像 Python 一樣,有 r"Raw String" 可以使用,脫逸字元在 raw string 中是無效的。還有多行的字串,也是跟 Python 用法相同使用三個引號來定義。

鄰接的字串定值 (string literal) 會自動的在編譯時期被接合成一個字串,或是也可以像執行時期一樣用 + 來接合字串。

字串中可以使用 ${expression} 來進行值的嵌入,最後 expression 被評估出的值物件中的 .toString() 會被用來產生遷入的字串值。

當字串嵌入中的 expression 只是一個識別字 (identifier, 通常就是變數名稱) 時,大括號可以省略。不過個人不太喜歡這樣用法,眼睛太大很容易會看錯...

在 if 中的判斷式,應該明確的評估出 true 或是 false 值來,否則行為會依平台有不同的結果。這點跟 Java 相同,但執行時期反應是依平台而定。

陣列 (array) 就是 List 物件,串列的大小是可以透過 .add() 延伸的,這點跟 Python 類似。要宣告內容不可變更的常數的串列,直接使用 const 關鍵字,寫法大概是 var a = const [1, 2, 3] 這個樣子。

也有字典,就是 Map 物件,使用起來也是類似 Python 的 dict 操作。與 List 一樣可以利用 const 關鍵字來製作不可變得常數字典,大概是 var d = const {1: "one", 2: "two",} 這樣的寫法。

Symbol 感覺像是取位址,語法是 #identifier。基本上所有識別字的位址都可以取,變數、函數、方法、類別都可以,主要是搭配 reflection 來使用。

函數的參數除了一般的以順序指定參數之外,也可以有使用選用的名稱的參數。參數大概是 (必要的依順序指定參數, {選用的依名稱指定參數}) 或是 (必要的依順序指定參數, [依序選用的依順序指定參數]) 兩種定義方式。一旦參數放到大括號中,就必須使用名稱指定。直接用程式來測試語法:

匿名函數是可以的,定義方式有點像 JavaScript: (param1, param2) { /* code */ }

另外也有 lambda 的語法,使用 => 來定義: (param1, param2) => /* expression */,基本上就是等價於 (param1, param2) { /* return expression; */ }

函數都會傳回值,沒有明確傳回或是定義為 void 的函數,會自動傳回 null 值。這點跟 Python 的行為相同。

運算子跟大部份語言差不多,不過多了個 .. (cascade operator) 可以連續在同一個物件上操作,而不用重複物件變數的識別字。

強制轉型的語法是 (variable as Type),型別測試則是 (variable is Type) 或是 (variable is! Type)

賦值的部份,比較特殊的是 variable ??= value 這個語法,等價於 variable = ((null == variable) ? value : variable)

條件評估的部份,比較特殊的是 (expression1 ?? expression2),等價於 (null == expression1) ? expression2 : expression1,不過評估不會有重複的問題。

物件成員存取除了一般的語法之外,還支援物件為 null 的存取,語法是 (obj?.member),等價於 (null == obj) ? null : obj.member

邏輯判斷和迴圈跟 Java 差不多,大概就是 if - else if - else, for ( ; ; ), for ( in ), while ( ), do-while, switch-case-default 這些,此外 continuebreak 也有支援。其中 for ( in ) 的語法是針對 IterableIterator 類別容器的遊走,內建的 List 物件支援 Iterable 但是 Map 則沒有,不過 Map 有支援 .forEach() 方法。

例外部份跟 Java 也是很類似,不過語法有點不一樣:

... } on OutOfLlamasException { ...
攔截特定例外類別,但不處理例外實體。
... } on Exception catch (e) { ...
攔截特定例外類別,並處理例外實體。
... } catch (e) { ...
擷取所有其餘的例外。

擷取例外實體的 catch() 可以接收 (e) 或是 (e, s) 兩種參數傳入的方式,前面的 e 是例外實體,後面的 s 是堆疊紀錄 (stack trace) 實體。

也支援 ... } finally { ... 語法,來處理不論是不是發生例外的資源釋放作業。

物件的建構子語法大概是 Constructor( /* 參數 */ ) : /* 其他建構子呼叫或值初始化 */, ... { /* 後續初始化作業 */ } 這樣。

針對 factory 模式,有個 factory 關鍵字。如果一個類別的建構子是 factory constructor 的話,則建立物件時必須使用 new 來建立,詳細的用法看文件範例 (Factory Constructors) 比較清楚。

有 property 的 getter, setter 的支援,可以讓 assignment 的 left-hand side 與 right-hand side 反應到比較複雜的邏輯上,而不只是單純的取值或賦值,語法看文件 (Getters and Setters) 比較快。

抽象類別跟運算子覆載跟 C++ 很像: Abstract Classes, Overridable Operators

每個 Class 會自動的定義一個 Interface 出來,可以說 Interface 就是把函數實作去掉的 Class 定義。保留下來的有: 屬性、方法原型。要把 Class 當 Interface 用,就是在宣告實作的 Class 時用 implement 關鍵字來引入即可。

還可以實作 Mix-in 這種語言模式,要被 Mix-in 的 Class 必須滿足以下要件:

  1. 不能繼承自 Object 以外的物件,
  2. 不能有建構子,
  3. 不能引用上層物件,也就是不能參考到 super 參考。

條件跟平台有關,上面的條件是針對網頁的 dart2js 平台,是目前各平台中比較嚴苛的條件,也許未來隨著新技術的導入可以再更放寬。

要把類別當成 Mix-in 混入,就是在宣告實作的 Class 時用 with 關鍵字把要混入的 Class 導入即可。

類別實體也可以當成函數物件呼叫,只要加上稱為 call() 的方法就好了: Callable Classes

也有支援泛型,長的很像 Java: Generics

匯入函式庫是用 import 關鍵字,預設是直接匯入所有套件內的 symbol 到全域空間中: import 'dart:html';

可以用 show 來僅匯入部份 symbol: import 'package:lib1/lib1.dart' show foo;

可以用 hide 來遮蔽部份匯入的 symbol: import 'package:lib2/lib2.dart' hide foo;

另外也可以用 as 關鍵字讓匯入的符號包裝在指定的名稱內: import 'package:lib2/lib2.dart' as lib2; 使用時的語法 lib2.Element element2 = new lib2.Element(); 。這樣的用法在文件內稱為 library prefix: Specifying a library prefix

匯入時 as 跟 show/hide 也可以混用: import 'package:lib2/lib2.dart' as lib2 show foo1, foo2, foo3;

在套件路徑中:

  • 如果是 dart: 開頭的,屬於核心函式庫,自 SDK 提供;
  • 如果是 package: 開頭的,則是透過 pub 工具所管理的套件函式庫,看 pubspec.yaml 中導入了哪些套件,有需要的話也可以將自己當成套件函式庫匯入;
  • 沒有特殊的開頭的話,就是相對目前的 .dart 檔的相對路徑。

可以參考建立套件函式庫的指引來瞭解關於函式庫的事情,即使只是使用函式庫沒有要製作函式庫: Create Library Packages

如果要製作函式庫的話,可以參考 shelf: Web server middleware for Dart 專案的作法,這個函式庫被用在 pub 中,應該會持續積極維護吧!

非同步的部份,有蠻多東西的... 在語言本身有 await 與 async 兩個關鍵字,搭配核心函式庫的 FutureStream 兩個類別。

關鍵字 async 是用來修飾一個函式,表示函式是以非同步的方式傳出參數。用 async 修飾過的函式,其函數原型必須定義為傳回 Future 物件,但仍然可以直接用 return 回傳結果的值,實際上函式的回傳值會自動地被包裝成 Future 物件。

要等待 Future 完成再往下執行的話,可以使用 await 或是 Future 物件的 .then() 方法。使用 await 的話,函數本身也必須用 async 修飾,因為函式的結束時機也變成不一定。使用 Future 物件的 .then() 方法的話,就無法確保函式結束時非同步的程式碼已經完成。用簡單的程式來測試 await 跟 Future.then() 兩種同步方式:

Stream 是代表一連串非同步傳回的結果物件,對應的同步方法是使用 await for (variable-declaration in expression-result-into-stream) { ... } 語法,詳細的說明可以看 Using asynchronous for loops with Streams

另外有兩篇講非同步的文章:

Dart Language Asynchrony Support: Phase 1:

這篇講的是比較基本的 async, await 的用法,大概就上面那些。

Dart Language Asynchrony Support: Phase 2 - async*, sync*, and all the rest:

這篇講到進階的用法,包含怎麼寫 generator 函數,同步的部分就會用到 sync*, yield,非同步的部分就會用到 async*, yield

還有提到撰寫 generator 時,如果要把另一個 iterator 的元素一個一個丟出去,除了寫迴圈一個一個 yield 之外,可以使用 yield* 語法。

在 C/C++ 會見到的 typedef 在 Dart 也是有,不過目前只能用來定義函數介面的別名: Typedefs

可以用 @annotation 來幫程式中的元素加註,可以用在:

  • library
  • class
  • typedef
  • type parameter
  • constructor
  • factory
  • function
  • field
  • parameter
  • variable declaration
  • import directive
  • export directive

關於名稱 annotation 與 metadata 的差異,似乎是在程式碼裡是 annotation,等到了執行時期變成物件可以被程式使用了,就轉而叫做 metadata 的樣子。也許是類似 class 跟 object instance 的關係吧。

依照文件所說,取出 metadata 的 dart:mirrors 函式庫介面仍然是不穩定的,不過還是做了一個小測試來看看大概介面大概長什麼樣子

加註的語法跟 Python 的 @decorator 差不多,但是實質上有非常大的差別,要小心不要弄混了。

註解寫法跟 C++, Java 差不多,另外還有類似 C# 的產生文件用註解語法。

程式風格可以參考 Effective Dart 的建議。列出幾個比較特殊的:

Library Tour 介紹了內建的基礎函式庫,大概是這些:

dart:core
基礎型別,包含: 數值、字串、正規表示式、網址、日期時間、各種容器 (List, Map, Set) 跟相關類別 (Comparable, Iterable, Iterator)、例外。
dart:async
基本上就是非同步操作相關的類別與函式,不過還是鼓勵利用 async, await 之類操作: dart:async - asynchronous programming
dart:math
數學函式庫,包含三角函數、最大值、最小值、亂數,可以考慮使用 import 'dart:math' as math; 的方式引入來區隔來自 dart:code 的函式: dart:math - math and random
dart:html
針對瀏覽器平台的函式庫,包含 DOM, CSS 操作、事件處理、HttpRequest (AJAX)、WebSocket 等介面。針對比較新的進階應用,有另外封裝在別的內建函式庫中: dart:html - browser-based apps - more information
dart:io
這個函式庫只能用在命令列介面中,可能就是只在 DartVM 平台上實作吧... 主要是檔案跟資料夾的 I/O、HTTP 與 WebSocket 的 server 與 client 的實作、子行程、Socket、標準輸出入。
dart:convert
一些轉來轉去用的函式: JSON 轉換,UTF-8 字串的轉換、Base64 轉換、HTML 脫逸... 之類的。
dart:mirrors
作 Reflection 用的函式庫,除非是要開發接近語言底層的工具,不然看起來是少碰為妙。

線上的函式庫 API 文件: Dart SDK - https://api.dartlang.org/stable/

Tuesday, December 18, 2007

twitter

鈍鈍的,大概是 AJAX 用太兇了。

實在是沒想到有什麼用,以後 imbot 有什麼動作的話,就丟上去好了,性質似乎蠻合的。

Friday, July 13, 2007

選字 → 查字典

以往都是把要查的字複製到字典網頁中,有點煩了,所以今天花了點時間希望能讓這個過程簡便一些。

就是這個: Yahoo!奇摩字典

不過我只有在 Firefox 上測試過就是,不知道其他瀏覽器上能不能用。

丟到 Bookmark Toolbar 上,瀏覽網頁時遇到要查的字只要選起來,然後按下這個 Bookmarklet 就會開啟新的視窗或是標籤頁,然後連到 Yahoo!奇摩字典去查詢要查的字。如果沒選字就按下這個 Bookmarklet 的話,就會直接在目前的視窗或標籤頁開啟 Yahoo!奇摩字典的首頁。

其實後來發現有不少一樣功能的東西別人已經寫好,內容其實差不多,動手前真是該先搜尋一下。

Wednesday, June 27, 2007

Google Docs 更新文件列表界面

今天下午大約四點多時發現的,可能剛好更新到我所在的主機,突然整個界面就變得不一樣,嚇我一大跳,還以為按到什麼東西。

更新之後的列表比較容易看到自己有什麼文件在手上,不過相對的似乎不是那麼容易把焦點集中在某些特定文件上。

另外就是似乎不會記住上次離開時的狀態,比如說所切換到的列表,或是 folder 的開啟或關閉狀態。