k6 icon indicating copy to clipboard operation
k6 copied to clipboard

Type of Response.request.headers[header] is not string

Open rugleb opened this issue 3 years ago • 2 comments

Brief summary

Short snippet:

import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.3.0/index.js'

const X_REQUEST_ID = 'X-Request-Id'

const fetch = (path, payload) => {
    const params = {
        headers: {
            [X_REQUEST_ID]: uuidv4(),
        },
    }
    return session.post(path, payload, params)
}

const responseHasRequestID = () => {
    return (response) => {
        return response.headers[X_REQUEST_ID] === response.request.headers[X_REQUEST_ID][0]  // problem here
    }
}

export default () => {
    const response = fetch('/health', '')
    check(response, {
        'response has X-Request-ID header': responseHasRequestID(),
    })

Problem: typeof response.request.headers[X_REQUEST_ID] is object array, not string as expected.

From docs:

Response.request.headers object Request headers.

I think Response.request.headers should be an object with string-string pairs, not string-objectarray. Or change the docs with explain of this object type.

k6 version

0.37.0

OS

Doesn't matter, run on docker

Docker version and image (if applicable)

0.37.0

Steps to reproduce the problem

See summary

Expected behaviour

typeof response.request.headers[X_REQUEST_ID] is string

Actual behaviour

typeof response.request.headers[X_REQUEST_ID] is array

rugleb avatar Jun 09 '22 09:06 rugleb

Thanks for opening this issue! This is a simpler code snippet to demonstrate it:

import http from 'k6/http';

export default function () {
    let resp = http.get('https://httpbin.test.k6.io/anything', { headers: { foo: 'bar' } });
    console.log(JSON.stringify(resp.request.headers, null, 4));
}

it will result in this log message:

{
    "Foo": [
        "bar"
    ],
    "User-Agent": [
        "k6/0.38.3 (https://k6.io/)"
    ]
}

I think this is because the http.Header type from the Go standard library is map[string][]string, so that is what we use to represent Response.headers in our JS API :disappointed: https://github.com/grafana/k6/blob/27f2ead359dac24b4131dac4b57c95c058f06e48/lib/netext/httpext/response.go#L91 https://github.com/grafana/k6/blob/27f2ead359dac24b4131dac4b57c95c058f06e48/lib/netext/httpext/request.go#L55

While that format falls more in line with the actual reality that HTTP requests can sometimes contain multiple headers with the same name (see this and this for RFC links and discussions), in k6 we don't allow users to do that, since request headers are specified with a simple {key: value} map...

And we haven't documented exactly what the structure of Response.request.headers is, just that it's an object that contains the "Request headers" :thinking: So yeah, we should either:

  1. fix the JS type, make Response.request.headers into a {string: string} map to match Request.headers
  2. or, document the current type structure properly in the docs

I'm leaning towards option number 1... @grafana/k6-devs, what do you think?

na-- avatar Jun 09 '22 10:06 na--

I am for 2. - documenting it.

This has been the behaviour forever. So this will be a breaking change.

That plus the fact that k6/http is more or less in maintenance mode until we get to implement the new HTTP API :tm:.

While this likely is a lot less used than response.headers I would expect other users have come across response.request.headers, figured it out the same way @rugleb did and then just used it in their scripts.

Given that we can't know how many people use it (or if at all for that matter) and this hasn't been brought up before - I would argue it's better to just document it. It seems to be of small enough usefulness in general that breaking it will not improve the experience for most users, but will break it for the ones that already use it.

mstoykov avatar Jun 09 '22 13:06 mstoykov