Skip to content

Commit

Permalink
Implemented round-robin load balancing in the reverse proxy.
Browse files Browse the repository at this point in the history
  • Loading branch information
nmihajlovski committed Aug 19, 2016
1 parent 140f70f commit 8915d3a
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 25 deletions.
3 changes: 3 additions & 0 deletions rapidoid-commons/src/main/resources/rapidoid-classes.txt
Expand Up @@ -537,8 +537,11 @@ org.rapidoid.render.Templates
org.rapidoid.render.TemplateToCode
org.rapidoid.render.XNode
org.rapidoid.reverseproxy.AbstractReverseProxyBean
org.rapidoid.reverseproxy.LoadBalancer
org.rapidoid.reverseproxy.ProxyMapping
org.rapidoid.reverseproxy.Reverse
org.rapidoid.reverseproxy.ReverseProxy
org.rapidoid.reverseproxy.RoundRobinLoadBalancer
org.rapidoid.scan.ClasspathUtil
org.rapidoid.scan.Scan
org.rapidoid.scan.ScanParams
Expand Down
Expand Up @@ -7,7 +7,7 @@

/*
* #%L
* rapidoid-web
* rapidoid-http-server
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
Expand All @@ -29,8 +29,6 @@
@Since("5.2.0")
public abstract class AbstractReverseProxyBean<T> extends RapidoidThing {

private volatile String host;

private volatile boolean reuseConnections = true;

private volatile int maxConnTotal = 100;
Expand Down Expand Up @@ -58,15 +56,6 @@ protected T me() {
return (T) this;
}

public String host() {
return host;
}

public T host(String host) {
this.host = host;
return me();
}

public boolean reuseConnections() {
return reuseConnections;
}
Expand Down
@@ -1,12 +1,8 @@
package org.rapidoid.reverseproxy;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;

/*
* #%L
* rapidoid-web
* rapidoid-http-server
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
Expand All @@ -24,12 +20,14 @@
* #L%
*/

import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.http.Req;

@Authors("Nikolche Mihajlovski")
@Since("5.2.0")
public class Reverse extends RapidoidThing {
public interface LoadBalancer {

public static ReverseProxy proxy() {
return new ReverseProxy();
}
String getTargetUrl(Req req);

}
@@ -0,0 +1,70 @@
package org.rapidoid.reverseproxy;

/*
* #%L
* rapidoid-http-server
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.http.Req;

import java.util.List;

@Authors("Nikolche Mihajlovski")
@Since("5.2.0")
public class ProxyMapping extends RapidoidThing {

private final String prefix;

private final List<String> targets;

private volatile LoadBalancer loadBalancer = new RoundRobinLoadBalancer(this);

public ProxyMapping(String prefix, List<String> targets) {
this.prefix = prefix;
this.targets = targets;
}

public String prefix() {
return prefix;
}

public List<String> targets() {
return targets;
}

public boolean matches(Req req) {
return req.path().startsWith(prefix);
}

public String getTargetUrl(Req req) {
return loadBalancer.getTargetUrl(req);
}

public LoadBalancer loadBalancer() {
return loadBalancer;
}

public ProxyMapping loadBalancer(LoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
return this;
}

}
@@ -0,0 +1,47 @@
package org.rapidoid.reverseproxy;

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.setup.On;

/*
* #%L
* rapidoid-http-server
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

@Authors("Nikolche Mihajlovski")
@Since("5.2.0")
public class Reverse extends RapidoidThing {

private static volatile ReverseProxy DEFAULT_PROXY;

public static synchronized ReverseProxy proxy() {
if (DEFAULT_PROXY == null) {
DEFAULT_PROXY = newProxy();
On.req(DEFAULT_PROXY);
}

return DEFAULT_PROXY;
}

public static ReverseProxy newProxy() {
return new ReverseProxy();
}

}
Expand Up @@ -6,7 +6,10 @@
import org.rapidoid.concurrent.Callback;
import org.rapidoid.http.*;
import org.rapidoid.http.impl.HttpIO;
import org.rapidoid.log.Log;
import org.rapidoid.u.U;

import java.util.List;
import java.util.Map;

/*
Expand All @@ -33,20 +36,27 @@
@Since("5.2.0")
public class ReverseProxy extends AbstractReverseProxyBean<ReverseProxy> implements ReqRespHandler {

private final List<ProxyMapping> mappings = U.list();

@Override
public Object execute(final Req req, final Resp resp) throws Exception {

ProxyMapping mapping = findMapping(req);
if (mapping == null) return null; // not found!

req.async();

Map<String, String> headers = req.headers();
String targetUrl = mapping.getTargetUrl(req);

Map<String, String> headers = req.headers();
headers.remove("transfer-encoding");
headers.remove("content-length");

HttpClient client = getOrCreateClient();

client.req()
.verb(req.verb())
.url(host() + req.uri())
.url(targetUrl)
.headers(headers)
.cookies(req.cookies())
.body(req.body())
Expand All @@ -72,7 +82,17 @@ public void onDone(HttpResp result, Throwable error) {
return req;
}

public void processResponseHeaders(Map<String, String> headers, Resp resp) {
protected ProxyMapping findMapping(Req req) {
for (ProxyMapping mapping : mappings) {
if (mapping.matches(req)) {
return mapping;
}
}

return null;
}

protected void processResponseHeaders(Map<String, String> headers, Resp resp) {
for (Map.Entry<String, String> hdr : headers.entrySet()) {
String name = hdr.getKey();
String value = hdr.getValue();
Expand All @@ -87,7 +107,7 @@ public void processResponseHeaders(Map<String, String> headers, Resp resp) {
}
}

public boolean ignoreResponseHeader(String name) {
protected boolean ignoreResponseHeader(String name) {
return name.equals("transfer-encoding")
|| name.equals("content-length")
|| name.equals("connection")
Expand All @@ -104,4 +124,13 @@ protected HttpClient createClient() {
.maxConnPerRoute(maxConnPerRoute());
}

public ProxyMapping map(String uriPrefix, List<String> targets) {
Log.info("Reverse proxy mapping", "!uriPrefix", uriPrefix, "!targets", targets);

ProxyMapping mapping = new ProxyMapping(uriPrefix, targets);
mappings.add(mapping);

return mapping;
}

}
@@ -0,0 +1,56 @@
package org.rapidoid.reverseproxy;

/*
* #%L
* rapidoid-http-server
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/

import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.commons.Str;
import org.rapidoid.http.Req;

import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

@Authors("Nikolche Mihajlovski")
@Since("5.2.0")
public class RoundRobinLoadBalancer extends RapidoidThing implements LoadBalancer {

private final ProxyMapping mapping;

private final AtomicLong counter = new AtomicLong();

public RoundRobinLoadBalancer(ProxyMapping mapping) {
this.mapping = mapping;
}

@Override
public String getTargetUrl(Req req) {
long n = counter.incrementAndGet();

List<String> targets = mapping.targets();
int index = (int) (n % targets.size());
String target = targets.get(index);

String trimmed = Str.triml(req.uri(), mapping.prefix());
return target + trimmed;
}

}

0 comments on commit 8915d3a

Please sign in to comment.