Skip to content

Improve documentation and/or error messages around request headers #235

@technomancy

Description

@technomancy

I've found that there are some easy mistakes you can make when using lua-http as a client which give error messages that are not very clear.

The documentation does not explain how to add request headers either in the introduction section or under new_from_uri. Most other HTTP clients allow you to pass a normal table in when creating your request. It's reasonable that someone might guess this is how lua-http works, but it fails:

local request = req.new_from_uri(url, {["content-accept"]="application/json"})
-- attempt to call a nil value (method 'get')

I think it would be best if a normal table were accepted here, but I understand there are downsides to this due to their lack of orderedness, etc. In any case, the documentation for new_from_uri should definitely mention the fact that it takes two arguments. I think it would also be worth mentioning in the introduction section too. Perhaps the error message could be improved as well to point people in the right direction.

Once you figure out this and construct an actual headers table, its behavior is still subject to some undocumented and unintuitive restrictions. It seems like you must first set the method, then construct the request, then set any other headers you have:

local req_headers = http_headers.new()

-- if you try to set the method pseudo-header *after* the request has been created, the error
-- message is not very good: PROTOCOL_ERROR(0x1): Protocol error detected:
-- All pseudo-header fields MUST appear in the header block before regular header fields
req_headers:append(":method", "POST")

local request = req.new_from_uri("https://example.com/whatever", req_headers)

-- if you try to set a *normal* header *before* the request has been created, you
-- also get the same error message as above, which doesn't actually tell you
-- what you did wrong.
req_headers:append("authorization", "Bearer XYZABC6514478")

In this case as well it would also be useful to either loosen the requirements, or if that's not possible then document the requirements or emit an error message explaining what the user actually did wrong.

Thanks! Once I got over these hurdles, I have found this library to be very useful. I'm willing to help make these changes, but I wanted to check first for agreement before making changes.

Activity

daurnimator

daurnimator commented on Jul 21, 2025

@daurnimator
Owner

the documentation for new_from_uri should definitely mention the fact that it takes two arguments.

huh, I completely forgot about it taking two arguments.
But also I'm not sure it entirely makes sense to expose it publically.
There are plenty of other properties that could be other arguments but are not.
Perhaps we should remove that arg from the public API?

its behavior is still subject to some undocumented and unintuitive restrictions. It seems like you must first set the method, then construct the request, then set any other headers you have:

local req_headers = http_headers.new()

-- if you try to set the method pseudo-header *after* the request has been created, the error
-- message is not very good: PROTOCOL_ERROR(0x1): Protocol error detected:
-- All pseudo-header fields MUST appear in the header block before regular header fields
req_headers:append(":method", "POST")

local request = req.new_from_uri("https://example.com/whatever", req_headers)

-- if you try to set a *normal* header *before* the request has been created, you
-- also get the same error message as above, which doesn't actually tell you
-- what you did wrong.
req_headers:append("authorization", "Bearer XYZABC6514478")

In this case as well it would also be useful to either loosen the requirements, or if that's not possible then document the requirements or emit an error message explaining what the user actually did wrong.

This is all expected and desired: lua-http borrows strongly from the http 2 specification which lays out all of these requirements e.g. https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.1

I'd be happy to add the psuedo-header ordering requirement to the docs in https://daurnimator.github.io/lua-http/0.4/#conventions and/or https://daurnimator.github.io/lua-http/0.4/#http.headers

technomancy

technomancy commented on Jul 22, 2025

@technomancy
Author

This is all expected and desired: lua-http borrows strongly from the http 2 specification which lays out all of these requirements e.g. https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.1

I understand that you must set the method before setting regular headers, but the fact that you can't set the method after the request has already been constructed is very confusing, as is the fact that you can't set normal headers after setting the method but before creating the request.

I guess that this is maybe just a problem that stems from using the undocumented second argument to new_from_uri, and maybe that is the root cause of the problem; having to guess at how to set the headers and finding a way that only kind of works.

I'd be happy to add the psuedo-header ordering requirement to the docs

Yes, but I think it's more important to add an example of how to set headers in a way that is fully-supported. Once you have that, then the existing error messages will at least point you in the right direction.

daurnimator

daurnimator commented on Jul 22, 2025

@daurnimator
Owner

but the fact that you can't set the method after the request has already been constructed is very confusing, as is the fact that you can't set normal headers after setting the method but before creating the request.

Sure you can. See e.g.

local req = request.new_from_uri(uri)
if req_body then
req.headers:upsert(":method", "POST")

I guess that this is maybe just a problem that stems from using the undocumented second argument to new_from_uri, and maybe that is the root cause of the problem; having to guess at how to set the headers and finding a way that only kind of works.

I doubt it: as I was saying above: the second argument to new_from_uri should probably just be straight up removed,

technomancy

technomancy commented on Dec 8, 2025

@technomancy
Author

but the fact that you can't set the method after the request has already been constructed is very confusing, as is the fact that you can't set normal headers after setting the method but before creating the request.

Sure you can. See e.g.

OK, I think I found the problem. You can do it as described above, but only if you use req.headers:upsert. Trying to do the same using req.headers:append causes the error.

For example, this code fails for no discernible reason:

local request = http_request.new_from_uri("https://technomancy.us")
request.headers:append(":method", "GET")
request.headers:append("user-agent", "some-test-thing")
return print(assert(request:go()))
-- /usr/bin/lua: PROTOCOL_ERROR(0x1): Protocol error detected: All pseudo-header fields MUST appear in the header block before regular header fields

It has in fact been added before regular fields, but claims that it has not. Using upsert instead of append fixes the problem, but the documentation implies that it shouldn't make a difference in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @technomancy@daurnimator

        Issue actions