fatjar.implementations.server.Undertow Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of FatJar Show documentation
Show all versions of FatJar Show documentation
FatJar simple API to quick prototyping and portable web services
The newest version!
package fatjar.implementations.server;
import fatjar.JSON;
import fatjar.Log;
import fatjar.RequestResponse;
import fatjar.Server;
import fatjar.dto.*;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HeaderValues;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.stream.Collectors;
public class Undertow implements Server {
private int port = 9080;
private String hostname = "localhost";
private String applicationCookieName = "APPLICATION_NAME";
private String cookieSignSecretKey = "SIGN_KEY";
private Map>> pathFunctions = new HashMap<>();
private Map>> parametrizedPathFunctions = new HashMap<>();
private Map>> parametrizedPaths = new HashMap<>();
private Map> filterFunctions = new HashMap<>();
private Map> filterWildcardFunctions = new HashMap<>();
private Map statusFunctions = new HashMap<>();
public Undertow(Map params) {
for (HttpMethod protocol : HttpMethod.values()) {
pathFunctions.put(protocol, new HashMap<>());
parametrizedPathFunctions.put(protocol, new HashMap<>());
parametrizedPaths.put(protocol, new HashMap<>());
}
this.port = Integer.parseInt(params.getOrDefault(ServerParams.PORT, String.valueOf(port)));
this.hostname = params.getOrDefault(ServerParams.HOST, hostname);
this.applicationCookieName = params.getOrDefault(ServerParams.APPLICATION_NAME, applicationCookieName);
this.cookieSignSecretKey = params.getOrDefault(ServerParams.SIGN_KEY, cookieSignSecretKey);
}
public static Server create(Map params) {
return new Undertow(params);
}
@Override
public Server listen(int port, String hostname) {
Log.info("listening host:port " + hostname + ":" + port);
this.port = port;
this.hostname = hostname;
return this;
}
@Override
public Server filter(String path, RequestResponse requestResponse) {
if (path.endsWith("*")) {
String wildcardPath = path.split("\\*")[0];
if (!this.filterWildcardFunctions.containsKey(wildcardPath)) {
this.filterWildcardFunctions.put(wildcardPath, new LinkedList<>());
}
this.filterWildcardFunctions.get(wildcardPath).add(requestResponse);
} else {
if (!this.filterFunctions.containsKey(path)) {
this.filterFunctions.put(path, new LinkedList<>());
}
this.filterFunctions.get(path).add(requestResponse);
}
return this;
}
@Override
public Server register(Status status, RequestResponse requestResponse) {
this.statusFunctions.put(status, requestResponse);
return this;
}
@Override
public Server get(String path, RequestResponse requestResponse) {
this.addPathFunctionList(HttpMethod.GET, path).add(requestResponse);
return this;
}
@Override
public Server post(String path, RequestResponse requestResponse) {
this.addPathFunctionList(HttpMethod.POST, path).add(requestResponse);
return this;
}
@Override
public Server delete(String path, RequestResponse requestResponse) {
this.addPathFunctionList(HttpMethod.DELETE, path).add(requestResponse);
return this;
}
@Override
public Server put(String path, RequestResponse requestResponse) {
this.addPathFunctionList(HttpMethod.PUT, path).add(requestResponse);
return this;
}
@Override
public void start() {
io.undertow.Undertow server = io.undertow.Undertow.builder()
.addHttpListener(port, hostname)
.setHandler(new UndertowHttpHandler()).build();
server.start();
}
private List addPathFunctionList(HttpMethod httpMethod, String path) {
if (path.contains("@")) {
String[] splits = path.split("@");
String wildPath = splits[0];
if (!this.parametrizedPaths.get(httpMethod).containsKey(wildPath)) {
this.parametrizedPaths.get(httpMethod).put(wildPath, new LinkedList<>());
}
for (int i = 1; i < splits.length; i++) {
this.parametrizedPaths.get(httpMethod).get(wildPath).add(splits[i].replace("/", ""));
}
if (!this.parametrizedPathFunctions.get(httpMethod).containsKey(wildPath)) {
this.parametrizedPathFunctions.get(httpMethod).put(wildPath, new LinkedList<>());
}
return this.parametrizedPathFunctions.get(httpMethod).get(wildPath);
} else {
if (!this.pathFunctions.get(httpMethod).containsKey(path)) {
this.pathFunctions.get(httpMethod).put(path, new LinkedList<>());
}
return this.pathFunctions.get(httpMethod).get(path);
}
}
/**
* Undertow's Http handler
*/
class UndertowHttpHandler implements HttpHandler {
@Override
public void handleRequest(final HttpServerExchange exchange) throws Exception {
// create Request DTO, Request object isolates undertow API
Request request = createRequest(exchange);
// Response object will write the content to undertow exchange's sender
Response response = new Response(new ParamMap<>(), request.getSession(), new OutputStream() {
@Override
public void write(int b) throws IOException {
write(new byte[]{(byte) b});
}
@Override
public void write(byte[] b) throws IOException {
if (b != null && b.length > 0) {
exchange.setResponseContentLength(b.length);
exchange.getResponseSender().send(ByteBuffer.wrap(b));
}
}
}) {
@Override
public void write() {
exchange.setStatusCode(getStatus().getStatus());
for (Param param : getHeaders().values()) {
exchange.getResponseHeaders().put(new HttpString(param.getKey()), String.valueOf(param.getValue()));
}
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, getContentType());
exchange.getResponseHeaders().put(Headers.SET_COOKIE, request.getSession().toCookie());
super.write();
}
};
try {
// find the http method, GET, POST etc.
HttpMethod httpMethod = HttpMethod.valueOf(exchange.getRequestMethod().toString());
// wildcards and filters will not called even if there are handlers
// call wildcards
this.handleWildcards(exchange.getRequestURI(), request, response);
// call filters
this.handleFilters(exchange.getRequestURI(), request, response);
// check if there are any handlers registered for this uri
if (pathFunctions.containsKey(httpMethod) && pathFunctions.get(httpMethod).containsKey(exchange.getRequestURI())) {
// run registered handlers for this url
this.handleRequestResponse(httpMethod, exchange.getRequestURI(), request, response);
// set request to handled
} else {
/**
* exchange.getRequestURI() /html/user/login.html
* parametrizedPathFunctions html, js, css, image
*/
Map> requestMethodMap = parametrizedPathFunctions.get(httpMethod);
Map> parametersMap = parametrizedPaths.get(httpMethod);
// status
boolean parametrizedStatus = false;
// check if parametrized method exists
for (String parametrized : requestMethodMap.keySet()) {
if (exchange.getRequestURI().startsWith(parametrized)) {
// add parameter keys and values
String remainingURI = exchange.getRequestURI().substring(parametrized.length());
List keys = parametersMap.get(parametrized);
String[] split = remainingURI.split("/");
for (int i = 0; i < split.length; i++) {
request.getQueryParams().addParam(new Param<>("@" + keys.get(i), split[i]));
}
// find and execute parameter functions
List requestResponseList = requestMethodMap.get(parametrized);
for (RequestResponse requestResponse : requestResponseList) {
requestResponse.apply(request, response);
parametrizedStatus = true;
}
}
}
// if no one handled this request, throw not found
if (!parametrizedStatus) {
// notify client that there is no handler for this call
throw new ServerException(Status.STATUS_NOT_FOUND, exchange.getRequestURI());
}
}
} catch (ServerException e) {
// notify client that server got an exception
this.handleServerError(request, response, e);
}
}
private void handleRequestResponse(HttpMethod httpMethod, String path, Request request, Response response) throws ServerException {
for (RequestResponse requestResponseFunction : pathFunctions.get(httpMethod).get(path)) {
requestResponseFunction.apply(request, response);
}
}
private void handleWildcards(String path, Request request, Response response) throws ServerException {
List wildcardRequestResponseFunctions = filterWildcardFunctions.keySet()
.stream()
.filter(path::startsWith)
.flatMap(wildcardPath -> filterWildcardFunctions.get(wildcardPath).stream())
.collect(Collectors.toList());
// calling apply inside the forEach() method will mask the wildcard function's exception,
// so call the apply function in a for loop
for (RequestResponse requestResponse : wildcardRequestResponseFunctions) {
requestResponse.apply(request, response);
}
}
private void handleFilters(String path, Request request, Response response) throws ServerException {
if (filterFunctions.containsKey(path)) {
List filters = filterFunctions.get(path);
for (RequestResponse requestResponse : filters) {
requestResponse.apply(request, response);
}
}
}
private void handleServerError(Request request, Response response, ServerException e) {
try {
if (e.getStatus() != null && statusFunctions.containsKey(e.getStatus())) {
response.setStatus(e.getStatus());
RequestResponse requestResponse = statusFunctions.get(e.getStatus());
requestResponse.apply(request, response);
} else {
this.handleError(request, response, e);
}
} catch (ServerException e1) {
this.handleError(request, response, e1);
}
}
private void handleError(Request request, Response response, ServerException e) {
Map responseMap = new TreeMap<>();
responseMap.put("error", String.valueOf(e));
if (e.getStatus() != null) {
response.setStatus(e.getStatus());
responseMap.put("status", e.getStatus());
} else {
response.setStatus(Status.STATUS_INTERNAL_SERVER_ERROR);
responseMap.put("status", String.valueOf(Status.STATUS_INTERNAL_SERVER_ERROR.getStatus()));
}
String content = JSON.create().toJson(responseMap);
response.setContent(content);
response.write();
}
private Request createRequest(HttpServerExchange exchange) {
ParamMap> headers = new ParamMap<>();
headers.addParam(new Param<>(RequestKeys.PROTOCOL.getValue(), exchange.getRequestMethod().toString()));
headers.addParam(new Param<>(RequestKeys.URI.getValue(), exchange.getRequestURI()));
headers.addParam(new Param<>(RequestKeys.URL.getValue(), exchange.getRequestURL()));
headers.addParam(new Param<>(RequestKeys.HEADER_NAMES.getValue(), exchange.getRequestHeaders().getHeaderNames().stream().map(HttpString::toString).collect(Collectors.toList())));
headers.addParam(new Param<>(RequestKeys.HOST_NAME.getValue(), exchange.getHostName()));
headers.addParam(new Param<>(RequestKeys.HOST_PORT.getValue(), exchange.getHostPort()));
headers.addParam(new Param<>(RequestKeys.QUERY_STRING.getValue(), exchange.getQueryString()));
headers.addParam(new Param<>(RequestKeys.REQUEST_METHOD.getValue(), exchange.getRequestMethod().toString()));
exchange.getRequestHeaders().getHeaderNames().forEach((headerName) -> {
HeaderValues headerValues = exchange.getRequestHeaders().get(headerName);
headers.addParam(new Param<>(headerName.toString(), headerValues.getFirst()));
});
ParamMap> params = new ParamMap<>();
exchange.getPathParameters().forEach((key, values) -> {
params.addParam(new Param<>(key, values.getFirst()));
});
exchange.getQueryParameters().forEach((key, values) -> {
params.addParam(new Param<>(key, values.getFirst()));
});
Session session;
if (headers.containsKey(SessionKeys.COOKIE.getValue().toLowerCase()) ||
headers.containsKey(SessionKeys.COOKIE.getValue())) {
try {
session = new Session(
String.valueOf(headers.get(SessionKeys.COOKIE.getValue()).getValue()),
cookieSignSecretKey,
applicationCookieName
);
} catch (Exception e) {
session = new Session(
String.valueOf(headers.get(SessionKeys.COOKIE.getValue().toLowerCase()).getValue()),
cookieSignSecretKey,
applicationCookieName
);
}
} else {
session = new Session("", cookieSignSecretKey, applicationCookieName);
}
Request request = new Request(params, headers, session);
if ("POST".equalsIgnoreCase(exchange.getRequestMethod().toString())) {
exchange.getRequestReceiver().receiveFullBytes((e, data) -> {
if (data != null) {
request.setPost(data);
}
},
(e, exception) -> {
Log.error("got exception at receiveFullBytes, exception: " + exception);
exception.printStackTrace();
}
);
} else {
exchange.getRequestReceiver().receiveFullBytes((e, data) -> {
request.setBody(data);
},
(e, exception) -> {
exception.printStackTrace();
}
);
}
// return Request object
return request;
}
}
}