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

com.intuit.karate.http.HttpClient Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
/*
 * The MIT License
 *
 * Copyright 2017 Intuit Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.intuit.karate.http;

import com.intuit.karate.exception.KarateException;
import com.intuit.karate.ScenarioContext;
import com.intuit.karate.ScriptValue;
import com.intuit.karate.XmlUtils;
import com.jayway.jsonpath.DocumentContext;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.w3c.dom.Node;

/**
 *
 * @author pthomas3
 * @param 
 */
public abstract class HttpClient {

    public static final String APPLICATION_JSON = "application/json";
    public static final String APPLICATION_XML = "application/xml";
    public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
    public static final String TEXT_PLAIN = "text/plain";
    public static final String MULTIPART_FORM_DATA = "multipart/form-data";
    public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";

    private static final String KARATE_HTTP_PROPERTIES = "karate-http.properties";

    protected HttpRequestBuilder request;

    /**
     * guaranteed to be called once if empty constructor was used
     *
     * @param config
     * @param context
     */
    public abstract void configure(HttpConfig config, ScenarioContext context);

    protected abstract T getEntity(List multiPartItems, String mediaType);

    protected abstract T getEntity(MultiValuedMap formFields, String mediaType);

    protected abstract T getEntity(InputStream stream, String mediaType);

    protected abstract T getEntity(String content, String mediaType);

    protected abstract void buildUrl(String url);

    protected abstract void buildPath(String path);

    protected abstract void buildParam(String name, Object... values);

    protected abstract void buildHeader(String name, Object value, boolean replace);

    protected abstract void buildCookie(Cookie cookie);

    protected abstract HttpResponse makeHttpRequest(T entity, ScenarioContext context);

    protected abstract String getRequestUri();

    private T getEntityInternal(ScriptValue body, String mediaType) {
        if (body.isJsonLike()) {
            if (mediaType == null) {
                mediaType = APPLICATION_JSON;
            }
            DocumentContext json = body.getAsJsonDocument();
            return getEntity(json.jsonString(), mediaType);
        } else if (body.isXml()) {
            Node node = body.getValue(Node.class);
            if (mediaType == null) {
                mediaType = APPLICATION_XML;
            }
            return getEntity(XmlUtils.toString(node), mediaType);
        } else if (body.isStream()) {
            InputStream is = body.getValue(InputStream.class);
            if (mediaType == null) {
                mediaType = APPLICATION_OCTET_STREAM;
            }
            return getEntity(is, mediaType);
        } else if (body.isByteArray()) {
            byte[] bytes = body.getValue(byte[].class);
            InputStream is = new ByteArrayInputStream(bytes);
            if (mediaType == null) {
                mediaType = APPLICATION_OCTET_STREAM;
            }
            return getEntity(is, mediaType);
        } else {
            if (mediaType == null) {
                mediaType = TEXT_PLAIN;
            }
            return getEntity(body.getAsString(), mediaType);
        }
    }

