package io.vertx.blueprint.todolist.common; import io.reactivex.Completable; import io.reactivex.Maybe; import io.reactivex.Single; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; import io.vertx.reactivex.core.AbstractVerticle; import io.vertx.reactivex.core.http.HttpServerResponse; import io.vertx.reactivex.ext.web.Router; import io.vertx.reactivex.ext.web.RoutingContext; import io.vertx.reactivex.ext.web.handler.CorsHandler; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; /** * An abstract base reactive verticle that provides several helper methods for RESTful API. * * @author <a href="http://www.sczyh30.com">Eric Zhao</a> */ public abstract class RestfulApiVerticle extends AbstractVerticle { /** * Create an HTTP server for the REST service. * * @param router router instance * @param host server host * @param port server port * @return asynchronous result */ protected Completable createHttpServer(Router router, String host, int port) { return vertx.createHttpServer() .requestHandler(router::accept) .rxListen(port, host) .toCompletable(); } /** * Enable CORS support for web router. * * @param router router instance */ protected void enableCorsSupport(Router router) { Set<String> allowHeaders = new HashSet<>(); allowHeaders.add("x-requested-with"); allowHeaders.add("Access-Control-Allow-Origin"); allowHeaders.add("origin"); allowHeaders.add("Content-Type"); allowHeaders.add("accept"); // CORS support router.route().handler(CorsHandler.create("*") .allowedHeaders(allowHeaders) .allowedMethod(HttpMethod.GET) .allowedMethod(HttpMethod.POST) .allowedMethod(HttpMethod.DELETE) .allowedMethod(HttpMethod.PATCH) .allowedMethod(HttpMethod.PUT) ); } // Helper status methods. /** * Resolve an asynchronous status and send back the response. * By default, the successful status code is 200 OK. * * @param context routing context * @param asyncResult asynchronous status with no result */ protected void sendResponse(RoutingContext context, Completable asyncResult) { HttpServerResponse response = context.response(); if (asyncResult == null) { internalError(context, "invalid_status"); } else { asyncResult.subscribe(response::end, ex -> internalError(context, ex)); } } /** * Resolve an asynchronous status and send back the response. * The successful status code depends on processor {@code f}. * * @param context routing context * @param asyncResult asynchronous status with no result */ protected void sendResponse(RoutingContext context, Completable asyncResult, Consumer<RoutingContext> f) { if (asyncResult == null) { internalError(context, "invalid_status"); } else { asyncResult.subscribe(() -> f.accept(context), ex -> internalError(context, ex)); } } protected <T> void sendResponse(RoutingContext context, Single<T> asyncResult, Function<T, String> converter, BiConsumer<RoutingContext, String> f) { if (asyncResult == null) { internalError(context, "invalid_status"); } else { asyncResult.subscribe(r -> f.accept(context, converter.apply(r)), ex -> internalError(context, ex)); } } /** * Resolve an asynchronous result and send back the response. * * @param context routing context * @param asyncResult asynchronous result * @param converter result content converter * @param <T> the type of result */ protected <T> void sendResponse(RoutingContext context, Single<T> asyncResult, Function<T, String> converter) { if (asyncResult == null) { internalError(context, "invalid_status"); } else { asyncResult.subscribe(r -> ok(context, converter.apply(r)), ex -> internalError(context, ex)); } } protected <T> void sendResponse(RoutingContext context, Maybe<T> asyncResult, Function<T, String> converter) { if (asyncResult == null) { internalError(context, "invalid_status"); } else { Single<Optional<T>> single = asyncResult.map(Optional::of) .switchIfEmpty(Maybe.just(Optional.empty())) .toSingle(); sendResponseOpt(context, single, converter); } } protected <T> void sendResponseOpt(RoutingContext context, Single<Optional<T>> asyncResult, Function<T, String> converter) { if (asyncResult == null) { internalError(context, "invalid_status"); } else { asyncResult.subscribe(r -> { if (r.isPresent()) { ok(context, converter.apply(r.get())); } else { notFound(context); } }, ex -> internalError(context, ex)); } } /** * Send back a response with status 200 Ok. * * @param context routing context */ protected void ok(RoutingContext context) { context.response().end(); } /** * Send back a response with status 200 OK. * * @param context routing context * @param content body content in JSON format */ protected void ok(RoutingContext context, String content) { context.response().setStatusCode(200) .putHeader("content-type", "application/json") .end(content); } /** * Send back a response with status 201 Created. * * @param context routing context */ protected void created(RoutingContext context) { context.response().setStatusCode(201).end(); } /** * Send back a response with status 201 Created. * * @param context routing context * @param content body content in JSON format */ protected void created(RoutingContext context, String content) { context.response().setStatusCode(201) .putHeader("content-type", "application/json") .end(content); } /** * Send back a response with status 204 No Content. * * @param context routing context */ protected void noContent(RoutingContext context) { context.response().setStatusCode(204).end(); } /** * Send back a response with status 400 Bad Request. * * @param context routing context * @param ex exception */ protected void badRequest(RoutingContext context, Throwable ex) { context.response().setStatusCode(400) .putHeader("content-type", "application/json") .end(new JsonObject().put("error", ex.getMessage()).encodePrettily()); } /** * Send back a response with status 400 Bad Request. * * @param context routing context */ protected void badRequest(RoutingContext context) { context.response().setStatusCode(400) .putHeader("content-type", "application/json") .end(new JsonObject().put("error", "bad_request").encodePrettily()); } /** * Send back a response with status 404 Not Found. * * @param context routing context */ protected void notFound(RoutingContext context) { context.response().setStatusCode(404) .putHeader("content-type", "application/json") .end(new JsonObject().put("message", "not_found").encodePrettily()); } /** * Send back a response with status 500 Internal Error. * * @param context routing context * @param ex exception */ protected void internalError(RoutingContext context, Throwable ex) { context.response().setStatusCode(500) .putHeader("content-type", "application/json") .end(new JsonObject().put("error", ex.getMessage()).encodePrettily()); } /** * Send back a response with status 500 Internal Error. * * @param context routing context * @param cause error message */ protected void internalError(RoutingContext context, String cause) { context.response().setStatusCode(500) .putHeader("content-type", "application/json") .end(new JsonObject().put("error", cause).encodePrettily()); } /** * Send back a response with status 503 Service Unavailable. * * @param context routing context */ protected void serviceUnavailable(RoutingContext context) { context.fail(503); } /** * Send back a response with status 503 Service Unavailable. * * @param context routing context * @param ex exception */ protected void serviceUnavailable(RoutingContext context, Throwable ex) { context.response().setStatusCode(503) .putHeader("content-type", "application/json") .end(new JsonObject().put("error", ex.getMessage()).encodePrettily()); } /** * Send back a response with status 503 Service Unavailable. * * @param context routing context * @param cause error message */ protected void serviceUnavailable(RoutingContext context, String cause) { context.response().setStatusCode(503) .putHeader("content-type", "application/json") .end(new JsonObject().put("error", cause).encodePrettily()); } }