8 Sep 2016

Simplify lua application inside nginx lua module

One solution to not-to-write additional applications as long as I can is, any common problem that can be solved by writing nginx module and/or nginx-lua, I will build an nginx module or a lua application for it, and re-use these module and application when it's possible

Another reason when moving to nginx instead of keep writing application then proxy_pass from nginx to it is: every engineer can install nginx within a minute in a way they compile any Linux application,  and nginx's configuration syntax is simple, straight forward and clean because of the way nginx developer designed its configuration style, now nginx supports dynamic module loading as well, once can write a module, compile .so module, give .so file to another one, load it into current nginx instance to use. 

Writing nginx module is a long story to tell and I will write about it later, but Lua module is something much simpler. Once can learn to write a simple business in Lua, plug it to nginx configuration within some minutes, like this one

And because it's simpler, one can write as many application as they can by copying and modify old source code, without re-use current existing code, even if these apps was made to solve the same problem.If we didn't manage development process, we would end-up having hundred applications with duplicated code serving the same or similar purpose. 


For example, we designed an application that processes user's request, examine its query parameters, check provided token again application policy then passing it to another backend to serve request/or return 403 Forbidden if those required conditions are not matched. It's written like this:



gearmanHost, gearmadPort, backendAddress, tokenParameter, and the next function getBucket were defined in the same source code, and it's ok so far. Next day, we need to make another application that has same function, serve the same purpose, but with different backendAddress and tokenParameter.

A not so careful guy will copy that lua script, modify values, and then configure nginx likes this:

and inside the conf/lua directory will be end-up like this:

Nginx lua modules provides 2 way to solve this problem: data sharing using Lua module model and lua_shared_dict. The difference between 2 is that the first one is memory sharing within nginx worker, that means if you have n nginx workers running, you have n copies of the same data. The latter is shared memory, which is shared among nginx workers, you can save memory by using it.

Choosing one of these solution will help you in some different situation. For example, for configuration you can choose the 1st, because configuration values wont take megabytes memory to store, and use the 2nd to store key-value runtime object, it's not persistent and will be reset once nginx restarted.

By using Lua Module model, we can rewrite the origin lua code to store configuration parameter and common function:

and whenever we need to read configuration, we just need to load conf file, and call its function

The final nginx configuration will be much more simpler:


inside app.lua you just have to determine which configuration to read based on $server_name as configuration key or some other specific nginx variable, depend on your application business logic.


7 Sep 2016

Ứng dụng currying trong scala

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:

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.



Disqus