Skip to content

Commit

Permalink
#601 Rewritting for OpenAPI - Refactoring of OpenAPI code
Browse files Browse the repository at this point in the history
  • Loading branch information
predic8 committed Jul 14, 2023
1 parent 8509d2f commit 32e8705
Show file tree
Hide file tree
Showing 16 changed files with 593 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public Exchange(AbstractHttpHandler handler) {
/**
* For HttpResendRunnable
*
* @param original
* @param original Exchange
*/
public Exchange(Exchange original, AbstractHttpHandler handler) {
super(original);
Expand Down Expand Up @@ -234,7 +234,7 @@ public long getId() {
}

@Override
public AbstractExchange createSnapshot(Runnable bodyUpdatedCallback, BodyCollectingMessageObserver.Strategy strategy, long limit) throws Exception {
public AbstractExchange createSnapshot(Runnable bodyUpdatedCallback, BodyCollectingMessageObserver.Strategy strategy, long limit) {
Exchange exc = updateCopy(this, new Exchange(null), bodyUpdatedCallback, strategy, limit);
exc.setId(this.getId());
return exc;
Expand All @@ -259,4 +259,11 @@ public Exception[] getNodeExceptions() {
public void setNodeExceptions(Exception[] nodeExceptions) {
this.nodeExceptions = nodeExceptions;
}

public String getInboundProtocol() {
if (getRule().getSslInboundContext() == null)
return "http";
else
return "https";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
import java.net.*;
import java.util.*;

import static com.predic8.membrane.core.openapi.serviceproxy.APIProxy.Spec.YesNoOpenAPIOption.*;

/**
* @description The APIProxy extends the serviceProxy with API related functions like OpenAPI support.
*
Expand Down Expand Up @@ -59,108 +57,18 @@ protected AbstractProxy getNewInstance() {
return new APIProxy();
}

/**
* @description Reads an OpenAPI description and deploys an API with the information of it.
*/
@MCElement(name = "openapi", topLevel = false)
public static class Spec {

String location;
String dir;
YesNoOpenAPIOption validateRequests = ASINOPENAPI;
YesNoOpenAPIOption validateResponses = ASINOPENAPI;
YesNoOpenAPIOption validationDetails = ASINOPENAPI;

public Spec() {
}

public String getLocation() {
return location;
}

/**
* @description Filename or URL pointing to an OpenAPI document. Relative filenames use the %MEMBRANE_HOME%/conf folder as base directory.
* @example openapi/fruitstore-v1.yaml, <a href="https://api.predic8.de/shop/swagger">https://api.predic8.de/shop/swagger</a>
*/
@MCAttribute()
public void setLocation(String location) {
this.location = location;
}

public String getDir() {
return dir;
}

/**
* @description Directory containing OpenAPI definitions to deploy.
* @example openapi
*/
@MCAttribute()
public void setDir(String dir) {
this.dir = dir;
}

public YesNoOpenAPIOption getValidateRequests() {
return validateRequests;
}

/**
* @description Turn validation of requests on or off.
* @example yes
* @default no
*/
@MCAttribute
public void setValidateRequests(YesNoOpenAPIOption validateRequests) {
this.validateRequests = validateRequests;
}

@SuppressWarnings("unused")
public YesNoOpenAPIOption getValidateResponses() {
return validateResponses;
}

/**
* @description Turn validation of responses on or off.
* @example yes
* @default no
*/
@MCAttribute()
public void setValidateResponses(YesNoOpenAPIOption validateResponses) {
this.validateResponses = validateResponses;
}

/**
* @description Show details of the validation to the caller.
* @example yes
* @default no
*/
@MCAttribute()
public void setValidationDetails(YesNoOpenAPIOption validationDetails) {
this.validationDetails = validationDetails;
}

public YesNoOpenAPIOption getValidationDetails() {
return validationDetails;
}

public enum YesNoOpenAPIOption {
YES,
NO,
ASINOPENAPI
}
}

protected List<Spec> specs = new ArrayList<>();
protected List<OpenAPISpec> specs = new ArrayList<>();

public List<Spec> getSpecs() {
public List<OpenAPISpec> getSpecs() {
return specs;
}

/**
* @description Deploys an API from an OpenAPI document.
*/
@MCChildElement(order = 25)
public void setSpecs(List<Spec> specs) {
public void setSpecs(List<OpenAPISpec> specs) {
this.specs = specs;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@

public class OpenAPIInterceptor extends AbstractInterceptor {

protected final APIProxy proxy;
protected final APIProxy apiProxy;

public OpenAPIInterceptor(APIProxy proxy) {
this.proxy = proxy;
public OpenAPIInterceptor(APIProxy apiProxy) {
this.apiProxy = apiProxy;
}

@Override
Expand All @@ -61,7 +61,7 @@ public Outcome handleRequest(Exchange exc) throws Exception {
return RETURN;
}

OpenAPIRecord rec = proxy.getBasePaths().get(basePath);
OpenAPIRecord rec = apiProxy.getBasePaths().get(basePath);

// If OpenAPIProxy has a <target> Element use this for routing otherwise
// take the urls from the info.servers field in the OpenAPI document.
Expand All @@ -71,7 +71,7 @@ public Outcome handleRequest(Exchange exc) throws Exception {
ValidationErrors errors = validateRequest(rec.api, exc);

if (!errors.isEmpty()) {
proxy.statisticCollector.collect(errors);
apiProxy.statisticCollector.collect(errors);
return returnErrors(exc, errors, REQUEST, validationDetails(rec.api));
}

Expand All @@ -81,7 +81,7 @@ public Outcome handleRequest(Exchange exc) throws Exception {
}

private boolean hasProxyATargetElement() {
return proxy.getTarget() != null && (proxy.getTarget().getHost() != null || proxy.getTarget().getUrl() != null);
return apiProxy.getTarget() != null && (apiProxy.getTarget().getHost() != null || apiProxy.getTarget().getUrl() != null);
}

@Override
Expand All @@ -92,14 +92,14 @@ public Outcome handleResponse(Exchange exc) throws Exception {

if (errors != null && errors.hasErrors()) {
exc.getResponse().setStatusCode(500); // A validation error in the response is a server error!
proxy.statisticCollector.collect(errors);
apiProxy.statisticCollector.collect(errors);
return returnErrors(exc, errors, RESPONSE, validationDetails(rec.api));
}
return CONTINUE;
}

protected String getMatchingBasePath(Exchange exc) {
for (String basePath : proxy.getBasePaths().keySet()) {
for (String basePath : apiProxy.getBasePaths().keySet()) {
if (exc.getRequest().getUri().startsWith(basePath)) {
return basePath;
}
Expand Down Expand Up @@ -196,7 +196,7 @@ public String getLongDescription() {
sb.append("<table>");
sb.append("<thead><th>API</th><th>Base Path</th><th>Validation</th></thead>");

for (Map.Entry<String, OpenAPIRecord> entry : proxy.getBasePaths().entrySet()) {
for (Map.Entry<String, OpenAPIRecord> entry : apiProxy.getBasePaths().entrySet()) {
sb.append("<tr>");
sb.append("<td>");
sb.append(entry.getValue().api.getInfo().getTitle());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import static com.predic8.membrane.core.http.MimeType.*;
import static com.predic8.membrane.core.interceptor.Outcome.*;
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.*;

public class OpenAPIPublisherInterceptor extends AbstractInterceptor {
Expand Down Expand Up @@ -83,7 +82,7 @@ public Outcome handleRequest(Exchange exc) throws Exception {
return handleOverviewOpenAPIDoc(exc);
}

private Outcome handleOverviewOpenAPIDoc(Exchange exc) throws JsonProcessingException, URISyntaxException {
private Outcome handleOverviewOpenAPIDoc(Exchange exc) throws IOException, URISyntaxException {
Matcher m = patternMeta.matcher(exc.getRequest().getUri());
if (!m.matches()) { // No id specified
if (acceptsHtmlExplicit(exc)) {
Expand Down Expand Up @@ -119,95 +118,31 @@ private Outcome returnNoFound(Exchange exc, String id) {
return RETURN;
}

private Outcome returnOpenApiAsYaml(Exchange exc, OpenAPIRecord rec) throws JsonProcessingException, URISyntaxException {
rewriteOpenAPIaccordingToRequest(exc, rec);
private Outcome returnOpenApiAsYaml(Exchange exc, OpenAPIRecord rec) throws IOException, URISyntaxException {
rec.rewriteOpenAPI(exc, getRouter().getUriFactory());
exc.setResponse(Response.ok().contentType(APPLICATION_X_YAML).body(omYaml.writeValueAsBytes(rec.node)).build());
return RETURN;
}

protected void rewriteOpenAPIaccordingToRequest(Exchange exc, OpenAPIRecord rec) throws URISyntaxException {
rewriteOpenAPIVersion3(exc, rec);
rewriteSwaggerVersion2(exc, rec);
}

private void rewriteSwaggerVersion2(Exchange exc, OpenAPIRecord rec) {
// Rewrite OpenAPI 2.X
JsonNode host = rec.node.get("host");
if (host == null)
return;

String rewrittenHost = rewriteHost(exc);
((ObjectNode) rec.node).put("host", rewrittenHost);

// Add protocol http or https
ArrayNode schemes = ((ObjectNode) rec.node).putArray("schemes");
schemes.add(getProtocol(exc));

log.debug("Rewriting {} to {}", host, rewrittenHost);
}

private void rewriteOpenAPIVersion3(Exchange exc, OpenAPIRecord rec) throws URISyntaxException {
JsonNode servers = rec.node.get("servers");
if (servers == null)
return;

for (JsonNode server : servers) {
String serverUrl = server.get("url").asText();
String rewrittenUrl = rewriteUrl(exc, serverUrl);
((ObjectNode) server).put("url", rewrittenUrl);
log.debug("Rewriting {} to {}", serverUrl, rewrittenUrl);
}
}

/**
* Rewrites URL from <b>OpenAPI 3.X</b>
* @param exc Exchange
* @param url URL to rewrite
* @return Rewritten URL
* @throws URISyntaxException syntax error ín URL
*/
protected String rewriteUrl(Exchange exc, String url) throws URISyntaxException {
return rewrite(router.getUriFactory(), url,
getProtocol(exc),
exc.getOriginalHostHeaderHost(),
exc.getOriginalHostHeaderPort());
}

/**
* Rewrites Host from <b>Swagger 2.X</b>
* @param exc Exchange
* @return Rewritten host with port
*/
protected String rewriteHost(Exchange exc) {
return exc.getOriginalHostHeaderHost() + ":" + exc.getOriginalHostHeaderPort();
}

private String getProtocol(Exchange exc) {
if (exc.getRule().getSslInboundContext() == null)
return "http";
else
return "https";
}

private Outcome handleSwaggerUi(Exchange exc) {
Matcher m = patternUI.matcher(exc.getRequest().getUri());

// No id specified
if (!m.matches()) {
Map<String,Object> details = new HashMap<>();
details.put("message","Please specify an id of an OpenAPI document. Path should match this pattern: /api-doc/ui/<<id>>");
exc.setResponse(createProblemDetails(404,"/openapi/wrong-id","No OpenAPI document id",details));
Map<String, Object> details = new HashMap<>();
details.put("message", "Please specify an id of an OpenAPI document. Path should match this pattern: /api-doc/ui/<<id>>");
exc.setResponse(createProblemDetails(404, "/openapi/wrong-id", "No OpenAPI document id", details));
return RETURN;
}

// /api-doc/ui/(.*)
String id = m.group(1);

log.info("OpenAPI with id {} requested",id);
log.info("OpenAPI with id {} requested", id);

OpenAPIRecord record = apis.get(id);
if (record == null) {
return returnNoFound(exc,id);
return returnNoFound(exc, id);
}

exc.setResponse(Response.ok().contentType(HTML_UTF_8).body(renderSwaggerUITemplate(id, record.api)).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@
package com.predic8.membrane.core.openapi.serviceproxy;

import com.fasterxml.jackson.databind.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.util.*;
import io.swagger.v3.oas.models.*;
import org.slf4j.*;

import java.io.*;
import java.net.*;

import static com.predic8.membrane.core.openapi.util.OpenAPIUtil.*;

Expand All @@ -33,15 +39,23 @@ public class OpenAPIRecord {
*/
JsonNode node;

OpenAPISpec spec;

/**
* Version of the OpenAPI standard e.g. 2.0, 3.0.1
*/
String version;

public OpenAPIRecord(OpenAPI api, JsonNode node) {
/**
* Used for tests
*/
public OpenAPIRecord() {}

public OpenAPIRecord(OpenAPI api, JsonNode node, OpenAPISpec spec) {
this.api = api;
this.node = node;
this.version = getOpenAPIVersion(node);
this.spec = spec;
}

public boolean isVersion2() {
Expand All @@ -51,4 +65,9 @@ public boolean isVersion2() {
public boolean isVersion3() {
return version.startsWith("3");
}

public JsonNode rewriteOpenAPI(Exchange exc, URIFactory uriFactory) throws URISyntaxException, IOException {
return spec.rewrite.rewrite(this,exc,uriFactory);
}

}
Loading

0 comments on commit 32e8705

Please sign in to comment.