/** * Copyright 2012 Comcast Corporation * * 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 com.comcast.cns.io; import java.io.IOException; import java.io.InterruptedIOException; import java.net.URL; import org.apache.http.HttpHost; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.concurrent.FutureCallback; import org.apache.http.impl.DefaultConnectionReuseStrategy; import org.apache.http.impl.nio.DefaultHttpClientIODispatch; import org.apache.http.impl.nio.pool.BasicNIOConnPool; import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.nio.entity.NStringEntity; import org.apache.http.nio.protocol.BasicAsyncRequestProducer; import org.apache.http.nio.protocol.BasicAsyncResponseConsumer; import org.apache.http.nio.protocol.HttpAsyncRequestExecutor; import org.apache.http.nio.protocol.HttpAsyncRequester; import org.apache.http.nio.reactor.ConnectingIOReactor; import org.apache.http.nio.reactor.IOEventDispatch; import org.apache.http.nio.reactor.IOReactorException; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpParams; import org.apache.http.params.SyncBasicHttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HttpProcessor; import org.apache.http.protocol.ImmutableHttpProcessor; import org.apache.http.protocol.RequestConnControl; import org.apache.http.protocol.RequestContent; import org.apache.http.protocol.RequestExpectContinue; import org.apache.http.protocol.RequestTargetHost; import org.apache.http.protocol.RequestUserAgent; import org.apache.log4j.Logger; import com.comcast.cmb.common.controller.CMBControllerServlet; import com.comcast.cmb.common.util.CMBProperties; import com.comcast.cns.model.CNSMessage.CNSMessageStructure; import com.comcast.cns.model.CNSMessage.CNSMessageType; import com.comcast.cns.model.CNSSubscription.CnsSubscriptionProtocol; /** * Asynchronous HTTP/1.1 client. * * This example demonstrates how HttpCore NIO can be used to execute multiple HTTP requests asynchronously using only one I/O thread. */ public class HTTPEndpointAsyncPublisher extends AbstractEndpointPublisher{ private IPublisherCallback callback; private static HttpProcessor httpProcessor; private static HttpParams httpParams; private static BasicNIOConnPool connectionPool; private static Logger logger = Logger.getLogger(HTTPEndpointAsyncPublisher.class); static { try { httpParams = new SyncBasicHttpParams(); httpParams .setIntParameter(CoreConnectionPNames.SO_TIMEOUT, CMBProperties.getInstance().getCNSPublisherHttpTimeoutSeconds() * 1000) .setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CMBProperties.getInstance().getCNSPublisherHttpTimeoutSeconds() * 1000) .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024) .setParameter(CoreProtocolPNames.USER_AGENT, "CNS/" + CMBControllerServlet.VERSION); httpProcessor = new ImmutableHttpProcessor(new HttpRequestInterceptor[] { new RequestContent(), new RequestTargetHost(), new RequestConnControl(), new RequestUserAgent(), new RequestExpectContinue()}); HttpAsyncRequestExecutor protocolHandler = new HttpAsyncRequestExecutor(); final IOEventDispatch ioEventDispatch = new DefaultHttpClientIODispatch(protocolHandler, httpParams); final ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(); connectionPool = new BasicNIOConnPool(ioReactor, httpParams); connectionPool.setDefaultMaxPerRoute(2); // maybe adjust pool size connectionPool.setMaxTotal(8); Thread t = new Thread(new Runnable() { public void run() { try { ioReactor.execute(ioEventDispatch); } catch (InterruptedIOException ex) { logger.error("event=failed_to_initialize_async_http_client action=exiting", ex); } catch (IOException ex) { logger.error("event=failed_to_initialize_async_http_client action=exiting", ex); } } }); t.start(); } catch (IOReactorException ex) { logger.error("event=failed_to_initialize_async_http_client action=exiting", ex); } } public HTTPEndpointAsyncPublisher(IPublisherCallback callback) { this.callback = callback; } @Override public void send() throws Exception { HttpAsyncRequester requester = new HttpAsyncRequester(httpProcessor, new DefaultConnectionReuseStrategy(), httpParams); final URL url = new URL(endpoint); final HttpHost target = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST", url.getPath() + (url.getQuery() == null ? "" : "?" + url.getQuery())); composeHeader(request); String msg = null; if (message.getMessageStructure() == CNSMessageStructure.json) { msg = message.getProtocolSpecificMessage(CnsSubscriptionProtocol.http); } else { msg = message.getMessage(); } if (!rawMessageDelivery && message.getMessageType() == CNSMessageType.Notification) { msg = com.comcast.cns.util.Util.generateMessageJson(message, CnsSubscriptionProtocol.http); } logger.debug("event=send_async_http_request endpoint=" + endpoint + "\" message=\"" + msg + "\""); request.setEntity(new NStringEntity(msg)); requester.execute( new BasicAsyncRequestProducer(target, request), new BasicAsyncResponseConsumer(), connectionPool, new BasicHttpContext(), new FutureCallback<HttpResponse>() { public void completed(final HttpResponse response) { int statusCode = response.getStatusLine().getStatusCode(); // accept all 2xx status codes if (statusCode >= 200 && statusCode < 300) { callback.onSuccess(); } else { logger.warn(target + "://" + url.getPath() + "?" + url.getQuery() + " -> " + response.getStatusLine()); callback.onFailure(statusCode); } } public void failed(final Exception ex) { logger.warn(target + " " + url.getPath() + " " + url.getQuery(), ex); callback.onFailure(0); } public void cancelled() { logger.warn(target + " " + url.getPath() + " " + url.getQuery() + " -> " + "cancelled"); callback.onFailure(1); } }); } private void composeHeader(BasicHttpEntityEnclosingRequest request) { request.setHeader("x-amz-sns-message-type", this.getMessageType()); request.setHeader("x-amz-sns-message-id", this.getMessageId()); request.setHeader("x-amz-sns-topic-arn", this.getTopicArn()); request.setHeader("x-amz-sns-subscription-arn", this.getSubscriptionArn()); request.setHeader("User-Agent", "Cloud Notification Service Agent"); if (this.getRawMessageDelivery()) { request.addHeader("x-amz-raw-message", "true"); } } }