#1188 Proxy example

vkuzkokov Sun 29 Aug 2010

Caching proxy server is a good example of language/standard library capabilities as it uses both Network IO and concurrency.

This is example is pretty much based on hello world #4 example

using concurrent
using util
using web
using wisp

class ProxyMain : AbstractMain
{
  @Opt { help = "http port" }
  Int port := 8080

  override Int run()
  {
    proxy := ProxyMod { hostname = port == 80 ? "localhost" : "localhost:$port" }
    return runServices([WispService { it.port = this.port; root = proxy}])
  }
}

const class CacheElem
{
  new make(|This| f) { f(this) }

  const Str:Str headers
  const Str data
}


const class ProxyMod : WebMod
{
  const Str hostname

  new make(|This| f) { f(this) }

  const Actor actor := Actor(ActorPool()) |uri| {
    if (Actor.locals["proxy::cache"] == null)
      Actor.locals["proxy::cache"] = Uri:CacheElem[:]
    Uri:CacheElem map := Actor.locals["proxy::cache"]
    if (map[uri] != null)
      return map[uri]
    client := WebClient(uri)
    stream := client.getIn
    stream.charset=Charset("ISO-8859-1")
    map[uri] = CacheElem {
      headers = client.resHeaders
      data = stream.readAllStr(false)
    }
    client.close
    return map[uri]
  }

  override Void onGet()
  {
    if (req.headers["Host"] == hostname)
    {
      res.headers["Content-Type"] = "text/plain; charset=utf-8"
      res.out.print("Nothing here")
      return
    }
    CacheElem? elem := actor.send(req.absUri).get
    res.headers.setAll(elem.headers)
    buf := Buf()
    buf.charset = Charset("ISO-8859-1")
    buf.print(elem.data)
    buf.flip
    res.out.writeBuf(buf)
  }
}

Well, this solution seems somewhat bloated to my taste:

  1. I use ISO-8859-1 because a) I know it is; b) It doesn't crash on .png files.
  2. Creating Buf in last lines of onGet may seem unnecessary if you think that res.out.charset will do anything.
  3. All charset magic is used because I don't have immutable Buf.

Of (2): As far as I get res.out is an instance of WebOutStream which is wrapper around SysOutStream, therefore changing charset in res.out does nothing. I think it would be nice to add such delegation to OutStream.

Of (3): I can store content as Str or Int[] (or something I didn't come up with). Plus side of Int[] - no charsets. Minus side - reading/writing by one byte (even more bloated, set aside performance). Buf-like immutable entity surely would help.

ivan Mon 30 Aug 2010

vkuzkokov,

Nice idea! Few times I needed a simple proxy for testing but was too lazy to find and configure something. So having customizable pure fan proxy can be very useful sometimes.

Few notes on the code:

  1. I'd use Unsafe to store data buf in CacheElem:
    const class CacheElem 
    {
      new make(Buf data, |This|? f := null)
      {
        dataStore = Unsafe(data)
        f?.call(this)
      }
      private const Unsafe dataStore
      Buf data() { dataStore.val }
    
      const Str:Str headers
    
      Void fillRes(WebRes res)
      {
        res.headers.setAll(headers)
        res.out.writeBuf(data)
      }
    } 
  2. I'd rather create a separate actor class for proxy:
    const class Cache : Actor
    { 
      new make(ActorPool pool := ActorPool()) : super(pool) {} 
      private Uri:CacheElem map() { locals.getOrAdd("cache") |->Obj| { [:] } }
      override Obj? receive(Obj? msg)
      {
        map.getOrAdd(msg) |->Obj| 
        {
          client := WebClient(msg)
          result := CacheElem(client.readAllBuf) { headers = client.headers }
          return result
        }
      }
    
      CacheElem get(Uri uri) { send(uri).get }
    }
  3. So, ProxyMod turns to something like this:
    const class ProxyMod : WebMod
    {
      const Str hostname
      private const Cache := Cache()
      new make(|This| f) { f(this) }
    
      override Void onGet()
      {
        if (req.headers["Host"] == hostname)
        {
          res.headers["Content-Type"] = "text/plain; charset=utf-8"
          res.out.print("Nothing here")
          return
        }
    
        cache[req.absUri].fillRes(res)
      }
    }

This is just an idea, haven't tried to compile or launch this code :)

Login or Signup to reply.