Khi dùng scala để phát triển các web application, chúng ta nên sử dụng các web framework được phát triển bằng scala.
Đương nhiên sử dụng các framework bằng java cũng không sao, vì scala có thể tái sử dụng thư viện và gọi hàm của java mà không gặp vấn đề gì cả. 2 framework thường được sử dụng là play (cho cả scala và java) và spray framework.
Play cùng cấp nhiều feature hơn, tích hợp nhiều thư viện và công cụ để phát triển các web application nhanh chóng, tuy nhiên nó khá nặng nề (về mặt development và tìm hiểu), do vậy nên bắt đầu bằng spray khi tìm hiểu scala.
Spray có nhiều module trong đó có spray-client, xây dựng trên nền Akka, là một async http client, dễ dàng làm quen và sử dụng.
Một ứng dụng ví dụ của spray-client được cung cấp cùng với spray-client source code ở github như sau:
Ví dụ trên sử dụng spray-json để implicit convert response sang json object. Bỏ qua việc xử lí json này, chúng ta thấy flow của spray-client là khởi một một http client, và request đến google api sử dụng method GET và nhận một một Future object, xử lí response mong muốn trả về với một operator function có return type là Unit.
Vậy để tái sử dụng Http client ta chỉ cần biến ví dụ trên thành một hàm nhận vào url và một function có return type là Unit.
Khi cần xử lí một url nào đó:
Hoặc gọn hơn
Nhưng nếu ta phải xử lí nhiều HTTP request, trong đó request sau phải phụ thuộc vào request trước, ví dụ request trước dùng để lấy tên file, và request sau để lấy response và write vào file đó. ở function myOp thông tin chúng ta có được chỉ là file name, content của file phải cần một request tiếp theo.
Thay vì phải lưu file name ở đâu đó, chờ request tiếp theo hoàn thành, lấy ra filename và write nội dung vào file, ta có thể dùng curried function:
Khi xử lí request đầu tiên và có được file name, dùng myWriter với name có được, myWriter sẽ trả về một hàm với biến đầu vào là HttpResponse, truyền hàm này cho request tiếp theo để tái sử dụng:
Thật ra thì ở đây ta có thể gán fileWriter bằng write_file(x.filename, _: op: HttpResponse => Unit) sau đó dùng fileWriter như bên trên, tuy nhiên dùng currying ngắn gọn hơn "một chút", và giải thích được phần nào một ứng dụng của curried function trong scala.
Đương nhiên sử dụng các framework bằng java cũng không sao, vì scala có thể tái sử dụng thư viện và gọi hàm của java mà không gặp vấn đề gì cả. 2 framework thường được sử dụng là play (cho cả scala và java) và spray framework.
Play cùng cấp nhiều feature hơn, tích hợp nhiều thư viện và công cụ để phát triển các web application nhanh chóng, tuy nhiên nó khá nặng nề (về mặt development và tìm hiểu), do vậy nên bắt đầu bằng spray khi tìm hiểu scala.
Spray có nhiều module trong đó có spray-client, xây dựng trên nền Akka, là một async http client, dễ dàng làm quen và sử dụng.
Một ứng dụng ví dụ của spray-client được cung cấp cùng với spray-client source code ở github như sau:
package spray.examples import akka.actor.ActorSystem import akka.event.Logging import akka.io.IO import akka.pattern.ask import spray.can.Http import spray.client.pipelining._ import spray.json.{DefaultJsonProtocol, JsonFormat} import spray.util._ import scala.concurrent.duration._ import scala.util.{Failure, Success} case class Elevation(location: Location, elevation: Double) case class Location(lat: Double, lng: Double) case class GoogleApiResult[T](status: String, results: List[T]) object ElevationJsonProtocol extends DefaultJsonProtocol { implicit val locationFormat = jsonFormat2(Location) implicit val elevationFormat = jsonFormat2(Elevation) implicit def googleApiResultFormat[T :JsonFormat] = jsonFormat2(GoogleApiResult.apply[T]) } object Main extends App { // we need an ActorSystem to host our application in implicit val system = ActorSystem("simple-spray-client") import system.dispatcher // execution context for futures below val log = Logging(system, getClass) log.info("Requesting the elevation of Mt. Everest from Googles Elevation API...") val pipeline = sendReceive ~> unmarshal[GoogleApiResult[Elevation]] val responseFuture = pipeline { Get("http://maps.googleapis.com/maps/api/elevation/json?locations=27.988056,86.925278&sensor=false") } responseFuture onComplete { case Success(GoogleApiResult(_, Elevation(_, elevation) :: _)) => log.info("The elevation of Mt. Everest is: {} m", elevation) shutdown() case Success(somethingUnexpected) => log.warning("The Google API call was successful but returned something unexpected: '{}'.", somethingUnexpected) shutdown() case Failure(error) => log.error(error, "Couldn't get elevation") shutdown() } def shutdown(): Unit = { IO(Http).ask(Http.CloseAll)(1.second).await system.shutdown() } }
Ví dụ trên sử dụng spray-json để implicit convert response sang json object. Bỏ qua việc xử lí json này, chúng ta thấy flow của spray-client là khởi một một http client, và request đến google api sử dụng method GET và nhận một một Future object, xử lí response mong muốn trả về với một operator function có return type là Unit.
Vậy để tái sử dụng Http client ta chỉ cần biến ví dụ trên thành một hàm nhận vào url và một function có return type là Unit.
def process(url: String, op: HttpResponse => Unit) = { ... }
Khi cần xử lí một url nào đó:
private def myOp(httpResponse: HttpResponse): Unit = { println(httpResponse.entity.asString) } client.process("http://blogger.com", myOp)
Hoặc gọn hơn
client.process("http://blogger.com", (x: HttpResponse) => println(x.entity.asString))
Nhưng nếu ta phải xử lí nhiều HTTP request, trong đó request sau phải phụ thuộc vào request trước, ví dụ request trước dùng để lấy tên file, và request sau để lấy response và write vào file đó. ở function myOp thông tin chúng ta có được chỉ là file name, content của file phải cần một request tiếp theo.
Thay vì phải lưu file name ở đâu đó, chờ request tiếp theo hoàn thành, lấy ra filename và write nội dung vào file, ta có thể dùng curried function:
private def myWriter(name: String)(httpResponse: HttpResponse): Unit = { httpResponse.status.intValue match { case 200 => println("write file " + name) new PrintWriter(name) { write(httpResponse.entity.asString) close() } case _ => } }
Khi xử lí request đầu tiên và có được file name, dùng myWriter với name có được, myWriter sẽ trả về một hàm với biến đầu vào là HttpResponse, truyền hàm này cho request tiếp theo để tái sử dụng:
private def anUnusualOp(httpResponse: HttpResponse): Unit = { // business to get file name val jsonResult: JsValue = JsonParser(httpResponse.entity.asString) val files = jsonResult.convertTo[files] files.details.filter(fileInfo => { fileInfo.filename.endsWith(".md") && fileInfo.status != "removed" }).foreach(x => { val content_url = "https://raw.githubusercontent.com/%s/master/%s".format(repo, x.filename) // use currying val fileWriter = write_file(x.filename) _ client.process(content_url)(fileWriter) }) }
Thật ra thì ở đây ta có thể gán fileWriter bằng write_file(x.filename, _: op: HttpResponse => Unit) sau đó dùng fileWriter như bên trên, tuy nhiên dùng currying ngắn gọn hơn "một chút", và giải thích được phần nào một ứng dụng của curried function trong scala.
No comments:
Post a Comment