Skip to content

Commit

Permalink
Merge pull request #1070 from Apicurio/feat/httpRefs
Browse files Browse the repository at this point in the history
UI also supports external http/s references.
  • Loading branch information
EricWittmann authored Feb 6, 2020
2 parents f90850a + 2617ef6 commit 649e66c
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 33 deletions.
4 changes: 4 additions & 0 deletions front-end/servlet/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-cache</artifactId>
</dependency>

<!-- Project Dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Date;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.net.ssl.SSLContext;
import javax.servlet.ServletException;
Expand Down Expand Up @@ -82,6 +83,15 @@ public boolean isTrusted(X509Certificate[] chain, String authType)
throw new RuntimeException(e);
}
}

@PreDestroy
protected void preDestroy() {
try {
httpClient.close();
} catch (IOException e) {
logger.error("Error closing HTTP client.", e);
}
}

/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2020 Red Hat
*
* 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 io.apicurio.studio.fe.servlet.servlets;

import java.io.IOException;
import java.io.InputStream;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Servlet used to fetch external content to avoid CORS issues.
* @author [email protected]
*/
public class FetchServlet extends HttpServlet {

private static final long serialVersionUID = -3103265971135926034L;
private static Logger logger = LoggerFactory.getLogger(FetchServlet.class);

private CloseableHttpClient httpClient;

@PostConstruct
protected void postConstruct() {
try {
// TODO make these settings configurable!!
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxCacheEntries(2000)
.setMaxObjectSize(16384)
.build();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(30000)
.setSocketTimeout(30000)
.build();
this.httpClient = CachingHttpClients.custom()
.setCacheConfig(cacheConfig)
.setDefaultRequestConfig(requestConfig)
.build();
} catch (Exception e) {
logger.error("Error creating HTTP client.", e);
throw new RuntimeException(e);
}
}

@PreDestroy
protected void preDestroy() {
try {
httpClient.close();
} catch (IOException e) {
logger.error("Error closing HTTP client.", e);
}
}

/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String url = req.getParameter("url");
proxyUrlTo(url, req, resp);
}

