diff --git a/backend/pom.xml b/backend/pom.xml index 72bfcaa3a3..9a01cdd495 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -146,6 +146,12 @@ 2.9 + + org.apache.httpcomponents + httpclient + 4.5.3 + + diff --git a/backend/src/main/java/io/metersphere/base/domain/FucTestLog.java b/backend/src/main/java/io/metersphere/base/domain/FucTestLog.java index ca981eb1bd..f2867f94b0 100644 --- a/backend/src/main/java/io/metersphere/base/domain/FucTestLog.java +++ b/backend/src/main/java/io/metersphere/base/domain/FucTestLog.java @@ -1,6 +1,6 @@ package io.metersphere.base.domain; -public class FucLog { +public class FucTestLog { private String seleniumLog; private String browserDriverLog; diff --git a/backend/src/main/java/io/metersphere/base/domain/ZaleniumTest.java b/backend/src/main/java/io/metersphere/base/domain/ZaleniumTest.java index 0c95af241c..3631aba0d8 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ZaleniumTest.java +++ b/backend/src/main/java/io/metersphere/base/domain/ZaleniumTest.java @@ -1,4 +1,194 @@ package io.metersphere.base.domain; public class ZaleniumTest { + + private String seleniumSessionId; + private String testName; + private String timestamp; + private String addedToDashboardTime; + private String browser; + private String browserVersion; + private String proxyName; + private String platform; + private String fileName; + private String fileExtension; + private String videoFolderPath; + private String logsFolderPath; + private String testNameNoExtension; + private String screenDimension; + private String timeZone; + private String build; + private String testFileNameTemplate; + private String browserDriverLogFileName; + private String retentionDate; + private String testStatus; + private boolean videoRecorded; + + public String getSeleniumSessionId() { + return seleniumSessionId; + } + + public void setSeleniumSessionId(String seleniumSessionId) { + this.seleniumSessionId = seleniumSessionId; + } + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getAddedToDashboardTime() { + return addedToDashboardTime; + } + + public void setAddedToDashboardTime(String addedToDashboardTime) { + this.addedToDashboardTime = addedToDashboardTime; + } + + public String getBrowser() { + return browser; + } + + public void setBrowser(String browser) { + this.browser = browser; + } + + public String getBrowserVersion() { + return browserVersion; + } + + public void setBrowserVersion(String browserVersion) { + this.browserVersion = browserVersion; + } + + public String getProxyName() { + return proxyName; + } + + public void setProxyName(String proxyName) { + this.proxyName = proxyName; + } + + public String getPlatform() { + return platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileExtension() { + return fileExtension; + } + + public void setFileExtension(String fileExtension) { + this.fileExtension = fileExtension; + } + + public String getVideoFolderPath() { + return videoFolderPath; + } + + public void setVideoFolderPath(String videoFolderPath) { + this.videoFolderPath = videoFolderPath; + } + + public String getLogsFolderPath() { + return logsFolderPath; + } + + public void setLogsFolderPath(String logsFolderPath) { + this.logsFolderPath = logsFolderPath; + } + + public String getTestNameNoExtension() { + return testNameNoExtension; + } + + public void setTestNameNoExtension(String testNameNoExtension) { + this.testNameNoExtension = testNameNoExtension; + } + + public String getScreenDimension() { + return screenDimension; + } + + public void setScreenDimension(String screenDimension) { + this.screenDimension = screenDimension; + } + + public String getTimeZone() { + return timeZone; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public String getBuild() { + return build; + } + + public void setBuild(String build) { + this.build = build; + } + + public String getTestFileNameTemplate() { + return testFileNameTemplate; + } + + public void setTestFileNameTemplate(String testFileNameTemplate) { + this.testFileNameTemplate = testFileNameTemplate; + } + + public String getBrowserDriverLogFileName() { + return browserDriverLogFileName; + } + + public void setBrowserDriverLogFileName(String browserDriverLogFileName) { + this.browserDriverLogFileName = browserDriverLogFileName; + } + + public String getRetentionDate() { + return retentionDate; + } + + public void setRetentionDate(String retentionDate) { + this.retentionDate = retentionDate; + } + + public String getTestStatus() { + return testStatus; + } + + public void setTestStatus(String testStatus) { + this.testStatus = testStatus; + } + + public boolean isVideoRecorded() { + return videoRecorded; + } + + public void setVideoRecorded(boolean videoRecorded) { + this.videoRecorded = videoRecorded; + } } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtFunctionalTestReportMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtFunctionalTestReportMapper.xml index a3f34634a9..4bf8d17a5d 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtFunctionalTestReportMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtFunctionalTestReportMapper.xml @@ -5,7 +5,7 @@ select ltr.id, ltr.name, ltr.test_id as testId, ltr.description, - ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, lt.name as testName, + ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, ltr.content as content, + lt.name as testName, p.id as projectId, p.name as projectName - from fuc_test_report ltr join fuc_test lt on ltr.test_id = lt.id join project p on lt.project_id = p.id + from fuc_test_report ltr left join fuc_test lt on ltr.test_id = lt.id left join project p on lt.project_id = p.id where ltr.id = #{id} diff --git a/backend/src/main/java/io/metersphere/config/ProxyServletConfiguration.java b/backend/src/main/java/io/metersphere/config/ProxyServletConfiguration.java index df124ad61c..821927635a 100644 --- a/backend/src/main/java/io/metersphere/config/ProxyServletConfiguration.java +++ b/backend/src/main/java/io/metersphere/config/ProxyServletConfiguration.java @@ -2,19 +2,16 @@ package io.metersphere.config; import io.metersphere.proxy.ProxyServlet; import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; @Configuration -public class SolrProxyServletConfiguration { +public class ProxyServletConfiguration { @Bean public ServletRegistrationBean servletRegistrationBean(){ - ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new ProxyServlet(), "/solr/*"); -// servletRegistrationBean.addInitParameter("targetUri", "http://localhost:4444"); -// servletRegistrationBean.addInitParameter(ProxyServlet.P_LOG, "false"); + //代理到hub节点获取录像 + ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new ProxyServlet(), "/proxy/*"); return servletRegistrationBean; } diff --git a/backend/src/main/java/io/metersphere/config/RestTemplateConfig.java b/backend/src/main/java/io/metersphere/config/RestTemplateConfig.java index 143ac4c546..d44b0dc08b 100644 --- a/backend/src/main/java/io/metersphere/config/RestTemplateConfig.java +++ b/backend/src/main/java/io/metersphere/config/RestTemplateConfig.java @@ -1,4 +1,20 @@ package io.metersphere.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration public class RestTemplateConfig { + + @Bean + public RestTemplate getRestTemplate() { + HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); + httpRequestFactory.setConnectionRequestTimeout(3000); + httpRequestFactory.setConnectTimeout(3000); + httpRequestFactory.setReadTimeout(3000); + return new RestTemplate(httpRequestFactory); + } + } diff --git a/backend/src/main/java/io/metersphere/controller/FunctionalReportController.java b/backend/src/main/java/io/metersphere/controller/FunctionalReportController.java index 560b9ce6b7..d4a9e41807 100644 --- a/backend/src/main/java/io/metersphere/controller/FunctionalReportController.java +++ b/backend/src/main/java/io/metersphere/controller/FunctionalReportController.java @@ -2,6 +2,7 @@ package io.metersphere.controller; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.metersphere.base.domain.FucTestLog; import io.metersphere.base.domain.FucTestReport; import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.utils.PageUtils; @@ -30,7 +31,6 @@ public class FunctionalReportController { String currentWorkspaceId = SessionUtils.getCurrentWorkspaceId(); ReportRequest request = new ReportRequest(); request.setWorkspaceId(currentWorkspaceId); - // 最近 `count` 个项目 PageHelper.startPage(1, count); return functionalReportService.getRecentReportList(request); } @@ -46,11 +46,15 @@ public class FunctionalReportController { functionalReportService.deleteReport(reportId); } - @GetMapping("/test/pro/info/{reportId}") public FunctionalReportDTO getReportTestAndProInfo(@PathVariable String reportId) { return functionalReportService.getReportTestAndProInfo(reportId); } + @GetMapping("/test/log/{reportId}") + public FucTestLog getTestLog(@PathVariable String reportId) { + return functionalReportService.getTestLog(reportId); + } + } diff --git a/backend/src/main/java/io/metersphere/job/ZaleniumJob.java b/backend/src/main/java/io/metersphere/job/ZaleniumJob.java index 0930b153d8..556e6382fa 100644 --- a/backend/src/main/java/io/metersphere/job/ZaleniumJob.java +++ b/backend/src/main/java/io/metersphere/job/ZaleniumJob.java @@ -1,4 +1,25 @@ package io.metersphere.job; +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.service.ZaleniumService; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +import javax.annotation.Resource; + +@Configuration +@EnableScheduling public class ZaleniumJob { + + + @Resource + ZaleniumService zaleniumService; + + @Scheduled(cron = "0 0/1 * * * ?") + public void syncFucTestReport(){ + LogUtil.info("============= start sync FucTestReport ============="); + zaleniumService.syncTestResult(); + LogUtil.info("============= end sync FucTestReport ============="); + } } diff --git a/backend/src/main/java/io/metersphere/proxy/ProxyServlet.java b/backend/src/main/java/io/metersphere/proxy/ProxyServlet.java index bafff9bb4c..f8cf255fc7 100644 --- a/backend/src/main/java/io/metersphere/proxy/ProxyServlet.java +++ b/backend/src/main/java/io/metersphere/proxy/ProxyServlet.java @@ -1,17 +1,11 @@ package io.metersphere.proxy; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpHost; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; +import io.metersphere.commons.utils.LogUtil; +import org.apache.http.*; import org.apache.http.client.HttpClient; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.AbortableHttpRequest; -import org.apache.http.client.utils.URIUtils; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.message.BasicHeader; @@ -30,320 +24,181 @@ import java.io.IOException; import java.io.OutputStream; import java.net.HttpCookie; import java.net.URI; -import java.util.BitSet; +import java.net.URISyntaxException; import java.util.Enumeration; -import java.util.Formatter; -import java.util.List; /** - * An HTTP reverse proxy/gateway servlet. It is designed to be extended for customization - * if desired. Most of the work is handled by - * Apache HttpClient. - *

- * There are alternatives to a servlet based proxy such as Apache mod_proxy if that is available to you. However - * this servlet is easily customizable by Java, secure-able by your web application's security (e.g. spring-security), - * portable across servlet engines, and is embeddable into another web application. - *

- *

- * Inspiration: http://httpd.apache.org/docs/2.0/mod/mod_proxy.html - *

- * - * @author David Smiley dsmiley@apache.org + * Http代理 + * 获取 zalenium 录像等资源 */ -@SuppressWarnings({ "deprecation", "serial" }) public class ProxyServlet extends HttpServlet { - /* INIT PARAMETER NAME CONSTANTS */ + public static final String PRESERVEHOST = "preserveHost"; - /** A boolean parameter name to enable logging of input and target URLs to the servlet log. */ - public static final String P_LOG = "log"; + public static final String PRESERVECOOKIES = "preserveCookies"; - /** A boolean parameter name to enable forwarding of the client IP */ - public static final String P_FORWARDEDFOR = "forwardip"; + public static final String HANDLEREDIRECTS = "http.protocol.handle-redirects"; - /** A boolean parameter name to keep HOST parameter as-is */ - public static final String P_PRESERVEHOST = "preserveHost"; + public static final String CONNECTTIMEOUT = "http.socket.timeout"; - /** A boolean parameter name to keep COOKIES as-is */ - public static final String P_PRESERVECOOKIES = "preserveCookies"; - - /** A boolean parameter name to have auto-handle redirects */ - public static final String P_HANDLEREDIRECTS = "http.protocol.handle-redirects"; // ClientPNames.HANDLE_REDIRECTS - - /** A integer parameter name to set the socket connection timeout (millis) */ - public static final String P_CONNECTTIMEOUT = "http.socket.timeout"; // CoreConnectionPNames.SO_TIMEOUT - - /** A integer parameter name to set the socket read timeout (millis) */ - public static final String P_READTIMEOUT = "http.read.timeout"; + public static final String READTIMEOUT = "http.read.timeout"; - /** A boolean parameter whether to use JVM-defined system properties to configure various networking aspects. */ - public static final String P_USESYSTEMPROPERTIES = "useSystemProperties"; - - /** The parameter name for the target (destination) URI to proxy to. */ - protected static final String P_TARGET_URI = "targetUri"; - protected static final String ATTR_TARGET_URI = - ProxyServlet.class.getSimpleName() + ".targetUri"; - protected static final String ATTR_TARGET_HOST = - ProxyServlet.class.getSimpleName() + ".targetHost"; + public static final String USESYSTEMPROPERTIES = "useSystemProperties"; - /* MISC */ + public static final String FORWARDEDFOR = "forwardip"; - protected boolean doLog = false; - protected boolean doForwardIP = true; - /** User agents shouldn't send the url fragment but what if it does? */ - protected boolean doSendUrlFragment = true; protected boolean doPreserveHost = false; + protected boolean doForwardIP = true; protected boolean doPreserveCookies = false; protected boolean doHandleRedirects = false; protected boolean useSystemProperties = false; protected int connectTimeout = -1; protected int readTimeout = -1; - //These next 3 are cached here, and should only be referred to in initialization logic. See the - // ATTR_* parameters. - /** From the configured parameter "targetUri". */ + protected String targetUri; - protected URI targetUriObj;//new URI(targetUri) - protected HttpHost targetHost;//URIUtils.extractHost(targetUriObj); + protected URI targetUriObj; + protected HttpHost targetHost; private HttpClient proxyClient; - @Override - public String getServletInfo() { - return "A proxy servlet by David Smiley, dsmiley@apache.org"; - } - - protected String getTargetUri(HttpServletRequest servletRequest) { - return (String) servletRequest.getAttribute(ATTR_TARGET_URI); - } - - protected HttpHost getTargetHost(HttpServletRequest servletRequest) { - return (HttpHost) servletRequest.getAttribute(ATTR_TARGET_HOST); - } - - /** - * Reads a configuration parameter. By default it reads servlet init parameters but - * it can be overridden. - */ protected String getConfigParam(String key) { return getServletConfig().getInitParameter(key); } @Override - public void init() throws ServletException { - String doLogStr = getConfigParam(P_LOG); - if (doLogStr != null) { - this.doLog = Boolean.parseBoolean(doLogStr); - } + public void init() { - String doForwardIPString = getConfigParam(P_FORWARDEDFOR); + String doForwardIPString = getConfigParam(FORWARDEDFOR); if (doForwardIPString != null) { this.doForwardIP = Boolean.parseBoolean(doForwardIPString); } - String preserveHostString = getConfigParam(P_PRESERVEHOST); + String preserveHostString = getConfigParam(PRESERVEHOST); if (preserveHostString != null) { this.doPreserveHost = Boolean.parseBoolean(preserveHostString); } - String preserveCookiesString = getConfigParam(P_PRESERVECOOKIES); + String preserveCookiesString = getConfigParam(PRESERVECOOKIES); if (preserveCookiesString != null) { this.doPreserveCookies = Boolean.parseBoolean(preserveCookiesString); } - String handleRedirectsString = getConfigParam(P_HANDLEREDIRECTS); + String handleRedirectsString = getConfigParam(HANDLEREDIRECTS); if (handleRedirectsString != null) { this.doHandleRedirects = Boolean.parseBoolean(handleRedirectsString); } - String connectTimeoutString = getConfigParam(P_CONNECTTIMEOUT); + String connectTimeoutString = getConfigParam(CONNECTTIMEOUT); if (connectTimeoutString != null) { this.connectTimeout = Integer.parseInt(connectTimeoutString); } - String readTimeoutString = getConfigParam(P_READTIMEOUT); + String readTimeoutString = getConfigParam(READTIMEOUT); if (readTimeoutString != null) { this.readTimeout = Integer.parseInt(readTimeoutString); } - String useSystemPropertiesString = getConfigParam(P_USESYSTEMPROPERTIES); + String useSystemPropertiesString = getConfigParam(USESYSTEMPROPERTIES); if (useSystemPropertiesString != null) { this.useSystemProperties = Boolean.parseBoolean(useSystemPropertiesString); } - - initTarget();//sets target* - proxyClient = createHttpClient(buildRequestConfig()); } - /** - * Sub-classes can override specific behaviour of {@link org.apache.http.client.config.RequestConfig}. - */ protected RequestConfig buildRequestConfig() { return RequestConfig.custom() .setRedirectsEnabled(doHandleRedirects) - .setCookieSpec(CookieSpecs.IGNORE_COOKIES) // we handle them in the servlet instead + .setCookieSpec(CookieSpecs.IGNORE_COOKIES) .setConnectTimeout(connectTimeout) .setSocketTimeout(readTimeout) .build(); } - protected void initTarget() throws ServletException { - targetUri = getConfigParam(P_TARGET_URI); - if (targetUri == null) - throw new ServletException(P_TARGET_URI+" is required."); - //test it's valid - try { - targetUriObj = new URI(targetUri); - } catch (Exception e) { - throw new ServletException("Trying to process targetUri init parameter: "+e,e); - } - targetHost = URIUtils.extractHost(targetUriObj); - } - - /** - * Called from {@link #init(javax.servlet.ServletConfig)}. - * HttpClient offers many opportunities for customization. - * In any case, it should be thread-safe. - */ protected HttpClient createHttpClient(final RequestConfig requestConfig) { HttpClientBuilder clientBuilder = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig); - if (useSystemProperties) - clientBuilder = clientBuilder.useSystemProperties(); + if (useSystemProperties){ + clientBuilder = clientBuilder.useSystemProperties(); + } return clientBuilder.build(); } - /** - * The http client used. - * @see #createHttpClient(RequestConfig) - */ - protected HttpClient getProxyClient() { - return proxyClient; - } @Override public void destroy() { - //Usually, clients implement Closeable: if (proxyClient instanceof Closeable) { try { ((Closeable) proxyClient).close(); } catch (IOException e) { - log("While destroying servlet, shutting down HttpClient: "+e, e); + LogUtil.error(e.getMessage(),e); } } else { - //Older releases require we do this: - if (proxyClient != null) - proxyClient.getConnectionManager().shutdown(); + if (proxyClient != null){ + proxyClient.getConnectionManager().shutdown(); + } } super.destroy(); } @Override - protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) + protected void service(HttpServletRequest request, HttpServletResponse servletResponse) throws ServletException, IOException { - //initialize request attributes from caches if unset by a subclass by this point - if (servletRequest.getAttribute(ATTR_TARGET_URI) == null) { - servletRequest.setAttribute(ATTR_TARGET_URI, targetUri); - } - if (servletRequest.getAttribute(ATTR_TARGET_HOST) == null) { - servletRequest.setAttribute(ATTR_TARGET_HOST, targetHost); - } - // Make the Request - //note: we won't transfer the protocol version because I'm not sure it would truly be compatible - String method = servletRequest.getMethod(); - String proxyRequestUri = rewriteUrlFromRequest(servletRequest); - HttpRequest proxyRequest; - //spec: RFC 2616, sec 4.3: either of these two headers signal that there is a message body. - if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null || - servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) { - proxyRequest = newProxyRequestWithEntity(method, proxyRequestUri, servletRequest); + String method = request.getMethod(); + + String targetUri = getTargetUri(request); + + HttpRequest targetRequest; + if (request.getHeader(HttpHeaders.CONTENT_LENGTH) != null || + request.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) { + targetRequest = getTargetRequestWithEntity(method, targetUri, request); } else { - proxyRequest = new BasicHttpRequest(method, proxyRequestUri); + targetRequest = new BasicHttpRequest(method, targetUri); } - copyRequestHeaders(servletRequest, proxyRequest); + copyRequestHeaders(request, targetRequest); - setXForwardedForHeader(servletRequest, proxyRequest); + setXForwardedForHeader(request, targetRequest); HttpResponse proxyResponse = null; try { - // Execute the request - proxyResponse = doExecute(servletRequest, servletResponse, proxyRequest); - - // Process the response: - - // Pass the response code. This method with the "reason phrase" is deprecated but it's the - // only way to pass the reason along too. + proxyResponse = proxyClient.execute(targetHost, targetRequest);; int statusCode = proxyResponse.getStatusLine().getStatusCode(); - //noinspection deprecation servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase()); - - // Copying response headers to make sure SESSIONID or other Cookie which comes from the remote - // server will be saved in client when the proxied url was redirected to another one. - // See issue [#51](https://github.com/mitre/HTTP-Proxy-Servlet/issues/51) - copyResponseHeaders(proxyResponse, servletRequest, servletResponse); + copyResponseHeaders(proxyResponse, request, servletResponse); if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) { - // 304 needs special handling. See: - // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304 - // Don't send body entity/content! servletResponse.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0); } else { - // Send the content to the client - copyResponseEntity(proxyResponse, servletResponse, proxyRequest, servletRequest); + copyResponseEntity(proxyResponse, servletResponse); } } catch (Exception e) { - handleRequestException(proxyRequest, e); + handleRequestException(targetRequest, e); } finally { - // make sure the entire entity was consumed, so the connection is released - if (proxyResponse != null) - EntityUtils.consumeQuietly(proxyResponse.getEntity()); - //Note: Don't need to close servlet outputStream: - // http://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter + if (proxyResponse != null){ + EntityUtils.consumeQuietly(proxyResponse.getEntity()); + } } } - protected void handleRequestException(HttpRequest proxyRequest, Exception e) throws ServletException, IOException { - //abort request, according to best practice with HttpClient + protected void handleRequestException(HttpRequest proxyRequest, Exception e) { if (proxyRequest instanceof AbortableHttpRequest) { AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) proxyRequest; abortableHttpRequest.abort(); } - if (e instanceof RuntimeException) - throw (RuntimeException)e; - if (e instanceof ServletException) - throw (ServletException)e; - //noinspection ConstantConditions - if (e instanceof IOException) - throw (IOException) e; - throw new RuntimeException(e); + throw new RuntimeException(e.getMessage(), e); } - protected HttpResponse doExecute(HttpServletRequest servletRequest, HttpServletResponse servletResponse, - HttpRequest proxyRequest) throws IOException { - if (doLog) { - log("proxy " + servletRequest.getMethod() + " uri: " + servletRequest.getRequestURI() + " -- " + - proxyRequest.getRequestLine().getUri()); - } - return proxyClient.execute(getTargetHost(servletRequest), proxyRequest); + protected HttpRequest getTargetRequestWithEntity(String method, String zaleniumUri, + HttpServletRequest servletRequest) throws IOException { + HttpEntityEnclosingRequest targetRequest = new BasicHttpEntityEnclosingRequest(method, zaleniumUri); + targetRequest.setEntity(new InputStreamEntity(servletRequest.getInputStream(), + getContentLength(servletRequest))); + return targetRequest; } - protected HttpRequest newProxyRequestWithEntity(String method, String proxyRequestUri, - HttpServletRequest servletRequest) - throws IOException { - HttpEntityEnclosingRequest eProxyRequest = - new BasicHttpEntityEnclosingRequest(method, proxyRequestUri); - // Add the input entity (streamed) - // note: we don't bother ensuring we close the servletInputStream since the container handles it - eProxyRequest.setEntity( - new InputStreamEntity(servletRequest.getInputStream(), getContentLength(servletRequest))); - return eProxyRequest; - } - - // Get the header value as a long in order to more correctly proxy very large requests private long getContentLength(HttpServletRequest request) { String contentLengthHeader = request.getHeader("Content-Length"); if (contentLengthHeader != null) { @@ -352,19 +207,7 @@ public class ProxyServlet extends HttpServlet { return -1L; } - protected void closeQuietly(Closeable closeable) { - try { - closeable.close(); - } catch (IOException e) { - log(e.getMessage(), e); - } - } - /** These are the "hop-by-hop" headers that should not be copied. - * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html - * I use an HttpClient HeaderGroup class instead of Set<String> because this - * approach does case insensitive lookup faster. - */ protected static final HeaderGroup hopByHopHeaders; static { hopByHopHeaders = new HeaderGroup(); @@ -376,13 +219,8 @@ public class ProxyServlet extends HttpServlet { } } - /** - * Copy request headers from the servlet client to the proxy request. - * This is easily overridden to add your own. - */ + protected void copyRequestHeaders(HttpServletRequest servletRequest, HttpRequest proxyRequest) { - // Get an Enumeration of all of the header names sent by the client - @SuppressWarnings("unchecked") Enumeration enumerationOfHeaderNames = servletRequest.getHeaderNames(); while (enumerationOfHeaderNames.hasMoreElements()) { String headerName = enumerationOfHeaderNames.nextElement(); @@ -390,30 +228,24 @@ public class ProxyServlet extends HttpServlet { } } - /** - * Copy a request header from the servlet client to the proxy request. - * This is easily overridden to filter out certain headers if desired. - */ protected void copyRequestHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest, String headerName) { - //Instead the content-length is effectively set via InputStreamEntity - if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) - return; - if (hopByHopHeaders.containsHeader(headerName)) - return; + if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)){ + return; + } + if (hopByHopHeaders.containsHeader(headerName)){ + return; + } - @SuppressWarnings("unchecked") Enumeration headers = servletRequest.getHeaders(headerName); - while (headers.hasMoreElements()) {//sometimes more than one value + while (headers.hasMoreElements()) { String headerValue = headers.nextElement(); - // In case the proxy host is running multiple virtual servers, - // rewrite the Host header to ensure that we get content from - // the correct virtual server if (!doPreserveHost && headerName.equalsIgnoreCase(HttpHeaders.HOST)) { - HttpHost host = getTargetHost(servletRequest); + HttpHost host = targetHost; headerValue = host.getHostName(); - if (host.getPort() != -1) - headerValue += ":"+host.getPort(); + if (host.getPort() != -1){ + headerValue += ":"+host.getPort(); + } } else if (!doPreserveCookies && headerName.equalsIgnoreCase(org.apache.http.cookie.SM.COOKIE)) { headerValue = getRealCookie(headerValue); } @@ -438,7 +270,6 @@ public class ProxyServlet extends HttpServlet { } } - /** Copy proxied response headers back to the servlet client. */ protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { for (Header header : proxyResponse.getAllHeaders()) { @@ -446,58 +277,43 @@ public class ProxyServlet extends HttpServlet { } } - /** Copy a proxied response header back to the servlet client. - * This is easily overwritten to filter out certain headers if desired. - */ protected void copyResponseHeader(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Header header) { String headerName = header.getName(); - if (hopByHopHeaders.containsHeader(headerName)) - return; + if (hopByHopHeaders.containsHeader(headerName)){ + return; + } String headerValue = header.getValue(); if (headerName.equalsIgnoreCase(org.apache.http.cookie.SM.SET_COOKIE) || headerName.equalsIgnoreCase(org.apache.http.cookie.SM.SET_COOKIE2)) { copyProxyCookie(servletRequest, servletResponse, headerValue); } else if (headerName.equalsIgnoreCase(HttpHeaders.LOCATION)) { - // LOCATION Header may have to be rewritten. servletResponse.addHeader(headerName, rewriteUrlFromResponse(servletRequest, headerValue)); } else { servletResponse.addHeader(headerName, headerValue); } } - /** - * Copy cookie from the proxy to the servlet client. - * Replaces cookie path to local path and renames cookie to avoid collisions. - */ protected void copyProxyCookie(HttpServletRequest servletRequest, HttpServletResponse servletResponse, String headerValue) { - //build path for resulting cookie - String path = servletRequest.getContextPath(); // path starts with / or is empty string - path += servletRequest.getServletPath(); // servlet path starts with / or is empty string + String path = servletRequest.getContextPath(); + path += servletRequest.getServletPath(); if(path.isEmpty()){ path = "/"; } for (HttpCookie cookie : HttpCookie.parse(headerValue)) { - //set cookie name prefixed w/ a proxy value so it won't collide w/ other cookies String proxyCookieName = doPreserveCookies ? cookie.getName() : getCookieNamePrefix(cookie.getName()) + cookie.getName(); Cookie servletCookie = new Cookie(proxyCookieName, cookie.getValue()); servletCookie.setComment(cookie.getComment()); servletCookie.setMaxAge((int) cookie.getMaxAge()); - servletCookie.setPath(path); //set to the path of the proxy servlet - // don't set cookie domain + servletCookie.setPath(path); servletCookie.setSecure(cookie.getSecure()); servletCookie.setVersion(cookie.getVersion()); servletResponse.addCookie(servletCookie); } } - /** - * Take any client cookies that were originally from the proxy and prepare them to send to the - * proxy. This relies on cookie headers being set correctly according to RFC 6265 Sec 5.4. - * This also blocks any local cookies from being sent to the proxy. - */ protected String getRealCookie(String cookieValue) { StringBuilder escapedCookie = new StringBuilder(); String cookies[] = cookieValue.split("[;,]"); @@ -517,14 +333,11 @@ public class ProxyServlet extends HttpServlet { return escapedCookie.toString(); } - /** The string prefixing rewritten cookies. */ protected String getCookieNamePrefix(String name) { return "!Proxy!" + getServletConfig().getServletName(); } - /** Copy response body data (the entity) from the proxy to the servlet client. */ - protected void copyResponseEntity(HttpResponse proxyResponse, HttpServletResponse servletResponse, - HttpRequest proxyRequest, HttpServletRequest servletRequest) + protected void copyResponseEntity(HttpResponse proxyResponse, HttpServletResponse servletResponse) throws IOException { HttpEntity entity = proxyResponse.getEntity(); if (entity != null) { @@ -533,81 +346,32 @@ public class ProxyServlet extends HttpServlet { } } - /** - * Reads the request URI from {@code servletRequest} and rewrites it, considering targetUri. - * It's used to make the new request. - */ - protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) { - StringBuilder uri = new StringBuilder(500); - uri.append(getTargetUri(servletRequest)); - // Handle the path given to the servlet + protected String getTargetUri(HttpServletRequest servletRequest) { String pathInfo = servletRequest.getPathInfo(); - if (pathInfo != null) {//ex: /my/path.html - // getPathInfo() returns decoded string, so we need encodeUriQuery to encode "%" characters - uri.append(encodeUriQuery(pathInfo, true)); - } - // Handle the query string & fragment - String queryString = servletRequest.getQueryString();//ex:(following '?'): name=value&foo=bar#fragment - String fragment = null; - //split off fragment from queryString, updating queryString if found - if (queryString != null) { - int fragIdx = queryString.indexOf('#'); - if (fragIdx >= 0) { - fragment = queryString.substring(fragIdx + 1); - queryString = queryString.substring(0,fragIdx); + targetUri = pathInfo.split("/")[1]; + String host = targetUri.split(":")[0]; + String port = targetUri.split(":")[1]; + try { + targetUriObj = new URI(targetUri); + } catch (URISyntaxException e) { + e.printStackTrace(); + throw new RuntimeException(e.getMessage(),e); } - } - - queryString = rewriteQueryStringFromRequest(servletRequest, queryString); - if (queryString != null && queryString.length() > 0) { - uri.append('?'); - // queryString is not decoded, so we need encodeUriQuery not to encode "%" characters, to avoid double-encoding - uri.append(encodeUriQuery(queryString, false)); - } - - if (doSendUrlFragment && fragment != null) { - uri.append('#'); - // fragment is not decoded, so we need encodeUriQuery not to encode "%" characters, to avoid double-encoding - uri.append(encodeUriQuery(fragment, false)); - } - return uri.toString(); + targetHost = new HttpHost(host, Integer.valueOf(port)); + return "http:/" + pathInfo; } - protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) { - return queryString; - } - /** - * For a redirect response from the target server, this translates {@code theUrl} to redirect to - * and translates it to one the original client can use. - */ protected String rewriteUrlFromResponse(HttpServletRequest servletRequest, String theUrl) { - //TODO document example paths - final String targetUri = getTargetUri(servletRequest); if (theUrl.startsWith(targetUri)) { - /*- - * The URL points back to the back-end server. - * Instead of returning it verbatim we replace the target path with our - * source path in a way that should instruct the original client to - * request the URL pointed through this Proxy. - * We do this by taking the current request and rewriting the path part - * using this servlet's absolute path and the path from the returned URL - * after the base target URL. - */ - StringBuffer curUrl = servletRequest.getRequestURL();//no query + StringBuffer curUrl = servletRequest.getRequestURL(); int pos; - // Skip the protocol part if ((pos = curUrl.indexOf("://"))>=0) { - // Skip the authority part - // + 3 to skip the separator between protocol and authority if ((pos = curUrl.indexOf("/", pos + 3)) >=0) { - // Trim everything after the authority part. curUrl.setLength(pos); } } - // Context path starts with a / if it is not blank curUrl.append(servletRequest.getContextPath()); - // Servlet path starts with a / if it is not blank curUrl.append(servletRequest.getServletPath()); curUrl.append(theUrl, targetUri.length(), theUrl.length()); return curUrl.toString(); @@ -615,66 +379,4 @@ public class ProxyServlet extends HttpServlet { return theUrl; } - /** The target URI as configured. Not null. */ - public String getTargetUri() { return targetUri; } - - /** - * Encodes characters in the query or fragment part of the URI. - * - *

Unfortunately, an incoming URI sometimes has characters disallowed by the spec. HttpClient - * insists that the outgoing proxied request has a valid URI because it uses Java's {@link URI}. - * To be more forgiving, we must escape the problematic characters. See the URI class for the - * spec. - * - * @param in example: name=value&foo=bar#fragment - * @param encodePercent determine whether percent characters need to be encoded - */ - protected static CharSequence encodeUriQuery(CharSequence in, boolean encodePercent) { - //Note that I can't simply use URI.java to encode because it will escape pre-existing escaped things. - StringBuilder outBuf = null; - Formatter formatter = null; - for(int i = 0; i < in.length(); i++) { - char c = in.charAt(i); - boolean escape = true; - if (c < 128) { - if (asciiQueryChars.get((int)c) && !(encodePercent && c == '%')) { - escape = false; - } - } else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) {//not-ascii - escape = false; - } - if (!escape) { - if (outBuf != null) - outBuf.append(c); - } else { - //escape - if (outBuf == null) { - outBuf = new StringBuilder(in.length() + 5*3); - outBuf.append(in,0,i); - formatter = new Formatter(outBuf); - } - //leading %, 0 padded, width 2, capital hex - formatter.format("%%%02X",(int)c);//TODO - } - } - return outBuf != null ? outBuf : in; - } - - protected static final BitSet asciiQueryChars; - static { - char[] c_unreserved = "_-!.~'()*".toCharArray();//plus alphanum - char[] c_punct = ",;:$&+=".toCharArray(); - char[] c_reserved = "?/[]@".toCharArray();//plus punct - - asciiQueryChars = new BitSet(128); - for(char c = 'a'; c <= 'z'; c++) asciiQueryChars.set((int)c); - for(char c = 'A'; c <= 'Z'; c++) asciiQueryChars.set((int)c); - for(char c = '0'; c <= '9'; c++) asciiQueryChars.set((int)c); - for(char c : c_unreserved) asciiQueryChars.set((int)c); - for(char c : c_punct) asciiQueryChars.set((int)c); - for(char c : c_reserved) asciiQueryChars.set((int)c); - - asciiQueryChars.set((int)'%');//leave existing percent escapes in place - } - } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/service/FunctionalReportService.java b/backend/src/main/java/io/metersphere/service/FunctionalReportService.java index 7b8211fb8a..a24b20f5c2 100644 --- a/backend/src/main/java/io/metersphere/service/FunctionalReportService.java +++ b/backend/src/main/java/io/metersphere/service/FunctionalReportService.java @@ -1,5 +1,8 @@ package io.metersphere.service; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.base.domain.FucTestLog; import io.metersphere.base.domain.FucTestReport; import io.metersphere.base.domain.FucTestReportExample; import io.metersphere.base.mapper.FucTestReportMapper; @@ -20,6 +23,8 @@ public class FunctionalReportService { private FucTestReportMapper fucTestReportMapper; @Resource private ExtFunctionalTestReportMapper extFunctionalTestReportMapper; + @Resource + private ZaleniumService zaleniumService; public List getRecentReportList(ReportRequest request) { FucTestReportExample example = new FucTestReportExample(); @@ -38,4 +43,12 @@ public class FunctionalReportService { public FunctionalReportDTO getReportTestAndProInfo(String reportId) { return extFunctionalTestReportMapper.getReportTestAndProInfo(reportId); } + + public FucTestLog getTestLog(String reportId) { + FucTestReport fucTestReport = fucTestReportMapper.selectByPrimaryKey(reportId); + String content = fucTestReport.getContent(); + String endpoint = "http://localhost:4444"; + FucTestLog fucTestLogs = JSON.parseObject(content, FucTestLog.class); + return zaleniumService.getFucTestLog(endpoint, fucTestLogs); + } } diff --git a/backend/src/main/java/io/metersphere/service/ZaleniumService.java b/backend/src/main/java/io/metersphere/service/ZaleniumService.java index 7e5b937bfe..3e8b069fe7 100644 --- a/backend/src/main/java/io/metersphere/service/ZaleniumService.java +++ b/backend/src/main/java/io/metersphere/service/ZaleniumService.java @@ -1,4 +1,93 @@ package io.metersphere.service; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.base.domain.*; +import io.metersphere.base.mapper.FucTestMapper; +import io.metersphere.base.mapper.FucTestReportMapper; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import javax.annotation.Resource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service public class ZaleniumService { + + @Resource + RestTemplate restTemplate; + + @Resource + FucTestReportMapper fucTestReportMapper; + + @Resource + FucTestMapper fucTestMapper; + + public void syncTestResult(){ + + List zaleniumTests = getZaleniumTest(); + + List fucTestIds = zaleniumTests.stream().map(test -> test.getTestName()) + .collect(Collectors.toList()); + + FucTestExample fucTestExample = new FucTestExample(); + fucTestExample.createCriteria().andIdIn(fucTestIds); + List fucTests = fucTestMapper.selectByExample(fucTestExample); + + List reportIds = fucTestReportMapper.selectByExample(new FucTestReportExample()).stream().map(report -> report.getId()).collect(Collectors.toList()); + + Map fucTestMaps = fucTests.stream() + .collect(Collectors.toMap(FucTest::getId, Function.identity())); + + zaleniumTests.forEach(item -> { + if(!reportIds.contains(item.getTestName())){ + saveFucTestReport(item, fucTestMaps.get(item.getTestName())); + } + }); + } + + private void saveFucTestReport(ZaleniumTest item, FucTest fucTest) { + FucTestReport fucTestReport = new FucTestReport(); + fucTestReport.setCreateTime(System.currentTimeMillis()); + fucTestReport.setUpdateTime(System.currentTimeMillis()); + fucTestReport.setTestId(item.getTestName()); + fucTestReport.setStatus("1"); + fucTestReport.setId(item.getTestName()); + JSONObject content = new JSONObject(); + content.put("videoUrl", "dashboard/" + item.getFileName()); + content.put("seleniumLog", "dashboard/logs/" + item.getTestNameNoExtension() + "/selenium-multinode-stderr.log"); + content.put("browserDriverLog", "dashboard/" + item.getBrowserDriverLogFileName()); + fucTestReport.setContent(content.toJSONString()); + if(fucTest != null){ + fucTestReport.setDescription(fucTest.getDescription()); + fucTestReport.setName(fucTest.getName()); + } else { + fucTestReport.setDescription("ZaleniumTest"); + fucTestReport.setName(item.getTestName()); + } + fucTestReportMapper.insert(fucTestReport); + } + + private List getZaleniumTest() { + List tests = new ArrayList<>(); + String url = "http://localhost:4444/dashboard/information?lastDateAddedToDashboard=0"; + tests.addAll(Arrays.asList(restTemplate.getForObject(url, ZaleniumTest[].class))); + return tests; + } + + public FucTestLog getFucTestLog(String endpoint, FucTestLog logPaths) { + FucTestLog testLog = new FucTestLog(); + testLog.setSeleniumLog(getZaleniumTestLog(endpoint, logPaths.getSeleniumLog())); + testLog.setBrowserDriverLog(getZaleniumTestLog(endpoint, logPaths.getBrowserDriverLog())); + return testLog; + } + + private String getZaleniumTestLog(String endpoint, String logPath) { + return restTemplate.getForObject(endpoint + "/" + logPath, String.class); + } + } diff --git a/backend/src/test/java/io/metersphere/ZaleniumTest.java b/backend/src/test/java/io/metersphere/ZaleniumTest.java index 5751d8a9ed..7b6031c2c7 100644 --- a/backend/src/test/java/io/metersphere/ZaleniumTest.java +++ b/backend/src/test/java/io/metersphere/ZaleniumTest.java @@ -1,4 +1,23 @@ package io.metersphere; +import io.metersphere.service.ZaleniumService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import javax.annotation.Resource; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ZaleniumTest { + + @Resource + ZaleniumService zaleniumService; + + @Test + public void sysnZaleniumTest(){ + zaleniumService.syncTestResult(); + } + }