Closed DavidTPate closed 9 years ago
+1 simply for the cost of ssl certs
Great idea! I've added a request label, maybe @thefosk or @thibaultCha could chime in on the implementation details as I don't think this is something that could be added as a plugin since the routing is handled by the Kong core.
This is a feature I have been thinking for a while, and I really like the idea. Will draft out a simple implementation.
proxy is a good aproach i guess we can have them standalone next to each other
like
api1.example.com api2.example.com api3.example.com
and after that we setup a new subdomain and add the "api id" + a location folder
not sure if its that trivial but that would not change the way the actual routing works instead it acts as a compliment layer
just my 2 cents here .. please ignore if im beeing missleading with a simplistic aproach
:+1:
I've been thinking of this feature for making an abstraction layer between consumer and internal API.
Glad to hear that you guys think it is a useful feature. Another thing to keep in mind is that this would also likely be used for micro-services and splitting up functionality.
So keeping with my previous example and just focusing on auth
service available at: api.externaldomain.com/auth
. I would have a route setup to send the path /auth
to the server at auth.internaldomain.com
.
if I noticed that my login functionality was taking lots of hits and causing me to scale even though the rest of my end-points weren't experiencing much load I would split this up to do something like the following. The path api.externaldomain.com/auth/login
would be sent to login.internaldomain.com
and api.externaldomain.com/auth
would remain sending to auth.internaldomain.com
.
I think this comes down to just the fact that order or specificity matters. I think order (where first match is the one that matters) is the more common approach due to it being simpler to implement. So in my example the order of my APIs would be something like this:
api.externaldomain.com/auth/login
-> login.internaldomain.com
api.externaldomain.com/auth
-> auth.internaldomain.com
api.externaldomain.com/pizza
-> pizza.internaldomain.com
api.externaldomain.com/tacos
-> tacos.internaldomain.com
I like the idea :+1:
Your latest example (with an order of priority to define the routing) is something that we are used to see in code, but I am curious as to what it would look like when configuring it with simple API calls... A priority value maybe?
As I've been going through trying out other API gateways I thought Tyk had some easy to understand options that were useful even in my simple testing. For the configuration of their Proxy piece they have a few simple options which I think would serve as a good starting point.
/api
or /
/listen-path/widgets/new
and the URL to proxy to is http://your.api.com/api/
becomes http://your.api.com/api/listen-path/widgets/new
, actually changes the outgoing request to be: http://your.api.com/api/widgets/new
.@thibaultCha Yeah, it's a weird one when dealing with an API. You obviously don't want to go by creation date, or update the entire set of APIs. The way that AWS does it for things like Network ACLs which I think is simple is that they have you put in a kind of sort order
value. So keeping with my previous example:
Rule Number | Path | Target |
---|---|---|
100 | /auth/login |
login.internaldomain.com |
200 | /auth |
auth.internaldomain.com |
300 | /pizza |
pizza.internaldomain.com |
400 | /tacos |
tacos.internaldomain.com |
I think it clearly shows the order (you would just need to make sure Rule Number
is unique when sent), and provides an easy for me to insert something between /pizza
and /tacos
by simply giving it a rule number such as 350
.
On an other note, maybe the resolvers too could be plugins. We call the current resolver (by Host
header) a core-plugin, since it is identifying an API in the system from a user's request. One resolver could work with the Host
header, and another with paths.
Btw, it is worth noting that this is also close to how NGINX natively supports proxying, even if less flexible than what you are describing @DavidTPate:
location /match/this {
proxy_pass http://example.com/;
}
# A request sent to `/match/this/auth` will be sent to upstream as `/match/this/auth`
location /match/this {
proxy_pass http://example.com/new/path;
}
# A request sent to `/match/this/auth` will be sent to upstream as `/new/path/auth`
I was thinking about such a rule @DavidTPate. Here the interval between two routes is huge but to be 100% future proof, if one decides to insert a route at a value that already exists we could also "push" all values after the one being inserted.
I don't see another solution right now than this, but it seems decent. An UI can easily and nicely deal with this on top of the admin API.
I really like this idea. We at QuizUp developed our own nginx based routing solution where we route based on the request path (location).
Additionally, something I would be very interested in seeing (and potentially developing in this project) is support for registering backend nodes directly with Kong. This is to avoid having to rely on (potentially stale) DNS records for discovering backends. Having a complete list of backends (internal IPs usually) per microservice would also allow for more sophisticated load balancing algorithms to be employed (I am thinking least connection, and since there is the possibility of sharing state via cassandra, this makes a lot of sense to me!).
Awesome project btw :-)
@steinnes Could what you described somewhat be related to #157? Load balancing your API with Kong?
Absolutely similar. The #157 issue seems quite focused on how nginx does this, but I assume we could do this in two ways.
*1. Deltas (ie. add/rem particular backend/upstream from an api):
curl -XPOST --url http://localhost:8001/apis/backends/add \
--data 'name=mockbin' \
--data 'upstream=10.0.0.99:1234'
or
curl -XPOST --url http://localhost:8001/apis/backends/rem \
--data 'name=mockbin' \
--data 'upstream=10.0.0.99:1234'
*2. Complete overwriting of upstreams (basically a "set" operation):
curl -XPOST --url http://localhost:8001/apis/backends/set \
--data 'name=mockbin' \
--data 'upstreams=10.0.0.99:1234,10.0.0.88:4321,10.0.0.77:5678'
Or whatever makes most sense. I just came across your project and immediately decided to start suggesting stuff -- but in my defence, if you guys like the ideas I wouldn't mind contributing :-)
:+1: We are using a home-built nginx routing to different upstreams based on the request path in the API. We replace the proxy_pass using a variable after matching the path pattern in a regex.
This will also help in having endpoint level rate limiting/throttling.
Would love to have this in kong. Will make our migration to kong much easier!
+1
+1
+1
Here's a real example of where I do routing based on endpoints in nginx:
server {
server_name img.apistatus.org;
location /online {
proxy_pass http://127.0.0.1:4445/;
}
location /status {
proxy_pass http://127.0.0.1:4446/;
}
}
Before implementing this, a quick follow-up to see what we think about it.
We currently have a resolver that we can call the "host resolver". I shall refer to the resolver described by @DavidTPate as the "path resolver".
It would be nice to be able to configure an API wether its routing should happen by host or by path. Say the API now has 2 properties: public_dns
(for the host resolver) and path
(for the path resolver).
My 2 cents: separate those resolvers but keep them bundled into the core. Have an API use one or the other depending on which property is set. Refuse an API that uses both.
A nice solution would be to now separate the properties used by the resolvers and the APIs:
host_resolver
table, which maps a public_dns
to an API.path_resolver
table, which maps a path
to an API.public_dns
property from the current apis
table.target_url
property on the apis
table to upstream
(bonus).@thefosk @montanaflynn thoughts?
That sounds like a good solution to me. I could see someone attempting to use both a "host resolver" and a "path resolver" but to me that screams of poor API design and I'm honestly not sure if even Nginx has the ability to do both (without duplication of configuration).
@thibaultCha I would say to allow for both with only one or the other being required.
This way you can set up multiple APIs in one Kong install and still handle all the above use cases that @DavidTPate described. The ordering for how Kong would pick which one could be like this:
There's a good answer on stackoverflow about how nginx handles prioritizing paths.
Here's a bigger snippet of the nginx config I put above showing how I'm matching by host & path. Two paths are the same but lead to different outcomes dependent on the host. You'll also notice that I'm using regex in the path which is something that we should consider as well.
server {
# Matches this host
server_name img.apistatus.org;
# And this path
location /online {
proxy_pass http://127.0.0.1:4445/apistatus/online;
}
# Or this path
location /status {
proxy_pass http://127.0.0.1:4446/apistatus/status;
}
# Or this path
location /robots.txt {
return 200 "User-agent: *\nAllow: /";
}
}
server {
# Matches this host
server_name apistatus.org;
# this path matches if none of the others do
location / {
root /usr/share/nginx/www/apistatus;
index index.html;
}
# Or this path which is also defined above
location /robots.txt {
return 200 "User-agent: *\nDisallow: /";
}
# Or this path which uses regex
location ~* \.(gif|jpg|jpeg)$ {
rewrite ^/images/(.*)(png|jpg|gif)$ http://127.0.0.1:4447/images/$1$2 redirect;
return 302;
}
}
Matches both host and path Matches host Matches path
@montanaflynn I agree with this.
+1
+1
this feature would be a requirement for us to adopt kong. (+1)
I just would like to tell that this feature is coming in the 0.3.0
release about 3/4 weeks from today.
@thefosk Thanks for the quick response :+1:
:tada:
There are a lot of things we need to figure out before implementing this.
strip
property (ignored here).Considering this, and the way we want to query Cassandra, all that by keeping our RESTful configuration capabilities, this is a potential model (and the most valid I could think of):
CREATE TABLE apis(
id uuid,
name text,
PRIMARY KEY(id)
);
CREATE TABLE hosts(
id uuid,
api_id uuid, -- foreign to apis.id
public_dns text,
target_url text,
PRIMARY KEY(id)
);
CREATE TABLE paths(
id uuid,
api_id uuid, -- foreign to apis.id
host_id uuid, -- useful to require this Path to first match a Host (rule 3)
listen_path text,
priority int,
target_url text,
PRIMARY KEY(id, priority) -- priority allows us to ORDER BY a query, but I would probably rather do that in the application level
);
CREATE INDEX ON hosts(public_dns);
CREATE INDEX ON paths(listen_path);
This schema allows us to follow the requirements:
host_id
host_id
and it matches the one of the previously found Host(s), Path is valid -> redirecthost_id
or it doesn't match the previously found Host -> nextThe problems here are:
/path1
, /path1/overriden
), this schema will force us to query all Paths from the DB to be able to compare them with the current URI. That is actually the case for all models except the presented schema 2.host_id
.Proposed workarounds
I also considered such a schema:
CREATE TYPE path(
listen_path text,
priority int,
host text,
target_url text
);
CREATE TYPE host(
public_dns text,
target_url text
);
CREATE TABLE apis(
id uuid,
name text,
host host, -- one Host
paths frozen<set<path>>, -- multiple Paths
PRIMARY KEY(id)
);
But it arises more concerning problems:
listen_path
without knowing the other properties of a Path type in advance. I wasn't able to find a way to index Collections of UDTs in Cassandra. From here, I see 2 solutions:
Finally, another problem to consider is that almost any schema change will require a heavy migration. By heavy I mean moving data around, possibly by providing a script or something to migrate from the current apis
table to any of the newly created tables. That means our migrations will not be able to do the job. We need something that:
or
All that should be done with users doing a backup of their data first. Kong is not 1.0 yet so I don't see handling that as a priority. Users should expect having to reconfigure their APIs if they want to upgrade.
I have some feedback and questions.
Host
? Likewise with paths, an API will be matched if any of the Hosts matches.[A-Za-z0-9_\-]+\.myapi\.com
, that match all the paths specific to a user like user1.myapi.com
or user2.myapi.com
, etc. If we decide that a full regex support can't be implemented, or it will take too long, it would be nice to at least cover this one scenario (which from my experience with Mashape, will cover 90% of the times a regex will be used). It can be done in many ways without introducing a real regex support.apis
(similar to your second solution), and query it like:SELECT id FROM apis WHERE public_dns = ? OR listen_path = ?;
or (if possible in Cassandra):
SELECT id FROM apis WHERE public_dns CONTAINS ? OR listen_path CONTAINS ?;
Not sure if there is any limitation with Cassandra if we do this.
priority
, I would say to remove it in the first implementation if that will allows us to use a simpler schema.we could also allow multiple Host
Yeah I thought about it, but it brings a lot of configurations headaches, because one could have 2 hosts, 2 paths that only validates if Path A + Host A, and Path B + Host B, but it can be extremely confusing very fast. But having 1 Host and X Paths, we respect the nginx behaviour as showed in the examples in this thread. I think it does more harm than anything.
I think it's important to support regular expression
See my point about supporting it: it means everything will have to be in memory and the routing will be O(n), because we need to compare a path against every configured Path. Also your example is a Host? Even if we support 1 Host per API, same, we would need to have every Host in memory too. (See the conclusion about that)
Regarding the schemas, can't we just add one more field to apis and query it like:
Cassandra does not have support for such an OR
.
Regarding the priority, I would say to remove it in the first implementation if that will allows us to use a simpler schema.
If we drop this we absolutely cannot have multiple Paths per API like @DavidTPate described it (/auth/login
and /auth
would overlap). Ex: if one sets a listen_path to: /pizza/
, another to /pizza/hello
and queries /pizza/hello/world
, which listen_path gets applied? We can't know without a priority value, or having them ordered as an array.
Our problems are:
priority
property.listen_path
to: /pizza/api
and queries /pizza/api/hello/world
. From the code's POV, am I supposed to query Cassandra with /pizza/
, /pizza/api/
, /pizza/api/hello/
or /pizza/api/hello/world/
. That is why we need to support something like "starts with or strict" modes, or just regex in a first version.To conclude, if we want to stick with those requirements, and fix 1, 2, 3, 4, I think we have no choice but to load the Host(s) (plural if we decide to support many Hosts, but that brings configuration concerns as mentioned) and Paths in memory. And somehow reload them when they get modified. After all, it is what nginx is doing too, except you don't expect a configuration file to have tens of APIs, where you can expect Kong to have such a number. Schema 2 or equivalent would be valid in that case.
Keep one host for this version. Simpler.
Just brainstorming here, but another options would be having only one property called matchers
or patterns
(or a better name) that contains both the DNS or the path. The table would look like:
CREATE TABLE IF NOT EXISTS apis(
id uuid,
name text,
matchers set<text>,
target_url text,
created_at timestamp,
PRIMARY KEY (id)
);
We could support multiple DNS and multiple paths in one field:
SELECT * FROM apis WHERE matchers CONTAINS 'something.com' ALLOW FILTERING;
or
SELECT * FROM apis WHERE matchers CONTAINS '/hello/world' ALLOW FILTERING;
This won't fix the two-queries problem because I think Cassandra doesn't support SELECT statements to search for multiple values in a field (I might be wrong):
SELECT * FROM apis WHERE matchers IN ('something.com', '/hello/world') ALLOW FILTERING;
First implementation drafted in #282. It only supports 1 path per API. Since supporting all the requested features means a lot of rewritten code, I opted for breaking down the implementation in 2 parts:
Closing this and adding support for multiple path/multiple hosts in one of the upcoming releases. Thank you all!
Hi there! Been messing with this a little bit as I'm vetting API gateways to use. I really like the idea of the project but I'm running into an issue where my main routing to services is done based upon path instead of Host.
Was wondering if I'm just overlooking the feature or if there has been any talk of the addition of such functionality. Details below.
If for example I had 3 services that I was running (let's call them
auth
,pizza
, andtacos
).As best as I understand it Kong is setup to handle this where each of these services might be available internally at:
auth.internaldomain.com
,pizza.internaldomain.com
,tacos.internaldomain.com
and the routing is done based upon someone's intention to go toauth.externaldomain.com
,pizza.externaldomain.com
, ortacos.externaldomain.com
.Instead my setup deals with routing to services based upon a part of the path. So, the equivalent calls would be made to
api.externaldomain.com/auth
,api.externaldomain.com/pizza
, orapi.externaldomain.com/tacos
.From looking through the docs this didn't seem like a feature that currently exists, love to hear your thoughts on it.