Conversation
I don't really get this saying theres no versioning, but then configuring "version_data" ... I know it's done the same way in Tyk, and they probably had a reason for doing so, but it just looks very awkward to me 😅 |
|
|
||
| # Transform request headers | ||
| @spec transform_req_headers(Proxy.endpoint(), Proxy.api_definition(), %Plug.Conn{}) :: | ||
| %Plug.Conn{} |
There was a problem hiding this comment.
I wonder, what's the difference between %Plug.Conn{} and Plug.Conn.t? I've seen the former, which refers to the module's defstruct quite often, but isn't the latter the real type we'd acutally would like to refer to? After all, the struct per se has only default values set, but no types. Might be wrong here. Any opinions?
There was a problem hiding this comment.
Didn't know about Plug.Conn.t, but I think it makes sense since Strings are defined the same way.
| %Plug.Conn{} | ||
| defp transform_req_headers( | ||
| %{"transform_request_headers" => true} = endpoint, | ||
| %{"versioned" => false} = api, |
There was a problem hiding this comment.
Can you please give me an RTFM link to what the meaning of "versioned" is here? Cause it's not obvious to me why versioning an endpoint would affect the headers 😕
There was a problem hiding this comment.
My reasoning behind this is:
versioned: false => means you have only default API, no versions no magic logic around it, you care just about endpoints from default
versioned: true => you have multiple versions, you can't use only default, you have to take care about all versions
if you have versioned: true, you clearly have multiple versions of the same API => every version of API may require different headers transformation (thus how to transform is per version), every endpoint may or may not want do use this header transformation (thus endpoints have to decide if they want to use transformations)
There was a problem hiding this comment.
but doesn't %{"transform_request_headers" => true} = endpoint say that the transformation should be enabled on this specific endpoint?
| conn = %{conn | resp_headers: headers} | ||
| if Serializer.header_value?(conn, "transfer-encoding", "chunked") do | ||
| send_chunked_response(conn, headers, status_code, body) | ||
| down_cased_headers = headers |> Serializer.downcase_headers |
There was a problem hiding this comment.
hm it's downcase in the std lib, it's downcase in Serializer, so I'd go with downcased_headers here
| send_chunked_response(conn, down_cased_headers, status_code, body) | ||
| else | ||
| %{conn | resp_headers: headers} |> send_resp(status_code, body) | ||
| %{conn | resp_headers: down_cased_headers} |> send_resp(status_code, body) |
There was a problem hiding this comment.
isn't this replacing resp_headers a second time?
| alias Plug.Conn.Query | ||
| alias RigInboundGateway.Proxy | ||
|
|
||
| @typep headers_list :: [{String.t, String.t}, ...] |
There was a problem hiding this comment.
I'd prefer either header_list or headers.
Also, can't old_headers be empty, ever? I mean, sending a request without any headers would look more like [{String.t, String.t}]
| |> Enum.find({}, fn({header_key, _}) -> header_key == key end) | ||
| |> Tuple.to_list | ||
| |> Enum.member?(value) | ||
| end |
There was a problem hiding this comment.
hm this work unexpectedly for headers_list=[{"a", "b"}], key="a", value="a", because keys and values are mixed up.
May I suggest a slightly different implementation:
def header_value?(headers, key, value) do
headers
|> Enum.find(fn
{^key, ^value} -> true
_ -> false
end)
|> case do
nil -> false
_ -> true
end
endNot sure about whether pattern matching makes it better or not, but the call to Tuple.to_list/1 definitely seems a bit odd here.
There was a problem hiding this comment.
Nice catch, added test as well for it
| key | ||
| |> String.downcase | ||
| key_downcase == key | ||
| {key_downcase, value} |
There was a problem hiding this comment.
perhaps it would suffice to simply write
{String.downcase(key), value}| old_headers | ||
| |> Enum.filter(fn({old_key, _}) -> | ||
| new_headers | ||
| |> Enum.find(nil, fn({new_key, _}) -> old_key == new_key end) |
There was a problem hiding this comment.
nil is the default "default" value, no need to specify it explicitly
| |> Enum.find(nil, fn({new_key, _}) -> old_key == new_key end) | ||
| |> is_nil | ||
| end) | ||
| |> Enum.concat(new_headers) |
There was a problem hiding this comment.
hm maybe I get this wrong, but it seems this
- takes all old headers that don't occur in
new_headersand - add the new headers to that list.
Would that be easier to read if written this way:
Map.merge(Map.new(old_headers), Map.new(new_headers))
|> Enum.to_list()There was a problem hiding this comment.
Definitely, totally overlooked it.
| assert get_req_header(conn, "john") == [] | ||
| assert conn.status == 200 | ||
| assert conn.resp_body =~ "{\"status\":\"ok\"}" | ||
| end |
There was a problem hiding this comment.
it'd be nice to have a test around how it behaves with versioned set or unset
There was a problem hiding this comment.
Since there is no support for versioning yet, it would crash with ** (FunctionClauseError) no function clause matching. Once we decide to add versioning it definitely makes sense.
| %Plug.Conn{} | ||
| defp transform_req_headers( | ||
| %{"transform_request_headers" => true} = endpoint, | ||
| %{"versioned" => false} = api, |
There was a problem hiding this comment.
but doesn't %{"transform_request_headers" => true} = endpoint say that the transformation should be enabled on this specific endpoint?
| conn = %{conn | resp_headers: downcased_headers} | ||
|
|
||
| if Serializer.header_value?(downcased_headers, "transfer-encoding", "chunked") do | ||
| send_chunked_response(conn, downcased_headers, status_code, body) |
There was a problem hiding this comment.
why are the headers passed here again - conn should already include them at this point
| alias Plug.Conn.Query | ||
| alias RigInboundGateway.Proxy | ||
|
|
||
| @typep headers :: [{String.t, String.t}, ...] |
There was a problem hiding this comment.
What if old_headers is empty? I mean, sending a request without any headers would look more like [{String.t, String.t}]
Fixed regex matching for proxy - previously
/abc/defwould match against/abc/def/1Fixed response headers before response send - Elixir/Plug needs lower cased headers => led to duplicate headers and wrong responses
Added request headers transformations via
add_headers, in future we can addremove_headers, etc.Unit tests and smoke tests should cover everything
Steps to test:
POSTlocalhost:4010/v1/apiswithGETlocalhost:4000/auth/registerwith headera=> proxy should overwrite it and add new headerb"transform_request_headers": truein endpoints will ignore header transformations