Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.jetdrone.vertx.yoke.middleware.Router Maven / Gradle / Ivy
/**
* Copyright 2011-2014 the original author or authors.
*/
package com.jetdrone.vertx.yoke.middleware;
import com.jetdrone.vertx.yoke.Middleware;
import com.jetdrone.vertx.yoke.Yoke;
import com.jetdrone.vertx.yoke.annotations.*;
import com.jetdrone.vertx.yoke.jmx.RouteMBean;
import com.jetdrone.vertx.yoke.util.AsyncIterator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.vertx.java.core.Handler;
import org.vertx.java.core.MultiMap;
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* # Router
*
* Route request by path or regular expression. All *HTTP* verbs are available:
*
* * `GET`
* * `PUT`
* * `POST`
* * `DELETE`
* * `OPTIONS`
* * `HEAD`
* * `TRACE`
* * `CONNECT`
* * `PATCH`
*/
public class Router extends Middleware {
private final List getBindings = new ArrayList<>();
private final List putBindings = new ArrayList<>();
private final List postBindings = new ArrayList<>();
private final List deleteBindings = new ArrayList<>();
private final List optionsBindings = new ArrayList<>();
private final List headBindings = new ArrayList<>();
private final List traceBindings = new ArrayList<>();
private final List connectBindings = new ArrayList<>();
private final List patchBindings = new ArrayList<>();
private final Map paramProcessors = new HashMap<>();
/**
* Create a new Router Middleware.
*
*
* new Router() {{
* get("/hello", new Handler<YokeRequest>() {
* public void handle(YokeRequest request) {
* request.response().end("Hello World!");
* }
* });
* }}
*
*/
public Router() {
}
private void init(Yoke yoke, String mount, List bindings) {
for (PatternBinding binding : bindings) {
for (Middleware m : binding.middleware) {
if (!m.isInitialized()) {
m.init(yoke, mount);
}
}
}
}
@Override
public Middleware init(@NotNull final Yoke yoke, @NotNull final String mount) {
super.init(yoke, mount);
// since this call can happen after the bindings are in place we need to update all bindings to have a reference
// to the vertx object
init(yoke, mount, getBindings);
init(yoke, mount, putBindings);
init(yoke, mount, postBindings);
init(yoke, mount, deleteBindings);
init(yoke, mount, optionsBindings);
init(yoke, mount, headBindings);
init(yoke, mount, traceBindings);
init(yoke, mount, connectBindings);
for (String key : paramProcessors.keySet()) {
final Middleware m = paramProcessors.get(key);
if (!m.isInitialized()) {
paramProcessors.get(key).init(yoke, mount);
}
}
return this;
}
@Override
public void handle(@NotNull final YokeRequest request, @NotNull final Handler next) {
switch (request.method()) {
case "GET":
route(request, next, getBindings);
break;
case "PUT":
route(request, next, putBindings);
break;
case "POST":
route(request, next, postBindings);
break;
case "DELETE":
route(request, next, deleteBindings);
break;
case "OPTIONS":
route(request, next, optionsBindings);
break;
case "HEAD":
route(request, next, headBindings);
break;
case "TRACE":
route(request, next, traceBindings);
break;
case "PATCH":
route(request, next, patchBindings);
break;
case "CONNECT":
route(request, next, connectBindings);
break;
}
}
/**
* Specify a middleware that will be called for a matching HTTP GET
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router get(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("GET", pattern, handlers, getBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP GET
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router get(@NotNull final String pattern, @NotNull final Handler handler) {
return get(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP PUT
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router put(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("PUT", pattern, handlers, putBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP PUT
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router put(@NotNull final String pattern, @NotNull final Handler handler) {
return put(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP POST
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router post(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("POST", pattern, handlers, postBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP POST
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router post(@NotNull final String pattern, @NotNull final Handler handler) {
return post(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP DELETE
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router delete(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("DELETE", pattern, handlers, deleteBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP DELETE
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router delete(@NotNull final String pattern, @NotNull final Handler handler) {
return delete(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP OPTIONS
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router options(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("OPTIONS", pattern, handlers, optionsBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP OPTIONS
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router options(@NotNull final String pattern, @NotNull final Handler handler) {
return options(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP HEAD
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router head(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("HEAD", pattern, handlers, headBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP HEAD
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router head(@NotNull final String pattern, @NotNull final Handler handler) {
return head(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP TRACE
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router trace(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("TRACE", pattern, handlers, traceBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP TRACE
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router trace(@NotNull final String pattern, @NotNull final Handler handler) {
return trace(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP CONNECT
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router connect(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("CONNECT", pattern, handlers, connectBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP CONNECT
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router connect(@NotNull final String pattern, @NotNull final Handler handler) {
return connect(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP PATCH
* @param pattern The simple pattern
* @param handlers The middleware to call
*/
public Router patch(@NotNull final String pattern, @NotNull final Middleware... handlers) {
addPattern("PATCH", pattern, handlers, patchBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP PATCH
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router patch(@NotNull final String pattern, @NotNull final Handler handler) {
return patch(pattern, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for all HTTP methods
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router all(@NotNull final String pattern, @NotNull final Middleware... handler) {
get(pattern, handler);
put(pattern, handler);
post(pattern, handler);
delete(pattern, handler);
options(pattern, handler);
head(pattern, handler);
trace(pattern, handler);
connect(pattern, handler);
patch(pattern, handler);
return this;
}
/**
* Specify a middleware that will be called for all HTTP methods
* @param pattern The simple pattern
* @param handler The middleware to call
*/
public Router all(@NotNull final String pattern, @NotNull final Handler handler) {
get(pattern, handler);
put(pattern, handler);
post(pattern, handler);
delete(pattern, handler);
options(pattern, handler);
head(pattern, handler);
trace(pattern, handler);
connect(pattern, handler);
patch(pattern, handler);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP GET
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router get(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("GET", regex, handlers, getBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP GET
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router get(@NotNull final Pattern regex, @NotNull final Handler handler) {
return get(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP PUT
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router put(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("PUT", regex, handlers, putBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP PUT
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router put(@NotNull final Pattern regex, @NotNull final Handler handler) {
return put(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP POST
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router post(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("POST", regex, handlers, postBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP POST
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router post(@NotNull final Pattern regex, @NotNull final Handler handler) {
return post(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP DELETE
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router delete(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("DELETE", regex, handlers, deleteBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP DELETE
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router delete(@NotNull final Pattern regex, @NotNull final Handler handler) {
return delete(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP OPTIONS
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router options(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("OPTIONS", regex, handlers, optionsBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP OPTIONS
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router options(@NotNull final Pattern regex, @NotNull final Handler handler) {
return options(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP HEAD
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router head(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("HEAD", regex, handlers, headBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP HEAD
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router head(@NotNull final Pattern regex, @NotNull final Handler handler) {
return head(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP TRACE
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router trace(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("TRACE", regex, handlers, traceBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP TRACE
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router trace(@NotNull final Pattern regex, @NotNull final Handler handler) {
return trace(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP CONNECT
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router connect(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("CONNECT", regex, handlers, connectBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP CONNECT
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router connect(@NotNull final Pattern regex, @NotNull final Handler handler) {
return connect(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for a matching HTTP PATCH
* @param regex A regular expression
* @param handlers The middleware to call
*/
public Router patch(@NotNull final Pattern regex, @NotNull final Middleware... handlers) {
addRegEx("PATCH", regex, handlers, patchBindings);
return this;
}
/**
* Specify a middleware that will be called for a matching HTTP PATCH
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router patch(@NotNull final Pattern regex, @NotNull final Handler handler) {
return patch(regex, new Middleware() {
@Override
public void handle(YokeRequest request, Handler next) {
handler.handle(request);
}
});
}
/**
* Specify a middleware that will be called for all HTTP methods
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router all(@NotNull final Pattern regex, @NotNull final Middleware... handler) {
get(regex, handler);
put(regex, handler);
post(regex, handler);
delete(regex, handler);
options(regex, handler);
head(regex, handler);
trace(regex, handler);
connect(regex, handler);
patch(regex, handler);
return this;
}
/**
* Specify a middleware that will be called for all HTTP methods
* @param regex A regular expression
* @param handler The middleware to call
*/
public Router all(@NotNull final Pattern regex, @NotNull final Handler handler) {
get(regex, handler);
put(regex, handler);
post(regex, handler);
delete(regex, handler);
options(regex, handler);
head(regex, handler);
trace(regex, handler);
connect(regex, handler);
patch(regex, handler);
return this;
}
public Router param(@NotNull final String paramName, @NotNull final Middleware handler) {
// also pass the vertx object to the routes
if (!handler.isInitialized() && isInitialized()) {
handler.init(yoke, mount);
}
paramProcessors.put(paramName, handler);
return this;
}
public Router param(@NotNull final String paramName, @NotNull final Pattern regex) {
return param(paramName, new Middleware() {
@Override
public void handle(@NotNull final YokeRequest request, @NotNull final Handler next) {
if (!regex.matcher(request.params().get(paramName)).matches()) {
// Bad Request
next.handle(400);
return;
}
next.handle(null);
}
});
}
private void addPattern(String verb, String input, Middleware[] handler, List bindings) {
// We need to search for any : tokens in the String and replace them with named capture groups
Matcher m = Pattern.compile(":([A-Za-z][A-Za-z0-9_]*)").matcher(input);
StringBuffer sb = new StringBuffer();
Set groups = new HashSet<>();
while (m.find()) {
String group = m.group().substring(1);
if (groups.contains(group)) {
throw new IllegalArgumentException("Cannot use identifier " + group + " more than once in pattern string");
}
m.appendReplacement(sb, "(?<$1>[^\\/]+)");
groups.add(group);
}
m.appendTail(sb);
// ignore tailing slash if not part of the input, not really REST but common on other frameworks
if (sb.charAt(sb.length() - 1) != '/') {
sb.append("\\/?$");
}
Pattern regex = Pattern.compile(sb.toString());
boolean exists = false;
// verify if the binding already exists, if yes add to it
for (PatternBinding pb : bindings) {
if (pb.pattern.equals(regex)) {
pb.addMiddleware(handler);
exists = true;
break;
}
}
if (!exists) {
PatternBinding binding = new PatternBinding(verb, input, regex, groups, handler);
bindings.add(binding);
}
// also pass the vertx object to the routes
for (Middleware h : handler) {
if (!h.isInitialized() && isInitialized()) {
h.init(yoke, mount);
}
}
}
private void addRegEx(String verb, Pattern regex, Middleware handler[], List bindings) {
boolean exists = false;
// verify if the binding already exists, if yes add to it
for (PatternBinding pb : bindings) {
if (pb.pattern.equals(regex)) {
pb.addMiddleware(handler);
exists = true;
break;
}
}
if (!exists) {
PatternBinding binding = new PatternBinding(verb, regex.pattern(), regex, null, handler);
bindings.add(binding);
}
// also pass the vertx object to the routes
for (Middleware h : handler) {
if (!h.isInitialized() && isInitialized()) {
h.init(yoke, mount);
}
}
}
private void route(final YokeRequest request, final Handler next, final List bindings) {
new AsyncIterator(bindings) {
@Override
public void handle(PatternBinding binding) {
if (hasNext()) {
route(request, binding, new Handler() {
@Override
public void handle(Object err) {
if (err == null) {
next();
} else {
next.handle(err);
}
}
});
} else {
// continue with yoke
next.handle(null);
}
}
};
}
private void route(final YokeRequest request, final PatternBinding binding, final Handler next) {
final Matcher m = binding.pattern.matcher(request.path());
if (m.matches()) {
final MultiMap params = request.params();
if (binding.paramNames != null) {
// Named params
new AsyncIterator(binding.paramNames) {
@Override
public void handle(String param) {
if (hasNext()) {
params.set(param, m.group(param));
Middleware paramMiddleware = paramProcessors.get(param);
if (paramMiddleware != null) {
paramMiddleware.handle(request, new Handler() {
@Override
public void handle(Object err) {
if (err == null) {
next();
} else {
next.handle(err);
}
}
});
} else {
next();
}
} else {
// middlewares
new AsyncIterator(binding.middleware) {
@Override
public void handle(Middleware middleware) {
if (hasNext()) {
middleware.handle(request, new Handler() {
@Override
public void handle(Object err) {
if (err == null) {
next();
} else {
next.handle(err);
}
}
});
} else {
next.handle(null);
}
}
};
}
}
};
} else {
// Un-named params
for (int i = 0; i < m.groupCount(); i++) {
params.set("param" + i, m.group(i + 1));
}
// middlewares
new AsyncIterator(binding.middleware) {
@Override
public void handle(Middleware middleware) {
if (hasNext()) {
middleware.handle(request, new Handler() {
@Override
public void handle(Object err) {
if (err == null) {
next();
} else {
next.handle(err);
}
}
});
} else {
next.handle(null);
}
}
};
}
} else {
next.handle(null);
}
}
private static class PatternBinding {
final Pattern pattern;
final List middleware = new ArrayList<>();
final Set paramNames;
private PatternBinding(@NotNull String verb, @Nullable String route, @NotNull Pattern pattern, Set paramNames, @NotNull Middleware[] middleware) {
this.pattern = pattern;
this.paramNames = paramNames;
Collections.addAll(this.middleware, middleware);
//Get the MBean server
final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// register on JMX
try {
mbs.registerMBean(new RouteMBean(), new ObjectName("com.jetdrone.yoke.router:type=Route@" + hashCode() + ",method=" + verb + ",path=" + ObjectName.quote(route)));
} catch (InstanceAlreadyExistsException e) {
// ignore
} catch (MalformedObjectNameException | MBeanRegistrationException | NotCompliantMBeanException e) {
throw new RuntimeException(e);
}
}
void addMiddleware(@NotNull Middleware[] middleware) {
Collections.addAll(this.middleware, middleware);
}
}
public static Router from(@NotNull Object... objs) {
final Router router = new Router();
from(router, objs);
return router;
}
/**
* Builds a Router from an annotated Java Object
*/
public static Router from(@NotNull final Router router, @NotNull Object... objs) {
for (Object o : objs) {
Processor.process(router, o);
}
return router;
}
}