All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.exense.step.examples.http.keywords.HttpClientKeyword Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright 2021 exense GmbH
 *
 * 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 ch.exense.step.examples.http.keywords;

import ch.exense.step.examples.http.HttpClient;
import ch.exense.step.examples.http.HttpRequest;
import ch.exense.step.examples.http.HttpResponse;
import ch.exense.step.library.commons.AbstractEnhancedKeyword;
import ch.exense.step.library.commons.BusinessException;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.message.BasicNameValuePair;
import step.core.accessors.Attribute;
import step.grid.io.AttachmentHelper;
import step.handlers.javahandler.Input;
import step.handlers.javahandler.Keyword;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Attribute(key = "category", value = "HTTP")
public class HttpClientKeyword extends AbstractEnhancedKeyword {

    private static final String HTTP_CLIENT = "httpClient";
    private static final String DATA = "Data";
    public static final String HEADER_PREFIX = "Header_";
    public static final String PARAM_PREFIX = "FormData_";
    public static final String EXTRACT_PREFIX = "Extract_";
    public static final String CHECK_PREFIX = "Check_";
    public static final String MULTIPART_PARAM_PREFIX = "MultiPartFormData_";

    /**
     * step Keyword to init an Apache HTTP client the client will be placed in the
     * current step session
     * 

* Keyword inputs: * BasicAuthUser: set basic authenticate username (require password passed as a protected parameter) * BasicAuthHost: enable preemptive authentication (require the 5 basic_auth fields) * BasicAuthHostScheme: target host scheme (i.e. https) * BasicAuthPort: target host port number * KeyStorePath (optional) * CustomDnsResolverTargetIP: enable a custom DNS resolver, requires "CustomDnsResolverHostWithCustomDns" * CustomDnsResolverHostWithCustomDns: requests to this host will be resolved to the "CustomDnsResolverTargetIP" * ProxyHost (optional) * ProxyPort (mandatory if ProxyHost is provided) * NoProxy: comma separated list of host(s) to not use the Proxy for (does not support wildcard) * * @throws IOException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws KeyStoreException * @throws KeyManagementException * @throws UnrecoverableKeyException */ @Keyword(schema = "{\"properties\":{" + "\"BasicAuthUser\":{\"type\":\"string\"}," + "\"BasicAuthHost\":{\"type\":\"string\"}," + "\"BasicAuthHostScheme\":{\"type\":\"string\"}," + "\"BasicAuthPort\":{\"type\":\"string\"}," + "\"KeyStorePath\":{\"type\":\"string\"}," + "\"TimeoutInMs\":{\"type\":\"string\"}," + "\"CustomDnsResolverTargetIP\":{\"type\":\"string\"}," + "\"CustomDnsResolverHostWithCustomDns\":{\"type\":\"string\"}," + "\"ProxyHost\":{\"type\":\"string\"}," + "\"ProxyPort\":{\"type\":\"integer\"}," + "\"NoProxy\":{\"type\":\"string\"}" + "},\"required\":[]}", properties = {""}, description = "Keyword used to init an Apache HTTP client.") public void InitHttpClient() throws UnrecoverableKeyException, KeyManagementException, KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { HttpClient httpClient; String basicAuthUser = null; String basicAuthPassword = null; String basicAuthHostScheme = null; String basicAuthHost = null; int basicAuthPort = 0; String keyStorePath = null; String keyStorePassword = null; String customDnsResolverTargetIP = null; String customDnsResolverHostWithCustomDns = null; String proxyHost = null; Integer proxyPort = null; String noProxy = null; if (input.containsKey("ProxyHost")) { if (!input.containsKey("ProxyPort")) { throw new BusinessException("ProxyHost provided without ProxyPort"); } else { proxyHost = input.getString("ProxyHost"); proxyPort = input.getInt("ProxyPort"); noProxy = input.getString("NoProxy", ""); } } if (input.containsKey("KeyStorePath")) { keyStorePath = input.getString("KeyStorePath"); if (input.containsKey("KeyStorePassword")) { throw new BusinessException(String.format("Passing the keystore password as input is deprecated. " + "Create a protected parameter called '%s_Password' instead",keyStorePath)); } keyStorePassword = getPassword(keyStorePath); } if (input.containsKey("CustomDnsResolverTargetIP")) { if (!input.containsKey("CustomDnsResolverHostWithCustomDns")) { throw new BusinessException( "'CustomDnsResolverTargetIP' provided without 'CustomDnsResolverHostWithCustomDns'."); } else { customDnsResolverTargetIP = input.getString("CustomDnsResolverTargetIP"); customDnsResolverHostWithCustomDns = input.getString("CustomDnsResolverHostWithCustomDns"); } } if (input.containsKey("BasicAuthUser")) { basicAuthUser = input.getString("BasicAuthUser"); basicAuthPassword = getPassword(basicAuthUser); if (input.containsKey("BasicAuthHost")) { if (!input.containsKey("BasicAuthPort") || !input.containsKey("BasicAuthHostScheme")) { throw new BusinessException( "BasicAuthHost provided without 'BasicAuthPort' or 'BasicAuthHostScheme'."); } else { basicAuthHost = input.getString("BasicAuthHost"); basicAuthHostScheme = input.getString("BasicAuthHostScheme"); basicAuthPort = Integer.parseInt(input.getString("BasicAuthPort")); } } } int timeoutInMs = Integer.parseInt(input.getString("TimeoutInMs", "60000")); httpClient = new HttpClient(timeoutInMs, keyStorePath, keyStorePassword, customDnsResolverTargetIP, customDnsResolverHostWithCustomDns, basicAuthHostScheme, basicAuthHost, basicAuthPort, basicAuthUser, basicAuthPassword, proxyHost, proxyPort, noProxy); getSession().put(HTTP_CLIENT, httpClient); } /** * step Keyword to execute one HTTP request *

* Keyword inputs: Name (optional): name of the request used for RTM * measurements (default: URL) URL: the URL of the request Method: GET, POST * (other methods might be supported) Header_* (optional): naming convention to * pass header parameters, ex: key: Header_accept, value: application/json, * text/plain key: Header_accept-language, value: * en-US,en;q=0.9,fr;q=0.8,de;q=0.7 Data (optional): body payload as string * (used only for Post method and only if param_* are not set) FormData_* * (optional): naming convention to pass request parameters, ex: key: * FormData_user, value: myname key: FormData_password, value: mypassword * Extract_*: naming convention to pass content check strings, ex: key: * extract_myId, value: regexp as string with one group Check_* (optional): * naming convention to pass content check strings, ex: key: check_pageTitle, * value: 'my website title' ReturnResponse: default true *

*

* Keyword output StatusCode: Request status code Headers: headers Cookies: * cookies set in this response's header Response: response payload (depends on * input returnResponsePayload, default true) Extract_*: extracted fields * Check_*: content checks result (true if found) * * @throws Exception */ @Keyword(description = "Keyword used to execute an HTTP request") public void HttpRequest(@Input(name = "URL", required = true) String url, @Input(name = "Method", defaultValue = "GET") String method, @Input(name = "Enable_Redirect", defaultValue = "true") boolean enableRedirect, @Input(name = "Headers", defaultValue = "{}") HashMap headers, @Input(name = "FormData") HashMap formDataMap, @Input(name = "Data", defaultValue = "") String payload, @Input(name = "MultiPartFormData") HashMap multiPartFormDataMap, @Input(name = "ExtractRegexp") HashMap extractRegexpMap, @Input(name = "ExtractJsonPath", defaultValue="{}") HashMap extractJsonPath, @Input(name = "myList") List list, @Input(name = "Checks") HashMap textChecks, @Input(name = "ReturnResponse", defaultValue = "true") boolean returnResponse, @Input(name = "SaveResponseAsAttachment", defaultValue = "false") boolean saveResponseAsAttachment, @Input(name = "Name", defaultValue = "GET") String name) throws Exception { String requestName = Objects.requireNonNullElse(name, url); //Transform inputs List formData = new ArrayList<>(); if (formDataMap != null) { formData = formDataMap.entrySet().stream().map(e -> new BasicNameValuePair(e.getKey(), e.getValue())).collect(Collectors.toList()); } List multiPartFormData = new ArrayList<>(); if (multiPartFormDataMap != null) { multiPartFormData = multiPartFormDataMap.entrySet().stream().map(e -> new BasicNameValuePair(e.getKey(), e.getValue())).collect(Collectors.toList()); } Map extractRegexp = new HashMap<>(); if (extractRegexpMap != null) { extractRegexpMap.forEach((k,v) -> extractRegexp.put(k, Pattern.compile(v))); } HttpClient httpClient = getHttpClientFromSession(); // Init the client if not available in the session if (httpClient == null) { InitHttpClient(); httpClient = getHttpClientFromSession(); } // Init request HttpRequest request = new HttpRequest(url, method); RequestConfig clientDefaultRequestConfig = httpClient.getRequestConfig(); RequestConfig config = RequestConfig.custom() .setConnectTimeout(clientDefaultRequestConfig.getConnectTimeout()) .setConnectionRequestTimeout(clientDefaultRequestConfig.getConnectionRequestTimeout()) .setSocketTimeout(clientDefaultRequestConfig.getSocketTimeout()) .setRedirectsEnabled(enableRedirect) .build(); request.setConfig(config); headers.forEach((k, v) -> request.appendHeader(k, v)); if (!formData.isEmpty()) { request.setParams(formData); } else if (payload != null && !payload.isEmpty()) { request.setRowPayload(payload); } else if (!multiPartFormData.isEmpty()) { request.setMultiPartParams(multiPartFormData); } Map measureData = new HashMap<>(); measureData.put("DestIP", httpClient.getTargetIP()); HttpResponse httpResponse; try { output.startMeasure(requestName); httpResponse = httpClient.executeRequestInContext(request); } catch (SSLException e) { output.setBusinessError("SSL error: "+e.getMessage()); output.addAttachment(AttachmentHelper.generateAttachmentForException(e)); return; } catch (UnknownHostException e) { output.setBusinessError("Unknown host: "+e.getMessage()); output.addAttachment(AttachmentHelper.generateAttachmentForException(e)); return; } catch (Exception e) { // stop network measure measureData.put("ExceptionType", e.getClass().toString()); measureData.put("ExceptionMessage", (e.getMessage() == null) ? "null" : e.getMessage()); throw e; } finally { output.stopMeasure(measureData); } output.add("StatusCode", Integer.toString(httpResponse.getStatus())); output.add("Headers", httpResponse.getResponseHeaders().toString()); output.add("Cookies", httpResponse.getCookies().toString()); String responsePayload = httpResponse.getResponsePayload(); if (returnResponse) { if (saveResponseAsAttachment) { String attachmentName = "attachment.data"; BasicNameValuePair result; if ((result = httpResponse.getResponseHeader("Content-Disposition")) != null) { Matcher pattern_1 = Pattern.compile(".*filename ?= ?([^\"]+?);?.*").matcher(result.getValue()); if (pattern_1.find()) { attachmentName = pattern_1.group(1); } Matcher pattern_2 = Pattern.compile(".*filename ?= ?\"([^\"]+?)\".*").matcher(result.getValue()); if (pattern_2.find()) { attachmentName = pattern_2.group(1); } } output.addAttachment(AttachmentHelper. generateAttachmentFromByteArray(httpResponse.getResponsePayloadAsBytes(), attachmentName)); } else { output.add("Response", responsePayload); } } //Content checks and extractions ArrayList businessErrors = new ArrayList<>(); // extract all fields for (String key : extractRegexp.keySet()) { String value = ""; Matcher m = extractRegexp.get(key).matcher(responsePayload); if (m.find()) { // return 1st group if defined or full match try { value = m.group(1); } catch (Exception e) { value = m.group(0); } output.add(key, value); } else { businessErrors.add("ExtractRegexp '" + key + "' with pattern '" + extractRegexp.get(key) + "' has no match"); } } for (String pathKey : extractJsonPath.keySet()) { try { Object value = JsonPath.read(responsePayload, extractJsonPath.get(pathKey)); Class valueType = value.getClass(); if (Boolean.class.isAssignableFrom(valueType) || valueType.equals(boolean.class)) { output.add(pathKey, (boolean) value); } else if (Integer.class.isAssignableFrom(valueType) || valueType.equals(int.class)) { output.add(pathKey, (int) value); } else if (Double.class.isAssignableFrom(valueType) || valueType.equals(double.class)) { output.add(pathKey, (double) value); } else if (Long.class.isAssignableFrom(valueType) || valueType.equals(long.class)) { output.add(pathKey, (long) value); } else { output.add(pathKey, value.toString()); } } catch (PathNotFoundException e) { businessErrors.add("ExtractJsonPath '" + pathKey + "' with JSON path '" + extractJsonPath.get(pathKey) + "' was not found"); } } // do all checks if (textChecks != null) { textChecks.forEach((k, v) -> { if (!responsePayload.contains(v)) { businessErrors.add("Content check '" + k + "' with text '" + v + "' was not found"); } }); } if (!businessErrors.isEmpty()) { output.setBusinessError(String.join(", ", businessErrors)); } } /** * step Keyword to close the Apache HTTP clients stored in the step session */ @Keyword(description = "Keyword used to close the HTTP client currently in session.") public void CloseHttpClient() { HttpClient httpClient = getHttpClientFromSession(); httpClient.close(); } protected HttpClient getHttpClientFromSession() { return (HttpClient) getSession().get(HTTP_CLIENT); } @Keyword(description = "Keyword used to read the existing cookies") public void GetCookies() { HttpClient httpClient = getHttpClientFromSession(); output.add("Cookies", httpClient.getCookiesFromStore().toString()); } @Keyword(description = "Keyword used to clear the existing cookies") public void ClearCookies() { HttpClient httpClient = getHttpClientFromSession(); httpClient.clearCookies(); } @Keyword(schema = "{\"properties\":{" + "\"Name\":{\"type\":\"string\"}," + "\"Value\":{\"type\":\"string\"}," + "\"Domain\":{\"type\":\"string\"}," + "\"Path\":{\"type\":\"string\"}" + "}, \"required\":[\"cookies\",\"value\",\"domain\",\"path\"]}", properties = {}, description = "Keyword used to add a cookie to the current http client") public void AddCookie() { HttpClient httpClient = getHttpClientFromSession(); String name = input.getString("Name"); String value = input.getString("Value"); String domain = input.getString("Domain"); String path = input.getString("Path"); httpClient.addCookie(name, value, domain, path); GetCookies(); } @Deprecated @Keyword(description = "Deprecated, use the InitHttpClient keyword") public void EnableProxy() { throw new BusinessException("This Keyword has been deprecated. Use the InitHttpClient one together with the ProxyHost and ProxyPort input parameters instead"); } @Deprecated @Keyword(description = "Deprecated, use the InitHttpClient keyword") public void DisableProxy() { throw new BusinessException("This Keyword has been deprecated"); } @Deprecated @Keyword(description = "Deprecated, use the InitHttpClient keyword") public void ShowProxySettings() { throw new BusinessException("This Keyword has been deprecated"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy