org.perfcake.message.sender.HttpClientSender.java Source code

Java tutorial

Introduction

Here is the source code for org.perfcake.message.sender.HttpClientSender.java

Source

/*
 * -----------------------------------------------------------------------\
 * PerfCake
 * 
 * Copyright (C) 2010 - 2016 the original author or authors.
 * 
 * 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 org.perfcake.message.sender;

import org.perfcake.PerfCakeException;
import org.perfcake.message.Message;
import org.perfcake.reporting.MeasurementUnit;
import org.perfcake.util.StringTemplate;
import org.perfcake.util.Utils;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.Serializable;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;

/**
 * Sends messages via HTTP protocol.
 *
 * @author <a href="mailto:marvenec@gmail.com">Martin Ve?ea</a>
 * @author <a href="mailto:pavel.macik@gmail.com">Pavel Mack</a>
 */
public class HttpClientSender extends AbstractSender {

    /**
     * The sender's logger.
     */
    private static final Logger log = LogManager.getLogger(HttpClientSender.class);

    /**
     * HTTP cookies header.
     */
    private static final String COOKIES_HEADER = "Set-Cookie";

    /**
     * The URL where the HTTP request is sent.
     */
    private URL url;

    /**
     * The HTTP method that will be used.
     */
    private Method method = Method.POST;

    /**
     * A string template determining the HTTP method to be used dynamically for each request.
     * If not configured (set to null), static configuration in {@link #method} is used instead.
     */
    private StringTemplate dynamicMethod = null;

    /**
     * HTTP method that should be used for the current send operation, pre-calculated in {@link #preSend(Message, Properties)}.
     */
    private Method currentMethod;

    private HttpClient httpClient = HttpClients.createSystem();

    private HttpUriRequest currentRequest;
    private HttpResponse currentHttpResponse;

    /**
     * Enumeration on available HTTP methods.
     */
    public static enum Method {
        GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE, PATCH
    }

    /**
     * The list of response codes that are expected to be returned by HTTP response.
     */
    private List<Integer> expectedResponseCodeList = new ArrayList<>();

    /**
     * The property for expected response codes.
     */
    private String expectedResponseCodes = null;

    /**
     * When true, cookies are stored between HTTP requests.
     */
    private boolean storeCookies = false;

    /**
     * Cookies storage for each client.
     */
    private static ThreadLocal<CookieManager> localCookieManager = new ThreadLocal<>();

    /**
     * The request payload.
     */
    private String payload;

    /**
     * The request payload length.
     */
    private int payloadLength;

    @Override
    public void doInit(final Properties messageAttributes) throws PerfCakeException {
        final String targetUrl = safeGetTarget(messageAttributes);
        try {
            if (log.isDebugEnabled()) {
                log.debug("Setting target URL to: " + targetUrl);
            }
            url = new URL(targetUrl);
        } catch (MalformedURLException e) {
            throw new PerfCakeException(
                    String.format("Cannot initialize HTTP connection, invalid URL %s: ", targetUrl), e);
        }
    }

    @Override
    public void doClose() {
        // nop
    }

    /**
     * Sets the value of expectedResponseCodes property.
     *
     * @param expectedResponseCodes
     *    The expectedResponseCodes property to set.
     * @return Instance of this to support fluent API.
     */
    public HttpClientSender setExpectedResponseCodes(final String expectedResponseCodes) {
        this.expectedResponseCodes = expectedResponseCodes;
        setExpectedResponseCodesList(expectedResponseCodes.split(","));
        return this;
    }

    /**
     * Gets the list of expected response codes.
     *
     * @return The list of expected response codes.
     */
    public List<Integer> getExpectedResponseCodeList() {
        return expectedResponseCodeList;
    }

    /**
     * Gets the value of expectedResponseCodes property.
     *
     * @return The expectedResponseCodes.
     */
    public String getExpectedResponseCodes() {
        return expectedResponseCodes;
    }

    /**
     * Sets a list of expected response codes.
     *
     * @param codes
     *    The array of codes.
     * @return Instance of this to support fluent API.
     */
    protected HttpClientSender setExpectedResponseCodesList(final String[] codes) {
        final LinkedList<Integer> numCodes = new LinkedList<Integer>();
        for (final String code : codes) {
            numCodes.add(Integer.parseInt(code.trim()));
        }
        expectedResponseCodeList = numCodes;

        return this;
    }

