Skip to content

fix(json): do not override custom content-type request header#848

Closed
arcanemachine wants to merge 1 commit intoelixir-tesla:masterfrom
arcanemachine:arcanemachine-patch-1
Closed

fix(json): do not override custom content-type request header#848
arcanemachine wants to merge 1 commit intoelixir-tesla:masterfrom
arcanemachine:arcanemachine-patch-1

Conversation

@arcanemachine
Copy link
Copy Markdown
Contributor

@arcanemachine arcanemachine commented Apr 24, 2026

Preamble

I am working with Elasticsearch, which uses a bulk endpoint that uses NDJSON, and expects a content-type: application/x-ndjson header.

However, Tesla's JSON encoder middleware does not allow the header to be overridden, so I have to use workarounds to set the header when sending the request:

defp client(opts \\ []) do
  request_content_type = Keyword.get(opts, :request_content_type, "application/json")

  base_headers = [{"accept", "application/json"}]

  # Set custom headers and middleware based on the payload type. (This is a workaround to fix
  # Tesla's lack of support for Elasticsearch's bulk NDJSON endpoint)
  {headers, tesla_json_middleware} =
    case request_content_type do
      "application/json" ->
        {base_headers, {Tesla.Middleware.JSON, engine: JSON}}

      "application/x-ndjson" ->
        headers = base_headers ++ [{"content-type", "application/x-ndjson"}]

        {headers, {Tesla.Middleware.DecodeJson, engine: JSON}}
    end

  middleware =
    [
      {Tesla.Middleware.BaseUrl, base_url},
      {Tesla.Middleware.Headers, headers},
      tesla_json_middleware,
      # Other middleware excluded for brevity...
    ]

  Tesla.client(middleware)
end

Solution

This pull request resolves the issue by allowing the content-type header to be overridden, but falls back to the default application/json if no custom content-type header is present in the request.

This allows me to remove my workaround logic and just use plain old Tesla.Middleware.JSON in my client with the custom header that is required by Elasticsearch.

@arcanemachine
Copy link
Copy Markdown
Contributor Author

arcanemachine commented Apr 24, 2026

I think the "Semantic PR Title" check failed... I believe my PR title is in line with the requirements.

Error message:

Error: Resource not accessible by integration

Comment thread lib/tesla/middleware/json.ex Outdated
env
|> Tesla.put_body(body)
|> Tesla.put_headers([{"content-type", encode_content_type(opts)}])}
env_has_content_type_header =
Copy link
Copy Markdown
Member

@yordis yordis Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the downcase actually required? under what conditions (adapter specific I am guessing)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use Tesla.get_header?

Copy link
Copy Markdown
Contributor Author

@arcanemachine arcanemachine Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re: Downcase - I figured that would be best in the event that some misconfigured server demands Content-Type or similar instead of complying with the (case-insensitive) spec.

Re: Use Tesla.get_header - I was not aware of that function. To resolve the issue an idiomatic manner, I have force-pushed a change to my branch to use that function instead.

The reason I did that extra downcasing work in my initial commit was to accommodate a future situation where poorly-written server demands e.g. a case-sensitive Content-Type header instead of complying with the spec. For my needs, though, Tesla.get_header/2 works fine.

@arcanemachine arcanemachine force-pushed the arcanemachine-patch-1 branch from 46f0fd1 to a444a98 Compare April 24, 2026 21:57
Comment on lines +320 to +323
test "does not put own content-type header if one is already present and body is encodable" do
env = %Tesla.Env{body: %{}, headers: [{"content-type", "application/x-ndjson"}]}

{:ok, %Tesla.Env{headers: headers}} = Tesla.Middleware.JSON.encode(env, [])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be something explicit as part of the middleware?

{Tesla.Middleware.JSON, content_type: "application/x-ndjson"}

@arcanemachine arcanemachine changed the title fix(json): allow custom content-type request header fix(json): do not override custom content-type request header Apr 25, 2026
@arcanemachine arcanemachine force-pushed the arcanemachine-patch-1 branch from a444a98 to 98b99f3 Compare April 25, 2026 06:12
@arcanemachine
Copy link
Copy Markdown
Contributor Author

I just realized I had a total XY problem issue.

I was having issues with the headers, but my solution fixed the problem for the wrong reason.

The JSON middleware already exposes options to set the request headers, which weren't working because my payload was technically a binary (NDJSON is a list of JSON objects, so not "encodable" per JSON middleware).

Long story short: My PR is not needed, I was able to set the headers correctly, and was getting errors from another endpoint which was misconfigured and causing errors for an unrelated reason, which I thought was related to the same issue, but was actually something else.

Sorry for wasting your time. 😅

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants