All Downloads are FREE. Search and download functionalities are using the official Maven repository.
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.predic8.membrane.core.openapi.serviceproxy.OpenAPIInterceptor Maven / Gradle / Ivy
/*
* Copyright 2022 predic8 GmbH, www.predic8.com
*
* 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.
*/
package com.predic8.membrane.core.openapi.serviceproxy;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.http.Response;
import com.predic8.membrane.core.interceptor.*;
import com.predic8.membrane.core.openapi.*;
import com.predic8.membrane.core.openapi.validators.*;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.servers.*;
import jakarta.mail.internet.*;
import redis.clients.jedis.*;
import java.io.*;
import java.net.*;
import java.util.*;
import static com.predic8.membrane.core.exceptions.ProblemDetails.*;
import static com.predic8.membrane.core.exchange.Exchange.*;
import static com.predic8.membrane.core.http.MimeType.*;
import static com.predic8.membrane.core.interceptor.Outcome.*;
import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.*;
import static com.predic8.membrane.core.openapi.util.OpenAPIUtil.*;
import static com.predic8.membrane.core.openapi.util.UriUtil.*;
import static com.predic8.membrane.core.openapi.util.Utils.*;
import static com.predic8.membrane.core.openapi.validators.ValidationErrors.Direction.*;
public class OpenAPIInterceptor extends AbstractInterceptor {
protected final APIProxy apiProxy;
public OpenAPIInterceptor(APIProxy apiProxy) {
this.apiProxy = apiProxy;
}
@Override
public Outcome handleRequest(Exchange exc) throws Exception {
String basePath = getMatchingBasePath(exc);
// No matching API found
if (basePath == null) {
Map m = new HashMap<>();
m.put("message","There is no API on the path %s deployed. Please check the path.".formatted(basePath));
m.put("path",basePath);
exc.setResponse(createProblemDetails(404, "/not-found", "No matching API found!",m));
return RETURN;
}
OpenAPIRecord rec = apiProxy.getBasePaths().get(basePath);
// If OpenAPIProxy has a Element use this for routing otherwise
// take the urls from the info.servers field in the OpenAPI document.
if (!hasProxyATargetElement())
setDestinationsFromOpenAPI(rec, exc);
ValidationErrors errors = validateRequest(rec.api, exc);
if (!errors.isEmpty()) {
apiProxy.statisticCollector.collect(errors);
return returnErrors(exc, errors, REQUEST, validationDetails(rec.api));
}
exc.setProperty("openApi", rec);
return CONTINUE;
}
private boolean hasProxyATargetElement() {
return apiProxy.getTarget() != null && (apiProxy.getTarget().getHost() != null || apiProxy.getTarget().getUrl() != null);
}
@Override
public Outcome handleResponse(Exchange exc) throws Exception {
OpenAPIRecord rec = (OpenAPIRecord) exc.getProperty("openApi");
ValidationErrors errors = validateResponse(rec.api, exc);
if (errors != null && errors.hasErrors()) {
exc.getResponse().setStatusCode(500); // A validation error in the response is a server error!
apiProxy.statisticCollector.collect(errors);
return returnErrors(exc, errors, RESPONSE, validationDetails(rec.api));
}
return CONTINUE;
}
protected String getMatchingBasePath(Exchange exc) {
for (String basePath : apiProxy.getBasePaths().keySet()) {
if (exc.getRequest().getUri().startsWith(basePath)) {
return basePath;
}
}
return null;
}
private ValidationErrors validateRequest(OpenAPI api, Exchange exc) throws IOException, ParseException {
ValidationErrors errors = new ValidationErrors();
if (!shouldValidate(api, REQUESTS))
return errors;
return new OpenAPIValidator(router.getUriFactory(), api).validate(getOpenapiValidatorRequest(exc));
}
private ValidationErrors validateResponse(OpenAPI api, Exchange exc) throws IOException, ParseException {
ValidationErrors errors = new ValidationErrors();
if (!shouldValidate(api, RESPONSES))
return errors;
return new OpenAPIValidator(router.getUriFactory(), api).validateResponse(getOpenapiValidatorRequest(exc), getOpenapiValidatorResponse(exc));
}
public boolean validationDetails(OpenAPI api) {
if (api.getExtensions() == null)
return true;
@SuppressWarnings("unchecked")
Map xValidation = (Map) api.getExtensions().get(X_MEMBRANE_VALIDATION);
if (xValidation == null)
return true;
Boolean validationDetails = (Boolean) xValidation.get("details");
if (xValidation.get("details") == null)
return true;
return validationDetails;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean shouldValidate(OpenAPI api, String direction) {
Map extenstions = api.getExtensions();
if (extenstions == null)
return false;
Map xValidation = getxValidation(extenstions);
return xValidation != null && xValidation.get(direction);
}
@SuppressWarnings("unchecked")
private Map getxValidation(Map extenstions) {
return (Map) extenstions.get(X_MEMBRANE_VALIDATION);
}
protected void setDestinationsFromOpenAPI(OpenAPIRecord rec, Exchange exc) {
exc.getDestinations().clear();
rec.api.getServers().forEach(server -> {
URL url = getServerUrlFromOpenAPI(rec, server);
setHostHeader(exc, url); // @TODO Check
exc.setProperty(SNI_SERVER_NAME, url.getHost());
exc.getDestinations().add(getUrlWithoutPath(url) + exc.getRequest().getUri());
});
}
private static URL getServerUrlFromOpenAPI(OpenAPIRecord rec, Server server) {
try {
if (rec.isVersion2()) {
return new URL(parseSwaggersInfoServer(server.getUrl()).getUrl());
}
// OpenAPI 3 or newer
return new URL(server.getUrl());
} catch (Exception e) {
throw new RuntimeException("Cannot parse server address from OpenAPI " + server.getUrl());
}
}
@Override
public String getDisplayName() {
return "OpenAPI";
}
@Override
public String getShortDescription() {
return "Autoconfiguration from OpenAPI";
}
@Override
public String getLongDescription() {
StringBuilder sb = new StringBuilder();
sb.append("");
sb.append("API Base Path Validation ");
for (Map.Entry entry : apiProxy.getBasePaths().entrySet()) {
sb.append("");
sb.append("");
sb.append(entry.getValue().api.getInfo().getTitle());
sb.append(" ");
sb.append("");
sb.append(entry.getKey());
sb.append(" ");
sb.append("");
if (entry.getValue().api.getExtensions() != null && entry.getValue().api.getExtensions().get(X_MEMBRANE_VALIDATION) != null) {
sb.append(entry.getValue().api.getExtensions().get(X_MEMBRANE_VALIDATION));
}
sb.append(" ");
sb.append(" ");
}
sb.append("
");
return sb.toString();
}
private void setHostHeader(Exchange exc, URL url) {
exc.getRequest().getHeader().setHost(new HostAndPort(url.getHost(), url.getPort()).toString());
}
private Outcome returnErrors(Exchange exc, ValidationErrors errors, ValidationErrors.Direction direction, boolean validationDetails) {
exc.setResponse(Response.ResponseBuilder.newInstance().status(errors.get(0).getContext().getStatusCode(), "Bad Request").body(getErrorMessage(errors, direction, validationDetails)).contentType(APPLICATION_JSON_UTF8).build());
return RETURN;
}
private byte[] getErrorMessage(ValidationErrors errors, ValidationErrors.Direction direction, boolean validationDetails) {
if (validationDetails)
return errors.getErrorMessage(direction);
return createErrorMessage("Message validation failed!");
}
}