diff --git a/README.md b/README.md index 45e58e1b47a03e3baedf5588d130b5c9d994ff15..2e50a5dcf2fdd5eec60c84fc0a98a226aaf135dd 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,157 @@ -consul_proxy +Consul Proxy ============ -An OTP application +A service router and load balancer that uses Consul for configuration and service lookups. -Build ------ +Building +-------- ```bash rebar3 compile ``` -Testing +Running ------- ```bash -while true ; do echo -e "HTTP/1.1 200 OK\r\nConnection:close\r\n\r\n1:$(date)" | nc -l 8081 ; done -while true ; do echo -e "HTTP/1.1 200 OK\r\nConnection:close\r\n\r\n2:$(date)" | nc -l 8082 ; done +docker run --name consul_proxy --net=host -d \ + -e CONSUL_PROXY_HTTP_PORT="80" \ + -e CONSUL_PROXY_CONSUL_URLS="\"http://localhost:8500\"" \ + -p 80:80 \ + -p 8081:8081 \ + -p 8082:8082 \ + -p 8083:8083 \ + erlangninja/consul_proxy ``` +Configuration +------------- -Running -------- +### Domains -```bash -docker run --name consul_proxy -d --net=host -e CONSUL_PROXY_CONSUL_URL="\"http://10.1.4.80:8500\"" -p 80:8080 ehedenst/consul_proxy:arm +Path is `consul_proxy/domains/[DOMAIN]` + +#### Services + +```javascript +{ + "ServiceName" : "...", + "ServiceID" : "...", + "ServiceTags" : "..." +} ``` -Todo ----- +#### Nodes -- Allow explicit setting of upstream/downstream headers +```javascript +{ + "ServiceNodes" : [ + "ServiceHost" : "...", + "ServiceAddress" : "...", + "ServicePort" : "..." + ] +} +``` +#### Resolver -Domain descriptor ------------------ +```javascript +{ + "ServiceResolver" : "..." +} +``` +#### Headers + +```javascript { - "ServiceName" : "test", (String) - "ServiceID" : "fast", (Regexp) - "ServiceTags" : "fast" (Regexp) -} \ No newline at end of file + "Headers" : { + "Upstream" : [], + "Downstream" : [] + } +} +``` + +### Scripts + +Path is `consul_proxy/scripts/[NAME]` + +Properies passed to the script are a combination of the domain configuration and the following request propeties. + +```erlang +{request, [ + {method, "..."}, + {domain, "..."}, + {port, "..."}, + {path, "..."}, + {query, []}, + {headers, []}, + {cookies, []}, + {meta, [ + {request_id, "..."}, + {initial_host, "..."}, + {host_capture, "..."} + ]} +]}. +``` + +#### Erlang + +When the properties are passed to an Erlang script the properties are bound as a proplist to the variable `Props`. The script must return a proplist that contains the domain configuration. + +```erlang +Request = proplists:get_value(request, Props, []), +Meta = proplists:get_value(meta, Request, []), +Capture = proplists:get_value(host_capture, Meta, []), +ServiceName = proplists:get_value(<<"service">>, Capture), +[{<<"ServiceName">>, ServiceName}]. +``` + +#### Lua + +When the properties are passed to a Lua script the properties are bound as a table to the variable`props`. The script must return a table that contains the domain configuration. + +```lua +return {ServiceName = props.request.meta.host_capture.service}; +``` + +### Watchers + +Path is `consul_proxy/watchers/(kv | nodes | services)` + +### Hijackers + +Path is `consul_proxy/hijackers` + +```javascript +[ + { + "username" : "...", + "password" : "...", + "domains" : [ + "...." + ] + } +] +``` + +Middlewares +----------- + +### Alias + +### Auth + +### Redirect + +### Rewrite + +### ACME + +### Hijack + +Load Balancing +-------------- + +Logging +------- diff --git a/apps/consul_proxy/src/consul_proxy_router.erl b/apps/consul_proxy/src/consul_proxy_router.erl index 30e07e80c135489651ed526148289ee9db3d7300..6ce1bba92c64b6986fd40e505a1382a7d4a4cf42 100644 --- a/apps/consul_proxy/src/consul_proxy_router.erl +++ b/apps/consul_proxy/src/consul_proxy_router.erl @@ -91,19 +91,14 @@ lookup_services(Domain, Upstream, State) -> undefined -> case consul_proxy_utils:get_and_decode(<<"consul_proxy/domains/", Domain/binary>>) of {ok, Properties} -> - {Capture, Upstream1} = cowboyku_req:meta(host_capture, Upstream, []), - InterpolatedProperties = consul_proxy_utils:string_interpolate(Properties, Capture, [<<"Rewrite">>]), - ServiceName = proplists:get_value(<<"ServiceName">>, InterpolatedProperties), - ServiceIDRE = proplists:get_value(<<"ServiceID">>, InterpolatedProperties, <<".*">>), - ServiceTagsRE = proplists:get_value(<<"ServiceTags">>, InterpolatedProperties, <<".*">>), - case lookup_services(Domain, Upstream1, InterpolatedProperties, {ServiceName, ServiceIDRE, ServiceTagsRE}, true) of + case lookup_services(Domain, Upstream, Properties, true) of {ok, Services, true} -> consul_proxy_utils:cache_add(<<"services:", Domain/binary>>, {ok, Services}), - {ok, Services, Upstream1, State}; + {ok, Services, Upstream, State}; {ok, Services, false} -> - {ok, Services, Upstream1, State}; + {ok, Services, Upstream, State}; {error, Reason} -> - {error, Reason, Upstream1, State} + {error, Reason, Upstream, State} end; {error, Reason} -> {error, Reason, Upstream, State} @@ -112,6 +107,14 @@ lookup_services(Domain, Upstream, State) -> {ok, Services, Upstream, State} end. +lookup_services(Domain, Upstream, Properties, CacheFlag) -> + {Capture, _} = cowboyku_req:meta(host_capture, Upstream, []), + InterpolatedProperties = consul_proxy_utils:string_interpolate(Properties, Capture, [<<"Rewrite">>]), + ServiceName = proplists:get_value(<<"ServiceName">>, InterpolatedProperties), + ServiceIDRE = proplists:get_value(<<"ServiceID">>, InterpolatedProperties, <<".*">>), + ServiceTagsRE = proplists:get_value(<<"ServiceTags">>, InterpolatedProperties, <<".*">>), + lookup_services(Domain, Upstream, InterpolatedProperties, {ServiceName, ServiceIDRE, ServiceTagsRE}, CacheFlag). + lookup_services(Domain, Upstream, Properties, {undefined, _ServiceIDRE, _ServiceTagsRE}, CacheFlag) -> lookup_nodes(Domain, Upstream, Properties, proplists:get_value(<<"ServiceNodes">>, Properties), CacheFlag); lookup_services(_Domain, _Upstream, _Properties, {ServiceName, ServiceIDRE, ServiceTagsRE}, CacheFlag) -> @@ -153,28 +156,9 @@ lookup_nodes(Domain, _Upstream, _Properties, Nodes, CacheFlag) -> lookup_resolver(_Domain, _Upstream, _Properties, undefined, _CacheFlag) -> {error, route_lookup_failed}; lookup_resolver(Domain, Upstream, Properties, ServiceResolver, _CacheFlag) -> - case consul_proxy_utils:eval_script(ServiceResolver, build_script_vars(Upstream)) of - {ok, ServiceName} when is_binary(ServiceName) -> - lookup_services(Domain, Upstream, Properties, {ServiceName, <<".*">>, <<".*">>}, false); - {ok, [ServiceName]} when is_binary(ServiceName) -> - lookup_services(Domain, Upstream, Properties, {ServiceName, <<".*">>, <<".*">>}, false); - {ok, {ServiceName, ServiceID}} when is_binary(ServiceName), is_binary(ServiceID) -> - lookup_services(Domain, Upstream, Properties, {ServiceName, ServiceID, <<".*">>}, false); - {ok, {ServiceName, ServiceID, ServiceTags}} when is_binary(ServiceName), is_binary(ServiceID), is_binary(ServiceTags) -> - lookup_services(Domain, Upstream, Properties, {ServiceName, ServiceID, ServiceTags}, false); - {ok, {Host, Port}} when is_binary(Host), is_integer(Port) -> - lookup_nodes(Domain, Upstream, Properties, [[{<<"ServiceHost">>, Host}, {<<"ServicePort">>, Port}]], false); - {ok, Values} when is_list(Values) -> - Nodes = lists:foldl( - fun({Host, Port}, AccIn) when is_binary(Host), is_integer(Port) -> - [[{<<"ServiceHost">>, Host}, {<<"ServicePort">>, Port}] | AccIn]; - (_, AccIn) -> - AccIn - end, [], Values), - lookup_nodes(Domain, Upstream, Properties, Nodes, false); - {ok, Value} -> - lager:error("Can't convert value into nodes: ~p", [Value]), - {error, route_lookup_failed}; + case consul_proxy_utils:eval_script(ServiceResolver, build_script_vars(Properties, Upstream)) of + {ok, Result} -> + lookup_services(Domain, Upstream, Result, false); {error, Reason} -> lager:error("Failed to evaluate '~s': ~p", [ServiceResolver, Reason]), {error, route_lookup_failed} @@ -217,8 +201,8 @@ matches(Subject, RE) -> false end. -build_script_vars(Req) -> - [ +build_script_vars(Props, Req) -> + Props ++ [ {request, [ {method, element(1, cowboyku_req:method(Req))}, {domain, element(1, cowboyku_req:host(Req))}, @@ -233,4 +217,4 @@ build_script_vars(Req) -> {host_capture, element(1, cowboyku_req:meta(host_capture, Req, []))} ]} ]} - ]. \ No newline at end of file + ]. diff --git a/apps/consul_proxy/src/consul_proxy_utils.erl b/apps/consul_proxy/src/consul_proxy_utils.erl index 2faae062556226bca16866faf89f149e61775c5c..cb0dddda9faf8f5377aeca224e20c46cdc7bba20 100644 --- a/apps/consul_proxy/src/consul_proxy_utils.erl +++ b/apps/consul_proxy/src/consul_proxy_utils.erl @@ -205,15 +205,38 @@ eval_script(Name, Vars, <<".erl">>) -> end end; eval_script(Name, Vars, <<".lua">>) -> - case consul_client:get(<<"consul_proxy/scripts/", Name/binary>>) of - {error, Reason} -> - {error, Reason}; - {ok, Binary} -> + Key = <<"lua_script:", Name/binary>>, + Parsed = case cache_get(Key) of + undefined -> + case consul_client:get(<<"consul_proxy/scripts/", Name/binary>>) of + {error, Reason} -> + {error, Reason}; + {ok, Binary} -> + case luerl:load(Binary) of + {error, Reason} -> + {error, Reason}; + {ok, Chunk, InitialState} -> + cache_add(Key, {Chunk, InitialState}), + {ok, Chunk, InitialState} + end + end; + {Chunk, InitialState} -> + {ok, Chunk, InitialState} + end, + case Parsed of + {error, Reason1} -> + {error, Reason1}; + {ok, Chunk1, InitialState1} -> State = lists:foldl( fun({Path, Value}, AccIn) -> luerl:set_table(Path, Value, AccIn) - end, luerl:init(), vars_to_table([props], Vars)), - luerl:eval(Binary, State) + end, InitialState1, vars_to_table([props], Vars)), + case luerl:eval(Chunk1, State) of + {ok, [Result]} -> + {ok, Result}; + Error -> + Error + end end; eval_script(Name, _Vars, _Suffix) -> {error, {unknown_language, Name}}.