/**
* Makes an HTTP connect to the given url and then proxies the response to
* the given HTTP response.
* @param url
* @param request
* @param response
*/
private void proxyUrlTo(String url, HttpServletRequest request, HttpServletResponse response) {
try {
HttpGet get = new HttpGet(url);
try (CloseableHttpResponse apiResponse = httpClient.execute(get)) {
response.setStatus(apiResponse.getStatusLine().getStatusCode());
Header[] headers = apiResponse.getAllHeaders();
for (Header header : headers) {
response.setHeader(header.getName(), header.getValue());
}
InputStream stream = apiResponse.getEntity().getContent();
IOUtils.copy(stream, response.getOutputStream());
response.getOutputStream().flush();
}
} catch (IOException e) {
logger.error("Error proxying URL: " + url, e);
try { response.sendError(500); } catch (IOException e1) {}
}
}

}
3 changes: 1 addition & 2 deletions front-end/studio/deploy-prod.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ rm -rf $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war/*.js
rm -rf $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war/*.css
rm -rf $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war/*.html
rm -rf $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war/assets
cp -rf dist/* $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war/
cp -rf dist-app/* $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war/
rm -rf $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war.*
touch $WILDFLY_DIRECTORY/standalone/deployments/apicurio-studio.war.dodeploy
rm -rf dist
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import {AbstractApiEditorComponent} from "./editor.base";
import {ComponentType} from "./_models/component-type.model";
import {ImportedComponent} from "./_models/imported-component.model";
import {ImportComponentsWizard} from "../_components/import-components.wizard";
import {HttpClient, HttpResponse} from "@angular/common/http";
import {HttpUtils} from "../../../../util/common";


enum PendingActionType {
Expand Down Expand Up @@ -204,7 +206,8 @@ export class ApiEditorPageComponent extends AbstractPageComponent implements Aft
retryTimerDate: Date;
reconnecting: boolean = false;
isOffline: boolean;
httpFetchEnabled: boolean = false; // TODO should be configurable - true in PROD mode and false in DEV mode (see usage below)
httpFetchEnabled: boolean = true; // TODO should be configurable - true in PROD mode and false in DEV mode (see usage below)
uiUrl: string = "";

private currentEditorSelection: string;

Expand All @@ -217,19 +220,23 @@ export class ApiEditorPageComponent extends AbstractPageComponent implements Aft
* @param router
* @param route
* @param zone
* @param http
* @param apis
* @param titleService
* @param validationService
* @param config
*/
constructor(private router: Router, route: ActivatedRoute, private zone: NgZone,
constructor(private router: Router, route: ActivatedRoute, private zone: NgZone, protected http: HttpClient,
private apis: ApisService, titleService: Title, private validationService: ValidationService,
private config: ConfigService) {
super(route, titleService);
this.apiDefinition = new EditableApiDefinition();
this.editorFeatures = new ApiEditorComponentFeatures();
this.editorFeatures.validationSettings = true;
this.editorFeatures.componentImports = true;
if (this.config.uiUrl()) {
this.uiUrl = this.config.uiUrl();
}
}

/**
Expand Down Expand Up @@ -280,36 +287,23 @@ export class ApiEditorPageComponent extends AbstractPageComponent implements Aft
return apiDef.spec;
});
} else if (externalRef.toLowerCase().startsWith("http") && this.httpFetchEnabled) {
// TODO implement external HTTP content - needs a servlet on the server to enable this due to CORS restrictions
// try {
// let options: any = {
// observe: "response"
// };
// this.http.get<HttpResponse<any>>(externalRef, options).toPromise(), response => {
// if (response.statusCode >= 200 && response.statusCode <= 299) {
// // Only handle the response if the cache is expecting this ref to be fetched.
// if (this.apiCache[externalRef] == null) {
// console.debug("[ApiCatalogService] Successfully fetched external http/s content:");
// console.debug(response.body);
// let body: any = response.body;
// let content: any = this.parseExternalResource(body);
// this.cacheContent(externalRef, content);
// } else {
// console.warn("[ApiCatalogService] Successfully fetched content for reference that is no longer needed: ", externalRef);
// }
// } else {
// console.error("[ApiCatalogService] Failed to fetch external resource: ", externalRef);
// this.cacheContent(externalRef, null);
// }
// };
// } catch (e) {
// console.error("[ApiCatalogService] Error fetching external http(s) API/content.");
// console.error(e);
// this.cacheContent(externalRef, null);
// }
} else {
return Promise.resolve(null);
try {
let fetchUrl: string = `${ this.uiUrl }fetch?url=${ encodeURI(externalRef) }`;
console.debug("[ApiEditorPageComponent] Fetch URL: ", fetchUrl);
let options: any = {
observe: "response"
};
return HttpUtils.mappedPromise<any>(
this.http.get<HttpResponse<any>>(fetchUrl, options).toPromise(),
(response) => {
return response.body; }
);
} catch (e) {
console.error("[ApiCatalogService] Error fetching external http(s) API/content.");
console.error(e);
}
}
return Promise.resolve(null);
}

/**
Expand Down
9 changes: 9 additions & 0 deletions platforms/thorntail/ui/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@
<url-pattern>/download</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>FetchServlet</servlet-name>
<servlet-class>io.apicurio.studio.fe.servlet.servlets.FetchServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FetchServlet</servlet-name>
<url-pattern>/fetch</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>PreviewServlet</servlet-name>
<servlet-class>io.apicurio.studio.fe.servlet.servlets.PreviewServlet</servlet-class>
Expand Down
9 changes: 9 additions & 0 deletions platforms/wildfly/ui/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@
<url-pattern>/download</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>FetchServlet</servlet-name>
<servlet-class>io.apicurio.studio.fe.servlet.servlets.FetchServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FetchServlet</servlet-name>
<url-pattern>/fetch</url-pattern>
</servlet-mapping>

<servlet>
<servlet-name>PreviewServlet</servlet-name>
<servlet-class>io.apicurio.studio.fe.servlet.servlets.PreviewServlet</servlet-class>
Expand Down
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,11 @@
<artifactId>httpclient</artifactId>
<version>${version.org.apache.httpcomponents}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-cache</artifactId>
<version>${version.org.apache.httpcomponents}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient</artifactId>
Expand Down

0 comments on commit 649e66c

Please sign in to comment.