Http get

use "http"
use "json"
use "ssl/net"
use "promises"

class JsonRequester
  let _creds: Credentials
  let _sslctx: (SSLContext | None)

  new create(creds: Credentials) =>
    _creds = creds

    _sslctx = try
      recover val
        SSLContext.>set_client_verify(true).>set_authority(None)?
      end
    else
      None
    end

  fun ref apply(url: String,
    receiver: JsonRequesterResultReceiver) ?
  =>
    let valid_url = URL.valid(url)?
    let r = RequestFactory("GET", valid_url, _creds.token)

    let handler_factory = JsonRequesterHandlerFactory(_creds, receiver)
    let client = HTTPClient(_creds.auth, handler_factory, _sslctx)
    client(consume r)?

interface tag JsonRequesterResultReceiver
  be success(json: JsonDoc val)
  be failure(status: U16, response_body: String, message: String)

class JsonRequesterHandlerFactory is HandlerFactory
  let _creds: Credentials
  let _receiver: JsonRequesterResultReceiver

  new val create(creds: Credentials,
    receiver: JsonRequesterResultReceiver)
  =>
    _creds = creds
    _receiver = receiver

  fun apply(session: HTTPSession tag): HTTPHandler ref^ =>
    let requester = JsonRequester(_creds)
    JsonRequesterHandler(requester, _receiver)

class JsonRequesterHandler is HTTPHandler
  let _requester: JsonRequester
  let _receiver: JsonRequesterResultReceiver
  var _payload_body: Array[U8] iso = recover Array[U8] end
  var _status: U16 = 0

  new create(requester: JsonRequester, receiver: JsonRequesterResultReceiver) =>
    _requester = requester
    _receiver = receiver

  fun ref apply(payload: Payload val) =>
    _status = payload.status


    if (_status == 301) or (_status == 307) then
      try
        // Redirect.
        // Let's start a new request to the redirect location
        _requester(payload("Location")?, _receiver)?
        return
      end
    end

    try
      for bs in payload.body()?.values() do
        _payload_body.append(bs)
      end
    end

    if payload.transfer_mode is OneshotTransfer then
      finished()
    end

  fun ref chunk(data: ByteSeq) =>
    _payload_body.append(data)

  fun ref failed(reason: HTTPFailureReason) =>
    let msg = match reason
    | AuthFailed => "Authorization failure"
    | ConnectFailed => "Unable to connect"
    | ConnectionClosed => "Connection was prematurely closed"
    end

    _receiver.failure(_status, "", consume msg)

  fun ref finished() =>
    let x = _payload_body = recover Array[U8] end
    let y: String iso = String.from_iso_array(consume x)

    if _status == 200 then
      try
        let json = recover val
          JsonDoc.>parse(consume y)?
        end
        _receiver.success(json)
      else
        _receiver.failure(_status, "", "Failed to parse response")
      end
    elseif (_status != 301) and (_status != 307) then
      _receiver.failure(_status, consume y, "")
    end