    /**
     * Checks if the code is expected.
     *
     * @param code
     *    Checked response code.
     * @return true/false according to if the code is expected or not.
     */
    private boolean checkResponseCode(final int code) {
        if (expectedResponseCodeList.isEmpty()) {
            return true;
        }
        for (final int i : expectedResponseCodeList) {
            if (i == code) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void preSend(final Message message, final Properties messageAttributes) throws Exception {
        super.preSend(message, messageAttributes);

        if (storeCookies && localCookieManager.get() == null) {
            localCookieManager.set(new CookieManager(null, CookiePolicy.ACCEPT_ALL));
        }

        currentMethod = getDynamicMethod(messageAttributes);

        payloadLength = 0;
        if (message == null) {
            payload = null;
        } else if (message.getPayload() != null) {
            payload = message.getPayload().toString();
            payloadLength = payload.length();
        }

        final URI uri = url.toURI();
        switch (currentMethod) {
        case GET:
            currentRequest = new HttpGet(uri);
            break;
        case POST:
            currentRequest = new HttpPost(uri);
            break;
        case PUT:
            currentRequest = new HttpPut(uri);
            break;
        case PATCH:
            currentRequest = new HttpPatch(uri);
            break;
        case DELETE:
            currentRequest = new HttpDelete(uri);
            break;
        case HEAD:
            currentRequest = new HttpHead(uri);
            break;
        case TRACE:
            currentRequest = new HttpTrace(uri);
            break;
        case OPTIONS:
            currentRequest = new HttpOptions(uri);
        }

        StringEntity msg = null;
        if (payload != null && (currentRequest instanceof HttpEntityEnclosingRequestBase)) {
            ((HttpEntityEnclosingRequestBase) currentRequest).setEntity(
                    new StringEntity(payload, ContentType.create("text/plain", Utils.getDefaultEncoding())));
        }

        if (storeCookies) {
            popCookies();
        }

        if (log.isDebugEnabled()) {
            log.debug("Setting HTTP headers: ");
        }

        // set message properties as HTTP headers
        if (message != null) {
            for (final Entry<Object, Object> property : message.getProperties().entrySet()) {
                final String pKey = property.getKey().toString();
                final String pValue = property.getValue().toString();
                currentRequest.setHeader(pKey, pValue);
                if (log.isDebugEnabled()) {
                    log.debug(pKey + ": " + pValue);
                }
            }
        }

        // set message headers as HTTP headers
        if (message != null) {
            if (message.getHeaders().size() > 0) {
                for (final Entry<Object, Object> property : message.getHeaders().entrySet()) {
                    final String pKey = property.getKey().toString();
                    final String pValue = property.getValue().toString();
                    currentRequest.setHeader(pKey, pValue);
                    if (log.isDebugEnabled()) {
                        log.debug(pKey + ": " + pValue);
                    }
                }
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("End of HTTP headers.");
        }
    }

    @Override
    public Serializable doSend(final Message message, final MeasurementUnit measurementUnit) throws Exception {
        currentHttpResponse = httpClient.execute(currentRequest);

        final int respCode = currentHttpResponse.getStatusLine().getStatusCode();

        if (!checkResponseCode(respCode)) {
            final StringBuilder errorMess = new StringBuilder();
            errorMess.append("The server returned an unexpected HTTP response code: ").append(respCode).append(" ")
                    .append("\"").append(currentHttpResponse.getStatusLine().getReasonPhrase())
                    .append("\". Expected HTTP codes are ");
            for (final int code : expectedResponseCodeList) {
                errorMess.append(Integer.toString(code)).append(", ");
            }
            throw new PerfCakeException(errorMess.substring(0, errorMess.length() - 2) + ".");
        }
        return new String(EntityUtils.toByteArray(currentHttpResponse.getEntity()), Utils.getDefaultEncoding());
    }

    @Override
    public void postSend(final Message message) throws Exception {
        super.postSend(message);

        if (storeCookies) {
            pushCookies();
        }
    }

    /**
     * Stores cookies from request connection in the thread local cookie manager.
     */
    private void pushCookies() throws IOException {
        final List<Header> cookiesHeader = Arrays.asList(currentHttpResponse.getHeaders(COOKIES_HEADER));

        if (cookiesHeader != null) {
            for (Header cookie : cookiesHeader) {
                localCookieManager.get().getCookieStore().add(null, HttpCookie.parse(cookie.getValue()).get(0));
            }
        }
    }

    /**
     * Sets the stored cookies to the request connection.
     */
    private void popCookies() throws IOException {
        if (localCookieManager.get().getCookieStore().getCookies().size() > 0) {
            currentRequest.setHeader("Cookie",
                    StringUtils.join(localCookieManager.get().getCookieStore().getCookies(), ";"));
        }
    }

    /**
     * Gets the value of HTTP method.
     *
     * @return The HTTP method.
     */
    public Method getMethod() {
        return method;
    }

    /**
     * Sets the value of HTTP method.
     *
     * @param method
     *    The HTTP method to set.
     * @return Instance of this to support fluent API.
     */
    public HttpClientSender setMethod(final Method method) {
        this.method = method;
        return this;
    }

    /**
     * Sets the template used to determine HTTP method dynamically.
     *
     * @param dynamicMethod
     *    The string template to dynamically determine HTTP method.
     * @return Instance of this to support fluent API.
     */
    public HttpClientSender setDynamicMethod(final String dynamicMethod) {
        if (dynamicMethod == null || dynamicMethod.isEmpty()) {
            this.dynamicMethod = null;
        } else {
            this.dynamicMethod = new StringTemplate(dynamicMethod);
        }
        return this;
    }

    /**
     * Gets the template used to determine HTTP method dynamically.
     *
     * @param placeholders
     *    The properties to render the string template.
     * @return The HTTP method.
     */
    public Method getDynamicMethod(final Properties placeholders) {
        if (dynamicMethod == null) {
            return this.method;
        } else {
            return Method.valueOf(dynamicMethod.toString(placeholders));
        }
    }

    /**
     * Gets whether the sender will store cookies between requests.
     *
     * @return If and only if the cookies will be stored between requests.
     */
    public boolean isStoreCookies() {
        return storeCookies;
    }

    /**
     * Sets whether the sender will store cookies between requests.
     *
     * @param storeCookies
     *    True if and only if the cookies should be stored between requests.
     * @return Instance of this to support fluent API.
     */
    public HttpClientSender setStoreCookies(final boolean storeCookies) {
        this.storeCookies = storeCookies;

        return this;
    }
}