From 93c1eb2302cec7396c347f2a2829a5d0d34ff1dd Mon Sep 17 00:00:00 2001 From: Konrad Krakowiak Date: Wed, 18 Mar 2015 13:06:32 -0700 Subject: [PATCH] ability to mark execution as success only if the response contains specific text --- .../plugins/http_request/HttpRequest.java | 181 ++++++++++++------ .../http_request/auth/FormAuthentication.java | 2 +- .../http_request/util/HttpClientUtil.java | 39 +--- .../http_request/HttpRequest/config.jelly | 3 + .../help-validResponseContent.html | 3 + 5 files changed, 140 insertions(+), 88 deletions(-) create mode 100644 src/main/resources/jenkins/plugins/http_request/HttpRequest/help-validResponseContent.html diff --git a/src/main/java/jenkins/plugins/http_request/HttpRequest.java b/src/main/java/jenkins/plugins/http_request/HttpRequest.java index 3d02fffb..d1aa966c 100644 --- a/src/main/java/jenkins/plugins/http_request/HttpRequest.java +++ b/src/main/java/jenkins/plugins/http_request/HttpRequest.java @@ -1,26 +1,12 @@ package jenkins.plugins.http_request; -import static com.google.common.base.Preconditions.checkArgument; - -import javax.servlet.ServletException; -import java.io.IOException; -import java.io.PrintStream; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import com.google.common.base.Objects; +import com.google.common.base.Strings; +import com.google.common.base.Supplier; import com.google.common.collect.Range; import com.google.common.collect.Ranges; import com.google.common.primitives.Ints; -import hudson.EnvVars; -import hudson.Extension; -import hudson.FilePath; -import hudson.Launcher; -import hudson.Util; +import hudson.*; import hudson.init.InitMilestone; import hudson.init.Initializer; import hudson.model.AbstractBuild; @@ -45,10 +31,23 @@ import org.apache.http.impl.client.SystemDefaultHttpClient; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; +import javax.servlet.ServletException; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; + /** * @author Janario Oliveira */ @@ -65,6 +64,7 @@ public class HttpRequest extends Builder { private List customHeaders = new ArrayList(); private final Integer timeout; private String validResponseCodes; + private String validResponseContent; /** * @deprecated only to deserialize and serialize in a new form @@ -77,8 +77,8 @@ public HttpRequest(String url, HttpMode httpMode, String authentication, MimeTyp MimeType acceptType, String outputFile, Boolean returnCodeBuildRelevant, Boolean consoleLogResponseBody, Boolean passBuildParameters, List customHeaders, Integer timeout, - String validResponseCodes) - throws URISyntaxException { + String validResponseCodes, String validResponseContent) + throws URISyntaxException { this.url = url; this.contentType = contentType; this.acceptType = acceptType; @@ -86,6 +86,7 @@ public HttpRequest(String url, HttpMode httpMode, String authentication, MimeTyp this.httpMode = httpMode; this.customHeaders = customHeaders; this.validResponseCodes = validResponseCodes; + this.validResponseContent = validResponseContent; this.authentication = Util.fixEmpty(authentication); this.consoleLogResponseBody = consoleLogResponseBody; this.passBuildParameters = passBuildParameters; @@ -165,6 +166,10 @@ public String getValidResponseCodes() { return validResponseCodes; } + public String getValidResponseContent() { + return validResponseContent; + } + @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { defineDefaultConfigurations(); @@ -172,35 +177,18 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen final PrintStream logger = listener.getLogger(); logger.println("HttpMode: " + httpMode); - final DefaultHttpClient httpclient = new SystemDefaultHttpClient(); - final HttpContext context = new BasicHttpContext(); final EnvVars envVars = build.getEnvironment(listener); final List params = createParameters(build, logger, envVars); String evaluatedUrl = evaluate(url, build.getBuildVariableResolver(), envVars); logger.println(String.format("URL: %s", evaluatedUrl)); - final RequestAction requestAction = new RequestAction(new URL(evaluatedUrl), httpMode, params); - final HttpClientUtil clientUtil = new HttpClientUtil(); - if (outputFile != null && !outputFile.isEmpty()) { - FilePath outputFilePath = build.getWorkspace().child(outputFile); - clientUtil.setOutputFile(outputFilePath); - } - final HttpRequestBase httpRequestBase = clientUtil.createRequestBase(requestAction); - if (contentType != MimeType.NOT_SET) { - httpRequestBase.setHeader("Content-type", contentType.getValue()); - logger.println("Content-type: " + contentType); - } - - if (acceptType != MimeType.NOT_SET){ - httpRequestBase.setHeader("Accept", acceptType.getValue()); - logger.println("Accept: " + acceptType); - } - - for (NameValuePair header : customHeaders) { - httpRequestBase.addHeader(header.getName(), header.getValue()); - } + DefaultHttpClient httpclient = new SystemDefaultHttpClient(); + RequestAction requestAction = new RequestAction(new URL(evaluatedUrl), httpMode, params); + HttpClientUtil clientUtil = new HttpClientUtil(); + HttpRequestBase httpRequestBase = getHttpRequestBase(logger, requestAction, clientUtil); + HttpContext context = new BasicHttpContext(); if (authentication != null) { final Authenticator auth = getDescriptor().getAuthentication(authentication); @@ -211,22 +199,80 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen logger.println("Using authentication: " + auth.getKeyName()); auth.authenticate(httpclient, context, httpRequestBase, logger, timeout); } + final HttpResponse response = clientUtil.execute(httpclient, context, httpRequestBase, logger, timeout); + + try { + ResponseContentSupplier responseContentSupplier = new ResponseContentSupplier(response); + logResponse(build, logger, responseContentSupplier); - final HttpResponse execute = clientUtil.execute(httpclient, context, httpRequestBase, logger, consoleLogResponseBody, timeout); + return responseCodeIsValid(response, logger) && contentIsValid(responseContentSupplier, logger); + } finally { + EntityUtils.consume(response.getEntity()); + } + } - boolean successCode = false; + private boolean contentIsValid(ResponseContentSupplier responseContentSupplier, PrintStream logger) { + if (Strings.isNullOrEmpty(validResponseContent)) { + return true; + } + + String response = responseContentSupplier.get(); + if (!response.contains(validResponseContent)) { + logger.println("Fail: Response with length " + response.length() + " doesn't contain '" + validResponseContent + "'"); + return false; + } + return true; + } + + private boolean responseCodeIsValid(HttpResponse response, PrintStream logger) { List> ranges = getDescriptor().parseToRange(validResponseCodes); for (Range range : ranges) { - if (range.contains(execute.getStatusLine().getStatusCode())) { + if (range.contains(response.getStatusLine().getStatusCode())) { logger.println("Success code from " + range); - successCode = true; - break; + return true; + } + } + logger.println("Fail: Any code list (" + ranges + ") match the returned code " + response.getStatusLine().getStatusCode()); + return false; + + } + + private void logResponse(AbstractBuild build, PrintStream logger, ResponseContentSupplier responseContentSupplier) throws IOException, InterruptedException { + FilePath outputFilePath = getOutputFilePath(build); + if (consoleLogResponseBody || outputFilePath != null) { + if (consoleLogResponseBody) { + logger.println("Response: \n" + responseContentSupplier.get()); } + if (outputFilePath != null && responseContentSupplier.get() != null) { + outputFilePath.write().write(responseContentSupplier.get().getBytes()); + } + } + } + + private HttpRequestBase getHttpRequestBase(PrintStream logger, RequestAction requestAction, HttpClientUtil clientUtil) throws IOException { + HttpRequestBase httpRequestBase = clientUtil.createRequestBase(requestAction); + + if (contentType != MimeType.NOT_SET) { + httpRequestBase.setHeader("Content-type", contentType.getValue()); + logger.println("Content-type: " + contentType); + } + + if (acceptType != MimeType.NOT_SET) { + httpRequestBase.setHeader("Accept", acceptType.getValue()); + logger.println("Accept: " + acceptType); } - if (!successCode) { - logger.println("Fail: Any code list (" + ranges + ") match the returned code " + execute.getStatusLine().getStatusCode()); + + for (NameValuePair header : customHeaders) { + httpRequestBase.addHeader(header.getName(), header.getValue()); } - return successCode; + return httpRequestBase; + } + + private FilePath getOutputFilePath(AbstractBuild build) { + if (outputFile != null && !outputFile.isEmpty()) { + return build.getWorkspace().child(outputFile); + } + return null; } private List createParameters( @@ -268,19 +314,19 @@ public static final class DescriptorImpl extends BuildStepDescriptor { private List basicDigestAuthentications = new ArrayList(); private List formAuthentications = new ArrayList(); private boolean defaultReturnCodeBuildRelevant = true; - private boolean defaultLogResponseBody = true; + private boolean defaultLogResponseBody = true; public DescriptorImpl() { load(); } - public boolean isDefaultLogResponseBody() { - return defaultLogResponseBody; - } + public boolean isDefaultLogResponseBody() { + return defaultLogResponseBody; + } - public void setDefaultLogResponseBody(boolean defaultLogResponseBody) { - this.defaultLogResponseBody = defaultLogResponseBody; - } + public void setDefaultLogResponseBody(boolean defaultLogResponseBody) { + this.defaultLogResponseBody = defaultLogResponseBody; + } public HttpMode getDefaultHttpMode() { return defaultHttpMode; @@ -420,7 +466,7 @@ List> parseToRange(String value) { String[] fromTo = code.trim().split(":"); checkArgument(fromTo.length <= 2, "Code %s should be an interval from:to or a single value", code); Integer from = Ints.tryParse(fromTo[0]); - checkArgument(from !=null , "Invalid number %s", fromTo[0]); + checkArgument(from != null, "Invalid number %s", fromTo[0]); Integer to = from; if (fromTo.length != 1) { @@ -448,4 +494,25 @@ public FormValidation doCheckValidResponseCodes(@QueryParameter String value) { return FormValidation.ok(); } } + + private class ResponseContentSupplier implements Supplier { + + private String content; + private final HttpResponse response; + + private ResponseContentSupplier(HttpResponse response) { + this.response = response; + } + + public String get() { + try { + if (content == null) { + content = EntityUtils.toString(response.getEntity()); + } + return content; + } catch (IOException e) { + return null; + } + } + } } diff --git a/src/main/java/jenkins/plugins/http_request/auth/FormAuthentication.java b/src/main/java/jenkins/plugins/http_request/auth/FormAuthentication.java index eb951742..4229e6bd 100644 --- a/src/main/java/jenkins/plugins/http_request/auth/FormAuthentication.java +++ b/src/main/java/jenkins/plugins/http_request/auth/FormAuthentication.java @@ -50,7 +50,7 @@ public void authenticate(DefaultHttpClient client, HttpContext context, for (RequestAction requestAction : actions) { final HttpRequestBase method = clientUtil.createRequestBase(requestAction); - final HttpResponse execute = clientUtil.execute(client, context, method, logger, true, timeout); + final HttpResponse execute = clientUtil.execute(client, context, method, logger, timeout); //from 400(client error) to 599(server error) if ((execute.getStatusLine().getStatusCode() >= 400 && execute.getStatusLine().getStatusCode() <= 599)) { diff --git a/src/main/java/jenkins/plugins/http_request/util/HttpClientUtil.java b/src/main/java/jenkins/plugins/http_request/util/HttpClientUtil.java index ed434bca..e9f49752 100644 --- a/src/main/java/jenkins/plugins/http_request/util/HttpClientUtil.java +++ b/src/main/java/jenkins/plugins/http_request/util/HttpClientUtil.java @@ -1,33 +1,23 @@ package jenkins.plugins.http_request.util; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintStream; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.List; - import hudson.FilePath; import jenkins.plugins.http_request.HttpMode; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.*; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.HttpContext; -import org.apache.http.util.EntityUtils; + +import java.io.*; +import java.net.URI; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; /** * @author Janario Oliveira @@ -111,7 +101,7 @@ public HttpDelete makeDelete(RequestAction requestAction) throws UnsupportedEnco } public HttpResponse execute(DefaultHttpClient client, HttpContext context, HttpRequestBase method, - PrintStream logger, boolean consolLogResponseBody, Integer timeout) throws IOException, InterruptedException { + PrintStream logger, Integer timeout) throws IOException, InterruptedException { doSecurity(client, method.getURI()); logger.println("Sending request to url: " + method.getURI()); @@ -126,17 +116,6 @@ public HttpResponse execute(DefaultHttpClient client, HttpContext context, HttpR final HttpResponse httpResponse = client.execute(method, context); logger.println("Response Code: " + httpResponse.getStatusLine()); - if (consolLogResponseBody || outputFilePath != null) { - String httpData = EntityUtils.toString(httpResponse.getEntity()); - if (consolLogResponseBody) { - logger.println("Response: \n" + httpData); - } - if (outputFilePath != null) { - outputFilePath.write().write(httpData.getBytes()); - } - } - EntityUtils.consume(httpResponse.getEntity()); - return httpResponse; } @@ -157,7 +136,7 @@ public boolean isTrusted(X509Certificate[] chain, final int port = uri.getPort() < 0 ? 443 : uri.getPort(); schemeRegistry.register(new Scheme(uri.getScheme(), port, ssf)); } catch (Exception ex) { - throw new IOException("Error unknow", ex); + throw new IOException("Error unknown", ex); } } } diff --git a/src/main/resources/jenkins/plugins/http_request/HttpRequest/config.jelly b/src/main/resources/jenkins/plugins/http_request/HttpRequest/config.jelly index 2c8ff34f..1f24304a 100644 --- a/src/main/resources/jenkins/plugins/http_request/HttpRequest/config.jelly +++ b/src/main/resources/jenkins/plugins/http_request/HttpRequest/config.jelly @@ -13,6 +13,9 @@ + + + diff --git a/src/main/resources/jenkins/plugins/http_request/HttpRequest/help-validResponseContent.html b/src/main/resources/jenkins/plugins/http_request/HttpRequest/help-validResponseContent.html new file mode 100644 index 00000000..10fa82ef --- /dev/null +++ b/src/main/resources/jenkins/plugins/http_request/HttpRequest/help-validResponseContent.html @@ -0,0 +1,3 @@ +
+ If set response must contain this string to mark an execution as success.
+