package org.rakam.server.http; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Throwables; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.util.List; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static java.lang.String.format; import static org.rakam.server.http.HttpServer.*; public class JsonParametrizedRequestHandler implements HttpRequestHandler { private final ObjectMapper mapper; private final List<IRequestParameter> bodyParams; private final MethodHandle methodHandle; private final HttpService service; private final boolean isAsync; private final List<RequestPreprocessor> jsonPreprocessors; private final boolean bodyExtractionRequired; static final String bodyError; static { try { bodyError = DEFAULT_MAPPER.writeValueAsString(errorMessage("Body must be an json object.", BAD_REQUEST)); } catch (JsonProcessingException e) { throw Throwables.propagate(e); } } private final List<ResponsePostProcessor> postProcessors; private final HttpServer httpServer; private final boolean isJson; public JsonParametrizedRequestHandler(HttpServer httpServer, ObjectMapper mapper, List<IRequestParameter> bodyParams, MethodHandle methodHandle, List<ResponsePostProcessor> postProcessors, HttpService service, List<RequestPreprocessor> jsonPreprocessors, boolean isAsync, boolean isJson) { this.mapper = mapper; this.httpServer = httpServer; this.bodyParams = bodyParams; this.methodHandle = methodHandle; this.service = service; this.postProcessors = postProcessors; this.isAsync = isAsync; this.jsonPreprocessors = jsonPreprocessors; this.isJson = isJson; this.bodyExtractionRequired = bodyParams.stream().anyMatch(a -> a instanceof IRequestParameter.BodyParameter || a instanceof IRequestParameter.FullBodyParameter); } @Override public void handle(RakamHttpRequest request) { if(bodyExtractionRequired) { request.bodyHandler(body -> { ObjectNode node; try { // TODO: use custom deserialization to avoid the overhead of garbage generated by Jackson node = (ObjectNode) mapper.readTree(body); } catch (ClassCastException e) { request.response(bodyError, BAD_REQUEST).end(); return; } catch (UnrecognizedPropertyException e) { returnError(request, "Unrecognized field: " + e.getPropertyName(), BAD_REQUEST); return; } catch (InvalidFormatException e) { returnError(request, format("Field value couldn't validated: %s ", e.getOriginalMessage()), BAD_REQUEST); return; } catch (JsonMappingException e) { returnError(request, e.getCause() != null ? e.getCause().getMessage() : e.getMessage(), BAD_REQUEST); return; } catch (JsonParseException e) { returnError(request, format("Couldn't parse json: %s ", e.getOriginalMessage()), BAD_REQUEST); return; } catch (IOException e) { returnError(request, format("Error while mapping json: ", e.getMessage()), BAD_REQUEST); return; } handleInternal(request, node); }); } else { handleInternal(request, null); } } private void handleInternal(RakamHttpRequest request, ObjectNode node) { try { if(!jsonPreprocessors.isEmpty()) { for (RequestPreprocessor preprocessor : jsonPreprocessors) { preprocessor.handle(request, node); } } } catch (Throwable e) { httpServer.requestError(e, request, postProcessors); return; } Object[] values = new Object[bodyParams.size() + 1]; values[0] = service; for (int i = 0; i < bodyParams.size(); i++) { IRequestParameter param = bodyParams.get(i); Object value; try { value = param.extract(node, request); } catch (Exception e) { httpServer.requestError(e, request, postProcessors); return; } values[i + 1] = value; } Object invoke; try { invoke = methodHandle.invokeWithArguments(values); } catch (Throwable e) { httpServer.requestError(e, request, postProcessors); return; } if(isJson) { httpServer.handleRequest(mapper, isAsync, invoke, request, postProcessors); } } }