kit icon indicating copy to clipboard operation
kit copied to clipboard

Way to access the response body size in the hook

Open acarl005 opened this issue 1 year ago • 5 comments

Describe the problem

I want to set up logging in my sveltekit hook. One of the things I want to log is the number of bytes in the response body. It would be nice if the Response exposed the body length so that we can access it. Something like this...

export async function handle({ event, resolve }) {
    response = await resolve(event)
    // these don't actually work, they are undefined. But something LIKE this would be nice
    console.log(response.body.length)
    console.log(response.headers.get("content-length"))
    return response
}

I notice that the length is somewhere on the object if I just console.log(response) when returning JSON...

Response {
  [Symbol(realm)]: { settingsObject: {} },
  [Symbol(state)]: {
    aborted: false,
    rangeRequested: false,
    timingAllowPassed: false,
    requestIncludesCredentials: false,
    type: 'default',
    status: 200,
    timingInfo: null,
    cacheState: '',
    statusText: '',
    headersList: HeadersList {
      [Symbol(headers map)]: [Map],
      [Symbol(headers map sorted)]: null
    },
    urlList: [],
    body: {
      stream: undefined,
      source: '{"result":{"rows":[["to become more autonomous in data analysis"],["to better structure your thoughts"],["to develop a useful professional skill"]],"columns":[{"name":"reason","type":"VARCHAR"}],"moreRows":false}}',
      length: 213
    }
  },
  [Symbol(headers)]: HeadersList {
    [Symbol(headers map)]: Map(2) {
      'content-type' => 'application/json; charset=utf-8',
      'etag' => '"1ba7x9x"'
    },
    [Symbol(headers map sorted)]: null
  }
}

...but that piece of data is private (attached to a symbol) and I can't access it.

In my opinion, being able to observe information like this in my hook is important to building debuggable, observable web services. Response payload size is an important metric to monitor. Putting that logging logic in the hook seems to be the only way to make sure it is reliably logged.

Describe the proposed solution

The workaround I have right now is await response.clone().text(), but that is not ideal because I have to consume the stream. This is wasteful especially if I only want the length.

Alternatives considered

I suppose I could do something with Object.getOwnPropertySymbols(), but I think that's frowned upon?

Importance

would make my life easier

Additional Information

As I understand it (may be wrong), the content length isn't necessarily always known without reading the stream, but there are many cases where the length is known at the time of the response instantiation, like when JSON or HTML are being returned from a string in memory. Sveltekit should be able to set the length in cases where length is known.

Here is a simple case where content length is known but not exposed: https://stackblitz.com/edit/sveltejs-kit-template-default-9ydsbu?file=src%2Fhooks.js&terminal=dev

acarl005 avatar Jul 28 '22 22:07 acarl005

Response is not an API we've defined: https://developer.mozilla.org/en-US/docs/Web/API/Response

benmccann avatar Jul 28 '22 23:07 benmccann

Thanks for the response @benmccann (see what I did there 😂). Anyways, what if Sveltekit set the content-length header? That wouldn't require alteration of the standard Response API. Is that feasible to do efficiently?

acarl005 avatar Jul 29 '22 00:07 acarl005

This is terrible and you shouldn't use it, but it can go in the hooks handle (really what you already mentioned, I realize after re-reading):

const bytes = await response.clone().arrayBuffer()
console.log('response size:', bytes.byteLength)

You might be able to avoid the clone impact somewhat by returning your own streaming response that you pipe the current response to, counting the size as you do.

CaptainCodeman avatar Jul 30 '22 23:07 CaptainCodeman

In the case where you're rendering a page, you can do this:

let length = 0;

const response = await resolve(event, {
  transformPageChunk({ html }) => {
    length += html.length;
    return html;
  }
});

(In future if we add a stream option you'd need to wait until the markup was rendered before reading the length, but for now length will be accurate as soon as you have the response.)

As of https://github.com/sveltejs/kit/discussions/5748, non-page responses must be generated by you, the app author — we don't have a way to inspect the Response object to determine the length.

Rich-Harris avatar Jul 31 '22 01:07 Rich-Harris

@Rich-Harris Thanks for looking into this. I suppose there are 3 cases here:

  1. pages
  2. page endpoints
  3. standalone endpoints

Getting the content size for (1) pages is taken care of by transformPageChunk as you suggested. As for (3) standalone endpoints, since I am responsible for creating the whole Response, I can calculate content-length and set it myself inside the endpoint handler. However, I don't see a way to do it in (2) page endpoints, since only a POJO is returned.

As a tangent, I'm grateful for the thoughtful changes in #5748. I like the new distinction between standalone endpoints (returning a Response) and page endpoints (returning only data). I definitely had been conflating the 2 up to this point. Overall the changes are nice and I'm excited to upgrade.

acarl005 avatar Aug 03 '22 08:08 acarl005