Jetty 跟 Tomcat 一樣,都支援標準 Servlet 及 JavaEE 規範,但 Jetty 架構比 Tomcat 簡單,可以獨立運作,也可以用 embedded 的方式嵌入 project 中。目前 Jetty 支援 Servlet Spec 3.1 及 JSP 2.3,有部分規範是以模組的方式支援,可藉由設定的方式 enable/disable。
在專案配置 Jetty 只需要以 Maven POM 引用 jetty,就可以用嵌入的方式啟動 Jetty。
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>9.4.12.v20180830</version>
</dependency>
</dependencies>
也可以直接下載 Jetty,以 standalone 方式啟動,下載 Jetty 有兩種,第一種是標準的完整的 Jetty,裡面包含了 modules 以及一個 demo server 配置。另一種是 jetty-home,是最小的 package,提供給進階使用者使用。
要啟動 Jetty 可執行下載的 $JETTY_HOME/bin 目錄中的 ./jetty.sh start
,只要用 ./jetty.sh stop
停止 Jetty。
如果要以 console 模式啟動 jetty,可切換到 $JETTY_HOME 目錄,然後直接以 java 執行
java -jar start.jar
預設都是提供 http://localhost:8080 的服務,因為預設沒有部署任何 webapp,所以連接該網址後會得到 404 Error。
Jetty 裡面有個 Demo Base 配置範例,可以切換到該目錄,啟動範例
cd demo-base/
java -jar ../start.jar
可用以下指令,檢查 demo base 裡面的 modules 及 config
cd demo-base/
java -jar ../start.jar --list-modules
java -jar ../start.jar --list-config
Configuration
- jetty.home
定義 Jetty 主程式、lib、default modules與 default XML files 的目錄,這是不會修改異動的部分 - jetty.base
定義了 Jetty Server、configurtion、logs與 webapps 目錄
這兩個 properties 可透過 command line 設定,或是由環境變數 $JETTY_HOME, $JETTY_BASE
決定
以下是建立一個新的 jetty.base 目錄,然後 enable HTTP connector 與 web app deployer modules,並複製並部署 demo webapp
# JETTY_HOME 設定為下載 Jetty Distribution 解壓縮後的目錄
JETTY_HOME=~/java/jetty
JETTY_BASE=/tmp/mybase
mkdir $JETTY_BASE
cd $JETTY_BASE
直接啟動,會得到 error
$ java -jar $JETTY_HOME/start.jar
WARNING: Nothing to start, exiting ...
$ java -jar $JETTY_HOME/start.jar --create-startd
MKDIR : ${jetty.base}/start.d
INFO : Base directory was modified
$ java -jar $JETTY_HOME/start.jar --add-to-start=http,deploy
INFO : webapp transitively enabled, ini template available with --add-to-start=webapp
INFO : server transitively enabled, ini template available with --add-to-start=server
INFO : security transitively enabled
INFO : servlet transitively enabled
INFO : http initialized in ${jetty.base}/start.d/http.ini
INFO : threadpool transitively enabled, ini template available with --add-to-start=threadpool
INFO : deploy initialized in ${jetty.base}/start.d/deploy.ini
MKDIR : ${jetty.base}/webapps
INFO : Base directory was modified
$ cp $JETTY_HOME/demo-base/webapps/async-rest.war webapps/ROOT.war
$ java -jar $JETTY_HOME/start.jar
在 command line 可直接修改 http port
java -jar $JETTY_HOME/start.jar jetty.http.port=8081
但這只是暫時的設定,實際上設定值會用以下順序決定
start.d/http.ini
該檔案是 http module 的設定參數modules/httpd.mod
定義料 http module 並指定使用etc/jetty-http.xml
設定檔jetty.http.port
屬性,是由/etc/jetty-http.xml
指定使用該屬性
後續再增加 SSL 及 http2
$ java -jar $JETTY_HOME/start.jar --add-to-start=https,http2
$ java -jar $JETTY_HOME/start.jar
啟動後,可使用 https://localhost:8443/
可用 --help 查詢所有 command line 指令
java -jar $JETTY_HOME/start.jar --help
deployment
部署 Jetty Webapp 有幾種方式,而且都可以在不停止 Jetty 的條件下,直接部署該 webapp
- 一個目錄 (ex: example/) 裡面有 WEB-INF 目錄,以及 web.xml,將目錄放到 $JETTY_BASE 的 webapps 目錄中,就可以部署該 webapp
- example.war
- 一個獨立的 XML 檔案,裡面定義 webapp 的相關資訊。可參考 demo-base/test.xml 為範例
在剛剛的 /tmp/mybase
新的 $JETTY_BASE 目錄中,如果要部署一個 example webapp,裡面有 JSP 網頁,必須先增加 jsp module support
$ java -jar $JETTY_HOME/start.jar --add-to-start=jsp
example 目錄結構包含一個目錄,兩個檔案
example/
index.jsp
WEB-INF/
web.xml
web.xml 就是很單純的標準 webapp
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
</web-app>
啟動 jetty 後,將 example 複製到 /tmp/mybase/webapps 裡面,可看到 example hot deploy 的 log
$ java -jar $JETTY_HOME/start.jar
.....
oejsh.ContextHandler:Scanner-0: Started o.e.j.w.WebAppContext@7a5904a3{ex,/ex,file:///private/tmp/mybase/webapps/example/,AVAILABLE}{/example}
另外要注意的是,在 webapps 裡面如果有 ROOT 目錄,或是 ROOT.war,這個是 jetty server 的 default web context application。也就是 http://localhost:8080/ 連線取得的網頁。
Configuration
如何設定 jetty
POJO configuration
直些撰寫 java code 設定 jetty。或是使用 etc/jetty.xml,這是 main jetty XML config。或是利用 IoC framework (spring) 初始化 jetty objects as Spring beans
Jetty Start Configuration Files
jetty distribution 使用以下設定檔,透過 start.jar 初始化 jetty
ini files
jetty 利用 command line 讀取
$JETTY_BASE/start.ini and/or $JETTY_BASE/start.d/*.ini
files,產生 command line arguments- --module=name 啟用 module
- name=value 用在參數化的 Jetty IoC XML
- XML files: Jetty IoC(Spring) XML 格式
- 標準 property file,包含附加的 start properties
- 其他 start.jar options (
java -jar start.jar --help
) - 一些 JVM options ,以 --exec 整合,例如 -Xbootclasspath
mod files
$JETTY_HOME/modules/*.mod files,以 --module=name 啟用,每個 mod 都定義了
- module dependencies for ordering and activation
- 需增加的 libraries
- module 增加的 command line 參數
- 啟用 module 需要的檔案
- template ini,可用
--add-to-start=name
啟用
XML files
Jetty IoC XML format 或是 Spring IoC format,通常放在
$JETTY_HOME/etc/
,附加的 XML 放在$JETTY_BASE/etc/
以下是這些設定檔的關係圖
在 Jetty 需要設定什麼?
Server
核心設定檔為 /etc/jetty.xml,可加上其他 server configurations:
ThreadPool
server instance 提供了一個 ThreadPool instance,這是 jetty server components 使用的預設 Executor service,可在 start.ini 或 start.d/server.ini 調整 max/min sizeHandlers
jetty 只能有一個 Handler instance 處理 incomping HTTP request,預設 handler tree 設定在 etc/jetty.xml,包含了 a context Handler Collection 及 Default Handler。Context Handler Collection 由 context path 選擇 handler,也就是 deployed Context Handler 及 Web Application Contexts
Default Handler 無法處理的 request 會產生 404 page,可增加其他 handlers(ex: jetty-rewrite.xml, jetty-requestlog.xml) 或增加 hot deploy handlers (ex: jetty-deploy.xml)
Server Attributes
server 會儲存 attribute map of strings,給 components 使用,如果 value objects 實作了 LifeCycle interface,就會 started/stopped with the server,通常 server attributes 是儲存 server-wide default valuesServer fields
可設定在 start.ini 或 start.d/server.init,控制 HTTP responses 的 dates, versionsConnectors
接收 HTTP 或其他 protocol 的 connectionsServices 儲存 service objects,通常會以 LifeCycle beans 存在,例如 Login Services 及 DataSources。以 server level 設定,在 webapp 使用。
Connector
network endpoint,接收某個 protocol 的 connection,標準 protocol 為 http.ini, https.ini, jetty-http2.ini
- Port
jetty.http.port (jetty.ssl.port),預設為 8080 (8443) Host
jetty.host 預設為 0.0.0.0Idle Timeout
在 connector 動作前,可 idle 多少 ms,否則就 close connectionHTTP Configuration
包含 http, https, http2SSL Context Factory
TLS connector type (https, http2) 設定 keystore 及 truststore
jetty 9 是以單一 ServerConnector type,他是 NIO based,並以 Connection Factories 處理多個 protocols
- Port
Contexts
contextPath
URL prefix,例如 /foo 可處理 /foo, /foo/index.html ... 這些 URL。 / 稱為 root contextvirtualHost
context 可設定多個 virtual hosts,virtual host 不需要設定 network parameters。virtual host 代表 IP 的 name service aliasclassPath
context 可有自訂的 classpath,該 context 內執行的 handler,有增加該 classpath 的 thread context classloader。標準 webapp 會增加 WEB-INF/lib 及 WEB-INF/classes 這兩個目錄到 classpathattributes
ex: javax.servlet.context.tempdir 用在 webapp 使用 File instance 的 temp dirresourceBase
包含 static resource for the context,圖片或 html
Context Configuration by API
在 embedded server,是呼叫 ContextHandler API 進行 context 設定
package org.eclipse.jetty.embedded;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
public class OneContext
{
public static void main( String[] args ) throws Exception
{
Server server = new Server( 8080 );
// Add a single handler on context "/hello"
ContextHandler context = new ContextHandler();
context.setContextPath( "/hello" );
context.setHandler( new HelloHandler() );
// Can be accessed using http://localhost:8080/hello
server.setHandler( context );
// Start the server
server.start();
server.join();
}
}
Context Configuration by IoC XML
以下 XML 設定,提供 jetty distribution 的 javadoc context
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC
"-//Mort Bay Consulting//DTD Configure//EN"
"http://www.eclipse.org/jetty/configure_9_3.dtd">
<!--
Configure a custom context for serving javadoc as static resources
-->
<Configure class="org.eclipse.jetty.server.handler.ContextHandler">
<Set name="contextPath">/javadoc</Set>
<Set name="resourceBase"><SystemProperty name="jetty.home" default="."/>/javadoc/</Set>
<Set name="handler">
<New class="org.eclipse.jetty.server.handler.ResourceHandler">
<Set name="welcomeFiles">
<Array type="String">
<Item>index.html</Item>
</Array>
</Set>
<Set name="cacheControl">max-age=3600,public</Set>
</New>
</Set>
</Configure>
Configuring Web Applications
jetty 用以下方式處理 WAR application
- classpath 包含 WEB-INF/lib, WEB-INF/classes
- WEB-INF/web.xml 定義 init parameters, filters, servlets, listeners, security constraints, welcome files, resources
- annotations 處理 WEB-INF/lib 是定義的 filters, servlets, listeners
- (optional) WEB-INF/jetty-web.xml 定義 Jetty IoC config
Setting the Context Path
在 WEB-INF/jetty-web.xml 可設定 context path
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Set name="contextPath">/contextpath</Set> </Configure>
也可以直接在 $JETTY_HOME/webapps/test.xml,就定義了一個 webapp,test.xml 包含 war 的位置,及 contextPath
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure class="org.eclipse.jetty.webapp.WebAppContext"> <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test.war</Set> <Set name="contextPath">/test</Set> </Configure>
Setting an Authentication Realm
標準 realm name 設定在 web.xml,例如以下是宣告使用 BASIC authentication,並使用 "Test Realm"
<login-config> <auth-method>BASIC</auth-method> <realm-name>Test Realm</realm-name> </login-config>
"Test Realm" 設定在 $JETTY_BASE/etc/test-realm.xml,並傳入 start.ini 及 start.d/server.ini
以下設定 LoginService
<?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure id="Server" class="org.eclipse.jetty.server.Server"> <!-- =========================================================== --> <!-- Configure Authentication Login Service --> <!-- Realms may be configured for the entire server here, or --> <!-- they can be configured for a specific web app in a context --> <!-- configuration (see $(jetty.home)/webapps/test.xml for an --> <!-- example). --> <!-- =========================================================== --> <Call name="addBean"> <Arg> <New class="org.eclipse.jetty.security.HashLoginService"> <Set name="name">Test Realm</Set> <Set name="config"><Property name="jetty.demo.realm" default="etc/realm.properties"/></Set> <Set name="hotReload">false</Set> </New> </Arg> </Call> <Get class="org.eclipse.jetty.util.log.Log" name="rootLogger"> <Call name="warn"><Arg>demo test-realm is deployed. DO NOT USE IN PRODUCTION!</Arg></Call> </Get> </Configure>
隱藏 Server 資訊
在 start.ini 將 server module 裡面的 jetty.httpConfig.sendServerVersion
設定改為 false
--module=server
## Whether to send the Server: header
jetty.httpConfig.sendServerVersion=false
Swift 5 在2019年3月25日正式release,主要最廣為人討論的就是ABI Stability,也就是Swift runtime將被放在作業系統裡而不是包含在App裡,更進一步的說明可以參考ABI Stability and More 和 Evolving Swift On Apple Platforms After ABI Stability。
此外,Swift 5語言本身也增加了一些新的特性,包含新的語法及標準函式庫的更新。本篇文章將概略說明部分較常被提出的新特性。關於完整的Swift 5更新內容請參考Swift部落格的這篇文章:Swift 5 Released!
Raw Strings SE-0200
在字串前後加上#符號,即可使用raw string
在raw string中,反斜線與雙引號都可以直接使用,不再需要額外的跳脫字元
印出結果:
若依然要使用跳脫字元,使用反斜線加上#符號:
如此才可印出有換行的結果:
你可以改在字串前後加上兩個或更多個#符號,使用raw string。如此一來,即可改在字串中直接輸入"#;此外,跳脫字元變也為\##
印出結果:
Customizing string Interpolation SE-0228
String Interpolation是swift在字串中放入變數的方式之一,如下:
然而,若是自訂的類別物件,在以往,需要令該類別實作CustomStringConvertible的description property,才能印出自訂的內容;類似Java的toString。
而現在,在Swift 5中,可以使用extension替String.StringInterpolation增加新的appendInterpolation方法,以處理自訂的類別。
此外,在增加新的appendInterpolation方法時,
你也可以使用多個參數,以及使用argument label,就像自訂函數一樣,進一步客製想要的String Interpolation內容:
Standard Result type SE-0235
Swift 5 標準函式庫中加入了Result型別,讓程式開發者可以更一致地處理錯誤。
Result是一個enum,有success與failure兩個case,並分別各有一個associated value,分別代表執行成功時欲回傳的資料,以及執行失敗時發生的錯誤。
假設我們使用要定義一個取得新聞資料的API,並使用Result表示結果,可以定義如下:
MyNews是某個自訂的類別;MyNewsError則是自訂的enum,繼承自標準函式庫的Error。
如此一來,在呼叫fetchNews取新聞資料時,可以用switch case以更直覺簡單地分別處理成功與失敗的結果:
Dynamically callable types SE-0216
dynamicCallable可以讓你定義一個型別,其宣告出來的變數可以如同函式般直接使用,如下:
建立MyMultiplier型別的物件,並令其當作函式來呼叫:
如上,該型別必須實作dynamicallyCall方法,而參數可以是withArguments任意型別的陣列,如上例為Int陣列。
此外,也可以改用withKeywordArguments,並把參數改為KeyValuePairs<String, Any>,讓函式可以取得傳入的參數名稱。
dynamicCallable可以用於struct, class, enum。
Handling Future Enumeration Cases SE-0192
當switch述句中的條件判斷若其值來自非自定義的enum,如:C enums或是來自standard library的enum,則意味著此switch述句的條件判斷值,在未來可能需要處理非預期的內容;而通常在這種情況下,我們一定會用到default來處理。
以下以來自standard library的enum: AVAudioSession.InterruptionType為例。
Standard library中AVAudioSession.InterruptionType的定義如下:
假設我們有一個switch述句需要針對AVAudioSession.InterruptionType進行處理:
由於AVAudioSession.InterruptionType是來自standard library的enum,未來有可能會增加新的值;所以編譯器提出警告:
使用 @unknown 標註,暗示未來隨著標準函式庫的更新,此switch case有可能會遇到其他未知的enum類型:
為何不直接寫上default?
如果直接寫個default,雖然也不會出現編譯錯誤,
但就邏輯上來說,這是令人困惑的,
因為就現況而言,AVAudioSession.InterruptionType確實只有 .began 與 .ended 兩種值;
若是自定義的enum,而你的 switch case 也已明確地處理好所有enum值的話,再加上default,編譯器甚至還會回報Warning,請你將這個累贅的 default陳述句 移除:
一般的default應該用於如下情境,switch case中尚有值仍未處理的情況;
而加上 @unknown 則意味著,此處的 default 可能目前不會遇到,但是在往後有可能會遇到的,所以通常會是來自 非自定義的enum 或是C enums。
Proposal SE-0192 就是希望能在程式中刻意區分這一點而提出,也才有了用 @unknown 來處理future enumeration的這個變動。
Flatten nested optionals resulting from ‘try?’ SE-0230
因爲try而造成的nested optionals,意即如:String?? 這樣有兩個 ?? 的情況,在swift 5之後也會變為普通的optional。如:
在swift 5 之前,myItemInfo為String??
在swift 5 之後,myItemInfo為String?
這個改變是因為,原本多數的 optional 使用情境,都會避免發生 nested optionals,如下:
上面程式中的myItemDesc為String?,
並不會因為 initializer 回傳 optional 型態,而getItemDesc方法也回傳 optional 型態,
進而造成有兩個 ?? 的nested optionals。
因此,於Swift 5中,為了使 try? 與其他 optional 的使用情境更一致,而做了此更動。
Reference
https://swift.org/blog/swift-5-released/
What’s new in Swift 5.0 – Hacking with Swift
What’s New in Swift 5? | raywenderlich.com
https://developer.apple.com/documentation/xcode_release_notes/xcode_10_2_release_notes/swift_5_release_notes_for_xcode_10_2