/* * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you 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.wso2.siddhi.extension.output.transport.http; import org.apache.commons.httpclient.methods.EntityEnclosingMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.log4j.Logger; import org.wso2.siddhi.annotation.Extension; import org.wso2.siddhi.core.exception.ConnectionUnavailableException; import org.wso2.siddhi.core.exception.TestConnectionNotSupportedException; import org.wso2.siddhi.core.stream.output.sink.Sink; import java.net.URL; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import javax.xml.bind.DatatypeConverter; @Extension( name = "http", namespace = "sink", description = "" ) public class HttpSink extends Sink { public static final String ADAPTER_TYPE_HTTP = "http"; public static final String ADAPTER_MESSAGE_URL = "http.url"; public static final String ADAPTER_MESSAGE_URL_HINT = "http.url.hint"; public static final int ADAPTER_MIN_THREAD_POOL_SIZE = 8; public static final int ADAPTER_MAX_THREAD_POOL_SIZE = 100; public static final int ADAPTER_EXECUTOR_JOB_QUEUE_SIZE = 2000; public static final long DEFAULT_KEEP_ALIVE_TIME_IN_MILLIS = 20000; public static final String ADAPTER_MIN_THREAD_POOL_SIZE_NAME = "minThread"; public static final String ADAPTER_MAX_THREAD_POOL_SIZE_NAME = "maxThread"; public static final String ADAPTER_KEEP_ALIVE_TIME_NAME = "keepAliveTimeInMillis"; public static final String ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME = "jobQueueSize"; public static final String ADAPTER_PROXY_HOST = "http.proxy.host"; public static final String ADAPTER_PROXY_HOST_HINT = "http.proxy.host.hint"; public static final String ADAPTER_PROXY_PORT = "http.proxy.port"; public static final String ADAPTER_PROXY_PORT_HINT = "http.proxy.port.hint"; public static final String ADAPTER_USERNAME = "http.username"; public static final String ADAPTER_USERNAME_HINT = "http.username.hint"; public static final String ADAPTER_PASSWORD = "http.password"; public static final String ADAPTER_PASSWORD_HINT = "http.password.hint"; public static final String ADAPTER_HEADERS = "http.headers"; public static final String ADAPTER_HEADERS_HINT = "http.headers.hint"; public static final String HEADER_SEPARATOR = ","; public static final String ENTRY_SEPARATOR = ":"; public static final String ADAPTER_HTTP_CLIENT_METHOD = "http.client.method"; public static final String CONSTANT_HTTP_POST = "HttpPost"; public static final String CONSTANT_HTTP_PUT = "HttpPut"; //configurations for the httpConnectionManager public static final String DEFAULT_MAX_CONNECTIONS_PER_HOST = "defaultMaxConnectionsPerHost"; public static final int DEFAULT_DEFAULT_MAX_CONNECTIONS_PER_HOST = 2; public static final String MAX_TOTAL_CONNECTIONS = "maxTotalConnections"; public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 20; private static final Logger log = Logger.getLogger(HttpSink.class); private static ExecutorService executorService; private static HttpConnectionManager connectionManager; private String contentType; private HttpClient httpClient = null; private HostConfiguration hostConfiguration = null; @Override public void init(String type, Map<String, String> options, Map<String, String> unmappedDynamicOptions) { if (executorService == null) { int minThread = (options.get(ADAPTER_MIN_THREAD_POOL_SIZE_NAME) != null) ? Integer.parseInt(options.get(ADAPTER_MIN_THREAD_POOL_SIZE_NAME)) : ADAPTER_MIN_THREAD_POOL_SIZE; int maxThread = (options.get(ADAPTER_MAX_THREAD_POOL_SIZE_NAME) != null) ? Integer.parseInt(options.get(ADAPTER_MAX_THREAD_POOL_SIZE_NAME)) : ADAPTER_MAX_THREAD_POOL_SIZE; long defaultKeepAliveTime = (options.get(ADAPTER_KEEP_ALIVE_TIME_NAME) != null) ? Integer.parseInt(options.get(ADAPTER_KEEP_ALIVE_TIME_NAME)) : DEFAULT_KEEP_ALIVE_TIME_IN_MILLIS; int jobQueSize = (options.get(ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME) != null) ? Integer.parseInt(options.get(ADAPTER_EXECUTOR_JOB_QUEUE_SIZE_NAME)) : ADAPTER_EXECUTOR_JOB_QUEUE_SIZE; int defaultMaxConnectionsPerHost = (options.get(DEFAULT_MAX_CONNECTIONS_PER_HOST) != null) ? Integer.parseInt(options.get(DEFAULT_MAX_CONNECTIONS_PER_HOST)) : DEFAULT_DEFAULT_MAX_CONNECTIONS_PER_HOST; int maxTotalConnections = (options.get(MAX_TOTAL_CONNECTIONS) != null) ? Integer.parseInt(options.get(MAX_TOTAL_CONNECTIONS)) : DEFAULT_MAX_TOTAL_CONNECTIONS; executorService = new ThreadPoolExecutor(minThread, maxThread, defaultKeepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(jobQueSize)); connectionManager = new MultiThreadedHttpConnectionManager(); connectionManager.getParams().setDefaultMaxConnectionsPerHost(defaultMaxConnectionsPerHost); connectionManager.getParams().setMaxTotalConnections(maxTotalConnections); } } @Override public void testConnect() throws TestConnectionNotSupportedException { throw new TestConnectionNotSupportedException("Test connection is not available"); } @Override public void connect() throws ConnectionUnavailableException { // TODO: 1/8/17 fix properly // checkHTTPClientInit(eventAdapterConfiguration.getStaticProperties()); checkHTTPClientInit(new HashMap<String, String>()); } @Override public void publish(Object event, Map<String, String> dynamicTransportOptions) throws ConnectionUnavailableException { // TODO: 1/8/17 load dynamic properties and fix properly // String url = dynamicProperties.get(ADAPTER_MESSAGE_URL); // String username = dynamicProperties.get(ADAPTER_USERNAME); // String password = dynamicProperties.get(ADAPTER_PASSWORD); // Map<String, String> headers = this.extractHeaders(dynamicProperties.get(ADAPTER_HEADERS)); String url = "http://0.0.0.0:8080/endpoint"; String username = null; String password = null; Map<String, String> headers = this.extractHeaders(null); String payload = event.toString(); try { executorService.submit(new HTTPSender(url, payload, username, password, headers, httpClient)); } catch (RejectedExecutionException e) { log.error("Job queue is full : " + e.getMessage(), e); } } @Override public void disconnect() { // not required } @Override public void destroy() { // not required } @Override public boolean isPolled() { return false; } private void checkHTTPClientInit(Map<String, String> staticProperties) { if (httpClient != null) { return; } synchronized (HttpSink.class) { if (this.httpClient != null) { return; } httpClient = new HttpClient(connectionManager); String proxyHost = staticProperties.get(ADAPTER_PROXY_HOST); String proxyPort = staticProperties.get(ADAPTER_PROXY_PORT); if (proxyHost != null && proxyHost.trim().length() > 0) { try { HttpHost host = new HttpHost(proxyHost, Integer.parseInt(proxyPort)); this.httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, host); } catch (NumberFormatException e) { log.error("Invalid proxy port: " + proxyPort + ", " + "ignoring proxy settings for HTTP output event adaptor..."); } } // TODO: 1/8/17 fix properly // String messageFormat = eventAdapterConfiguration.getMessageFormat(); String messageFormat = "json"; if (messageFormat.equalsIgnoreCase("json")) { contentType = "application/json"; } else if (messageFormat.equalsIgnoreCase("text")) { contentType = "text/plain"; } else { contentType = "text/xml"; } } } private Map<String, String> extractHeaders(String headers) { if (headers == null || headers.trim().length() == 0) { return null; } String[] entries = headers.split(HEADER_SEPARATOR); String[] keyValue; Map<String, String> result = new HashMap<String, String>(); for (String header : entries) { try { keyValue = header.split(ENTRY_SEPARATOR, 2); result.put(keyValue[0].trim(), keyValue[1].trim()); } catch (Exception e) { log.warn("Header property '" + header + "' is not defined in the correct format.", e); } } return result; } /** * This class represents a job to send an HTTP request to a target URL. */ class HTTPSender implements Runnable { private String url; private String payload; private String username; private String password; private Map<String, String> headers; private HttpClient httpClient; public HTTPSender(String url, String payload, String username, String password, Map<String, String> headers, HttpClient httpClient) { this.url = url; this.payload = payload; this.username = username; this.password = password; this.headers = headers; this.httpClient = httpClient; } public String getUrl() { return url; } public String getPayload() { return payload; } public String getUsername() { return username; } public String getPassword() { return password; } public Map<String, String> getHeaders() { return headers; } public HttpClient getHttpClient() { return httpClient; } public void run() { EntityEnclosingMethod method = null; try { // TODO: 1/8/17 fix properly // if (clientMethod.equalsIgnoreCase(CONSTANT_HTTP_PUT)) { // method = new PutMethod(this.getUrl()); // } else { // method = new PostMethod(this.getUrl()); // } method = new PostMethod(this.getUrl()); if (hostConfiguration == null) { URL hostUrl = new URL(this.getUrl()); hostConfiguration = new HostConfiguration(); hostConfiguration.setHost( hostUrl.getHost(), hostUrl.getPort(), hostUrl.getProtocol() ); } method.setRequestEntity(new StringRequestEntity(this.getPayload(), contentType, "UTF-8")); if (this.getUsername() != null && this.getUsername().trim().length() > 0) { method.setRequestHeader( "Authorization", "Basic " + DatatypeConverter.printBase64Binary( (this.getUsername() + ENTRY_SEPARATOR + this.getPassword()).getBytes() ) ); } if (this.getHeaders() != null) { for (Map.Entry<String, String> header : this.getHeaders().entrySet()) { method.setRequestHeader(header.getKey(), header.getValue()); } } this.getHttpClient().executeMethod(hostConfiguration, method); } catch (UnknownHostException e) { log.error("Cannot connect to " + this.getUrl() + " : " + e.getMessage(), e); } catch (Throwable e) { log.error("Event dropped at Output Adapter : " + e.getMessage(), e); } finally { if (method != null) { method.releaseConnection(); } } } } }