markdown:
# Hướng dẫn sử dụng Xitrum
[¶](#huong-dan-su-dung-xitrum) [Có cả bản tiếng Anh, Nhật, Hàn Quốc và Nga.](http://xitrum-framework.github.io/guide.html)
## Giới thiệu
[¶](#gioi-thieu)
+--------------------+ | Clients | +--------------------+ | +--------------------+ | Netty | +--------------------+ | Xitrum | | +----------------+ | | | HTTP(S) Server | | | |----------------| | | | Web framework | | <- Akka, Hazelcast -> Other instances | +----------------+ | +--------------------+ | Your app | +--------------------+
Xitrum là một Scala web framework bất đồng bộ, clustered và cũng là một HTTPS(server) trên nền của
[Netty](http://netty.io/) và [Akka](http://akka.io/). [Một người dùng Xiturm](https://groups.google.com/group/xitrum-framework/msg/d6de4865a8576d39) đã nói rằng:
Wow, đây thực sự là một sản phẩm tuyệt vời, có thể coi như một Scala framework hoàn chỉnh tới mức có thể so sánh với Lift (nhưng dễ sử dụng hơn nhiều).
[Xitrum]một web framework full-stack đúng nghĩa, đáp ứng tất cả các chức năng cơ bản của một web framework, ngoài ra còn có những phần mở rộng như ETags, file cache tĩnh, công cụ nén Gzip tự động. Tích hợp công cụ chuyển đổi JSON, before/around/after interceptors, request/session/cookie/flash scopes, các bộ chuẩn hóa input tích hợp ở cả server và client, tích hợp cả tính năng cache ( [Hazelcast]), tính năng đa ngôn ngữ i18n theo phong cách GNU gettext, Netty (nhanh không kém Nginx), v.v . Và bạn có thể sử dụng nhiều tính năng khác nữa.
### Tính năng
[¶](#tinh-nang)
- Typesafe, dựa trên tinh thần của Scala. Tất cả các APIs đều được thiết kế an toàn nhất có thể
- Bất đồng bộ, theo tinh thần của Netty. Việc xử lý các yêu cầu (request) không cần phải đáp ứng (respond) ngay lập lức. Long polling, chunked response (streaming), WebSocket, và SockJS.
- Tích hợp sẵn HTTP và HTTPS server có tốc độ nhanh dựa trên
[Netty](http://netty.io/)(HTTPS có thể sử dụng công nghệ Java hoặc công nghệ mã OpenSSL tự nhiên). Tốc độ phục vụ tập tin tĩnh của Xitrum có thể đạt mức [tương đương Nginx](https://gist.github.com/3293596).
- Tối ưu hóa cache cả ở phía máy chủ (server) và máy khách (client) để tăng tốc độ đáp ứng.
Ở tầng máy chủ web, các tập tin nhỏ được cache vào bộ nhớ, đối với các tập tin lớn thì sử dụng kỹ thuật
zero copy của NIO.
Ở tầng web framework bạn có thể khai báo cache ở các mức page, action và object theo phong cách
[Rails framework](https://github.com/rails/rails). [Tất cả thủ thuật mà Google khuyên nên dùng để tăng tốc trang web](http://code.google.com/speed/page-speed/docs/rules_intro.html)như method GET có điều kiện được áp dụng để cache phía client. Bạn cũng có thể buộc các trình duyệt để luôn gửi yêu cầu đến máy chủ để kiểm tra lại cache trước khi sử dụng.
- Tính năng
[range request](http://en.wikipedia.org/wiki/Byte_serving)hỗ trợ các tập tin tĩnh. Tính năng này cần cho việc cung cấp dịch vụ video cho điện thoại thông minh. Bạn có thể tạm dừng/tiếp tục việc tải tập tin video.
- Hỗ trợ
[CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing).
- Tính năng định tuyến được thực hiện tự động trên tinh thần của JAX-RS và Rails Engines.
Bạn không cần phải được khai báo ở tất cả các tuyến kết nối tại cùng một điểm mà có thể tại nhiều điểm khác nhau.
Tính năng này có thể hiểu như định tuyến một cách phân tán. Bạn có thể cài cắm ứng dụng này vào ứng dụng khác.
Nếu bạn có một blog engine, bạn có thể đóng gói nó thành một tập tin JAR và đặt tập tin JAR đó trong một
ứng dụng khác, với cách làm như vậy ứng dụng đó sẽ có thêm tính năng blog.
Việc định tuyến thì bao gồm 2 chiều: bạn có thể tái tạo đường dẫn URL (reverse routing) một cách an toàn từ action.
Bạn có thể tạo tài liệu về các định tuyến bằng cách sử dụng
[Swagger Doc](http://swagger.wordnik.com/).
- Các lớp và các đường định tuyến được tải lại một cách tự động trong chế độ phát triển (development mode).
- Các View có thể viết bằng các tập tin mẫu
[Scalate](http://scalate.fusesource.org/)một các riêng biệt hoặc bằng Scala inline XML. Cả hai cách đều an toàn.
- Phiên làm việc(Sessions) có thể lưu trữ ngay trong cookies(đáp ứng đuợc nhiều user cùng lúc hơn) hoặc lưu trữ bằng
[Hazelcast](http://www.hazelcast.org/)(tính bảo mật cao hơn). Hazelcast cũng chạy ngay trong cùng process với việc sử dụng cache phân tán(do đó nhanh hơn và dễ sử dụng hơn) , vì vậy bạn không cần phải có một máy chủ cache riêng biệt. Điều này cũng đúng trong chức năng pubsub của Akka. [jQuery Validation](http://jqueryvalidation.org/)được tích hợp trong việc chuẩn hóa dữ liệu ở cả máy chủ(server) và máy khách (client)
- i18n theo phong cách
[GNU gettext](http://en.wikipedia.org/wiki/GNU_gettext). Việc trích các chuổi văn bản ra ngoài để thực hiện dịch được thực hiện tự động, bạn sẽ không cần làm thủ công với properties file. Bạn cũng có thể sử dụng các công cụ mạnh như [Poedit](http://www.poedit.net/screenshots.php)để dịch và hợp nhất các bản dịch. gettext, không giống như hầu hết các giải pháp khác, hỗ trợ các định dạng của cả số ít và số nhiều.
Xitrum cố gắng khắc phục các nhược điểm của
[Scalatra](https://github.com/scalatra/scalatra)
và [Lift](http://liftweb.net/): mạnh hơn Scalatra và dễ sử dụng hơn Lift. Bạn có thể dễ dàng tạo cả RESTful APIs và postbacks.
[Xitrum](http://xitrum-framework.github.io/) là hệ thống controller-first như Scalatra, không phải là
[view-first](http://www.assembla.com/wiki/show/liftweb/View_First) như Lift.
Đa số mọi người đã quen thuộc với phong cách controller-first.
Hãy xem
[ để có được danh sách các bản demos, plugins v.v. các dự án liên quan](index.html#document-deps)
### Đóng góp
[¶](#dong-gop) [Xitrum](http://xitrum-framework.github.io/) là một framework mã nguồn mở, mã nguồn của Xitrum có thể tìm thấy
[tại đây](https://github.com/xitrum-framework/xitrum),
bạn có thể tham gia vào [Google group](http://groups.google.com/group/xitrum-framework) của chúng tôi.
Những người đóng góp dưới đây được xếp theo thứ tự
[đóng góp đầu tiên của họ](https://github.com/xitrum-framework/xitrum/graphs/contributors).
(*): Hiện tại là thành viên hoạt động chính.
## Hướng dẫn
[¶](#huong-dan)
Chương này giới thiệu ngắn gọn cách tạo và chạy một project Xitrum.
**Việc tạo project được thực hiện với giả định bạn sử dụng Linux và đã cài Java.**
### Tạo một project Xitrum mới
[¶](#tao-mot-project-xitrum-moi)
Để tạo mới một project Xitrum bạn chỉ cần tải về tập tin
[xitrum-new.zip](https://github.com/xitrum-framework/xitrum-new/archive/master.zip):
wget -O xitrum-new.zip https://github.com/xitrum-framework/xitrum-new/archive/master.zip
Hoặc:
curl -L -o xitrum-new.zip https://github.com/xitrum-framework/xitrum-new/archive/master.zip
### Khởi động project Xitrum
[¶](#khoi-dong-project-xitrum)
Cách chuẩn nhất để build một project Scala là sử dụng
[SBT](https://github.com/harrah/xsbt/wiki/Setup). Các project mới được tạo đã có sẵn SBT 0.13 trong thư mục
sbt. Nếu bạn muốn tự cài đặt SBT, bạn có thể xem
[hướng dẫn cài đặt](https://github.com/harrah/xsbt/wiki/Setup).
Sử dụng terminal, chuyển đến thư mục của project mới tạo và chạy lệnh
sbt/sbt run:
unzip xitrum-new.zip cd xitrum-new sbt/sbt run
Câu lệnh này sẽ download tất cả
[, biên dịch toàn bộ project,
và khởi động web server qua class dependencies](index.html#document-deps)
quickstart.Boot. Trong cửa sổ dòng lệnh Terminal, bạn sẽ thấy tất cả các định tuyến:
[INFO] Load routes.cache or recollect routes... [INFO] Normal routes: GET / quickstart.action.SiteIndex [INFO] SockJS routes: xitrum/metrics/channel xitrum.metrics.XitrumMetricsChannel websocket: true, cookie_needed: false [INFO] Error routes: 404 quickstart.action.NotFoundError 500 quickstart.action.ServerError [INFO] Xitrum routes: GET /webjars/swagger-ui/2.0.17/index xitrum.routing.SwaggerUiVersioned GET /xitrum/xitrum.js xitrum.js GET /xitrum/metrics/channel xitrum.sockjs.Greeting GET /xitrum/metrics/channel/:serverId/:sessionId/eventsource xitrum.sockjs.EventSourceReceive GET /xitrum/metrics/channel/:serverId/:sessionId/htmlfile xitrum.sockjs.HtmlFileReceive GET /xitrum/metrics/channel/:serverId/:sessionId/jsonp xitrum.sockjs.JsonPPollingReceive POST /xitrum/metrics/channel/:serverId/:sessionId/jsonp_send xitrum.sockjs.JsonPPollingSend WEBSOCKET /xitrum/metrics/channel/:serverId/:sessionId/websocket xitrum.sockjs.WebSocket POST /xitrum/metrics/channel/:serverId/:sessionId/xhr xitrum.sockjs.XhrPollingReceive POST /xitrum/metrics/channel/:serverId/:sessionId/xhr_send xitrum.sockjs.XhrSend POST /xitrum/metrics/channel/:serverId/:sessionId/xhr_streaming xitrum.sockjs.XhrStreamingReceive GET /xitrum/metrics/channel/info xitrum.sockjs.InfoGET WEBSOCKET /xitrum/metrics/channel/websocket xitrum.sockjs.RawWebSocket GET /xitrum/metrics/viewer xitrum.metrics.XitrumMetricsViewer GET /xitrum/metrics/channel/:iframe xitrum.sockjs.Iframe GET /xitrum/metrics/channel/:serverId/:sessionId/websocket xitrum.sockjs.WebSocketGET POST /xitrum/metrics/channel/:serverId/:sessionId/websocket xitrum.sockjs.WebSocketPOST [INFO] HTTP server started on port 8000 [INFO] HTTPS server started on port 4430 [INFO] Xitrum started in development mode
Khi khởi động, tất cả các định tuyến (routers) sẽ được kiểm tra và lưu vào log. Bạn đã có luôn danh sách các định tuyến (routers), điều này rất thuận tiện với bạn để viết tài liệu về RESTful APIs của ứng dụng web cho bên thứ 3.
Truy cập đến đường dẫn
[http://localhost:8000/](http://localhost:8000/) hoặc [https://localhost:4430/](https://localhost:4430/) bằng trình duyệt web. Trong cửa sổ
dòng lệnh bạn sẽ thấy thông tin của các yêu cầu (request):
[INFO] GET quickstart.action.SiteIndex, 1 [ms]
### Import một project Xitrum vào Eclipse
[¶](#import-mot-project-xitrum-vao-eclipse)
Bạn có thể
[sử dụng Eclipse để viết code Scala](http://scala-ide.org/).
Sử dụng cửa sổ dòng lệnh và từ thư mục của project Xitrum chạy lệnh sau:
sbt/sbt eclipse
file
.project cho Eclipse sẽ được tạo với thông tin trong file
build.sbt.
Sau đó chạy Eclipse và import project.
### Import một project Xitrum vào IntelliJ
[¶](#import-mot-project-xitrum-vao-intellij)
Bạn cũng có thể sử dụng
[IntelliJ](http://www.jetbrains.com/idea/) như Eclipse để viết code.
IntelliJ có Scala plugin rất tốt, chỉ cần mở project SBT là xong, không cần tạo trước project file như trường hợp Eclipse ở trên.
### Nạp lại tự động (Autoreload)
[¶](#nap-lai-tu-dong-autoreload)
Bạn có thể thiết lập nạp lại tự động các tập tin .class (hot swap) mà không cần phải khởi động lại chương trình. Tuy nhiên, để tránh gặp phải các vấn đề về hiệu suất cũng như tính ổn định của chương trình, bạn chỉ nên thiết lập nạp lại tự động các tập tin .class trong quá trình phát triển (development mode).
#### Chạy project với IDEs
[¶](#chay-project-voi-ides)
Trong quá trình phát triển, khi chạy project với các IDE cấp cao như Eclipse hoặc IntelliJ, code sẽ được tự động nạp lại bởi thiết lập mặc định của IDE.
#### Chạy project với SBT
[¶](#chay-project-voi-sbt)
Khi bạn chạy project với SBT, bạn cần phải mở 2 cửa sổ dòng lệnh:
- Một để chạy
sbt/sbt run. Câu lệnh này để chạy chương trình và tải lại các tập .class khi chúng được thay đổi.
- Một để chạy
sbt/sbt ~compile. Mỗi khi bạn thay đổi các file mã nguồn, câu lệnh này sẽ biên dịch mã nguồn thành các file .class.
Thư mục sbt có chứa một tập tin là
[agent7.jar](https://github.com/xitrum-framework/agent7).
Tập tin này chịu trách nhiệm tải lại các tập tin .class trong thư mục hiện hành (và các thư mục con).
Nếu nhìn vào đoạn mã
sbt/sbt, bạn sẽ thấy tùy chọn
-javaagent:agent7.jar.
#### DCEVM
[¶](#dcevm)
Thông thường JVM chỉ cho phép thay đổi nội dung của một method. Bạn có thể sử dụng
[DCEVM](https://github.com/dcevm/dcevm), một biến thể mã nguồn mở của máy ảo Java HotSpot
VM cho phép bạn định nghĩa lại không hạn chế các class đã được tải.
Bạn có thể cài DCEVM bằng 2 cách:
- Sử dụng bản
[Patch](https://github.com/dcevm/dcevm/releases)với bản Java đã được cài đặt sẵn trên máy của bạn.
- Cài đặt một bản
[prebuilt](http://dcevm.nentjes.com/)(cách dễ dàng hơn).
Nếu bạn chọn cách sử dụng Patch:
- Bạn có thể kích hoạt DCEVM chạy vĩnh viễn.
- Hoặc sử dụng JVM thay thế (“alternative” JVM). Trong trường hợp này, để chạy DCEVM bạn cần chạy câu lệnh
javavới tùy chọn
-XXaltjvm=dcevm. Ví dụ, bạn cần thêm tùy chọn
-XXaltjvm=dcevmvào câu lệnh
sbt/sbt.
Nếu bạn sử dụng IDE như Eclipse hoặc IntelliJ, bạn cần thiết lập IDE để sử dụng DCEVM (mà không phải JVM mặc định) để chạy project.
Nếu bạn sử dụng SBT, bạn cần cài đặt biến môi trường
PATH với đường dẫn câu lệnh
java từ DCEVM (không phải bản JVM mặc định). Bạn vẫn có thể cần đến
javaagent trên đây, bởi vì mặc dù DCEVM hỗ trợ các tiện ích khi sửa đổi class, bản thân nó không thể tự tải lại các class.
Để có thêm thông tin chi tiết bạn có thể tham khảo
[DCEVM - A JRebel free alternative](http://javainformed.blogspot.jp/2014/01/jrebel-free-alternative.html).
## Action and view
[¶](#action-and-view)
Để linh hoạt, Xitrum cung cấp 3 loại actions sau:
Action thông thường,
FutureAction, và
ActorAction.
### Action thông thường
[¶](#action-thong-thuong)
import xitrum.Action import xitrum.annotation.GET @GET("hello") class HelloAction extends Action { def execute() { respondText("Hello") } }
Bởi vì các action sẽ chạy trực tiếp trên luồng (thread) IO của Netty nên các action không nên tốn thời gian xử lý (block process), mặt khác nếu thời gian xử lý của thread IO của Netty kéo dài, Netty sẽ không còn khả năng đáp ứng các yêu cầu từ phía client hoặc không thể tiếp nhận các kết nối mới.
### FutureAction
[¶](#futureaction)
import xitrum.FutureAction import xitrum.annotation.GET @GET("hello") class HelloAction extends FutureAction { def execute() { respondText("hi") } }
FutureAction sẽ chạy trong cùng thread pool với
ActorAction dưới đây, được tách
ra từ một phần của Netty thread pool.
### Actor action
[¶](#actor-action)
Nếu vạn muốn action của bạn hoạt động như một Akka actor, hãy kế thừa nó từ
ActorAction:
import scala.concurrent.duration._ import xitrum.ActorAction import xitrum.annotation.GET @GET("actor") class HelloAction extends ActorAction { def execute() { // See Akka doc about scheduler import context.dispatcher context.system.scheduler.scheduleOnce(3 seconds, self, System.currentTimeMillis()) // See Akka doc about "become" context.become { case pastTime => respondInlineView(s"It's $pastTime Unix ms 3s ago.") } } }
Một actor instance sẽ được tạo khi có một yêu cầu (request), actor sẽ được dừng khi
đóng kết nối hoặc response được gửi bởi các method
respondText,
respondView, v.v.
Với chunked response, actor sẽ không dừng lại ngay lập tức mà dừng lại khi chunk cuối cùng
được gửi đi.
Actor này sẽ chạy trong thread pool của Akka actor có tên là “xitrum”
### Gửi Respond cho client
[¶](#gui-respond-cho-client)
Từ một action để trả về một respond cho phía client bạn có thể sử dụng những method sau:
respondView: trả về một tệp view , có hoặc không có layout
respondInlineView: trả về một được nhúng (không phải một tệp riêng lẻ), có hoặc không có layout
respondText("hello"): trả về một chuỗi ký tự không có layout
respondHtml("<html>...</html>"): như trên, với content type là “text/html”
respondJson(List(1, 2, 3)): chuyển đối tượng (object) Scala thành đối tượng JSON và trả về client.
respondJs("myFunction([1, 2, 3])")
respondJsonP(List(1, 2, 3), "myFunction"): kết hợp của 2 loại trên.
respondJsonText("[1, 2, 3]")
respondJsonPText("[1, 2, 3]", "myFunction")
respondBinary: trả về một mảng byte
respondFile: gửi file trực tiếp từ đĩa một cách nhanh chóng bằng kỹ thuật
[zero-copy](http://www.ibm.com/developerworks/library/j-zerocopy/)(aka send-file)
respondEventSource("data", "event")gửi chunk respond
### Gửi trả một view file
[¶](#gui-tra-mot-view-file)
- Mỗi action có thể liên kết với
[Scalate](http://scalate.fusesource.org/)
- view file. Thay vì gửi tra trực tiếp ngay trong action với các method trên đây, bạn có
thể sử dụng một view file riêng biệt.
scr/main/scala/mypackage/MyAction.scala:
package mypackage import xitrum.Action import xitrum.annotation.GET @GET("myAction") class MyAction extends Action { def execute() { respondView() } def hello(what: String) = "Hello %s".format(what) }
scr/main/scalate/mypackage/MyAction.jade:
- import mypackage.MyAction !!! 5 html head != antiCsrfMeta != xitrumCss != jsDefaults title Welcome to Xitrum body a(href={url}) Path to the current action p= currentAction.asInstanceOf[MyAction].hello("World") != jsForView
xitrumCssbao gồm các tệp CSS mặc định cho Xitrum. Bạn có thể xóa nó nếu bạn không muốn sử dụng xitrum-framework.
jsDefaultsbao gồm các jQuery, jQuery Validate plugin, v.v, bạn nên đặt nó trong thẻ <head>
jsForViewbao gồm các đoạn mã JavaScript thêm bởi
jsAddToView, nên đặt ở phần cuối.
Trong bạn có thể sử dụng các method của class
[xitrum.Action](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/Action.scala).
Không những thế bạn có thể sử dụng các utility methods cung cấp bởi Scalate điển hình như
unescape.
Xem thêm
[Scalate doc](http://scalate.fusesource.org/documentation/index.html).
mặc định của Scalate là
[Jade].
Bạn cũng có thể sử dụng
[Mustache](http://scalate.fusesource.org/documentation/mustache.html),
[Scaml](http://scalate.fusesource.org/documentation/scaml-reference.html), hoặc [Ssp](http://scalate.fusesource.org/documentation/ssp-reference.html).
Để cấu hình cho mặc định, bạn có thể xem xitrum.conf tại thư mục config trong ứng dụng Xitrum
Bạn cũng có thể override mặc định bằng cách truyền các giá trị “jade”, “mustache”, “scaml”,hoặc “ssp” vào tham số “type” trong method respondView.
val options = Map("type" ->"mustache") respondView(options)
#### Ép kiểu cho currentAction
[¶](#ep-kieu-cho-currentaction)
Nếu bạn muốn có chính xác instance của action hiện thời, bạn có thể ép kiểu cho (casting)
currentAction thành action mà bạn mong muốn.
p= currentAction.asInstanceOf[MyAction].hello("World")
Nếu bạn có có nhiều dòng code như dưới đây, bạn chỉ cần ép kiểu một lần duy nhất:
- val myAction = currentAction.asInstanceOf[MyAction]; import myAction._ p= hello("World") p= hello("Scala") p= hello("Xitrum")
#### Mustache
[¶](#id1)
Các tài liệu tham khảo cho Mustache:
Bạn không thể làm một vài điều với Mustache như với Jade bởi vì cú pháp của Mustache khá cứng nhắc và cần tuân thủ nghiêm ngặt.
Để truyền tham số từ action vào của Mustache bạn phải sử dụng method
at:
Action:
at("name") = "Jack" at("xitrumCss") = xitrumCss
Mustache :
My name is {{name}} {{xitrumCss}}
Ghi nhớ rằng bạn không thể sử dụng các từ khóa dưới đây cho method
at để truyền tham số cho Scalate , bởi vì chúng đã được sử dụng từ trước.
- “context”: dùng cho đối tượng (object) Sclate utility, đối tượng này đã bao gồm các method như
unescape
- “helper”: sử dụng cho đối tượng current action
#### CoffeeScript
[¶](#coffeescript)
Bạn có thể nhúng CoffeeScript trong Scalate bằng cách sử dụng:
[:coffeescript filter](http://scalate.fusesource.org/documentation/jade-syntax.html#filters):
body :coffeescript alert "Hello, Coffee!"
Output:
<body> <script type='text/javascript'> //<![CDATA[ (function() { alert("Hello, Coffee!"); }).call(this); //]]> </script> </body>
Nhưng bạn cũng nhớ rằng việc sử dụng chúng
[tốn thời gian](http://groups.google.com/group/xitrum-framework/browse_thread/thread/6667a7608f0dc9c7):
jade+javascript+1thread: 1-2ms for page jade+coffesscript+1thread: 40-70ms for page jade+javascript+100threads: ~40ms for page jade+coffesscript+100threads: 400-700ms for page
Để tăng tốc độ bạn có thể generate CoffeeScript trước JavaScript.
### Layout
[¶](#layout)
Khi bạn gửi trả một view với
respondView hoặc
respondInlineView, Xitrum sẽ chuyển nó thành một String, và đặt String đó trong biến
renderedView. Xitrum sau đó sẽ gọi đến method
layout của current action, cuối cùng Xitrum sẽ gửi trả kết quả của method này về trình duyệt web.
Mặc định, medthod
layout sẽ tự trả về
renderedView. Nếu bạn muốn trang trí cho view bạn cần override method này. Nếu bạn include
renderView trong method này, view sẽ bao gồm các phần trong layout của bạn.
Điểm mấu chốt ở đây là
layout được gọi sau khi action view của bạn hiện lên, và trong mọi trường hợp đều trả về trình duyệt một kết quả. Kỹ thuật này khá đơn giản và rõ ràng. Nói một cách dễ hiểu hơn, bạn có thể nghĩ rằng sẽ không có một layout nào trong Xitrum. Tất cả chỉ xoay quanh method
layout và bạn có thể làm bất cứ điều gì với method này.
Thông thường, bạn tạo một class cha bao gồm các layout chung:
src/main/scala/mypackage/AppAction.scala
package mypackage import xitrum.Action trait AppAction extends Action { override def layout = renderViewNoLayout[AppAction]() }
src/main/scalate/mypackage/AppAction.jade
!!! 5 html head != antiCsrfMeta != xitrumCss != jsDefaults title Welcome to Xitrum body != renderedView != jsForView
src/main/scala/mypackage/MyAction.scala
package mypackage import xitrum.annotation.GET @GET("myAction") class MyAction extends AppAction { def execute() { respondView() } def hello(what: String) = "Hello %s".format(what) }
scr/main/scalate/mypackage/MyAction.jade:
- import mypackage.MyAction a(href={url}) Path to the current action p= currentAction.asInstanceOf[MyAction].hello("World")
#### Sử dụng layout không dùng tệp riêng biệt:
[¶](#su-dung-layout-khong-dung-tep-rieng-biet)
AppAction.scala
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>Welcome to Xitrum</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
#### Truyền trực tiếp layout đến method respondView
[¶](#truyen-truc-tiep-layout-den-method-respondview)
val specialLayout = () => DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>Welcome to Xitrum</title> </head> <body> {renderedView} {jsForView} </body> </html> ) respondView(specialLayout _)
### Inline view
[¶](#inline-view)
Thông thường, bạn viết view trong một tệp Scalate, ạn cũng có thể viết chúng trực tiếp như sau:
import xitrum.Action import xitrum.annotation.GET @GET("myAction") class MyAction extends Action { def execute() { val s = "World" // Will be automatically HTML-escaped respondInlineView( <p>Hello <em>{s}</em>!</p> ) } }
### Render fragment
[¶](#render-fragment)
Giả sử tệp MyAction.jade có đường dẫn: scr/main/scalate/mypackage/MyAction.jade
Nếu bạn muốn tạo tệp fragment trong cùng thư mục: scr/main/scalate/mypackage/_MyFragment.jade
renderFragment[MyAction]("MyFragment")
Nếu
MyAction là current action, bạn có thể bỏ qua:
renderFragment("MyFragment")
### Trả về view cho action khác
[¶](#tra-ve-view-cho-action-khac)
Sử dụng cú pháp
respondView[ClassName]():
package mypackage import xitrum.Action import xitrum.annotation.{GET, POST} @GET("login") class LoginFormAction extends Action { def execute() { // Respond scr/main/scalate/mypackage/LoginFormAction.jade respondView() } } @POST("login") class DoLoginAction extends Action { def execute() { val authenticated = ... if (authenticated) redirectTo[HomeAction]() else // Reuse the view of LoginFormAction respondView[LoginFormAction]() } }
#### Một action - nhiều view
[¶](#mot-action-nhieu-view)
Nếu bạn muốn có nhiều view cho một action:
package mypackage import xitrum.Action import xitrum.annotation.GET // These are non-routed actions, for mapping to view files: // scr/main/scalate/mypackage/HomeAction_NormalUser.jade // scr/main/scalate/mypackage/HomeAction_Moderator.jade // scr/main/scalate/mypackage/HomeAction_Admin.jade trait HomeAction_NormalUser extends Action trait HomeAction_Moderator extends Action trait HomeAction_Admin extends Action @GET("") class HomeAction extends Action { def execute() { val userType = ... userType match { case NormalUser => respondView[HomeAction_NormalUser]() case Moderator => respondView[HomeAction_Moderator]() case Admin => respondView[HomeAction_Admin]() } } }
Sử dụng các non-routed action như trên khá phức tạp, nhưng đó là cách typesafe.
Bạn cũng có thể sử dụng
[``](#id2)String``để chỉ ra đường dẫn đến :
respondView("mypackage/HomeAction_NormalUser") respondView("mypackage/HomeAction_Moderator") respondView("mypackage/HomeAction_Admin")
### Component
[¶](#component)
Bạn có thể tạo và tái sử dụng các component của view. Về cơ bản, một component gần giống với một action và có các tính chất sau:
- Component không có route, do đó không cần đến method
execute.
- Component không trả về một respond hoàn chỉnh, Component chỉ
renderra các fragment của view. Do đó trong một component, thay vì sử dụng
repondXXX, bạn hãy sử dụng
renderXXX.
- Giống với một action, một component có thể không có, có một, hoặc có nhiều view liên kết với nhau.
package mypackage import xitrum.{FutureAction, Component} import xitrum.annotation.GET class CompoWithView extends Component { def render() = { // Render associated view , e.g. CompoWithView.jade // Note that this is renderView, not respondView! renderView() } } class CompoWithoutView extends Component { def render() = { "Hello World" } } @GET("foo/bar") class MyAction extends FutureAction { def execute() { respondView() } }
MyAction.jade:
- import mypackage._ != newComponent[CompoWithView]().render() != newComponent[CompoWithoutView]().render()
## RESTful APIs
[¶](#restful-apis)
Bạn có thể tạo RESTful APIs cho ứng dụng trên iPhone,Android v.v một cách rất dễ dàng.
import xitrum.Action import xitrum.annotation.GET @GET("articles") class ArticlesIndex extends Action { def execute() {...} } @GET("articles/:id") class ArticlesShow extends Action { def execute() {...} }
Tương tự cho các method POST, PUT, PATCH, DELETE, và OPTIONS. Xitrum tự động kiểm soát phần HEAD như như một method GET với phần response body rỗng.
Với các HTTP client như các trình duyệt web thông thường không hỗ trợ method PUT và DELETE, để mô phỏng PUT và DELETE, sử dụng thủ thuật gửi một method PÓST với
_method=put hoặc
_method=delete trong request body.
Khi các ứng dụng web được khởi chạy, Xitrum sẽ quét tất cả các annotation, xây dựng bảng định tuyến và ghi ra out put để thông báo cho bạn biết bạn có APIs nào:
[INFO] Routes: GET /articles quickstart.action.ArticlesIndex GET /articles/:id quickstart.action.ArticlesShow
Các Route được tự động gom lại theo tinh thần của JAX-RS và Rails Engines. Bạn không cần khai báo tất cả các route tại cùng một nơi. Hãy nghĩ về tính năng nay tương tự như distributed route. Bạn có thể sử dụng một ứng dụng trong một ứng dụng khác. Nếu bạn có một blog engine, bạn có thể đóng gói nó thành một tập tin JAR và đặt tập tin JAR đó trong một ứng dụng khác, với cách làm như vậy ứng dụng đó sẽ có thêm tính năng blog.
Việc định tuyến thì bao gồm 2 chiều: bạn có thể tái tạo đường dẫn URL (reverse routing) một cách an toàn từ action.
Bạn có thể tạo tài liệu về các định tuyến bằng cách sử dụng
[Swagger Doc](http://swagger.wordnik.com/).
### Route cache
[¶](#route-cache)
Để khởi động nhanh hơn, route được cache trong file
routes.cache.
Trong quá trình phát triển, các route trong các tệp
*.class tại thư mục
target sẽ không được cache. Nếu bạn thực hiện cập nhất các thư viện phụ thuộc có chứa route, bạn có thể cần phải xóa tệp
routes.cache. Tệp này không nên được commit đến kho mã nguồn.
### Mức độ ưu tiên của các route (first, last)
[¶](#muc-do-uu-tien-cua-cac-route-first-last)
; Nếu bạn muốn các route như sau:
/articles/:id --> ArticlesShow /articles/new --> ArticlesNew
Bạn phải chắc chắn rằng route thứ 2 sẽ được kiểm tra trước.
Annotation
@First sẽ được thêm vào:
import xitrum.annotation.{GET, First} @GET("articles/:id") class ArticlesShow extends Action { def execute() {...} } @First // This route has higher priority than "ArticlesShow" above @GET("articles/new") class ArticlesNew extends Action { def execute() {...} }
Last is similar.
### Nhiều đường dẫn cho một action
[¶](#nhieu-duong-dan-cho-mot-action)
@GET("image", "image/:format") class Image extends Action { def execute() { val format = paramo("format").getOrElse("png") // ... } }
### Dot trong route
[¶](#dot-trong-route)
@GET("articles/:id", "articles/:id.:format") class ArticlesShow extends Action { def execute() { val id = param[Int]("id") val format = paramo("format").getOrElse("html") // ... } }
### Regular Expression trong route
[¶](#regular-expression-trong-route)
Regex có thể được sử dụng trong route:
GET("articles/:id<[0-9]+>")
### Xử lý các phần còn lại của route
[¶](#xu-ly-cac-phan-con-lai-cua-route)
Kí tự đặc biệt
/ không được phép có mặt trong tên của parameter. Nếu bạn muốn sử dụng kí tự này, parameter phải được đặt cuối cùng và bạn phải sử dụng nó như dưới đây:
GET("service/:id/proxy/:*")
Đường dẫn dưới đây sẽ xuất hiện:
/service/123/proxy/http://foo.com/bar
để lấy ra phần
*:
val url = param("*") // Will be "http://foo.com/bar"
### Liên kết đến một action
[¶](#lien-ket-den-mot-action)
Để bảo toàn tính typesafe của Xitrum, bạn không nên sử dụng URL một cách thủ công, hãy sử dụng các dưới đây:
<a href={url[ArticlesShow]("id" -> myArticle.id)}>{myArticle.title}</a>
### Redirect đến một action khác
[¶](#redirect-den-mot-action-khac)
Đọc thêm để biết
[redirection là gì](http://en.wikipedia.org/wiki/URL_redirection).
import xitrum.Action import xitrum.annotation.{GET, POST} @GET("login") class LoginInput extends Action { def execute() {...} } @POST("login") class DoLogin extends Action { def execute() { ... // After login success redirectTo[AdminIndex]() } } GET("admin") class AdminIndex extends Action { def execute() { ... // Check if the user has not logged in, redirect him to the login page redirectTo[LoginInput]() } }
Bạn cũng có thể redirect đến action hiện tại (current action) với method
redirectToThis().
### Forward đến action khác
[¶](#forward-den-action-khac)
Sử dụng method
forwardTo[AnotherAction](). Nếu bạn sử dụng method
redirectTo ở trên đây, trình duyệt sẽ tạo một request khác, trong khi đó method
forwardTo thì không.
### Xác định Ajax request
[¶](#xac-dinh-ajax-request)
Sử dụng method
isAjax.
// In an action val msg = "A message" if (isAjax) jsRender("alert(" + jsEscape(msg) + ")") else respondText(msg)
### Anti-CSRF
[¶](#anti-csrf)
Với các requests, Xitrum mặc định bảo vệ ứng dụng web của bạn khỏi kỹ thuật tấn công
[Giả mạo Cross-site request](http://en.wikipedia.org/wiki/CSRF).
Khi bạn incluede
antiCsrfMeta trong layout của bạn:
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>Welcome to Xitrum</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
Thẻ
<head> sẽ tưong tự như sau:
<!DOCTYPE html> <html> <head> ... <meta name="csrf-token" content="5402330e-9916-40d8-a3f4-16b271d583be" /> ... </head> ... </html>
Các token sẽ được tự động include trong tất cả các non-GET Ajax requests như
X-CSRF-Token header gửi bởi jQuery nếu bạn include
[xitrum.js](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/js.scala) trong view template.
xitrum.js được include trong
jsDefaults. Nếu bạn không sử dụng
jsDefaults, bạn có thể include
xitrum.js trong template như sau:
<script type="text/javascript" src={url[xitrum.js]}></script>
### antiCsrfInput và antiCsrfToken
[¶](#anticsrfinput-va-anticsrftoken)
Xitrum lây CSRF token từ
X-CSRF-Token request header. Nếu header không tồn tại, Xitrum sẽ lấy token từ parameter
csrf-token tại request body (chú ý: không phải parameter trong URL).
Nếu bạn tự tạo form, và bạn không sử dụng thẻ meta và xitrum.js như đã trình bày ở trên, bạn cần sử dụng
antiCsrfInput hoặc
antiCsrfToken:
form(method="post" action={url[AdminAddGroup]}) != antiCsrfInput
form(method="post" action={url[AdminAddGroup]}) input(type="hidden" name="csrf-token" value={antiCsrfToken})
### SkipCsrfCheck
[¶](#skipcsrfcheck)
Khi bạn tạo các APIs cho thiết bị, ví dụ điện thoại thông minh, bạn có thể muốn bỏ qua việc tự động kiểm tra CSRS. Thêm trait xitrum.SkipCsrfCheck vào action của bạn:
import xitrum.{Action, SkipCsrfCheck} import xitrum.annotation.POST trait Api extends Action with SkipCsrfCheck @POST("api/positions") class LogPositionAPI extends Api { def execute() {...} } @POST("api/todos") class CreateTodoAPI extends Api { def execute() {...} }
### Kiểm soát các route
[¶](#kiem-soat-cac-route)
Khi khởi động Xitrum sẽ tự động gom các route lại. Nếu bạn muốn điều khiển các route, bạn có thể sử dụng
[xitrum.Config.routes](http://xitrum-framework.github.io/api/3.17/index.html#xitrum.routing.RouteCollection).
Ví dụ:
import xitrum.{Config, Server} object Boot { def main(args: Array[String]) { // You can modify routes before starting the server val routes = Config.routes // Remove routes to an action by its class routes.removeByClass[MyClass]() if (demoVersion) { // Remove routes to actions by a prefix routes.removeByPrefix("premium/features") // This also works routes.removeByPrefix("/premium/features") } ... Server.start() } }
### Lấy tất cẩ các request content
[¶](#lay-tat-ca-cac-request-content)
Thông thường, nếu request content không phải là
application/x-www-form-urlencoded, bạn có thể cần phải lấy tất cả các request content (và tự phân tích chúng).
Để lấy ra một chuối ký tự (string):
val body = requestContentString
Để lấy ra một string và phân tích chúng thành JSON:
val myJValue = requestContentJValue // => JSON4S (http://json4s.org) JValue val myMap = xitrum.util.SeriDeseri.fromJValue[Map[String, Int]](myJValue)
Nếu bạn muốn kiểm soát toàn bộn, sử dụng
[request.getContent](http://netty.io/4.0/api/io/netty/handler/codec/http/FullHttpRequest.html). Nó sẽ trả về một [ByteBuf](http://netty.io/4.0/api/io/netty/buffer/ByteBuf.html).
### Viết tài liệu API với Swagger
[¶](#viet-tai-lieu-api-voi-swagger)
Bạn có thể viết tài liệu cho API của bạn với
[Swagger](https://developers.helloreverb.com/swagger/). Thêm annotation
@Swagger vào action cần được viết tài liệu.
Xitrum sẽ generate
[/xitrum/swagger.json](https://github.com/wordnik/swagger-core/wiki/API-Declaration).
Tệp này có thể sử dụng với [Swagger UI](https://github.com/wordnik/swagger-ui) để tạo giao diện cho tài liệu của API.
Xitrum đã bao gồm Swagger UI. Sử dụng chúng tại đường dẫn
[``](#id1)/xitrum/swagger-ui` của chưong trình của bạn.
Ví dụ [http://localhost:8000/xitrum/swagger-ui](http://localhost:8000/xitrum/swagger-ui). ![ _images/swagger.png](_images/swagger.png)
Bạn có thể xem
[một ví dụ](https://github.com/xitrum-framework/xitrum-placeholder):
import xitrum.{Action, SkipCsrfCheck} import xitrum.annotation.{GET, Swagger} @Swagger( Swagger.Tags("image", "APIs to create images"), Swagger.Description("Dimensions should not be bigger than 2000 x 2000"), Swagger.OptStringQuery("text", "Text to render on the image, default: Placeholder"), Swagger.Produces("image/png"), Swagger.Response(200, "PNG image"), Swagger.Response(400, "Width or height is invalid or too big") ) trait ImageApi extends Action with SkipCsrfCheck { lazy val text = paramo("text").getOrElse("Placeholder") } @GET("image/:width/:height") @Swagger( // <-- Inherits other info from ImageApi Swagger.Summary("Generate rectangle image"), Swagger.IntPath("width"), Swagger.IntPath("height") ) class RectImageApi extends Api { def execute { val width = param[Int]("width") val height = param[Int]("height") // ... } } @GET("image/:width") @Swagger( // <-- Inherits other info from ImageApi Swagger.Summary("Generate square image"), Swagger.IntPath("width") ) class SquareImageApi extends Api { def execute { val width = param[Int]("width") // ... } }
[JSON cho Swagger](https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md) sẽ được tạo khi bạn sử dụng
/xitrum/swagger.
Swagger UI sử dụng JSON dưới đây để tạo giao diện cho tài liệu API.
Ngoài các parameter như Swagger.IntPath và Swagger.OptStringQuery còn các tham số sau: BytePath, IntQuery, OptStringForm etc. Chúng ta có thể tạo theo mẫu They are in the form:
<Value type><Param type>(required parameter)
Opt<Value type><Param type>(optional parameter)
Kiểu dữ liệu: Byte, Int, Int32, Int64, Long, Number, Float, Double, String, Boolean, Date, DateTime
Kiểu tham số: Path, Query, Body, Header, Form
Đọc thêm về
[kiểu dữ liệu](https://github.com/wordnik/swagger-core/wiki/Datatypes) và [kiểu tham số](https://github.com/wordnik/swagger-core/wiki/Parameters).
## Template engines
[¶](#template-engines)
Template engine đã được cấu hình dẽ được gọi khi
[ được gọi tới. renderView, renderFragment,
hoặc respondView](index.html#document-action_view)
### Cấu hình template engine
[¶](#cau-hinh-template-engine)
Trong tệp
[config/xitrum.conf](https://github.com/xitrum-framework/xitrum-new/blob/master/config/xitrum.conf), template engine có thể cấu hình theo 2 mẫu dưới dây, phụ thuộc vào engine mà bạn sử dụng:
template = my.template.EngineClassName
Hoặc:
template { "my.template.EngineClassName" { option1 = value1 option2 = value2 } }
Template engine mặc định là
[xitrum-scalate](https://github.com/xitrum-framework/xitrum-scalate).
### Xóa template engine
[¶](#xoa-template-engine)
Nếu bạn chỉ tạo RESTful APIs trong project, thông thường bạn không sử dụng method renderView, renderFragment, hoặc respondView. Trong trường hợp này, bạn còn có thể xóa template engine khỏi porject để project nhẹ hơn. Bạn chỉ cần xóa hoặc comment dòng
templateEngine trong tệp config/xitrum.conf.
Sau đó bạn xóa các cấu hình template liên quan khỏi project của bạn.
### Tự tạo template engine cho riêng bạn
[¶](#tu-tao-template-engine-cho-rieng-ban)
Để tạo template engine cho riêng bạn, tạo một class kế thừa từ
[xitrum.view.TemplateEngine](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/view/TemplateEngine.scala).
Và đặt class này của bạn trong tệp config/xitrum.conf.
Ví dụ, xem
[xitrum-scalate](https://github.com/xitrum-framework/xitrum-scalate).
## Postbacks
[¶](#postbacks)
Có 2 use case chính của ứng dụng web:
- Để phục vụ các thiết bị: bạn cần tạo các RESTful APIs cho smartphones, web service cho các web site khác.
- Để phục vụ các người dùng cuối: bạn cần tạo giao diện web.
Như một web framework thông thường, Xitrum hướng tới việc hỗ trợ giải quyết các use case một cách dễ dàng. Để giải quyết use case đầu tiền, bạn sử dụng
[. Để giải quyết use case thứ hai, bạn có thể sử dụng tính năng Ajax form postback của Xitrum.
Bạn có thể xem thêm các trang dưới đây để biết thêm về postback: RESTful actions](index.html#document-restful)
Tính năng postback của Xitrum có liên hệ tới
[Nitrogen](http://nitrogenproject.com/).
### Layout
[¶](#layout)
AppAction.scala
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>Welcome to Xitrum</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
### Form
[¶](#form)
Articles.scala
import xitrum.annotation.{GET, POST, First} import xitrum.validator._ @GET("articles/:id") class ArticlesShow extends AppAction { def execute() { val id = param("id") val article = Article.find(id) respondInlineView( <h1>{article.title}</h1> <div>{article.body}</div> ) } } @First // Force this route to be matched before "show" @GET("articles/new") class ArticlesNew extends AppAction { def execute() { respondInlineView( <form data-postback="submit" action={url[ArticlesCreate]}> <label>Title</label> <input type="text" name="title" class="required" /><br /> <label>Body</label> <textarea name="body" class="required"></textarea><br /> <input type="submit" value="Save" /> </form> ) } } @POST("articles") class ArticlesCreate extends AppAction { def execute() { val title = param("title") val body = param("body") val article = Article.save(title, body) flash("Article has been saved.") jsRedirectTo(show, "id" -> article.id) } }
Khi sự kiện
submit của JavaScript trong form xảy ra, form sẽ postback về
ArticlesCreate.
Thuộc tính
action của
<form> được tạo ra. URL được mã hóa hoạt động như một anti-CSRF token.
### Non-form
[¶](#non-form)
Postback có thể được đặt trong bất kỳ phần tử nào, không chỉ là form.
Một ví dụ sử dụng link:
<a href="#" data-postback="click" action={postbackUrl[LogoutAction]}>Logout</a>
Khi click vào link ở trên sẽ tạo ra postback đến LogoutAction.
### Hộp thoại xác nhận
[¶](#hop-thoai-xac-nhan)
Nếu bạn muốn hiển thị một hộp thoại xác nhận:
<a href="#" data-postback="click" action={url[LogoutAction]} data-confirm="Do you want to logout?">Logout</a>
Nếu người dùng click “Cancel”, postback sẽ không được gửi đi.
### Thêm parameter khác
[¶](#them-parameter-khac)
Với các form element, bạn có thể thêm
<input type="hidden"... để gửi thêm các parameter khác với postback.
Với các element khác, bạn làm như sau:
<a href="#" data-postback="click" action={url[ArticlesDestroy]("id" -> item.id)} data-params="_method=delete" data-confirm={"Do you want to delete %s?".format(item.name)}>Delete</a>
Bạn cũng có thể thêm các parameter trong một form riêng biệt:
<form id="myform" data-postback="submit" action={url[SiteSearch]}> Search: <input type="text" name="keyword" /> <a class="pagination" href="#" data-postback="click" data-form="#myform" action={url[SiteSearch]("page" -> page)}>{page}</a> </form>
#myform là một jQuery selector để chọn form có chứa các parameter được thêm vào.
## XML
[¶](#xml)
Scala cho phép viết literal XML. Xitrum sử dụng tính năng này như “template engine”:
- Scala check cú pháp XML khi compile: Các View là typesafe.
- Scala tự động bỏ qua XML: Các view ngăn chặn
[XSS](http://en.wikipedia.org/wiki/Cross-site_scripting).
Dưới đây là một vài thủ thuật.
### Unescape XML
[¶](#unescape-xml)
Sử dụng
scala.xml.Unparsed:
import scala.xml.Unparsed <script> {Unparsed("if (1 < 2) alert('Xitrum rocks');")} </script>
hoặc sử dụng
<xml:unparsed>:
<script> <xml:unparsed> if (1 < 2) alert('Xitrum rocks'); </xml:unparsed> </script>
<xml:unparsed> sẽ được ẩn đi trong output.
<script> if (1 < 2) alert('Xitrum rocks'); </script>
### Các nhóm XML element
[¶](#cac-nhom-xml-element)
<div id="header"> {if (loggedIn) <xml:group> <b>{username}</b> <a href={url[LogoutAction]}>Logout</a> </xml:group> else <xml:group> <a href={url[LoginAction]}>Login</a> <a href={url[RegisterAction]}>Register</a> </xml:group>} </div>
<xml:group> sẽ được ẩn đi trong output, ví dụ khi người dùng thực hiện đăng nhập:
<div id="header"> <b>My username</b> <a href="/login">Logout</a> </div>
### Render XHTML
[¶](#render-xhtml)
Xitrum tự động render view và layout như XHTML. Nếu bạn muốn tự render chúng (hiếm khi), chú ý đến các dòng code dưới đây.
import scala.xml.Xhtml val br = <br /> br.toString // => <br></br>, một vài trình duyệt sẽ render dòng này như 2 thẻ <br /> Xhtml.toXhtml(<br />) // => "<br />"
## JavaScript and JSON
[¶](#javascript-and-json)
### JavaScript
[¶](#javascript)
Xitrum đã inlcude jQuery. Có một vài jsXXX helper.
#### Thêm các đoạn JavaScript vào một view
[¶](#them-cac-doan-javascript-vao-mot-view)
Trong action, gọi method
jsAddToView (nhiều lần nếu cần):
class MyAction extends AppAction { def execute() { ... jsAddToView("alert('Hello')") ... jsAddToView("alert('Hello again')") ... respondInlineView(<p>My view</p>) } }
Trong layout, gọi method
jsForView:
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} </head> <body> <div id="flash">{jsFlash}</div> {renderedView} {jsForView} </body> </html> )
#### Respond JavaScript trực tiếp không sử dụng view
[¶](#respond-javascript-truc-tiep-khong-su-dung-view)
Để respond JavaScript:
jsRespond("$('#error').html(%s)".format(jsEscape(<p class="error">Could not login.</p>)))
Một các trực tiếp:
jsRedirectTo("http://cntt.tv/") jsRedirectTo[LoginAction]()
### JSON
[¶](#json)
Xitrum đã include
[JSON4S](https://github.com/json4s/json4s).
Bạn có thể đọc thêm để biết các parse và generate ra JSON.
Để convert từ Scala case object thành JSON string và ngược lại:
import xitrum.util.SeriDeseri case class Person(name: String, age: Int, phone: Option[String]) val person1 = Person("Jack", 20, None) val json = SeriDeseri.toJson(person1) val person2 = SeriDeseri.fromJson[Person](json)
Để respond JSON:
val scalaData = List(1, 2, 3) // An example respondJson(scalaData)
JSON cũng thuận tiện cho các tệp cấu hình cần tới các cấu trúc lồng nhau:
Xem
[. Load config files](index.html#document-howto)
### Plugin cho Knockout.js
[¶](#plugin-cho-knockout-js)
## Async response
[¶](#async-response)
Danh sách các method responding thông thường:
respondView: respond tệp view, có hoặc không có layout.
respondInlineView: respond template đã được nhúng(không tách rời các tệp template), có hoặc không có layout.
respondText("hello"): respond một string, không có layout
respondHtml("<html>...</html>"): như trên, với content type đặt là “text/html”
respondJson(List(1, 2, 3)): convert Scala object thành JSON object sau đó respond
respondJs("myFunction([1, 2, 3])")
respondJsonP(List(1, 2, 3), "myFunction"): kết hợp cả 2 method ở trên
respondJsonText("[1, 2, 3]")
respondJsonPText("[1, 2, 3]", "myFunction")
respondBinary: respond một mảng byte
respondFile: send một tệp trực tiếp từ đĩa với tốc độ cao, sử dụng
[zero-copy](http://www.ibm.com/developerworks/library/j-zerocopy/)(aka send-file)
respondEventSource("data", "event")
Xitrum không tự động gửi bất kỳ response nào.
Bạn phải gọi method
respondXXX ở trên để gửi response.
Nếu bạn không gọi
respondXXX, Xitrum sẽ giữ kết nối HTTP, và bạn có thể
gọi
respondXXX sau.
Để kiểm tra kết nối còn mở hay không, gọi
channel.isOpen.
Bạn cũng có thể sử dụng
addConnectionClosedListener:
addConnectionClosedListener { // The connection has been closed // Unsubscribe from events, release resources etc. }
Vì tính năng async response không được gửi ngay lập tức.
respondXXX trả về
[ChannelFuture](http://netty.io/4.0/api/io/netty/channel/ChannelFuture.html).
Bạn có thể sử dụng nó để thực hiện action khi response đã thực sự được gửi đi.
Ví dụ, bạn muốn đóng kết nối sau khi response đã được gửi đi:
import io.netty.channel.{ChannelFuture, ChannelFutureListener} val future = respondText("Hello") future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { future.getChannel.close() } })
hoặc ngắn hơn:
respondText("Hello").addListener(ChannelFutureListener.CLOSE)
### WebSocket
[¶](#websocket)
import scala.runtime.ScalaRunTime import xitrum.annotation.WEBSOCKET import xitrum.{WebSocketAction, WebSocketBinary, WebSocketText, WebSocketPing, WebSocketPong} @WEBSOCKET("echo") class EchoWebSocketActor extends WebSocketAction { def execute() { // Here you can extract session data, request headers etc. // but do not use respondText, respondView etc. // To respond, use respondWebSocketXXX like below. log.debug("onOpen") context.become { case WebSocketText(text) => log.info("onTextMessage: " + text) respondWebSocketText(text.toUpperCase) case WebSocketBinary(bytes) => log.info("onBinaryMessage: " + ScalaRunTime.stringOf(bytes)) respondWebSocketBinary(bytes) case WebSocketPing => log.debug("onPing") case WebSocketPong => log.debug("onPong") } } override def postStop() { log.debug("onClose") super.postStop() } }
Một actor sẽ được tạo khi có một request. Actor sẽ được dừng lại khi một trong các điều kiện sau xảy ra:
- Kết nối bị đóng.
- WebSocket close frame được nhận hoặc gửi đi
Sử dụng các method sau để gửi WebSocket frames:
respondWebSocketText
respondWebSocketBinary
respondWebSocketPing
respondWebSocketClose
Không có respondWebSocketPong, vì Xitrum sẽ tự động gửi pong frame khi nó nhận được ping frame.
Để lấy URL cho WebSocket action ở trên:
// Probably you want to use this in Scalate view etc. val url = webSocketAbsUrl[EchoWebSocketActor]
### SockJS
[¶](#sockjs) [SockJS](https://github.com/sockjs/sockjs-client) là một thư viện trình duyệt
JavaScript cung cấp một WebSocket-like object, dành cho các trình duyệt không hỗ
trợ WebSocket. Đầu tiên SockJS thử sử dụng WebSocket. Nếu không thành công, nó có thể sử dụng một số cách nhưng vẫn đưa về sử dụng WebSocket-like object.
Nếu bạn muốn làm việc với WebSocket API trên mọi trình duyệt, bạn nên sử dụng SockJS và tránh sử dụng trực tiếp WebSocket directly.
<script> var sock = new SockJS('http://mydomain.com/path_prefix'); sock.onopen = function() { console.log('open'); }; sock.onmessage = function(e) { console.log('message', e.data); }; sock.onclose = function() { console.log('close'); }; </script>
Xitrum bao gồm các tệp JavaScript của SockJS. Trong view template, chỉ cần viết như sau:
... html head != jsDefaults ...
SockJS đòi hỏi một
[server counterpart](https://github.com/sockjs/sockjs-protocol).
Xitrum sẽ tự động cung cấp.
import xitrum.{Action, SockJsAction, SockJsText} import xitrum.annotation.SOCKJS @SOCKJS("echo") class EchoSockJsActor extends SockJsAction { def execute() { // To respond, use respondSockJsXXX like below log.info("onOpen") context.become { case SockJsText(text) => log.info("onMessage: " + text) respondSockJsText(text) } } override def postStop() { log.info("onClose") super.postStop() } }
Một actor sẽ được tạo khi có một SockJS session mới. Nó sẽ dừng lại khi SockJS session này đóng lại.
Sử dụng các method sau để gửi các send SockJS frames:
respondSockJsText
respondSockJsClose
Xem
[Various issues and design considerations](https://github.com/sockjs/sockjs-node#various-issues-and-design-considerations):
Về cơ bản, cookie không phù hợp với mô hình SockJS. Nếu bạn muốn authorize cho một session, cũng cấp một token đặc biệt trên một page, gửi chúng như những thứ đầu tiên qua kết nối SockJS và validate nó ở server. Về cơ bản thì đây là cách thức hoạt động của cookie
Để cấu hình SockJS clustering, xem
[. Clustering với Akka](index.html#document-cluster)
### Chunked response
[¶](#chunked-response)
Để gửi
[chunked response](http://en.wikipedia.org/wiki/Chunked_transfer_encoding):
- Gọi
setChunked
- Gọi
respondXXXbao nhiêu lần bạn muốn
- Cuối cùng, gọi
respondLastChunk
Chunked response có nhiều use cases. Ví dụ, khi bạn cần generate một tệp CSV lớn hơn bộ nhớ, bạn có thể generate chunk by chunk và gửi chúng khi bạn generate:
// "Cache-Control" header will be automatically set to: // "no-store, no-cache, must-revalidate, max-age=0" // // Note that "Pragma: no-cache" is linked to requests, not responses: // http://palizine.plynt.com/issues/2008Jul/cache-control-attributes/ setChunked() val generator = new MyCsvGenerator generator.onFirstLine { line => val future = respondText(header, "text/csv") future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) generator.next() } } } generator.onNextLine { line => val future = respondText(line) future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) generator.next() } }) } generator.onLastLine { line => val future = respondText(line) future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) respondLastChunk() } }) } generator.generate()
Ghi nhớ:
- Header được gửi ở lần gọi
respondXXXđầu tiên.
- Bạn có thể gửi các optional trailing header tại
respondLastChunk
[không thế sử dụng với chunked response.](index.html#document-cache) *Page và action cache*
Với việc sử dụng chunked response cùng với
ActorAction, bạn có thể dễ dàng implement
[Facebook BigPipe](http://www.cubrid.org/blog/dev-platform/faster-web-page-loading-with-facebook-bigpipe/).
#### Forever iframe
[¶](#forever-iframe)
Chunked response
[có thể được sử dụng](http://www.shanison.com/2010/05/10/stop-the-browser-%E2%80%9Cthrobber-of-doom%E2%80%9D-while-loading-comet-forever-iframe/)
cho [Comet](http://en.wikipedia.org/wiki/Comet_%28programming%29).
Page nhúng iframe:
... <script> var functionForForeverIframeSnippetsToCall = function() {...} </script> ... <iframe width="1" height="1" src="path/to/forever/iframe"></iframe> ...
Action respond
<script> snippets mãi mãi:
// Prepare forever iframe setChunked() // Need something like "123" for Firefox to work respondText("<html><body>123", "text/html") // Most clients (even curl!) do not execute <script> snippets right away, // we need to send about 2KB dummy data to bypass this problem for (i <- 1 to 100) respondText("<script></script>\n")
Sau đo, bất cứ khi nào bạn muốn truyền dữ liệu đến trình duyệt, chỉ cần gửi một snippet:
if (channel.isOpen) respondText("<script>parent.functionForForeverIframeSnippetsToCall()</script>\n") else // The connection has been closed, unsubscribe from events etc. // You can also use ``addConnectionClosedListener``.
#### Event Source
[¶](#event-source)
Xem
[http://dev.w3.org/html5/eventsource/](http://dev.w3.org/html5/eventsource/)
Event Source response là một loại chunked response đặc biệt. Dữ liệu phải là kiểu UTF-8.
Để respond event source, gọi
respondEventSource.
respondEventSource("data1", "event1") // Event name is "event1" respondEventSource("data2") // Event name is set to "message" by default
## Các tệp tĩnh
[¶](#cac-tep-tinh)
### Cung cấp các tệp tĩnh trên đĩa
[¶](#cung-cap-cac-tep-tinh-tren-dia)
Thư mục của dự án:
config public favicon.ico robots.txt 404.html 500.html img myimage.png css mystyle.css js myscript.js src build.sbt
Xitrum tự động cung cấp các tệp tĩnh trong thư mực
public.
URLs đến các tệp này:
/img/myimage.png /css/mystyle.css /css/mystyle.min.css
Để dẫn đến chúng:
<img src={publicUrl("img/myimage.png")} />
Để cung cấp các tệp thường trong môi trường phát triển và bản rút gọn trong môi trường của sản phẩm (mystyle.css và mystyle.min.css as above):
<img src={publicUrl("css", "mystyle.css", "mystyle.min.css")} />
Để gửi các tệp tĩnh trên đĩa từ action, sử dụng method
respondFile.
respondFile("/absolute/path") respondFile("path/relative/to/the/current/working/directory")
Để tối ưu hóa tốc độ cung cấp các tệp tĩnh, bạn có thể bỏ qua các tệp không cần thiết với bộ lọc regex. Nếu request url không match với pathRegex, Xitrum sẽ respond lỗi 404 cho request đó.
Xem
pathRegex trong
config/xitrum.conf.
### index.html fallback
[¶](#index-html-fallback)
Nếu không có route (không có action) cho URL
/foo/bar (hoặc
/foo/bar/), Xitrum sẽ tìm các tệp tĩnh
public/foo/bar/index.html (năm
trong thư mục
public). Nếu tìm thây tệp, Xitrum sẽ respond nó về cho phía
client.
### 404 và 500
[¶](#va-500)
404.html và 500.html trong thư mục
public được sử dụng khi không có route
nào matched và có một lỗi trong quá trình thực thi. Nếu bạn muốn tự kiểm soát
lỗi:
import xitrum.Action import xitrum.annotation.{Error404, Error500} @Error404 class My404ErrorHandlerAction extends Action { def execute() { if (isAjax) jsRespond("alert(" + jsEscape("Not Found") + ")") else renderInlineView("Not Found") } } @Error500 class My500ErrorHandlerAction extends Action { def execute() { if (isAjax) jsRespond("alert(" + jsEscape("Internal Server Error") + ")") else renderInlineView("Internal Server Error") } }
Response status được đặt thành 404 hoặc 500 trước khi action được thực thi, vì vậy bạn không cần phải đặt chúng một các thủ công.
### Cung cấp các tệp tài nguyên trong classpath với WebJars convention
[¶](#cung-cap-cac-tep-tai-nguyen-trong-classpath-voi-webjars-convention)
#### WebJars
[¶](#webjars) [`](#id1)WebJars < [http://www.webjars.org/](http://www.webjars.org/)>_ cung cấp rất nhiều các thư viện web mà bạn
[`](#id3)có sử dụng trong project.
Ví dụ, nếu bạn muốn sử dụng
[Underscore.js](http://underscorejs.org/), khai
báo trong tệp
build.sbt của project như sau:
libraryDependencies += "org.webjars" % "underscorejs" % "1.6.0-3"
Sau đó trong tệp .jade:
script(src={webJarsUrl("underscorejs/1.6.0", "underscore.js", "underscore-min.js")})
Xitrum sẽ tự động sử dụng
underscore.js cho môi trường phát triển và
underscore-min.js cho môi trường sản phẩm.
Kết quả như sau:
/webjars/underscorejs/1.6.0/underscore.js?XOKgP8_KIpqz9yUqZ1aVzw
Nếu bạn muốn sử dụng cũng một tệp trong cả 2 môi trường:
script(src={webJarsUrl("underscorejs/1.6.0/underscore.js")})
Khi thư viện này phụ thuộc vào thư viện kia, SBT sẽ tự động tải các thư viện
liên quan về. Nếu thấy SBT không tải đúng phiên bản (có thể xác nhận bằng cách
chạy lệnh sbt xitrum-package` rồi xem các tệp trong thư mục
target/xitrum/lib
được tạo ra), bạn có thể ép SBT dùng đúng phiên bản bạn muốn bằng
dependencyOverrides.
Ví dụ nếu bạn thấy SBT chọn thư viện jQuery phiên bản 2.x, mà bạn lại muốn
dùng phiên bản 1.x để có thể hỗ trợ Internet Explorer 6, 7, hoặc 8, thì có
thể khai báo như sau:
dependencyOverrides += "org.webjars" % "jquery" % "1.11.3"
#### Lưu resource file trong tệp .jar với WebJars convention
[¶](#luu-resource-file-trong-tep-jar-voi-webjars-convention)
Nếu bạn là người phát triển thư viện và muốn cung cấp tệp myimage.png từ thư
viện của bạn, một tệp .jar trong classpath, sau đó lưu myimage.png trong tệp
.jar với
[WebJars](http://www.webjars.org/) convention, ví dụ:
META-INF/resources/webjars/mylib/1.0/myimage.png
Để cung cấp tệp:
<img src={webJarsUrl("mylib/1.0/myimage.png")} />
Trong cả môi trường, đường dẫn URL sẽ là:
/webjars/mylib/1.0/myimage.png?xyz123
#### Respond một tệp trong classpath
[¶](#respond-mot-tep-trong-classpath)
Để respond một tệp trong một classpath element (một tệp .jar hoặc một thư
mục), kể cả khi tệp không được lưu với
[WebJars](http://www.webjars.org/)
convention:
respondResource("path/relative/to/the/classpath/element")
Ex:
respondResource("akka/actor/Actor.class") respondResource("META-INF/resources/webjars/underscorejs/1.6.0/underscore.js") respondResource("META-INF/resources/webjars/underscorejs/1.6.0/underscore-min.js")
### Cache ở phía client với ETag và max-age
[¶](#cache-o-phia-client-voi-etag-va-max-age)
Xitrum tự động thêm
[Etag](http://en.wikipedia.org/wiki/HTTP_ETag) cho các tệp
tĩnh trên đĩa và classpath.
ETags sử dụng cho các tệp nhỏ như mã MD5 của file content. Chúng sẽ được cache
để sử dụng sau. Key của cache entry là
(file path, modified time). Bởi vì
modified time ở các server khác nhau thì khác nhau, nên mỗi web server trong
một cluster (nhóm) sẽ có riêng local ETag cache.
Với các tệp lớn, chỉ khi sửa đổi tệp mới sử dụng Etag. Có vẻ không thực sự hoàn hảo bởi không thể đồng nhất các tệp trên các server khác nhau vì chúng có nhiều ETag khác nhau, nhưng nó vẫn tốt hơn là không sử dụng ETag.
publicUrl và
webJarsUrl tự động thêm ETag vào URL khi chúng được generate. Ví dụ:
webJarsUrl("jquery/2.1.1/jquery.min.js") => /webjars/jquery/2.1.1/jquery.min.js?0CHJg71ucpG0OlzB-y6-mQ
Xitrum cũng đặt
max-age và
Exprires header thành
[one year](https://developers.google.com/speed/docs/best-practices/caching). Bạn không
cần lo lắng rằng trình duyệt không chọn tệp mới nhất khi bạn sửa đổi. Bởi vì khi một tệp
trên ổ đĩa được sửa, thuộc tính
modified time của tệp đó sẽ thay đổi, do đó URL tạo
ra bởi
publicUrl và
webJarUrl cũng thay đổi theo. ETag cache của tệp cũng sẽ thay
đổi bởi cache key thay đổi.
### GZIP
[¶](#gzip)
Xitrum thực hiện việc nén GZIP tự động. Thuộc tính
Content-Type tại header sẽ cho biết
định dạng của respond là
text/html hay
xml/application v.v.
Xitrum luôn tự động nén GZIP với các tệp tĩnh, nhưng định dạng responses được tùy biến, để tối ưu hóa, Xitrum chỉ thực hiện GZIP với các response lớn hơn 1KB.
### Cache ở phía Server
[¶](#cache-o-phia-server)
Để hạn chế load tệp từ đĩa, Xitrum cache các tệp tĩnh nhỏ trong bộ nhớ với quy tắc LRU (Lần cuối
sử dụng xa nhất). Xem
small_static_file_size_in_kb và
max_cached_small_static_files
trong
config/xitrum.conf.
## Cung cấp flash socket policy file
[¶](#cung-cap-flash-socket-policy-file)
Đọc thêm về flash socket policy:
[http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html](http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html) [http://www.lightsphere.com/dev/articles/flash_socket_policy.html](http://www.lightsphere.com/dev/articles/flash_socket_policy.html)
Giao thức để truyền tệp socket policy khác với giao thức HTTP. Để gửi:
- Sửa tệp
[config/flash_socket_policy.xml](https://github.com/xitrum-framework/xitrum-new/blob/master/config/flash_socket_policy.xml)một cách thích hợp
- Sửa tệp
[config/xitrum.conf](https://github.com/xitrum-framework/xitrum-new/blob/master/config/xitrum.conf)để có thể truyền tệp bên trên.
## Scopes
[¶](#scopes)
### Request
[¶](#request)
#### Các loại parameter
[¶](#cac-loai-parameter)
Có 2 loại request parameter: textual parameter và file upload parameter (binary).
Có 3 loại textual parameter, thuộc kiểu
scala.collection.mutable.Map[String, Seq[String]]:
queryParams: parameter nằm sau dấu ? trong URL ,ví dụ :
[http://example.com/blah?x=1&y=2](http://example.com/blah?x=1&y=2)
bodyTextParams: parameter trong phần body của POST request
pathParams: parameter nhúng trong URL, ví dụ:
GET("articles/:id/:title")
Các parameter được gộp thành kiểu
textParams (từ 1 đến 3, kiểu sau sẽ override kiểu trước).
bodyFileParams thuộc kiểu scala.collection.mutable.Map[String, Seq[
[FileUpload](http://netty.io/4.0/api/io/netty/handler/codec/http/multipart/FileUpload.html)]].
#### Accesing parameter
[¶](#accesing-parameter)
Từ một action, bạn có thể truy cập đến các parameter trực tiếp, hoặc bạn có thể sử dụng các accessor method.
Để truy cập
textParams:
param("x"): trả về
String, throws exception nếu x không tồn tại
paramo("x"): trả về
Option[String]
params("x"): trả về
Seq[String], Seq.empty nếu x không tồn tại
Bạn có thể convert các text parameter thành các kiểu khác như Int, Long, Float, Double
một các tự động bằng cách sử dụng
param[Int]("x"),
params[Int]("x") v.v. Để convert
các text parameter thành các kiểu khác, override
[convertTextParam](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala-2.11/xitrum/scope/request/ParamAccess.scala).
Với các file upload parameter:
param[FileUpload]("x"),
params[FileUpload]("x") v.v.
Để biết chi tiết, hãy xem
[. Upload chapter](index.html#document-upload)
#### “at”
[¶](#at)
Để truyền tham số khi thực hiện một request (từ action đến view hoặc layout), có thể
sử dụng
at.
at thuộc kiểu
scala.collection.mutable.HashMap[String, Any].
Nếu bạn từng tiếp xúc với Rails, bạn sẽ nhận ra rằng
at là một bản sao của
@
trong Rails.
Articles.scala
@GET("articles/:id") class ArticlesShow extends AppAction { def execute() { val (title, body) = ... // Get from DB at("title") = title respondInlineView(body) } }
AppAction.scala
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>{if (at.isDefinedAt("title")) "My Site - " + at("title") else "My Site"}</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
#### “atJson”
[¶](#atjson)
atJson là một helper method tự động convert
at("key") sang JSON.
Nếu bạn chuyển model từ Scala sang JavaScript.
atJson("key") tương đương với
xitrum.util.SeriDeseri.toJson(at("key")):
Action.scala
case class User(login: String, name: String) ... def execute() { at("user") = User("admin", "Admin") respondView() }
Action.ssp
<script type="text/javascript"> var user = ${atJson("user")}; alert(user.login); alert(user.name); </script>
#### RequestVar
[¶](#requestvar)
at không typesafe bởi vì bạn có thể đặt mọi thứ vào trong map. Để typesafe
hơn, bạn nên sử dụng RequestVar một class đóng gói
at.
RVar.scala
import xitrum.RequestVar object RVar { object title extends RequestVar[String] }
Articles.scala
@GET("articles/:id") class ArticlesShow extends AppAction { def execute() { val (title, body) = ... // Get from DB RVar.title.set(title) respondInlineView(body) } }
AppAction.scala
import xitrum.Action import xitrum.view.DocType trait AppAction extends Action { override def layout = DocType.html5( <html> <head> {antiCsrfMeta} {xitrumCss} {jsDefaults} <title>{if (RVar.title.isDefined) "My Site - " + RVar.title.get else "My Site"}</title> </head> <body> {renderedView} {jsForView} </body> </html> ) }
### Session
[¶](#session)
Xitrum tự động quản lý Session bao gồm lưu trữ, trả về dữ liệu, mã hóa, v.v. Bạn không cần phải bận tâm đến Session.
Trong action, bạn có thể sử dụng action
session, một instance
scala.collection.mutable.Map[String, Any]. Mọi thứ lưu trữ trong
session
phải serializable.
Ví dụ, để đánh dấu một người dùng đã đăng nhập, bạn có để đặt username của người dùng vào session:
session("userId") = userId
Sau đó, nếu bạn muốn kiểm tra người dùng đã đăng nhập hay chưa, chỉ cần kiểm tra đã có username trong session hay chưa:
if (session.isDefinedAt("userId")) println("This user has logged in")
Lưu trữ user ID và lấy thông tin người dùng từ database mỗi lần truy cập thường được sử dụng hơn, Cách này bạn có thể biết được thông tin người dùng đã được cập nhất (bao gồm quyền và xác thực) ở mỗi lần truy cập.
#### session.clear()
[¶](#session-clear) [Với một dòng mã bạn có thể bảo vệ ứng xụng khỏi session fixation](http://guides.rubyonrails.org/security.html#session-fixation).
Hãy đọc link trên đây để biết thêm về session fixation. Để ngăn chặn tấn công
bằng session fixation, trong action cho phép người dùng đăng nhập, gọi method
session.clear().
@GET("login") class LoginAction extends Action { def execute() { ... session.clear() // Reset first before doing anything else with the session session("userId") = userId } }
Để thực hiện đăng xuất, cũng gọi method
session.clear().
#### SessionVar
[¶](#sessionvar)
SessionVar, giống như RequestVar, là một cách làm cho session typesafe hơn.
Lấy một ví dụ, bạn muốn lưu trữ username vào session sau khi thực hiện đăng nhập:
Khai báo session var:
import xitrum.SessionVar object SVar { object username extends SessionVar[String] }
Sau khi đăng nhập thành công:
SVar.username.set(username)
Hiển thị username:
if (SVar.username.isDefined) <em>{SVar.username.get}</em> else <a href={url[LoginAction]}>Login</a>
- Để xóa session var:
SVar.username.remove()
- Để reset toàn bộ session:
session.clear()
#### Lưu trữ session
[¶](#luu-tru-session)
Xitrum cung cấp 3 cách lưu trữ session.
Trong tệp
[config/xitrum.conf](https://github.com/xitrum-framework/xitrum-new/blob/master/config/xitrum.conf)
bạn có thể chọn các lưu trữ bạn muốn:
CookieSessionStore:
# Store sessions on client side store = xitrum.scope.session.CookieSessionStore
LruSessionStore:
# Simple in-memory server side session store store { "xitrum.local.LruSessionStore" { maxElems = 10000 } }
Nếu bạn chạy một cụm nhiều máy chr, bạn có thể
[sử dụng Hazelcast để lưu trữ cluster-aware session](https://github.com/xitrum-framework/xitrum-hazelcast),
Lưu ý rằng khi bạn sử dụng CookieSessionStore hoặc Hazelcast, dữ liệu trong session phải được serializable. Nếu bạn phải lưu trữ những thứ unserializable, sử dụng LruSessionStore. Nếu bạn sử dụng LruSessionStore và vẫn muốn chạy một cụm nhiều máy chủ, bạn phải sử dụng load balancer có hỗ trợ sticky sessions.
3 cách lưu trữ session trên đây đủ sử dụng trong các trường hợp thông thường.
Nếu bạn có một trường hợp đặc biệt và muốn sử dụng cách lưu trữ session riêng,
kế thừa
[SessionStore](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/scope/session/SessionStore.scala)
hoặc
[ServerSessionStore](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/scope/session/ServerSessionStore.scala)
và implement các abstract method.
Việc cấu hình có thể sử dụng một trong 2 cách:
store = my.session.StoreClassName
Hoặc:
store { "my.session.StoreClassName" { option1 = value1 option2 = value2 } }
Lưu trữ session ở cookie của client bất cứ khi nào có thể (serializable và
[nhỏ hơn 4KB dữ liệu](http://stackoverflow.com/questions/640938/what-is-the-maximum-size-of-a-web-browsers-cookies-key)), it’s more scalable.
Lưu trữ session ở phía server (trong bộ nhớ hoặc Database) chỉ khi cần thiết.
Good read:
[Web Based Session Management - Best practices in managing HTTP-based client sessions](http://www.technicalinfo.net/papers/WebBasedSessionManagement.html).
#### Lưu trữ Session ở Client hay Server
[¶](#luu-tru-session-o-client-hay-server)
Có 2 hình thức lưu trữ session:
- Chỉ ở phía client
- Kết hợp cả 2 : client và server
Với chỉ lưu trữ ở client:
- Dữ liệu trong session được lưu trữ trong cookie mã hóa ở phía client.
- Phía server không cần phải lưu trữ bất cứ thứ gì.
- Khi có một request truyền tới, server sẽ tiến hành giải mã dữ liệu.
Kết hợp cả 2 : client và server:
- Một session có 2 phần: session ID và session data.
- Server lưu trữ dữ liệu trong session, theo cặp ID -> data
- ID cũng được lưu trữ trong cookie đã được mã hóa ở client.
- Khi có một request truyền tới, server sẽ giải mã ID, và sử dụng ID để tìm data
- Các này giống như sử dụng thẻ tín dụng. Số tiền không lưu trong thẻ tín dụng mà
ở ID
Trong cả 2 cách, client phải lưu trữ một vài thứ như cookie (dữ liệu được mã hóa và ID được mã hóa). “Lưu trữ session ở server” có nghĩa là lưu trữ dữ liệu của session ở phía server.
### object vs. val
[¶](#object-vs-val)
Sử dụng
object thay vì
val.
**Không làm như sau**:
object RVar { val title = new RequestVar[String] val category = new RequestVar[String] } object SVar { val username = new SessionVar[String] val isAdmin = new SessionVar[Boolean] }
Đoạn code trên là đúng cú pháp và sẽ được biên dịch nhưng không chạy, bởi vì các
Var bản thân chúng sử dụng class nameđể tìm kiếm. Khi sử dụng
val,
title
và
category sẽ có chung class name “xitrum.RequestVar”. Tương tự với
username
và
isAdmin.
## Validation
[¶](#validation)
Xitrum sử dụng
[jQuery Validation plugin](http://bassistance.de/jquery-plugins/jquery-plugin-validation/)
vào mục đích validation ở phía client và cung cấp các validation helper cho phía server.
### Validator mặc định
[¶](#validator-mac-dinh)
Xitrum cung cấp validator trong package
xitrum.validator.
Chúng có những method sau:
check(value): Boolean message(name, value): Option[String] exception(name, value)
Nếu validation báo lỗi,
message sẽ trả về
Some(error message),
exception sẽ throw
xitrum.exception.InvalidInput(error message).
Bạn có thể sử dụng validator bất cứ đâu.
Ví dụ action:
import xitrum.validator.Required @POST("articles") class CreateArticle { def execute() { val title = param("tite") val body = param("body") Required.exception("Title", title) Required.exception("Body", body) // Do with the valid title and body... } }
Nếu không sử dụng
try và
catch, khi có lỗi trong quá trình validation
(not pass), Xitrum sẽ tự động
catch các exception và respond thông báo lỗi
về phía client. Điều này giúp cho việc viết các web API hoặc sử dụng validation
ở phía client dễ dàng hơn.
Model example:
import xitrum.validator.Required case class Article(id: Int = 0, title: String = "", body: String = "") { def isValid = Required.check(title) && Required.check(body) def validationMessage = Required.message(title) orElse Required.message(body) }
Xem
[package xitrum.validator](https://github.com/xitrum-framework/xitrum/tree/master/src/main/scala/xitrum/validator) để có đầy đủ các validator mặc định.
### Tạo một validator
[¶](#tao-mot-validator)
Kế thừa
[xitrum.validator.Validator](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/validator/Validator.scala).
Bạn chỉ phải implement 2 method
check và
message.
Bạn cũng có thể sử dụng
[Commons Validator](http://commons.apache.org/proper/commons-validator/).
## Tải lên tệp
[¶](#tai-len-tep)
Xem thêm
[. Scopes chapter](index.html#document-scopes)
Trong form tải lên (upload form), bạn cần đặt
enctype thành
multipart/form-data.
MyUpload.scalate:
form(method="post" action={url[MyUpload]} enctype="multipart/form-data") != antiCsrfInput label Please select a file: input(type="file" name="myFile") button(type="submit") Upload
Trong
MyUpload action:
import io.netty.handler.codec.http.multipart.FileUpload val myFile = param[FileUpload]("myFile")
myFile là một instance của
[FileUpload](http://netty.io/4.0/api/io/netty/handler/codec/http/multipart/FileUpload.html).
Sử dụng các method của chúng để lấy tên tệp, di chuyển tệp vào một thư mục v.v.
Các tệp nhỏ (nhỏ hơn 16 KB) sẽ được lưu trong bộ nhớ. Các tệp lớn thường được lưu
trong hệ thống thư mục lưu trữ tạm (hoặc một thư mục xác định bởi
xitrum.request.tmpUploadDir
trong xitrum.conf), và sẽ được xóa tự động khi đóng kết nối hoặc một respond được
gửi đi.
### Ajax style upload
[¶](#ajax-style-upload)
Có rất nhiều thư viện JavaScript hỗ trợ tải lên Ajax style. Chúng sử dụng iframe
ẩn hoặc Flash để gửi
multipart/form-data ở bên trên đến server.
Nếu bạn không chắc chắn parameter nào của request trong thư viện sử dụng trong form
để gửi tệp, hãy xem Xitrum access log.
## Bộ lọc (filter) trong Action
[¶](#bo-loc-filter-trong-action)
### Before filters
[¶](#before-filters)
Before filters chạy trước khi action chạy. Nếu một before filter respond bất kì thứ gì, tất cả các filter sau đó và cả action sẽ không chạy.
import xitrum.Action import xitrum.annotation.GET @GET("before_filter") class MyAction extends Action { beforeFilter { log.info("I run therefore I am") } // Method này chạy sau filter bên trên def execute() { respondInlineView("Before filters should have been run, please check the log") } }
### After filters
[¶](#after-filters)
Before filters chạy sau khi action chạy. Chúng là các hàm (function) không tham số. Các giá trị trả về của các hàm này sẽ bị từ chối.
import xitrum.Action import xitrum.annotation.GET @GET("after_filter") class MyAction extends Action { afterFilter { log.info("Run at " + System.currentTimeMillis()) } def execute() { respondText("After filter should have been run, please check the log") } }
### Around filters
[¶](#around-filters)
import xitrum.Action import xitrum.annotation.GET @GET("around_filter") class MyAction extends Action { aroundFilter { action => val begin = System.currentTimeMillis() action() val end = System.currentTimeMillis() val dt = end - begin log.info(s"The action took $dt [ms]") } def execute() { respondText("Around filter should have been run, please check the log") } }
Nếu có nhiều around filter, chúng sẽ lồng nhau.
### Thứ tự thực hiện của các bộ lọc (filter)
[¶](#thu-tu-thuc-hien-cua-cac-bo-loc-filter)
- Before filters được chạy đầu tiên, sau đó là around filter, cuối cùng là after filter.
- Néu một trong nhưng before filter trả về false, các filter con lại ( bao gồm around và after filter) sẽ không được chạy.
- After filters luôn được chạy nếu ít nhát có một around filter được chạy.
- Nếu một around filter không gọi
action, các around filter lồng bên trong filter này sẽ không được chạy.
before1 -true-> before2 -true-> +--------------------+ --> after1 --> after2 | around1 (1 of 2) | | around2 (1 of 2) | | action | | around2 (2 of 2) | | around1 (2 of 2) | +--------------------+
## Cache ở server
[¶](#cache-o-server)
Cũng có thể xem phần nói về
[. clustering](index.html#document-cluster)
Tối ưu hóa cache cả ở phía máy chủ (server) và máy khách (client) để tăng tốc độ đáp ứng.
Ở tầng máy chủ web, các tập tin nhỏ được cache vào bộ nhớ, đối với các tập tin lớn thì sử dụng kỹ thuật
zero copy của NIO. . Các tệp tĩnh trong xitrum được
cung cấp với tốc độ
[tương đương với Nginx](https://gist.github.com/3293596).
Tại lớp web framework, bạn có thể khai báo cache ở mức page, action và object với
phong cách [Rails framework](https://github.com/rails/rails). [Tất cả thủ thuật mà Google khuyên nên dùng để tăng tốc trang web](http://code.google.com/speed/page-speed/docs/rules_intro.html)
như method GET có điều kiện được áp dụng để cache phía client.
Với các nội dung động (dynamic content), nếu content không đổi sau khi được tạo
(như một tệp tĩnh), bạn có thể cần đặt header để được lưu trữ một cách chủ động
ở phía client. Trong trường hợp này, sử dụng
setClientCacheAggressively() trong
Action.
Ngược lại, đôi khi bạn có thể không muốn cache ở phía client, bạn sử dụng method
setNoClientCache() trong action.
Cache ở phía server sẽ được trình bày chi tiết dưới dây.
### Cache ở mức page hoặc action
[¶](#cache-o-muc-page-hoac-action)
import xitrum.Action import xitrum.annotation.{GET, CacheActionMinute, CachePageMinute} @GET("articles") @CachePageMinute(1) class ArticlesIndex extends Action { def execute() { ... } } @GET("articles/:id") @CacheActionMinute(1) class ArticlesShow extends Action { def execute() { ... } }
Thuật ngữ “page cache” và “action cache” bắt nguồn từ
[Ruby on Rails](http://guides.rubyonrails.org/caching_with_rails.html).
Thứ tự thực thi một request được thiết kế như sa: (1) request -> (2) các method before filter -> (3) các method thực thi action -> (4) response
Ở request đầu tiên, Xitrum sẽ cache response trong một thời gian sống xác đinh.
@CachePageMinute(1) hoặc
@CacheActionMinute(1) đều có nghĩa là cache
trong 1 phút.
Xitrum chỉ cache khi response có trạng thái “200 OK”. Ví dụ, response với trạng
thái “500 Internal Server Error” hoặc “302 Found” (direct) sẽ không được cache.
Ở các request sau đến cùng một action, nếu response đã được cache vẫn nằm trong thời gian sống xác định bên trên, Xitrum sẽ chỉ respond chính response đã được cache.
- Với page cache, thứ tự thực hiện là (1) -> (4).
- Với action cache, thứ tự thực hiện là (1) -> (2) -> (4), hoặc chỉ là (1) -> (2) nếu một trong những before filter trả về “false”.
Sự khác biệt giữa 2 loại cache: với page cache, các before filter sẽ không chạy.
Thông tường, page cache thường được sử dụng khi các response giống nhau được gửi đến tất cả người dùng. Action cache được sử dụng khi bạn muốn chạy một before filter để “guard” (bảo vệ) response đã được cache, giống như việc kiểm ra người dùng đã đăng nhập hay chưa:
- Nếu người dùng đã đăng nhập, họ có thể sử dụng response đã được cache.
- Nếu người dùng chưa thực hiện đăng nhập, redirect họ đến trang đăng nhập.
### Cache ở mức object
[¶](#cache-o-muc-object)
Bạn sử dụng method trong
xitrum.Config.xitrum.cache, nó là một instance của
[xitrum.Cache](http://xitrum-framework.github.io/api/3.17/index.html#xitrum.Cache).
Không có một TTL(time to live - thời gian sống) rõ rõ ràng:
- put(key, value)
Với một TTL(time to live - thời gian sống) rõ rõ ràng:
- putSecond(key, value, seconds)
- putMinute(key, value, minutes)
- putHour(key, value, hours)
- putDay(key, value, days)
Only if absent:
- putIfAbsent(key, value)
- putIfAbsentSecond(key, value, seconds)
- putIfAbsentMinute(key, value, minutes)
- putIfAbsentHour(key, value, hours)
- putIfAbsentDay(key, value, days)
### Xóa cache
[¶](#xoa-cache)
Xóa “page cache” và “action cache”:
removeAction[MyAction]
Xóa “object cache”:
remove(key)
Xóa tất cả các khóa bắt đầu với một prefix:
removePrefix(keyPrefix)
Với
removePrefix, bạn có thể kế thừa form cache trong prefix.
Ví dụ bạn muốn cache những thứ liên quan đến một article, sau khi article thay đổi,
bạn muốn xóa tất cả những thứ đó.
import xitrum.Config.xitrum.cache // Cache với một prefix val prefix = "articles/" + article.id cache.put(prefix + "/likes", likes) cache.put(prefix + "/comments", comments) // Sau đó, khi xảy ra 1 sự kiện nào đó, và bạn muốn xóa tất cả các cache liên //quan đến artical cache.remove(prefix)
### Config
[¶](#config)
Tính năng cache trong Xitrum được cung cấp bởi các cache engine. Bạn có thể chọn engine phù hợp với yếu cầu của bạn.
Trong
[config/xitrum.conf](https://github.com/xitrum-framework/xitrum-new/blob/master/config/xitrum.conf),
bạn có thể cấu hình cache engine tại 1 trong 2 form sau, phụ thuộc vào engine bạn
chọn:
cache = my.cache.EngineClassName
Or:
cache { "my.cache.EngineClassName" { option1 = value1 option2 = value2 } }
Xitrum cung cấp:
cache { # Simple in-memory cache "xitrum.local.LruCache" { maxElems = 10000 } }
Nếu bạn có một cụm máy chủ, bạn có thể sử dụng
[Hazelcast](https://github.com/xitrum-framework/xitrum-hazelcast).
Nếu bạn muốn tạo cache engine cho riêng bạn, implement
[interface](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/Cache.scala)
xitrum.Cache.
### Cache hoạt động như thế nào
[¶](#cache-hoat-dong-nhu-the-nao)
Inbound:
action response nên được cache và cache đã tồn tại request trước đó? -------------------------+---------------NO---------------> | <---------YES------------+ respond từ cache
Outbound:
action response nên được cache và cache chưa tồn tại trước đó? response <---------NO-------------+--------------------------------- | <---------YES------------+ lưu response vào cache
### xitrum.util.LocalLruCache
[¶](#xitrum-util-locallrucache)
Cache trên đây là cache chia sẻ bởi toàn bộ hệ thống. Nếu bạn muốn cache ở trong
một phạm vi nhỏ, bạn có thể sử dụng
xitrum.util.LocalLruCache.
import xitrum.util.LocalLruCache // LRU (Least Recently Used) cache that can contain 1000 elements. // Keys and values are both of type String. val cache = LocalLruCache[String, String](1000)
cache đã được trả về là một
[java.util.LinkedHashMap](http://docs.oracle.com/javase/6/docs/api/java/util/LinkedHashMap.html).
Bạn có thể sử dụng method
LinkedHashMap từ nó.
## I18n
[¶](#i18n)
Phong cách GNU gettext được sử dụng. Không giống như các i18n method khác, gettext hỗ trợ đa số các form.
![ _images/poedit.png](_images/poedit.png)
### Viết các internationalized messages vào source code
[¶](#viet-cac-internationalized-messages-vao-source-code)
xitrum.Action kế thừa
xitrum.I18n, và có 2 method sau:
t("Message") tc("Context", "Message")
Bạn có thể gọi trực tiếp 2 method trên từ trong action.
Tại một nơi khác như model, bạn cần truyền current action vào đó và gọi
t và
tc.
// In an action respondText(MyModel.hello(this)) // In the model import xitrum.I18n object MyModel { def hello(i18n: I18n) = i18n.t("Hello World") }
### Triển khai các tin nhắn đến tệp pot
[¶](#trien-khai-cac-tin-nhan-den-tep-pot)
Tạo một tệp i18n.pot trong thư mục gốc của project, sau đó biên dịch lại cả project.
sbt/sbt clean rm i18n.pot touch i18n.pot sbt/sbt compile
sbt/sbt clean dùng để xóa tất cả các tệp .class , bắt SBT biên dịch lại cả
project. Vì sau
sbt/sbt clean, SBT sẽ thử tải lại toàn bộ
[,
bạn có thể tiến hành nhanh hơn một chút với lệnh dependencies](index.html#document-deps)
find target -name *.class -delete, nó sẽ xóa toàn bộ các tệp . class trong thư mục
target.
Sau khi biên dịch lại, i18n.pot sẽ được lấp đầy với các gettext message triển khai
từ mã nguồn. Để làm điều này,
[Scala compiler plugin technique](http://www.scala-lang.org/node/140)
được sử dụng.
Tuy nhiên, phương pháp này sẽ chỉ trích rút dữ liệu từ mã nguồn. Nếu bạn có các
tệp Java, bạn có thể sử dụng câu lệnh
xgettext để trích xuất dữ liệu:
xgettext -kt -ktc:1c,2 -ktn:1,2 -ktcn:1c,2,3 -o i18n_java.pot --from-code=UTF-8 $(find src/main/java -name "*.java")
Sau đó bạn gộp tệp i18n_java.pot và tệp i18n.pot.
### Lưu các tệp .po tại đâu
[¶](#luu-cac-tep-po-tai-dau)
i18n.pot là một tệp bản mẫu. Bạn cần sao chép nó đến tệp <language>.po và dịch.
Xitrum theo dõi thư mực có tên
i18n trong classpath.
Nếu một tệp <language>.po trong thư mục đó được thay đổi hoặc được thêm vào ở runtime,
Xitrum sẽ tự động tải lại tệp <language>.po đó.
src main scala view resources i18n ja.po vi.po ...
Sử dụng công cụ như
[Poedit](http://www.poedit.net/) để edit các tệp .po.
Bạn cũng có thể sử dụng nó để hợp các tệp pot mới vào tệp po cũ. ![ _images/update_from_pot.png](_images/update_from_pot.png)
Bạn có thể đóng gói các tệp .po trong nhiều tệp JAR. Xitrum sẽ tự động gộp chúng khi chạy.
mylib.jar i18n ja.po vi.po ... another.jar i18n ja.po vi.po ...
### Chọn ngôn ngữ
[¶](#chon-ngon-ngu)
- Để lấy cấc ngôn ngữ trong
Accept-Languagerequest header bởi browser, gọi
browserLanguages. Kết quả sẽ được sắp xếp theo mức ưu tiên đặt bởi trình duyệt từ cao xuống thấp.
- Ngôn ngữ mặc định là “en”. Để chuyển ngôn ngữ, ví dụ Nhật Bản, gọi
language = "ja"
- Để tự đặt ngôn ngữ phù hợp nhất trong resource, gọi
autosetLanguage(resourceLanguages), với
resourceLanguageslà một list các ngôn ngữ có trong thư mục
resources/i18nvà các tệp JAR. Nếu không có ngôn ngữ nào phù hợp, ngôn ngữ vẫn mặc định là “en”.
- Để lấy ngôn ngữ hiện thời được đặt bên trên, sử dụng
language.
Trong action, thông thường trong một before filter, để đặt language:
beforeFilter { val lango: Option[String] = yourMethodToGetUserPreferenceLanguageInSession() lango match { case None => autosetLanguage("ja", "vi") case Some(lang) => language = lang } }
### Validation messages
[¶](#validation-messages)
jQuery Validation plugin cung cấp
[i18n error messages](https://github.com/jzaefferer/jquery-validation/tree/master/src/localization).
Xitrum tự động thêm các tệp message tương ứng vào ngôn ngữ hiện thời.
Với validator mặc định ở phía server trong package
xitrum.validator, Xitrum cũng
cung cấp bản dịch tương ứng.
### Với đa số form
[¶](#voi-da-so-form)
tn("Message", "Plural form", n) tcn("Context", "Message", "Plural form", n)
Xitrum chỉ có thể chạy đúng với đa số form sau:
Phần lớn các form thường nằm trong số sau:
nplurals=1; plural=0 nplurals=2; plural=n != 1 nplurals=2; plural=n>1 nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2 nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2 nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2 nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2 nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2 nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2 nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2 nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3
### Định dạng ngày và số
[¶](#dinh-dang-ngay-va-so)
Nếu bạn sử dụng Scalate template engine, mặc định ngày và số sẽ được định dạng theo ngôn ngữ hiện thời.
Nếu bạn muốn sử dụng định dạng khác:
import java.text.{DateFormat, NumberFormat} val myDateFormat = ... val myNumberFormat = ... val options = Map("date" -> myDateFormat, "number" -> myNumberFormat) respondView(options)
## Log
[¶](#log)
### Sử dụng trực tiếp đối tượng xitrum.Log
[¶](#su-dung-truc-tiep-doi-tuong-xitrum-log)
Từ bất kỳ đâu, bạn có thể gọi một cách trực tiếp như sau:
xitrum.Log.debug("My debug msg") xitrum.Log.info("My info msg") ...
### Sử dụng trait xitrum.Log
[¶](#su-dung-trait-xitrum-log)
Nếu bạn muốn biết log tạo bởi class nào, bạn nên kế thừa trait xitrum.Log:
package my_package import xitrum.Log object MyModel extends Log { log.debug("My debug msg") log.info("My info msg") ... }
Trong tệp log/xitrum.log bạn sẽ thấy log message đến từ
MyModel.
Xitrum action kế thừa trait xitrum.Log, vì thế trong action, bạn có thể viết:
log.debug("Hello World")
### Không phải kiểm tra log level trước khi log
[¶](#khong-phai-kiem-tra-log-level-truoc-khi-log)
xitrum.Log dựa trên
[SLF4S](http://slf4s.org/) ( [API](http://slf4s.org/api/1.7.7/)),
SLFS4 lại được xây dựng trên
[SLF4J](http://www.slf4j.org/).
Thông thường, trước khi thực thi một phép tính lớn để log result, bạn phải kiểm tra log level để hạn chế lãng phí CPU cho phép tính.
[SLF4S tự động thực hiện việc kiểm tra](https://github.com/mattroberts297/slf4s/blob/master/src/main/scala/org/slf4s/Logger.scala),
do đó bạn không cần phải tự kiểm tra.
Trước đó (đoạn mã này không còn chạy với bản Xitrum hiện tại 3.13+):
if (log.isTraceEnabled) { val result = heavyCalculation() log.trace("Output: {}", result) }
Hiện tại:
log.trace(s"Output: #{heavyCalculation()}")
### Cấu hình log level
[¶](#cau-hinh-log-level)
Trong tệp build.sbt, có một dòng như sau:
libraryDependencies += "ch.qos.logback" % "logback-classic" % "1.1.2"
Dòng này có nghĩa rằng : mặc định
[Logback](http://logback.qos.ch/) được sử dụng.
Tệp cấu hình Logback nằm tại
config/logback.xml.
Bạn có thể thay thê Logback bằng bất kì implementation nào khác của
[SLF4J](http://www.slf4j.org/).
### Log vào Fluentd
[¶](#log-vao-fluentd) [Fluentd](http://www.fluentd.org/) là một log collector phổ biến. Bạn có thể
cấu hình Logback để gửi log (từ nhiều nơi) đến một Fluentd server.
Đầu tiên, thêm thư viện
[logback-more-appenders](https://github.com/sndyuk/logback-more-appenders)
vào trong project:
libraryDependencies += "org.fluentd" % "fluent-logger" % "0.2.11" resolvers += "Logback more appenders" at "http://sndyuk.github.com/maven" libraryDependencies += "com.sndyuk" % "logback-more-appenders" % "1.1.0"
Sau đó trong tệp
config/logback.xml:
... <appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender"> <tag>mytag</tag> <label>mylabel</label> <remoteHost>localhost</remoteHost> <port>24224</port> <maxQueueSize>20000</maxQueueSize> <!-- Save to memory when remote server is down --> </appender> <root level="DEBUG"> <appender-ref ref="FLUENT"/> <appender-ref ref="OTHER_APPENDER"/> </root> ...
## Triển khai ứng dụng web trên server
[¶](#trien-khai-ung-dung-web-tren-server)
Bạn có thể chạy trực tiếp Xitrum:
Browser ------ Xitrum instance
Hoăc behind a load balancer như HAProxy, hoặc reverse proxy như Apache hay Nginx:
Browser ------ Load balancer/Reverse proxy -+---- Xitrum instance1 +---- Xitrum instance2
### Đóng gí thư mục
[¶](#dong-gi-thu-muc)
Chạy
sbt/sbt xitrum-package để chuẩn bị cho thư mục
target/xitrum sẵn sàng
triển khai tại server sản phẩm:
target/xitrum config [config files] public [static public files] lib [dependencies and packaged project file] script runner runner.bat scalive scalive.jar scalive.bat
### Customize xitrum-package
[¶](#customize-xitrum-package)
Mặc định câu lệnh
sbt/sbt xitrum-package được cấu hình để sao chép các thư mục
config,
public, và
script đến
target/xitrum. Nếu bạn muốn câu lệnh
đó sao chép các thư mục hoặc tệp khác sửa tệp
build.sbt như sau:
XitrumPackage.copy("config", "public, "script", "doc/README.txt", "etc.")
Xem
[xitrum-package homepage](https://github.com/xitrum-framework/xitrum-package)
để biết thêm chi tiết.
### Kết nối Scala console đến một tiến trình JVM đang chạy
[¶](#ket-noi-scala-console-den-mot-tien-trinh-jvm-dang-chay)
Trong môi trường sản phẩm (production environment), nếu không có khởi tạo, bạn có
thể sử dụng
[Scalive](https://github.com/xitrum-framework/scalive)
để kết nối một Scala console đến một tiến trình JVM đang chạy để gỡ lỗi trực tiếp.
Chạy
scalive trong thư mục script:
script runner runner.bat scalive scalive.jar scalive.bat
### Cài đặt Oracle JDK trên CentOS hoặc Ubuntu
[¶](#cai-dat-oracle-jdk-tren-centos-hoac-ubuntu)
Dưới đây là hướng dẫn một cách đơn giản để cài đặt Java.Bạn có thể cài đặt Java bằng cách sử dụng trình quản lý gói.
Kiểm tra các phiên bản Java đã được cài đặt:
sudo update-alternatives --list java
Output example:
/usr/lib/jvm/jdk1.7.0_15/bin/java /usr/lib/jvm/jdk1.7.0_25/bin/java
Kiểm tra môi trường (32 bit hay 64 bit):
file /sbin/init
Output example:
/sbin/init: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x4efe732752ed9f8cc491de1c8a271eb7f4144a5c, stripped
Tải JDK từ
[Oracle](http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html).
Đây là một [thủ thuật](http://stackoverflow.com/questions/10268583/how-to-automate-download-and-instalation-of-java-jdk-on-linux)
để tải jdk mà không dùng trình duyệt:
wget --no-cookies --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com" "http://download.oracle.com/otn-pub/java/jdk/7u45-b18/jdk-7u45-linux-x64.tar.gz"
Giải nén và di chuyển thư mục
tar -xzvf jdk-7u45-linux-x64.tar.gz sudo mv jdk1.7.0_45 /usr/lib/jvm/jdk1.7.0_45
Cài đặt java:
sudo update-alternatives --install "/usr/bin/java" "java" "/usr/lib/jvm/jdk1.7.0_45/bin/java" 1 sudo update-alternatives --install "/usr/bin/javac" "javac" "/usr/lib/jvm/jdk1.7.0_45/bin/javac" 1 sudo update-alternatives --install "/usr/bin/javap" "javap" "/usr/lib/jvm/jdk1.7.0_45/bin/javap" 1 sudo update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/lib/jvm/jdk1.7.0_45/bin/javaws" 1
Chọn đường dẫn đến phiên bản Java
sudo update-alternatives --config java
Output example:
There are 3 choices for the alternative java (providing /usr/bin/java). Selection Path Priority Status ------------------------------------------------------------ * 0 /usr/lib/jvm/jdk1.7.0_25/bin/java 50001 auto mode 1 /usr/lib/jvm/jdk1.7.0_15/bin/java 50000 manual mode 2 /usr/lib/jvm/jdk1.7.0_25/bin/java 50001 manual mode 3 /usr/lib/jvm/jdk1.7.0_45/bin/java 1 manual mode Press enter to keep the current choice[*], or type selection number: 3 update-alternatives: using /usr/lib/jvm/jdk1.7.0_45/bin/java to provide /usr/bin/java (java) in manual mode
Kiểm tra phiên bản Java:
java -version
Output example:
java version "1.7.0_45" Java(TM) SE Runtime Environment (build 1.7.0_45-b18) Java HotSpot(TM) 64-Bit Server VM (build 24.45-b08, mixed mode)
Tương tự với javac, javap, javaws:
sudo update-alternatives --config javac sudo update-alternatives --config javap sudo update-alternatives --config javaws
### Chạy Xitrum ở chế độ sản phẩm khi hệ thống khởi động
[¶](#chay-xitrum-o-che-do-san-pham-khi-he-thong-khoi-dong)
script/runner (cho các hệ thông Unix-like) và
script/runner.bat (cho Windows) là các đoạn script
để chạy bất cứ đối tượng nào có method
main. Sử dụng chúng để khởi động web server trong môi trường
sản phẩm.
script/runner quickstart.Boot
Bạn có thể sửa
runner (hoặc
runner.bat) để chỉnh
[JVM settings](http://www.oracle.com/technetwork/java/hotspotfaq-138619.html).
Xem thêm
config/xitrum.conf.
Để khởi động Xitrum ẩn trên Linux khi khởi động hệ thống, cách đơn giản là thêm dòng
sau vào
/etc/rc.local:
su - user_foo_bar -c /path/to/the/runner/script/above &
[daemontools](http://cr.yp.to/daemontools.html) là một giải pháp khác, để cài đặt trên Centos
xem [hướng dẫn](http://whomwah.com/2008/11/04/installing-daemontools-on-centos5-x86_64/).
Hoặc sử dụng
[Supervisord](http://supervisord.org/).
Ví dụ
/etc/supervisord.conf:
[program:my_app] directory=/path/to/my_app command=/path/to/my_app/script/runner quickstart.Boot autostart=true autorestart=true startsecs=3 user=my_user redirect_stderr=true stdout_logfile=/path/to/my_app/log/stdout.log stdout_logfile_maxbytes=10MB stdout_logfile_backups=7 stdout_capture_maxbytes=1MB stdout_events_enabled=false environment=PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/opt/aws/bin:~/bin
Các giải pháp khác:
### Thiết lập cổng chuyển tiếp
[¶](#thiet-lap-cong-chuyen-tiep)
Xitrum mặc định giao tiếp trên cổng 8000 và 4430.
Bạn có thể đổi cổng trong
config/xitrum.conf.
Bạn có thể thay đổi
/etc/sysconfig/iptables với các lệnh sau để chuyển tiếp cổng
80 sang 8000 và 443 sang 4430:
sudo su - root chmod 700 /etc/sysconfig/iptables iptables-restore < /etc/sysconfig/iptables iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8000 iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 4430 iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 80 -j REDIRECT --to-ports 8000 iptables -t nat -I OUTPUT -p tcp -d 127.0.0.1 --dport 443 -j REDIRECT --to-ports 4430 iptables-save -c > /etc/sysconfig/iptables chmod 644 /etc/sysconfig/iptables
Tất nhiên nếu Apache sử dụng cổng 80 và 443, bạn sẽ cần phải dùng Apache:
sudo /etc/init.d/httpd stop sudo chkconfig httpd off
Tham khao:
### Cấu hình Linux để kết nối hàng loạt
[¶](#cau-hinh-linux-de-ket-noi-hang-loat)
Nhớ rằng trên MacOS,
[JDK có vấn đề nghiêm trọng với tốc độ IO (NIO)](https://groups.google.com/forum/#!topic/spray-user/S-SNR2m0BWU).
Tham khảo:
#### Tăng số lượng các tệp được mở
[¶](#tang-so-luong-cac-tep-duoc-mo)
Mỗi connection với Linux là một tệp được mở.
Mặc định số lượng tối đa các tệp được mở là 1024.
Để tăng giới hạn, sửa tệp
/etc/security/limits.conf:
* soft nofile 1024000 * hard nofile 1024000
Bạn cần đăng xuất và đăng nhập lại hệ thống để kết thúc việc sửa đổi.
Để xác nhận chạy
ulimit -n.
#### Điều chỉnh kernel
[¶](#dieu-chinh-kernel)
Như được dẫn trong
[A Million-user Comet Application with Mochiweb](http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-1),
sửa tệp /etc/sysctl.conf:
# General gigabit tuning net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216 # This gives the kernel more memory for TCP # which you need with many (100k+) open socket connections net.ipv4.tcp_mem = 50576 64768 98152 # Backlog net.core.netdev_max_backlog = 2048 net.core.somaxconn = 1024 net.ipv4.tcp_max_syn_backlog = 2048 net.ipv4.tcp_syncookies = 1 # If you run clients net.ipv4.ip_local_port_range = 1024 65535 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 10
Chạy
sudo sysctl -p để áp dụng các thay đổi.
Không cần khởi động lại hệ thống, kernel đã có khả năng xử lý nhiều kết nối hơn.
#### Lưu ý về backlog
[¶](#luu-y-ve-backlog)
TCP thực hiện bắt tay 3 bước để thiết lập kết nối. Khi một client từ xa kết nối đến máy chủ, client sẽ gửi một gói tin SYN. Và hệ điều hành của phía máy chủ sẽ gửi lại các gói tin SYN-ACK. Sau đó, khách hàng từ xa thiết lập một kết nối bằng cách gửi một gói tin ACK lại. Xitrum sẽ nhận được nó khi kết nối được thiết lập đầy đủ.
Theo như
[Socket backlog tuning for Apache](https://sites.google.com/site/beingroot/articles/apache/socket-backlog-tuning-for-apache),
connection timeout xảy ra khi gói tin SYN bị mất bởi backlog queue của web server bị
lấp đầy bởi các kết nối gửi SYN-ACK đến các client chậm.
Theo như
[FreeBSD Handbook](http://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/configtuning-kernel-limits.html),
giá trị mặc định của là 128 thường quá thấp để xử lý các kết nối mới trong một server
có tải lớn. Đối với các máy chủ như vậy, nên tăng giá trị này thành 1024 hoặc hơn.
Listen queue lớn hơn cũng là cách tốt để chống lại việc tấn công từ chối dịch vụ (Denial of Service - DoS)
Backlog size của Xitrum được đặt thành 1024 (memcached cũng dùng giá trị này), nhưng bạn cũng cần chỉnh kernel như trên. The backlog size of Xitrum is set to 1024 (memcached also uses this value),
Kiểm tra cấu hình backlog:
cat /proc/sys/net/core/somaxconn
hoặc:
sysctl net.core.somaxconn
Để điều chỉnh tạm thời, bạn có thể làm như sau:
sudo sysctl -w net.core.somaxconn=1024
### HAProxy tip
[¶](#haproxy-tip)
Để cấu hình HAProxy cho SockJS, xem
[ví dụ](https://github.com/sockjs/sockjs-node/blob/master/examples/haproxy.cfg):
defaults mode http timeout connect 10s timeout client 10h # Set to long time to avoid WebSocket connections being closed when there's no network activity timeout server 10h # Set to long time to avoid ERR_INCOMPLETE_CHUNKED_ENCODING on Chrome frontend xitrum_with_discourse bind 0.0.0.0:80 option forwardfor acl is_discourse path_beg /forum use_backend discourse if is_discourse default_backend xitrum backend xitrum server srv_xitrum 127.0.0.1:8000 backend discourse server srv_discourse 127.0.0.1:3000
Để HAProxy tải lại tệp cấu hình mà không cần khởi động lại, xem
[cuộc thảo luận](http://serverfault.com/questions/165883/is-there-a-way-to-add-more-backend-server-to-haproxy-without-restarting-haproxy).
HAProxy thì dễ sử dụng hơn Nginx. Nó phù hợp với Xitrum bởi như được đề cập đến trong
[, Các tệp tĩnh trong Xitrum thì
the section about caching](index.html#document-cache) [very fast](https://gist.github.com/3293596). Bạn không cần sử dụng các tệp tĩnh để phục vụ các tĩnh năng của Nginx.
### Nginx tip
[¶](#nginx-tip)
Nếu bạn sửu dụng tính năng WebSocket hoặc SockJS trong Xitrum và muốn chạy Xitrum ẩn sau
Nginx 1.2, bạn phải cài đặt thêm module như
[nginx_tcp_proxy_module](https://github.com/yaoweibin/nginx_tcp_proxy_module).
Nginx 1.3+ hỗ trợ WebSocket.
Mặc định Nginx sử dụng giao thức HTTP 1.0 để reverse proxy. Nếu backend server trả về chunked response, bạn cần báo Nginx sử dụng HTTP 1.1 như sau:
location / { proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass http://127.0.0.1:8000; }
[Tài liệu này](http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive) chỉ ra rằng để http keepalive, bạn cũng
nên đặt proxy_set_header Connection “”;
### Triển khai trên Heroku
[¶](#trien-khai-tren-heroku)
Bạn cũng có thẻ chạy Xitrum trên
[Heroku](https://www.heroku.com/).
#### Đăng ký và tạo repository
[¶](#dang-ky-va-tao-repository)
Làm theo
[Official Document](https://devcenter.heroku.com/articles/quickstart),
để đăng ký và tạo repository.
#### Tạo Procfile
[¶](#tao-procfile)
Tạo Procfile và lưu tại thư mục gốc của project. Heroku đọc tệp này thực thi khi khởi động.
web: target/xitrum/script/runner <YOUR_PACKAGE.YOUR_MAIN_CLASS>
#### Thay đổi thiết lập cổng
[¶](#thay-doi-thiet-lap-cong)
Vì Heroku sử dụng cổng một cách tự động, bạn cần làm như sau:
config/xitrum.conf:
port { http = ${PORT} # https = 4430 # flashSocketPolicy = 8430 # flash_socket_policy.xml will be returned }
Nếu bạn muốn sử dụng SSL, bạn cần
[add on](https://addons.heroku.com/ssl).
#### Xem log level
[¶](#xem-log-level)
config/logback.xml:
<root level="INFO"> <appender-ref ref="CONSOLE"/> </root>
Tail log từ Heroku command:
heroku logs -tail
#### Tạo alias cho
xitrum-package
[¶](#tao-alias-cho-xitrum-package)
Tại thời điểm triển khai, Heroku chạy
sbt/sbt clean compile stage. Vì vậy bạn cần thêm
alias cho
xitrum-package.
build.sbt:
addCommandAlias("stage", ";xitrum-package")
#### Push lên Heroku
[¶](#push-len-heroku)
Quá trình triển khai được nối bởi git push.
git push heroku master
Xem thêm
[Official document cho Scala](https://devcenter.heroku.com/articles/getting-started-with-scala).
## Clustering với Akka và Hazelcast
[¶](#clustering-voi-akka-va-hazelcast)
Xitrum được thiết kế để chạy trong môi trường sản xuất như nhiều instance đằng sau một máy chủ proxy hoặc cân bằng tải:
/ Xitrum instance 1 Load balancer/proxy server ---- Xitrum instance 2 \ Xitrum instance 3
Cache, sessions, và SockJS sessions có thể được be clustered bởi tính năng của
[Akka](http://akka.io/) và [Hazelcast](https://github.com/xitrum-framework/xitrum-hazelcast).
Với Hazelcast, Xitrum trở thành một in-process memory cache server. Bạn không cần sử dụng các máy chủ bổ sung như Memcache.
Xem thêm
config/akka.conf, và đọc
[Akka doc](http://akka.io/docs/) hay
[Hazelcast doc](http://hazelcast.org/documentation/) để biết cách cấu hình
Akka và Hazelcast cluster.
Nhớ rằng: Với session, bạn cũng có thể
lưu trữ ở client bằng cookie /scopes>.
## Netty handler
[¶](#netty-handler)
Chương này sử dụng các kiến thức nâng cao, bạn không cần biết sử dụng Xitrum
một cách thông thường. Để có thể hiểu, bạn cần có kiến thức về
[Netty](http://netty.io/). [Rack](http://en.wikipedia.org/wiki/Rack_(Web_server_interface)),
[WSGI](http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface), và
[PSGI](http://en.wikipedia.org/wiki/PSGI) đều có kiến trúc middleware.
Xitrum dựa trên [Netty](http://netty.io/) nên đều có handlers.
Bạn có thể tạo thêm handler và cấu hình chúng các kênh pipeline của hander
You can create additional handlers and customize the channel pipeline. Việc
làm này, bạn có thể tối ưu hiệu suất server cho một số use case cụ thể.
Chương này trình bày về:
- Kiến trúc của Netty handler
- Handlers cung cấp bởi Xitrum và thứ tự mặc định
- Cách tạo mới và cấu hình một handler
### Kiến trúc của Netty handler
[¶](#kien-truc-cua-netty-handler)
Với mỗi kết nối, sẽ có một kênh pipeline để handle dữ liệu IO. Mỗi kênh pipeline là một chuối cac handler. Có 2 kiểu handler.
- Inbound: request từ client -> server
- Outbound: response từ server -> client
Hãy đọc thêm tài liệu về
[ChannelPipeline](http://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html)
để biết thêm thông tin.
I/O Request via Channel or ChannelHandlerContext | +---------------------------------------------------+---------------+ | ChannelPipeline | | | \|/ | | +---------------------+ +-----------+----------+ | | | Inbound Handler N | | Outbound Handler 1 | | | +----------+----------+ +-----------+----------+ | | /|\ | | | | \|/ | | +----------+----------+ +-----------+----------+ | | | Inbound Handler N-1 | | Outbound Handler 2 | | | +----------+----------+ +-----------+----------+ | | /|\ . | | . . | | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()| | [ method call] [method call] | | . . | | . \|/ | | +----------+----------+ +-----------+----------+ | | | Inbound Handler 2 | | Outbound Handler M-1 | | | +----------+----------+ +-----------+----------+ | | /|\ | | | | \|/ | | +----------+----------+ +-----------+----------+ | | | Inbound Handler 1 | | Outbound Handler M | | | +----------+----------+ +-----------+----------+ | | /|\ | | +---------------+-----------------------------------+---------------+ | \|/ +---------------+-----------------------------------+---------------+ | | | | | [ Socket.read() ] [ Socket.write() ] | | | | Netty Internal I/O Threads (Transport Implementation) | +-------------------------------------------------------------------+
### Tùy chỉnh handler
[¶](#tuy-chinh-handler)
Khi khởi động server Xitrum, bạn có thể truyền vào
[ChannelInitializer](http://netty.io/4.0/api/io/netty/channel/ChannelInitializer.html):
import xitrum.Server object Boot { def main(args: Array[String]) { Server.start(myChannelInitializer) } }
Với server HTTTPS, Xitrum sẽ tự động thêm SSL handler vào trước pipeline. Bạn có thể tái sử dụng các Xitrum handler trong pipeline.
### Xitrum handler mặc định
[¶](#xitrum-handler-mac-dinh)
Xem
[xitrum.handler.DefaultHttpChannelInitializer](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/handler/DefaultHttpChannelInitializer.scala).
Sharable handlers (một instances được sử dụng chung bởi nhiều kết nối) được đặt trong
object
DefaultHttpChannelInitializer ở trên do đó chúng có thể được chọn bởi ứng dụng
muốn sử dụng pipeline tùy chỉnh. Những ứng dụng có thể chỉ muốn có một tập hợp con của các
handler mặc định.
Ví dụ, khi ứng dụng sử dụng dispatcher của chính nó (khong phải là routing/dispatcher của Xitrum) và chỉ cần tính năng xử lý tệp tĩnh nhanh của Xitrum, có thể chỉ cần sử dụng các handler:
Inbound:
HttpRequestDecoder
PublicFileServer
- Its own dispatcher
Outbound:
HttpResponseEncoder
ChunkedWriteHandler
XSendFile
## Metrics
[¶](#metrics)
Xitrum thu thập bộ nhớ JVM heap, CPU, và tình trạng thực thi các action từ mỗi node Akka cluster của ứng dụng. Nó xuất ra các số liệu trong định dạng dữ liệu JSON. Xitrum cũng để bạn thu thập cách các dữ liệu khác.
This metrics feature is based on the library
[Coda Hale Metrics](http://metrics.dropwizard.io/3.1.0/).
### Thu thập metrics
[¶](#thu-thap-metrics)
#### Bộ nhớ heap và CPU
[¶](#bo-nho-heap-va-cpu)
Bộ nhớ JVM heap và CPU sẽ được thu thập như
[NodeMetrics](http://doc.akka.io/api/akka/2.3.0/index.html#akka.cluster.NodeMetrics)
từ mỗi node của hệ thống Akka actor.
Bộ nhớ heap:
![ _images/metrics_heapmemory.png](_images/metrics_heapmemory.png)
CPU: số lượng tiến trình và tải trung bình
![ _images/metrics_cpu.png](_images/metrics_cpu.png)
#### Action metric
[¶](#action-metric)
Xitrum thu thập tình trạng thực thi các action của mỗi node như một
[Histogram](http://metrics.dropwizard.io/3.1.0/getting-started/#histograms).
Bạn có thể biết chính các bao nhiêu lần action được thực thim và thời gian
thực thi của những non-async action. ![ _images/metrics_action_count.png](_images/metrics_action_count.png)
Thời gian thực hiện lần gần nhất của một action:
![ _images/metrics_action_time.png](_images/metrics_action_time.png)
#### Thu thập các số liệu tùy chỉnh
[¶](#thu-thap-cac-so-lieu-tuy-chinh)
Ngoài các số liệu mặc định bên trên, bạn có thể thu thập các dữ liệu cho riêng mình.
xitrum.Metrics có thể truy cập vào
gauge,
counter,
meter,
timer và
histogram. Vui lòng tham khảo
[Coda Hale Metrics](http://metrics.dropwizard.io/3.1.0/) và
[its Scala implementation](https://github.com/erikvanoosten/metrics-scala)
để biết cách sử dụng chúng.
Ví dụ về timer:
import xitrum.{Action, Metrics} import xitrum.annotation.GET object MyAction { lazy val myTimer = Metrics.timer("myTimer") } @GET("my/action") class MyAction extends Action { import MyAction._ def execute() { myTimer.time { // Something that you want to measure execution time ... } ... } }
### Xuất ra các metric
[¶](#xuat-ra-cac-metric)
Xitrum xuất ra giá trị mới nhất của metric dưới định đạng JSON sau một chu kỳ xác định. Các dữ liệu thu thập được có nhiều biến động, sẽ không được lưu trữ vĩnh viễn
HeapMemory:
{ "TYPE" : "heapMemory", "SYSTEM" : akka.actor.Address.system, "HOST" : akka.actor.Address.host, "PORT" : akka.actor.Address.port, "HASH" : akka.actor.Address.hashCode, "TIMESTAMP" : akka.cluster.NodeMetrics.timestamp, "USED" : Number as byte, "COMMITTED" : Number as byte, "MAX" : Number as byte }
CPU:
{ "TYPE" : "cpu", "SYSTEM" : akka.actor.Address.system, "HOST" : akka.actor.Address.host, "PORT" : akka.actor.Address.port, "HASH" : akka.actor.Address.hashCode, "TIMESTAMP" : akka.cluster.NodeMetrics.timestamp "SYSTEMLOADAVERAGE" : Number, "CPUCOMBINED" : Number, "PROCESSORS" : Number }
MetricsRegistry sẽ được phân tách bởi
[metrics-json](http://metrics.dropwizard.io/3.1.0/manual/json/).
#### Xitrum viewer mặc định
[¶](#xitrum-viewer-mac-dinh)
Xitrum cung cấp metric viewer mặc định tại URL
/xitrum/metrics/viewer?api_key=<see xitrum.conf>.
URL này hiển thị các đồ thị như trên. Các đồ thị được tạo bởi
[D3.js](http://d3js.org/).
URL có thể được tạ ra với:
import xitrum.Config import xitrum.metrics.XitrumMetricsViewer url[XitrumMetricsViewer]("api_key" -> Config.xitrum.metrics.get.apiKey)
#### Jconsole viewer
[¶](#jconsole-viewer)
Bạn có thể xem nó với
[JVM Reporter](http://metrics.dropwizard.io/3.1.0/getting-started/#reporting-via-jmx). ![ _images/metrics_jconsole.png](_images/metrics_jconsole.png)
Khởi động JMX reporter:
import com.codahale.metrics.JmxReporter object Boot { def main(args: Array[String]) { Server.start() JmxReporter.forRegistry(xitrum.Metrics.registry).build().start() } }
Sau đó chạy
[jconsole](http://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html) command.
#### Hiển thị metiric với custom viewer
[¶](#hien-thi-metiric-voi-custom-viewer)
Metric sẽ được xuất ra tại SockJS URL
xitrum/metrics/channel như JSON.
jsAddMetricsNameSpace là một JavaScript snippet mà Xitrum cung cấp để tạo
kết nối.
Sử dụng JSON handler của bạn và gọi
initMetricsChannel với handler đó.
Ví dụ về action:
import xitrum.annotation.GET import xitrum.metrics.MetricsViewer @GET("my/metrics/viewer") class MySubscriber extends MetricsViewer { def execute() { jsAddMetricsNameSpace("window") jsAddToView(""" function onValue(json) { console.log(json); } function onClose(){ console.log("channel closed"); } window.initMetricsChannel(onValue, onClose); """) respondView() } }
#### Lưu metric
[¶](#luu-metric)
Để tiết kiệm bộ nhớ, Xitrum không ghi nhớ các giá trị metric cũ. Nếu bạn muốn lưu metric vào cơ sở dữ liệu hoặc tệp, bạn cần implement vào subscriber của bạn.
Ví dụ:
import akka.actor.Actor import xitrum.metrics.PublisherLookUp class MySubscriber extends Actor with PublisherLookUp { override def preStart() { lookUpPublisher() } def receive = { case _ => } override def doWithPublisher(globalPublisher: ActorRef) = { context.become { // When run in multinode environment case multinodeMetrics: Set[NodeMetrics] => // Save to DB or write to file. // When run in single node environment case nodeMetrics: NodeMetrics => // Save to DB or write to file. case Publish(registryAsJson) => // Save to DB or write to file. case _ => } } }
## HOWTO
[¶](#howto)
Chương này bao gồm một số thủ thuật nhỏ.
### Basic authentication
[¶](#basic-authentication)
Bạn có thể bảo vệ toàn bộ site hoặc chỉ action nào đó với
[basic authentication](http://en.wikipedia.org/wiki/Basic_access_authentication).
Ghi nhớ rằng Xitrum không hỗ trợ
[digest authentication](http://en.wikipedia.org/wiki/Digest_access_authentication)
vì nó cung cấp một cái nhìn sai về bảo mật. Từ đó làm cho digest authentication dễ bị tấn công man-in-the-middle.
Để bảo mật tốt hơn, bạn nên sử dụng HTTPS
(không cần sử dụng Apache hay Nginx như reverse proxy chỉ cần sử dụng HTTPS).
#### Cấu hình basic authentication cho toàn bộ site
[¶](#cau-hinh-basic-authentication-cho-toan-bo-site)
Trong tệp
config/xitrum.conf:
"basicAuth": { "realm": "xitrum", "username": "xitrum", "password": "xitrum" }
#### Thêm basic authentication vào một action
[¶](#them-basic-authentication-vao-mot-action)
import xitrum.Action class MyAction extends Action { beforeFilter { basicAuth("Realm") { (username, password) => username == "username" && password == "password" } } }
### Load các tệp config
[¶](#load-cac-tep-config)
#### Tệp JSON
[¶](#tep-json)
JSON thuận tiện cho việc sử dụng làm các tệp cấu hình với cấu trúc lồng nhau.
Lưu tệp cấu hình của bạn trong thư mục “config”. Thư mục này được đặt trong classpath ở chế độ phát triển bởi build.sbt và trong chế độ sản phẩm bởi script/runner (và script/runner.bat).
myconfig.json:
{ "username": "God", "password": "Does God need a password?", "children": ["Adam", "Eva"] }
Load:
import xitrum.util.Loader case class MyConfig(username: String, password: String, children: Seq[String]) val myConfig = Loader.jsonFromClasspath[MyConfig]("myconfig.json")
Ghi chú:
- Các Key and string phải được dùng dấu nháy kép
".
- Hiện tại, bạn không thể viết comment trong tệp JSON
#### Tệp properties
[¶](#tep-properties)
Bạn cũng có thể các tệp property, nhưng bạn nên sử dụng JSON. Tệp property không phải typesafe, không hỗ trợ UTF-8 và các cấu trúc lồng nhau v.v.
myconfig.properties:
username = God password = Does God need a password? children = Adam, Eva
Load:
import xitrum.util.Loader // Here you get an instance of java.util.Properties val properties = Loader.propertiesFromClasspath("myconfig.properties")
#### Typesafe tệp cấu hình
[¶](#typesafe-tep-cau-hinh)
Xitrum cũng bao gồm Akka mà Akka sử dụng
[thư viện cấu hình](https://github.com/typesafehub/config) tạp bởi
[company called Typesafe](http://typesafe.com/company).
Chúng có thẻ tốt hơn tải các tệp cấu hình.
myconfig.conf:
username = God password = Does God need a password? children = ["Adam", "Eva"]
Load:
import com.typesafe.config.{Config, ConfigFactory} val config = ConfigFactory.load("myconfig.conf") val username = config.getString("username") val password = config.getString("password") val children = config.getStringList("children")
### Serialize và deserialize
[¶](#serialize-va-deserialize)
Để serialize thành
Array[Byte]:
import xitrum.util.SeriDeseri val bytes = SeriDeseri.toBytes("my serializable object")
Để deserialize các byte ngược trở lại:
val option = SeriDeseri.fromBytes[MyType](bytes) // Option[MyType]
Nếu bạn muốn lưu tệp:
import xitrum.util.Loader Loader.bytesToFile(bytes, "myObject.bin")
To load from the file:
val bytes = Loader.bytesFromFile("myObject.bin")
### Mã hóa dữ liệu
[¶](#ma-hoa-du-lieu)
Để mã hóa dữ liệu mà bạn không cần giải mã sau đó (mã hóa một chiều), bạn có thể sử dụng MD5 hoặc những thuật toán tương tư.
Nếu bạn muốn giải mã về sau, bạn có thể sử dụng tiện ích mà Xitrum cung cấp:
import xitrum.util.Secure // Array[Byte] val encrypted = Secure.encrypt("my data".getBytes) // Option[Array[Byte]] val decrypted = Secure.decrypt(encrypted)
Bạn có thể sử dụng
xitrum.util.UrlSafeBase64 để mã hóa và giải mã các dữ liệu nhị phân
thanh chuỗi thông thường (nhúng vào HTML để response chẳng hạn).
// String that can be included in URL, cookie etc. val string = UrlSafeBase64.noPaddingEncode(encrypted) // Option[Array[Byte]] val encrypted2 = UrlSafeBase64.autoPaddingDecode(string)
Nếu bạn có thể phối hợp các quá trình bên trên trong một bước:
import xitrum.util.SeriDeseri val mySerializableObject = new MySerializableClass // String val encrypted = SeriDeseri.toSecureUrlSafeBase64(mySerializableObject) // Option[MySerializableClass] val decrypted = SeriDeseri.fromSecureUrlSafeBase64[MySerializableClass](encrypted)
SeriDeseri sử dụng
[Twitter Chill](https://github.com/twitter/chill)
để serialize và deserialize. Dữ liệu của bạn phải là serializable.
Bạn có thể chỉ rõ khóa (key) để mã hóa.
val encrypted = Secure.encrypt("my data".getBytes, "my key") val decrypted = Secure.decrypt(encrypted, "my key")
val encrypted = SeriDeseri.toSecureUrlSafeBase64(mySerializableObject, "my key") val decrypted = SeriDeseri.fromSecureUrlSafeBase64[MySerializableClass](encrypted, "my key")
Nếu bạn không chỉ rõ key nào,
secureKey trong tệp
xitrum.conf trong thư mục config
sẽ được sử dụng.
### Nhiều site với cùng một tên miền
[¶](#nhieu-site-voi-cung-mot-ten-mien)
Neus bạn muốn sử dụng một reverse proxy như Nginx để chạy nhiều site khác nhau tại cùng một tên miền:
http://example.com/site1/... http://example.com/site2/...
Bạn có thể cấu hình baseUrl trong
config/xitrum.conf.
Trong mã JS, để có chính xác URL cho Ajax request, sử dụng
withBaseUrl
trong
[xitrum.js](https://github.com/xitrum-framework/xitrum/blob/master/src/main/scala/xitrum/js.scala).
# If the current site's baseUrl is "site1", the result will be: # /site1/path/to/my/action xitrum.withBaseUrl('/path/to/my/action')
### Convert Markdown sang HTML
[¶](#convert-markdown-sang-html)
Nếu bạn đã configured project để sử dụng
[,
Bạn chỉ cần phải làm như sau: Scalate template engine](index.html#document-template_engines)
import org.fusesource.scalamd.Markdown val html = Markdown("input")
Ngoài ra, bạn cần thêm thành phần phụ thuộc này vào tệp
build.sbt của project.
libraryDependencies += "org.fusesource.scalamd" %% "scalamd" % "1.6"
### Theo dõi sự thay đổi của tệp
[¶](#theo-doi-su-thay-doi-cua-tep)
Bạn cần thiết lập callback cho
[StandardWatchEventKinds](http://docs.oracle.com/javase/7/docs/api/java/nio/file/StandardWatchEventKinds.html)
trên tệp và thư mục.
import java.nio.file.Paths import xitrum.util.FileMonitor val target = Paths.get("absolute_path_or_path_relative_to_application_directory").toAbsolutePath FileMonitor.monitor(FileMonitor.MODIFY, target, { path => // Do some callback with path println(s"File modified: $path") // And stop monitoring if necessary FileMonitor.unmonitor(FileMonitor.MODIFY, target) })
FileMonitor sử dụng
[Schwatcher](https://github.com/lloydmeta/schwatcher).
### Thư mục tạm thời
[¶](#thu-muc-tam-thoi)
Mặc định Xitrum project (xem
tmpDir trong xitrum.conf) sử dụng thư mục
tmp
trong thư mục hoạt động hiện thời để lưu các tệp .scala generate bởi Scalate, các tệp
lớn sẽ được tải lên v.v.
Để lấy đường dẫn đến thư mục đó:
xitrum.Config.xitrum.tmpDir.getAbsolutePath
Tạo một tệp mới hoặc thư mục trong thư mục đó:
val file = new java.io.File(xitrum.Config.xitrum.tmpDir, "myfile") val dir = new java.io.File(xitrum.Config.xitrum.tmpDir, "mydir") dir.mkdirs()
### Stream video
[¶](#stream-video)
Có nhiều cách để steam video. Cách đơn giản nhất:
- Cung cấp tệp video .mp4 theo từng đoạn, người dùng có thể xem video trong khi tải về.
- Và sử dụng một HTTP server như Xitrum có hỗ trợ
[range requests](http://en.wikipedia.org/wiki/Byte_serving), để người dùng có thể nhảy đến đoạn phim mà chưa được tải về.
Bạn có thể sử dụng
[MP4Box](http://gpac.wp.mines-telecom.fr/mp4box/) để tải nội dụng
của tệp phim một các xen kẽ mỗi 0.5 giây:
MP4Box -inter 500 movie.mp4
## Dependencies
[¶](#dependencies)
### Thư viện Dependency
[¶](#thu-vien-dependency)
Xitrum bao gồm một vài thư viện. Trong Xiturm project, bạn có thẻ sử dụng chúng một cách trực tiếp.
![ _images/deps.png](_images/deps.png)
Các dependency chính:
[Scala](http://www.scala-lang.org/): Xitrum được viết bằng ngôn ngữ Scala. [Netty](https://netty.io/): Với async HTTP(S) server. Nhiều tính năng trong Xitrum dựa trên Netty như WebSocket và cung cấp tệp bằng zero copy. [Akka](http://akka.io/): Với SockJS. Akka phụ thuộc vào [Typesafe Config](https://github.com/typesafehub/config), Typesafe Config lại được sử dụng trong Xitrum.
Các dependencies khác:
[Commons Lang](http://commons.apache.org/lang/): Để escaping dữ liệu JSON. [Glokka](https://github.com/xitrum-framework/glokka): Để clustering SockJS actors. [JSON4S](https://github.com/json4s/json4s): Để phân tích và tạo dữ liệu JSON. JSON4S phụ thuộc [Paranamer](http://paranamer.codehaus.org/). [Rhino](https://developer.mozilla.org/en-US/docs/Rhino): Để Scalate cho việc biên dịch CoffeeScript thành JavaScript. [Sclasner](https://github.com/xitrum-framework/sclasner): For scanning HTTP routes in action classes in .class and .jar files. [Scaposer](https://github.com/xitrum-framework/scaposer): For i18n. [Twitter Chill](https://github.com/twitter/chill): Để serializing và deserializing cookie và sessions. Chill dựa trên [Kryo](http://code.google.com/p/kryo/). [SLF4S](http://slf4s.org/), [Logback](http://logback.qos.ch/): Để logging. [`Skeleton project mới của Xitrum<https://github.com/xitrum-framework/xitrum-new>`_](#id1)‘
bao gồm các công cụ sau: [scala-xgettext](https://github.com/xitrum-framework/scala-xgettext): Để [từ tệp .scala files khi biên dịch chúng.](index.html#document-i18n) *trích chuỗi i18n* [xitrum-package](https://github.com/xitrum-framework/xitrum-package): Để [, sẵn sàng cho việc deploy trên production server.](index.html#document-deploy) *đóng gói project* [Scalive](https://github.com/xitrum-framework/scalive): Để két nối Scala console đến một tiến trình JVM đang chạy phục vụ gỡ lỗi trực tiếp.
### Các project liên quan
[¶](#cac-project-lien-quan)
Demos:
[xitrum-new](https://github.com/xitrum-framework/xitrum-new): Xitrum new project skeleton. [xitrum-demos](https://github.com/xitrum-framework/xitrum-demos): Bản demo các tính năng của Xitrum. [xitrum-placeholder](https://github.com/xitrum-framework/xitrum-placeholder): Bản demo API lấy hình ảnh. [comy](https://github.com/xitrum-framework/comy): Bản demo service rút ngon URL. [xitrum-multimodule-demo](https://github.com/xitrum-framework/xitrum-multimodule-demo): Ví dụ về tạo project multimodule [SBT](http://www.scala-sbt.org/).
Plugins:
[xitrum-scalate](https://github.com/xitrum-framework/xitrum-scalate): Đây là template engine mặc định của Xitrum, preconfigured trong [Xitrum new project skeleton](https://github.com/xitrum-framework/xitrum-new). Bạn có thể thay nó bằng các template engine khác, hoặc loại bỏ hoàng toàn nó nếu project của bạn không cần bất kì template engine nào. Nó phụ thuộc vào [Scalate](http://scalate.fusesource.org/)và [Scalamd](https://github.com/chirino/scalamd). [xitrum-hazelcast](https://github.com/xitrum-framework/xitrum-hazelcast): Để clustering cache và session tại server. [xitrum-ko](https://github.com/xitrum-framework/xitrum-ko): Cung cấp một số helper cho [Knockoutjs](http://knockoutjs.com/).
Các project khác:
[xitrum-doc](https://github.com/xitrum-framework/xitrum-doc): mã nguồn của [Xitrum Guide](http://xitrum-framework.github.io/guide.html). [xitrum-hp](https://github.com/xitrum-framework/xitrum-framework.github.io): mã nguồn của [Xitrum Homepage](http://xitrum-framework.github.io/).

Via: 1.1 varnish

X-Cache: MISS