Question on my implementation of an idempotency plugin

Hello,

I’ve implemented a custom plugin but I needed to do some hacking to achieve what I wanted. Being a beginner with Nginx and Kong, I’d very much like some opinion on whether this is OK or not.

Basically, my plugin stores the response of requests based on a Idempotency-Key header and replays them in case the same key is reused on a subsequent request. I’m trying to achieve the same kind of behaviour you can see on Stripe for example.

To get some context, here’s the overall logic of the plugin:

function MyHandler:access()
  -- Retrieve Idempotency-Key header
  -- If already used, resend previous response immediately.
  -- If not, store an entity in DB in a "pending" state.
end

function MyHandler:log()
  -- Store the response status and body in DB on the entity, and update state to "completed".
end

Now the questions that I have:

  1. I tried to use response() instead of log() first, which seems the most suitable hook, but I realised when upstream server is unavailable, it doesn’t get called, and my entity stays in a “pending” state forever. Did I understand well? Was there a better option than using log()?
  2. In log() I wasn’t able to use the database. I found this thread. I used a timer and it worked, but I don’t really understand why, can anyone enlighten me?
function MyHandler:log()
  
  local saved = {
    ngx_ctx = ngx.ctx,
    idempotency_key_header = idempotency_key_header,
    credential_id = credential_id,
    response_body = kong.service.response.get_raw_body(),
    response_status = ngx.status,
  }

  ngx.timer.at(0, function()
    ngx.ctx.KONG_PHASE = saved.ngx_ctx.KONG_PHASE
    -- Access DB from here
  end
end
  1. To make it work, I needed to call kong.service.request.enable_buffering() in access. I couldn’t find the potential issues of doing that in the documentation, are there any?
  2. In logs(), when upstream is down or there’s a timeout contacting it, there’s no body to read and it fails. I compensated by only reading responses below status 500. Do you think there’s a better condition to use here, like is there a flag like “upstream didn’t answer” somewhere in the context?
  local body
  if ngx.status < 500 then
    body = kong.service.response.get_raw_body()
  end
  1. Finally, I noticed that conveniently, even though I couldn’t read the body of a 502 error for example, I didn’t need to, because returning kong.response.exit(502, nil) in access() will actually add the same body later on. Is that a documented behaviour I can rely on?

Thanks for reading, sorry that’s a lot of question, feel free to answer partially, I’m taking every bit of information. :slight_smile:

Cheers,
Joan