    private T buildRequestInternal(HttpRequestBuilder request, ScenarioContext context) {
        String method = request.getMethod();
        if (method == null) {
            String msg = "'method' is required to make an http call";
            context.logger.error(msg);
            throw new RuntimeException(msg);
        }
        method = method.toUpperCase();
        request.setMethod(method);
        this.request = request;
        boolean methodRequiresBody
                = "POST".equals(method)
                || "PUT".equals(method)
                || "PATCH".equals(method)
                || "DELETE".equals(method);
        String url = request.getUrl();
        if (url == null) {
            String msg = "url not set, please refer to the keyword documentation for 'url'";
            context.logger.error(msg);
            throw new RuntimeException(msg);
        }
        buildUrl(url);
        if (request.getPaths() != null) {
            for (String path : request.getPaths()) {
                buildPath(path);
            }
        }
        if (request.getParams() != null) {
            for (Map.Entry entry : request.getParams().entrySet()) {
                buildParam(entry.getKey(), entry.getValue().toArray());
            }
        }
        if (request.getFormFields() != null && !methodRequiresBody) {
            // not POST, move form-fields to params
            for (Map.Entry entry : request.getFormFields().entrySet()) {
                buildParam(entry.getKey(), entry.getValue().toArray());
            }

        }
        if (request.getHeaders() != null) {
            for (Map.Entry entry : request.getHeaders().entrySet()) {
                for (Object value : entry.getValue()) {
                    buildHeader(entry.getKey(), value, false);
                }
            }
        }
        HttpConfig config = context.getConfig();
        Map configHeaders = config.getHeaders().evalAsMap(context);
        if (configHeaders != null) {
            for (Map.Entry entry : configHeaders.entrySet()) {
                buildHeader(entry.getKey(), entry.getValue(), true);
            }
        }
        if (request.getCookies() != null) {
            for (Cookie cookie : request.getCookies().values()) {
                buildCookie(cookie);
            }
        }
        Map configCookies = config.getCookies().evalAsMap(context);
        for (Cookie cookie : Cookie.toCookies(configCookies)) {
            buildCookie(cookie);
        }
        if (methodRequiresBody) {
            String mediaType = request.getContentType();
            if (configHeaders != null && configHeaders.containsKey(HttpUtils.HEADER_CONTENT_TYPE)) { // edge case if config headers had Content-Type
                mediaType = (String) configHeaders.get(HttpUtils.HEADER_CONTENT_TYPE);
            }
            if (request.getMultiPartItems() != null) {
                if (mediaType == null) {
                    mediaType = MULTIPART_FORM_DATA;
                }
                return getEntity(request.getMultiPartItems(), mediaType);
            } else if (request.getFormFields() != null) {
                if (mediaType == null) {
                    mediaType = APPLICATION_FORM_URLENCODED;
                }
                return getEntity(request.getFormFields(), mediaType);
            } else {
                ScriptValue body = request.getBody();
                if ((body == null || body.isNull())) {
                    if ("DELETE".equals(method)) {
                        return null; // traditional DELETE, we also support using a request body for DELETE
                    } else {
                        String msg = "request body is required for a " + method + ", please use the 'request' keyword";
                        throw new RuntimeException(msg);
                    }
                }
                return getEntityInternal(body, mediaType);
            }
        } else {
            return null;
        }
    }

    public HttpResponse invoke(HttpRequestBuilder request, ScenarioContext context) {
        T body = buildRequestInternal(request, context);        
        try {
            HttpResponse response = makeHttpRequest(body, context);
            context.updateConfigCookies(response.getCookies());
            return response;
        } catch (Exception e) {
            long startTime = context.getPrevRequest().getStartTime();
            long responseTime = System.currentTimeMillis() - startTime;
            String message = "http call failed after " + responseTime + " milliseconds for URL: " + getRequestUri();
            context.logger.error(e.getMessage() + ", " + message);
            throw new KarateException(message, e);
        }
    }

    public static HttpClient construct(String className) {
        try {
            Class clazz = Class.forName(className);
            return (HttpClient) clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static HttpClient construct(HttpConfig config, ScenarioContext context) {
        if (config.getClientInstance() != null) {
            return config.getClientInstance();
        }
        try {
            String className;
            if (config.getClientClass() != null) {
                className = config.getClientClass();
            } else {
                InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(KARATE_HTTP_PROPERTIES);
                if (is == null) {
                    String msg = KARATE_HTTP_PROPERTIES + " not found";
                    throw new RuntimeException(msg);
                }
                Properties props = new Properties();
                props.load(is);
                className = props.getProperty("client.class");
            }
            HttpClient client = construct(className);
            client.configure(config, context);
            return client;
        } catch (Exception e) {
            String msg = "failed to construct class by name: " + e.getMessage() + ", aborting";
            throw new RuntimeException(msg);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy