/******************************************************************************* * Copyright (c) 2006-2010 eBay Inc. All Rights Reserved. * 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 *******************************************************************************/ //B''H package org.ebayopensource.turmeric.runtime.sif.impl.transport.http; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; import org.ebayopensource.turmeric.runtime.binding.BindingConstants; import org.ebayopensource.turmeric.runtime.common.binding.DataBindingDesc; import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory; import org.ebayopensource.turmeric.runtime.common.exceptions.HTTPTransportException; import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException; import org.ebayopensource.turmeric.runtime.common.impl.attachment.InboundMessageAttachments; import org.ebayopensource.turmeric.runtime.common.impl.internal.pipeline.BaseMessageContextImpl; import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.AsyncCallBack; import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.IAsyncResponsePoller; import org.ebayopensource.turmeric.runtime.common.impl.internal.utils.ITransportPoller; import org.ebayopensource.turmeric.runtime.common.impl.utils.HTTPCommonUtils; import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager; import org.ebayopensource.turmeric.runtime.common.pipeline.InboundMessage; import org.ebayopensource.turmeric.runtime.common.pipeline.Message; import org.ebayopensource.turmeric.runtime.common.pipeline.MessageContext; import org.ebayopensource.turmeric.runtime.common.pipeline.OutboundMessage; import org.ebayopensource.turmeric.runtime.common.pipeline.Transport; import org.ebayopensource.turmeric.runtime.common.pipeline.TransportOptions; import org.ebayopensource.turmeric.runtime.common.service.ServiceId; import org.ebayopensource.turmeric.runtime.common.types.Cookie; import org.ebayopensource.turmeric.runtime.common.types.SOAConstants; import org.ebayopensource.turmeric.runtime.common.types.SOAHeaders; import org.ebayopensource.turmeric.runtime.common.types.ServiceAddress; import org.ebayopensource.turmeric.runtime.common.utils.BufferUtil; import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants; import org.ebayopensource.turmeric.runtime.sif.pipeline.ClientMessageContext; import org.ebayopensource.turmeric.runtime.sif.service.ClientServiceId; import com.ebay.kernel.service.invocation.client.exception.BaseClientSideException; import com.ebay.kernel.service.invocation.client.http.HttpStatusEnum; import com.ebay.kernel.service.invocation.client.http.Request; import com.ebay.kernel.service.invocation.client.http.RequestBodyWriter; import com.ebay.kernel.service.invocation.client.http.Response; import com.ebay.kernel.service.invocation.client.http.nio.NioAsyncCallback; import com.ebay.kernel.service.invocation.client.http.nio.NioAsyncHttpClient; import com.ebay.kernel.service.invocation.client.http.nio.NioAsyncHttpClientImpl; import com.ebay.kernel.service.invocation.client.http.nio.NioAsyncResponseFuture; import com.ebay.kernel.service.invocation.client.http.nio.NioSvcInvocationConfig; public class HTTPSyncAsyncClientTransport implements Transport { static final Logger LOGGER = LogManager .getInstance(HTTPSyncAsyncClientTransport.class); static public final String QUERY_STR = "?"; static public final char AMP_STR = '&'; static public final char EQUAL_STR = '='; private ClientServiceId m_svcId; private NioAsyncHttpClient m_client; private static Map<String, NioAsyncHttpClient> m_asyncHttpClients = new ConcurrentHashMap<String, NioAsyncHttpClient>(); private String m_httpVersion; //private Integer m_inMemoryAttachmentLimit; private boolean m_accept_gzip = false; private HTTPClientTransportConfig m_config; private static Map<String, HTTPClientTransportConfig> s_configs = new HashMap<String, HTTPClientTransportConfig>(); // private static Map<String, HttpTransportPollQueue> m_pollQueues = new // ConcurrentHashMap<String, HttpTransportPollQueue>(); public HTTPSyncAsyncClientTransport() { // empty } public void checkTransportPoller(IAsyncResponsePoller holder) { if (holder.getTransportPoller() == null || !(holder.getTransportPoller() instanceof HTTPSyncAsyncClientTransportPoller)) { holder.setTransportPoller(new HTTPSyncAsyncClientTransportPoller()); } } public void init(InitContext ctx) throws ServiceException { m_svcId = (ClientServiceId) ctx.getServiceId(); String configName = getConfigName(ctx.getServiceId(), ctx.getName()); m_config = createSvcConfig(configName, ctx.getOptions()); m_client = getAsyncHttpClient(configName, m_config .getNioSvcInvocationConfig()); String httpVersion = ctx.getOptions().getProperties().get( SOAConstants.HTTP_VERSION); if (httpVersion != null && httpVersion.equals(SOAConstants.TRANSPORT_HTTP_10)) { m_httpVersion = Request.HTTP_10; } else { m_httpVersion = Request.HTTP_11; } String useZipping = ctx.getOptions().getProperties().get( SOAConstants.GZIP_ENCODING); if (useZipping != null && Boolean.parseBoolean(useZipping)) m_accept_gzip = true; // m_inMemoryAttachmentLimit = readAttachmentConfiguration(ctx.getOptions().getProperties()); } // private Integer readAttachmentConfiguration(Map<String, String> properties) { // boolean attachmentFileCache = true; // String attachmentFileCacheStr = properties.get(SOAConstants.ATTACHMENT_FILE_CACHE); // if (attachmentFileCacheStr != null) { // attachmentFileCache = Boolean.valueOf(attachmentFileCacheStr).booleanValue(); // } // if (attachmentFileCache) { // String inMemoryAttachmentLimitStr = properties.get(SOAConstants.IN_MEMORY_ATTACHMENT_LIMIT); // if (inMemoryAttachmentLimitStr != null) { // try { // return Integer.valueOf(inMemoryAttachmentLimitStr); // } catch (NumberFormatException e) { // LOGGER.log(Level.WARNING, "Unable to parse property " + SOAConstants.IN_MEMORY_ATTACHMENT_LIMIT, e); // } // } // return InboundMessageAttachments.IN_MEMORY_ATTACHMENT_LIMIT; // } // // return null; // } private String getConfigName(ServiceId svcId, String name) { ClientServiceId clientId = (ClientServiceId) svcId; StringBuilder sb = new StringBuilder(); sb.append(clientId.getClientName()).append('.').append( clientId.getAdminName()).append('.').append(name); return sb.toString(); } private NioAsyncHttpClient getAsyncHttpClient(String configName, NioSvcInvocationConfig config) { NioAsyncHttpClient asyncClient; synchronized (m_asyncHttpClients) { asyncClient = m_asyncHttpClients.get(configName); if (asyncClient == null) { asyncClient = new NioAsyncHttpClientImpl(config); m_asyncHttpClients.put(configName, asyncClient); } } return asyncClient; } private HTTPClientTransportConfig createSvcConfig(String configName, TransportOptions options) { HTTPClientTransportConfig config; // ClientServiceDescFactory creates one instance of HTTPClientTransport // per service. We keep a map of all previously // initialized config beans by service name and transport name. This // avoids creation of multiple beans across uses, // for the same service name and transport name (currently equal to the // transport name, HTTP10 or HTTP11). synchronized (s_configs) { config = s_configs.get(configName); } if (config == null) { config = new HTTPClientTransportConfig(configName, options); synchronized (s_configs) { HTTPClientTransportConfig regetconfig = s_configs .get(configName); if (regetconfig == null) { s_configs.put(configName, config); } else { config = regetconfig; } } } return config; } // Preinvoke is used only in cases of deferred invoke (e.g. async). // Other cases do nothing. public Object preInvoke(MessageContext ctx) throws ServiceException { OutboundMessage clientRequestMsg = (OutboundMessage) ctx .getRequestMessage(); // Set the content-type of request only if it has not been set (in // soap1.2 case, // this gets over-written at the Client Protocol processor. // So make sure we are only setting here if it has not already been set) if (clientRequestMsg .getTransportHeader(SOAConstants.HTTP_HEADER_CONTENT_TYPE) == null) { DataBindingDesc binding = clientRequestMsg.getDataBindingDesc(); String mimeType = binding.getMimeType(); Charset charset = clientRequestMsg.getG11nOptions().getCharset(); String contentType = HTTPCommonUtils.formatContentType(mimeType, charset); clientRequestMsg.setTransportHeader( SOAConstants.HTTP_HEADER_CONTENT_TYPE, contentType); } return null; } public Future<?> invokeAsync(Message msg, TransportOptions transportOptions) throws ServiceException { // Client's Message Context BaseMessageContextImpl clientCtx = (BaseMessageContextImpl) msg .getContext(); // URL for destination URL serviceLocation = getServiceLocation(clientCtx); String serviceLocationString = serviceLocation.toString(); Request request = getRequest(msg, transportOptions, serviceLocation, serviceLocationString); AsyncCallBack callback = clientCtx.getServiceAsyncCallback(); return callback == null ? sendMessageGetResponseFuture( serviceLocationString, request, clientCtx) : sendMessageGetResponseOnCallBack(serviceLocationString, request, clientCtx, new AsyncClientCallBack(clientCtx)); } protected boolean isClientStreaming() { return m_client.getConfiguration().getUseResponseStreaming(); } public void invoke(Message msg, TransportOptions transportOptions) throws ServiceException { // Client's Message Context BaseMessageContextImpl clientCtx = (BaseMessageContextImpl) msg .getContext(); // URL for destination URL serviceLocation = getServiceLocation(clientCtx); String serviceLocationString = serviceLocation.toString(); Request request = getRequest(msg, transportOptions, serviceLocation, serviceLocationString); ResponseAvatar response = sendMessageGetResponse(serviceLocationString, request, clientCtx); setResponseToContextInputStream(serviceLocationString, response, clientCtx); } public void retrieve(MessageContext ctx, Future<?> futureResp) throws ServiceException { if (((BaseMessageContextImpl) ctx).getServiceAsyncCallback() != null) { return; } // Client's Message Context BaseMessageContextImpl clientCtx = (BaseMessageContextImpl) ctx; // URL for destination URL serviceLocation = getServiceLocation(clientCtx); String serviceLocationString = serviceLocation.toString(); ResponseAvatar response = getResponseFromFuture(serviceLocationString, futureResp, clientCtx); setResponseToContextInputStream(serviceLocationString, response, clientCtx); } URL getServiceLocation(BaseMessageContextImpl clientCtx) throws ServiceException { ServiceAddress serviceAddress = clientCtx.getServiceAddress(); if (serviceAddress == null || serviceAddress.getServiceUrl() == null) { throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_NO_SERVICE_ADDRESS, ErrorConstants.ERRORDOMAIN, new Object[] { clientCtx.getAdminName(), clientCtx.getOperationName() })); } return serviceAddress.getServiceUrl(); } private Request getRequest(Message msg, TransportOptions transportOptions, URL serviceLocation, String serviceLocationString) throws ServiceException { ClientMessageContext clientCtx = (ClientMessageContext) msg .getContext(); OutboundMessage clientRequestMsg = (OutboundMessage) msg; if (clientRequestMsg.isUnserializable()) { throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_UNSERIALIZABLE_MESSAGE, ErrorConstants.ERRORDOMAIN, new Object[] { clientCtx.getAdminName(), clientRequestMsg.getUnserializableReason() })); } boolean httpGet = clientRequestMsg.isREST(); int httpGetBufferSize = clientRequestMsg.getMaxURLLengthForREST(); String adminName = clientCtx.getAdminName(); if (httpGet) { if (clientRequestMsg.hasAttachment()) { throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_NO_GET_WITH_ATTACHMENTS, ErrorConstants.ERRORDOMAIN, new Object[] { adminName, serviceLocationString })); } String payloadType = clientRequestMsg.getPayloadType(); // Should ideally allow a data binding to register whether it // supports REST. However, only // NV is anticipated in the forseeable future. if (!payloadType.equals(BindingConstants.PAYLOAD_NV)) { throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_GET_REQUIRES_NV, ErrorConstants.ERRORDOMAIN, new Object[] { adminName, serviceLocationString })); } // Should ideally allow message protocols to register whether they // suport REST; but we have // only SOAP, and it does not support REST. String messageProtocol = clientCtx.getMessageProtocol(); if (!messageProtocol.equals(SOAConstants.MSG_PROTOCOL_NONE)) { throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_NO_GET_WITH_SOAP, ErrorConstants.ERRORDOMAIN, new Object[] { adminName, serviceLocationString })); } } Map<String, String> transportHeaders = clientRequestMsg .buildOutputHeaders(); Request request; if (httpGet) { request = createHTTPGetRequest(adminName, serviceLocationString, clientRequestMsg, transportHeaders, httpGetBufferSize); // request.setHttpVersion(m_httpVersion); } else { request = createHTTPPostRequest(adminName, serviceLocation, serviceLocationString, clientRequestMsg, transportHeaders); } request.setHttpVersion(m_httpVersion); Cookie[] cookies = clientRequestMsg.getCookies(); if (cookies != null && cookies.length != 0) { StringBuffer buf = new StringBuffer(); HTTPCommonUtils.encodeCookieValue(buf, cookies); String cookieString = buf.toString(); request.addHeader("Cookie", cookieString); } if (m_accept_gzip) request.addHeader(SOAConstants.HTTP_HEADER_ACCEPT_ENCODING, "gzip"); return request; } private Request createHTTPGetRequest(String adminName, String serviceLocationString, OutboundMessage clientRequestMsg, Map<String, String> transportHeaders, int httpGetBufferSize) throws ServiceException { StringBuilder urlBuffer = new StringBuilder(httpGetBufferSize); urlBuffer.append(serviceLocationString); if (urlBuffer.indexOf(QUERY_STR) != -1) { // already have the query start urlBuffer.append(AMP_STR); } else { urlBuffer.append(QUERY_STR); } // The bytes here will be in UTF-8 or whatever encoding is passed via // the g11n options in the // clientRequestMsg - indicated by "charset" below. // // TODO do we want to do a more efficient serialize process in which we // go directly to an OutputStream with // an underlying StringBuffer? XMLStreamWriters deal in character data. // However, all SOA output is normally based // on output streams. Since Get encodings are normally small, we can // probably leave it the way it is. byte[] httpPayloadData = serializeRequest(clientRequestMsg); String httpPayloadString; try { String charset = clientRequestMsg.getG11nOptions().getCharset() .name(); httpPayloadString = new String(httpPayloadData, charset); } catch (Exception e) { // UnsupportedEncodingException, etc. throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_UNSERIALIZABLE_MESSAGE, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString() }), e); } // this is a SOA-specific parameter, that is only analyzed on the server // end // until we have free-form HTTP paramters, we should not need it on the // client end // urlBuffer.append(SOAHeaders.REST_PAYLOAD); // urlBuffer.append("=true"); // urlBuffer.append(AMP_STR); urlBuffer.append(httpPayloadString); int urlLength = urlBuffer.length(); String urlString = urlBuffer.toString(); if (urlLength > httpGetBufferSize) { String urlStringToLog = getUrlLogString(urlString, urlLength); throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_URL_TOO_LONG, ErrorConstants.ERRORDOMAIN, new Object[] { adminName, urlStringToLog, Integer.valueOf(urlLength) })); } Request request; try { request = new Request(urlString); } catch (MalformedURLException e) { String urlStringToLog = getUrlLogString(urlString, urlLength); throw new ServiceException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_INVALID_SERVICE_ADDRESS, ErrorConstants.ERRORDOMAIN, new Object[] { adminName, urlStringToLog }), e); } addTransportHeaders(transportHeaders, request); request.setMethod(Request.GET); return request; } private String getUrlLogString(String urlString, int urlLength) { if (urlLength > 80) { return urlString.substring(0, 80); } return urlString; } private Request createHTTPPostRequest(String adminName, URL serviceLocation, String serviceLocationString, OutboundMessage clientRequestMsg, Map<String, String> transportHeaders) throws ServiceException { Request request = new Request(serviceLocation); // request.setHttpVersion(m_httpVersion); request.setMethod(Request.POST); addTransportHeaders(transportHeaders, request); // change "false" to isClientStreaming() once the Kernel supports a non-ByteArray RequestBodyWriter. boolean streaming = false; // if (isClientStreaming() && Request.HTTP_11.equals(m_httpVersion)) { // request.setChunkedEncoding(); if (streaming) { RequestBodyWriter outWriter = new StreamingMessageBodyWriter( clientRequestMsg); request.setBodyWriter(outWriter); } else { byte[] httpPayloadData = serializeRequest(clientRequestMsg); request.setRawData(httpPayloadData); } return request; } private void addTransportHeaders(Map<String, String> transportHeaders, Request request) { if (transportHeaders == null || transportHeaders.isEmpty()) { return; } for (Map.Entry<String, String> entry : transportHeaders.entrySet()) { String header = entry.getKey(); String value = entry.getValue(); request.addHeader(header, value); } } private byte[] serializeRequest(OutboundMessage clientRequestMsg) throws ServiceException { ByteArrayOutputStream bos = new ByteArrayOutputStream(8192); clientRequestMsg.serialize(bos); return bos.toByteArray(); } /** * Sends message for the pull (future-based) asynchronous mode. * * @param serviceLocationString * URL to send the message to. * @param request * the request to send. * @param clientCtx * the client context. * @return the future to poll for retrieving the response. * @throws ServiceException * when it hits it. */ private Future<?> sendMessageGetResponseFuture( String serviceLocationString, Request request, BaseMessageContextImpl clientCtx) throws ServiceException { //NioAsyncResponseFuture futureResponse = null; Future<Response> futureResponse = null; try { IAsyncResponsePoller poller = clientCtx.getServicePoller(); ITransportPoller transpPoller = null; if (poller != null) { checkTransportPoller(poller); transpPoller = poller.getTransportPoller(); } futureResponse = poller != null && transpPoller != null ? m_client .send(request, (HTTPSyncAsyncClientTransportPoller) transpPoller) : m_client.send(request); // if (isClientStreaming() && Request.HTTP_11.equals(m_httpVersion)) { // ((OutboundMessage) clientCtx.getCurrentMessage()).serialize(futureResponse.getRequestOutputStream()); // futureResponse.getRequestOutputStream().close(); // } } catch (BaseClientSideException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_COMM_FAILURE, ErrorConstants.ERRORDOMAIN, new Object[] { serviceLocationString, e.toString() }), -1, e); } // catch (IOException e) { // throw new HTTPTransportException(ErrorDataFactory.createErrorData( // ErrorConstants.SVC_TRANSPORT_COMM_FAILURE, // ErrorConstants.ERRORDOMAIN, new Object[] { // serviceLocationString, e.toString() }), -1, e); // } return futureResponse; } private Future<?> sendMessageGetResponseOnCallBack( String serviceLocationString, Request request, MessageContext clientCtx, AsyncClientCallBack callback) throws ServiceException { try { m_client.send(request, callback); // NioAsyncResponseFuture future = m_client.send(request, callback, true); // if (isClientStreaming() && Request.HTTP_11.equals(m_httpVersion)) { // ((OutboundMessage) clientCtx.getRequestMessage()).serialize(future.getRequestOutputStream()); // future.getRequestOutputStream().close(); // } } catch (BaseClientSideException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_COMM_FAILURE, ErrorConstants.ERRORDOMAIN, new Object[] { serviceLocationString, e.toString() }), -1, e); } // catch (IOException e) { // throw new HTTPTransportException(ErrorDataFactory.createErrorData( // ErrorConstants.SVC_TRANSPORT_COMM_FAILURE, // ErrorConstants.ERRORDOMAIN, new Object[] { // serviceLocationString, e.toString() }), -1, e); // } return new CallBackRequestFuture(callback); } private ResponseAvatar sendMessageGetResponse(String serviceLocationString, Request request, BaseMessageContextImpl clientCtx) throws ServiceException { Future<Response> future = null; ResponseAvatar response = null; try { future = m_client.send(request); if (isClientStreaming()) { NioAsyncResponseFuture nioAsyncResponseFuture = (NioAsyncResponseFuture) future; // ((OutboundMessage) clientCtx.getRequestMessage()).serialize( // nioAsyncResponseFuture.getRequestOutputStream()); // nioAsyncResponseFuture.getRequestOutputStream().close(); response = new FutureResponseWrapper(nioAsyncResponseFuture); } else { response = new ResponseWrapper(future.get( getInvocationTimeout(), TimeUnit.MILLISECONDS)); } } catch (BaseClientSideException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_COMM_FAILURE, ErrorConstants.ERRORDOMAIN, new Object[] { serviceLocationString, e.toString() }), -1, e); } catch (InterruptedException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString(), serviceLocationString }), -1, e); } catch (ExecutionException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString(), serviceLocationString }), -1, e); } catch (TimeoutException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString(), serviceLocationString }), -1, e); } // catch (IOException e) { // throw new HTTPTransportException(ErrorDataFactory.createErrorData( // ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, // ErrorConstants.ERRORDOMAIN, new Object[] { // m_svcId.getAdminName(), e.toString(), // serviceLocationString }), -1, e); // } return doCommonResponseProcessing(serviceLocationString, clientCtx, response); } private ResponseAvatar getResponseFromFuture(String serviceLocationString, Future<?> futureResp, MessageContext clientCtx) throws ServiceException { ResponseAvatar response = null; try { response = isClientStreaming() ? new FutureResponseWrapper( (NioAsyncResponseFuture) futureResp) : new ResponseWrapper( (Response) futureResp.get(getInvocationTimeout(), TimeUnit.MILLISECONDS)); } catch (InterruptedException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString(), serviceLocationString }), -1, e); } catch (ExecutionException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString(), serviceLocationString }), -1, e); } catch (TimeoutException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_OUTBOUND_IO_EXCEPTION, ErrorConstants.ERRORDOMAIN, new Object[] { m_svcId.getAdminName(), e.toString(), serviceLocationString }), -1, e); } return doCommonResponseProcessing(serviceLocationString, clientCtx, response); } private long getInvocationTimeout() { if (m_config == null) { throw new NullPointerException("Invalid Trasport Intialization"); } long timeout = m_config.getMaxInvocationDuration(); timeout = timeout < 1 ? HTTPClientTransportConfig.DEFAULT_HTTP_CONNECTION_TIMEOUT + HTTPClientTransportConfig.DEFAULT_SOCKET_RECV_TIMEOUT : timeout; return timeout; } ResponseAvatar doCommonResponseProcessing(String serviceLocationString, MessageContext clientCtx, ResponseAvatar response) throws ServiceException, HTTPTransportException { setResponseToContextHeaderMapping(response, (BaseMessageContextImpl) clientCtx); if (response.getRequestStatus() == HttpStatusEnum.SUCCESS) { return response; } boolean isSoap = false; if (clientCtx.getMessageProtocol().equals( SOAConstants.MSG_PROTOCOL_SOAP_11) || clientCtx.getMessageProtocol().equals( SOAConstants.MSG_PROTOCOL_SOAP_12)) { isSoap = true; } int httpStatusCode = response.getStatusCode(); if (httpStatusCode == HttpURLConnection.HTTP_INTERNAL_ERROR) { if (isSoap) { // if HTTP 500 and SOAP, we will manually set the ERROR_RESPON // header, to handler the case when // we are talking to foreign SOAP web servers that doesn't set // the ERROR_RESPONSE header. InboundMessage responseMsg = (InboundMessage) clientCtx .getResponseMessage(); responseMsg.setTransportHeader(SOAHeaders.ERROR_RESPONSE, "true"); } // if (response.getHeader(SOAHeaders.ERROR_RESPONSE) != null || // isSoap) { if (((InboundMessage) clientCtx.getResponseMessage()) .getTransportHeader(SOAHeaders.ERROR_RESPONSE) != null || isSoap) { return response; } generateExceptionMessage(serviceLocationString, response, httpStatusCode); } String responseBody = getAnyOtherErrorAsString(response); throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_HTTP_ERROR, ErrorConstants.ERRORDOMAIN, new Object[] { serviceLocationString, response.getRequestStatus().getName(), Integer.valueOf(httpStatusCode), responseBody }), httpStatusCode, null); } private static final int RESPONSE_BODY_LIMIT = 256; private static final String LOOKUP_BODY_TEXT = "HTTP Status 500"; private String getAnyOtherErrorAsString( ResponseAvatar response) { String responseBody = " "; final String responseBodyStr = response.getBody() == null ? " " : response.getBody(); responseBody = responseBodyStr .substring(0, responseBodyStr.length() > 256 ? 256 : responseBodyStr.length()); return responseBody; } private void generateExceptionMessage(String serviceLocationString, ResponseAvatar response, int httpStatusCode) throws HTTPTransportException { String responseBody = get500ErrorResponseAsString(response); throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_HTTP_ERROR, ErrorConstants.ERRORDOMAIN, new Object[] { serviceLocationString, response.getRequestStatus().getName(), Integer.valueOf(httpStatusCode), responseBody }), httpStatusCode, null); } private String get500ErrorResponseAsString(ResponseAvatar response) { String responseBody = " "; final String responseBodyStr = response.getBody() == null ? " " : response.getBody(); int startIndex = responseBodyStr.indexOf(LOOKUP_BODY_TEXT); startIndex = startIndex > 0 ? startIndex + LOOKUP_BODY_TEXT.length() : 0; try { responseBody = responseBodyStr.substring(startIndex, (responseBodyStr.length() > startIndex + RESPONSE_BODY_LIMIT ? startIndex + RESPONSE_BODY_LIMIT : Math.min(startIndex + RESPONSE_BODY_LIMIT, responseBodyStr.length()))); } catch (StringIndexOutOfBoundsException e) { responseBody = responseBodyStr.substring(0, Math.min( RESPONSE_BODY_LIMIT, responseBodyStr.length())); } return responseBody; } void setResponseToContextHeaderMapping(ResponseAvatar httpClientResponse, BaseMessageContextImpl clientCtx) throws ServiceException { InboundMessage clientResponse = (InboundMessage) clientCtx .getResponseMessage(); Iterator<String> headerNames = httpClientResponse.getHeaderNames(); while (headerNames.hasNext()) { String header = headerNames.next(); String value = httpClientResponse.getHeader(header); // HOT FIX: For Porlet call, there's a compatibility issue regarding // Upper case vs lower case operation. // Here, we want to make the first letter of the operation name // lower case always, // to handle 589 client talking to 587 server scenario // CLIENT SIDE RECEIVING SIDE LOGIC if (header != null && header .equalsIgnoreCase(SOAHeaders.SERVICE_OPERATION_NAME) && value != null && value.equals("Portlet")) { StringBuffer sb = new StringBuffer(); sb.append(Character.toLowerCase(value.charAt(0))); sb.append(value.substring(1)); value = sb.toString(); } clientResponse.setTransportHeader(header, value); } Iterator cookies = httpClientResponse.getCookies(); while (cookies.hasNext()) { String cookieString = (String) cookies.next(); clientResponse.setCookie(HTTPCommonUtils .parseSetCookieValue(cookieString)); } clientResponse.doHeaderMapping(); } void setResponseToContextInputStream(String serviceLocationString, ResponseAvatar httpClientResponse, BaseMessageContextImpl clientCtx) throws ServiceException { InboundMessage clientResponse = (InboundMessage) clientCtx .getResponseMessage(); try { if (clientCtx.isOutboundRawMode()) { // raw mode - read everything // in memory if (httpClientResponse.isGzipped()) { clientResponse.setByteBuffer(BufferUtil .readInputStream(new GZIPInputStream( httpClientResponse.getContentStream()))); httpClientResponse.deallocateContent(); } else { byte[] rawData = httpClientResponse.getRawData(); rawData = rawData == null ? new byte[0] : rawData; clientResponse.setByteBuffer(ByteBuffer.wrap(rawData)); } } else { InputStream bis = httpClientResponse.getContentStream(); clientResponse .setInputStream(httpClientResponse.isGzipped() ? new GZIPInputStream( bis) : bis); // clientResponse // .setInputStream( // httpClientResponse.isGzipped() ? new GZIPInputStream(bis) : bis, // m_inMemoryAttachmentLimit); } } catch (IOException e) { throw new HTTPTransportException(ErrorDataFactory.createErrorData( ErrorConstants.SVC_TRANSPORT_DECODE_ERROR, ErrorConstants.ERRORDOMAIN, new Object[] { serviceLocationString, "Unzip failed " + e.getLocalizedMessage() }), httpClientResponse.getStatusCode(), e); } } private class AsyncClientCallBack implements NioAsyncCallback { private final BaseMessageContextImpl m_msgContext; private final AsyncCallBack m_callback; private final PipedOutputStream m_pipeOS; private PipedInputStream m_pipeIS; AsyncClientCallBack(BaseMessageContextImpl msgContext) { m_msgContext = msgContext; m_callback = m_msgContext.getServiceAsyncCallback(); if (HTTPSyncAsyncClientTransport.this.isClientStreaming()) { m_pipeOS = new PipedOutputStream(); } else { m_pipeOS = null; } } public void onException(Throwable cause) { closePipeIfNecessary(); m_callback.onException(cause); } public void onResponse(Response response) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "******** onResponse() called"); } try { if (!HTTPSyncAsyncClientTransport.this.isClientStreaming()) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER .info("Processing message in non-clientStreaming mode"); } // URL for destination URL serviceLocation = getServiceLocation(m_msgContext); String serviceLocationString = serviceLocation.toString(); ResponseAvatar responseAvatar = new ResponseWrapper( response); doCommonResponseProcessing(serviceLocationString, m_msgContext, responseAvatar); setResponseToContextInputStream(serviceLocationString, responseAvatar, m_msgContext); m_callback.onResponseInContext(); } else { // we're in client streaming mode // if no data has been previously pushed with // onResponseData(), spawn off empty response processing if (m_pipeIS == null) { m_pipeIS = new PipedInputStream(m_pipeOS); spawnOffClientProcessing(response); } } } catch (IOException e) { m_callback.onException(e); } catch (ServiceException e) { m_callback.onException(e); } finally { closePipeIfNecessary(); } } /** * Checks for the streaming mode and closes the pipe if streaming is on. */ private void closePipeIfNecessary() { if (HTTPSyncAsyncClientTransport.this.isClientStreaming()) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("Adjudicating pipe for clientStreaming mode"); } closePipe(); } } /** * Closes the pipe - should only be called in client streaming mode. Use * {@link #closePipeIfNecessary()} if you don't know you're in client * streaming mode. */ private void closePipe() { try { m_pipeOS.close(); } catch (IOException e) { HTTPSyncAsyncClientTransport.LOGGER.log(Level.SEVERE, "Could not close the PipeOutputStream", e); } } public void onTimeout() { closePipeIfNecessary(); m_callback.onTimeout(); } public boolean isDone() { return m_callback.isDone(); } @Override public void onResponseData(byte[] data, Response response) { if (!HTTPSyncAsyncClientTransport.this.isClientStreaming()) { throw new IllegalStateException( "onResponseData only supported in responseStreaming mode"); } final boolean firstInvocation = m_pipeIS == null; try { if (firstInvocation) { // first invocation if (LOGGER.isLoggable(Level.INFO)) { LOGGER.info("First invocation"); } m_pipeIS = new PipedInputStream(m_pipeOS); } } catch (IOException e) { closePipe(); m_callback.onException(e); } if (LOGGER.isLoggable(Level.FINE)) { // all the following madness is // due to findBugs forcing // us to use an explicit // encoding :( try { LOGGER.fine("Received: " + new String(data, "UTF-8")); } catch (UnsupportedEncodingException e) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Error when trying to log :(", e); } } } try { m_pipeOS.write(data); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Could not write data to stream", e); } if (firstInvocation) { try { spawnOffClientProcessing(response); } catch (ServiceException e) { closePipe(); // it IS necessary, we know it for sure m_callback.onException(e); } } } private void spawnOffClientProcessing(Response response) throws ServiceException { URL serviceLocation = getServiceLocation(m_msgContext); final String serviceLocationString = serviceLocation.toString(); final ResponseAvatar responseAvatar = new ResponseWrapperWithInputStream( response, m_pipeIS); doCommonResponseProcessing(serviceLocationString, m_msgContext, responseAvatar); AsyncCallBack.RunBefore runBefore = new AsyncCallBack.RunBefore() { @SuppressWarnings("synthetic-access") @Override public void run() throws ServiceException { HTTPSyncAsyncClientTransport.this .setResponseToContextInputStream( serviceLocationString, responseAvatar, AsyncClientCallBack.this.m_msgContext); } }; m_callback.onResponseInContext(runBefore); } } private static class CallBackRequestFuture<T> implements Future<T> { private final AsyncClientCallBack m_asyncClientCB; CallBackRequestFuture(AsyncClientCallBack asyncClientCB) { m_asyncClientCB = asyncClientCB; } public boolean cancel(boolean mayInterruptIfRunning) { return false; } public T get() throws InterruptedException, ExecutionException { throw new UnsupportedOperationException( "isDone() is only valid function to call"); } public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException { return get(); } public boolean isCancelled() { return false; } public boolean isDone() { return m_asyncClientCB.isDone(); } } public boolean supportsPoll() { return true; } public HTTPClientTransportConfig getConfig() { return m_config; } private static interface ResponseAvatar { public int getStatusCode(); public HttpStatusEnum getRequestStatus(); public String getHeader(String name); /** * Returns an iterator of all header names. */ public Iterator<String> getHeaderNames(); /** * Returns an Iterator of Strings listing all Set-Cookie header values * in response. * * @return Iterator over all Set-Cookie header values */ public Iterator getCookies(); public InputStream getContentStream(); public byte[] getRawData(); public boolean isGzipped(); /** * deallocate content to free memory */ public void deallocateContent(); public String getBody(); } private static class ResponseWrapper implements ResponseAvatar { private final Response response; public ResponseWrapper(Response response) { super(); this.response = response; } public Iterator getCookies() { return this.response.getCookies(); } public String getHeader(String name) { return this.response.getHeader(name); } public Iterator<String> getHeaderNames() { return this.response.getHeaderNames(); } public HttpStatusEnum getRequestStatus() { return this.response.getRequestStatus(); } public int getStatusCode() { return this.response.getStatusCode(); } @Override public InputStream getContentStream() { byte[] rawData = this.response.getRawData(); return new ByteArrayInputStream(rawData == null ? new byte[0] : rawData); } @Override public byte[] getRawData() { return this.response.getRawData(); } @Override public boolean isGzipped() { return this.response.isGzipped(); } /** * deallocate content to free memory */ public void deallocateContent() { this.response.deallocateContent(); } @Override public String getBody() { return this.response.getBody(); } } private static class ResponseWrapperWithInputStream extends ResponseWrapper { private final InputStream is; public ResponseWrapperWithInputStream(Response response, InputStream is) { super(response); this.is = is; } @Override public InputStream getContentStream() { return this.is; } } private static class FutureResponseWrapper implements ResponseAvatar { private final NioAsyncResponseFuture future; /** * Do *NOT* use this directly - use {@link #getResponse()} instead. */ private Response response; public FutureResponseWrapper(NioAsyncResponseFuture future) { super(); this.future = future; } /** * Lazily retrieves the response from the future. * <strong>Caution!</strong> This method will block until last piece of * data is retrieved. */ protected Response getResponse() { if (this.response == null) { try { this.response = this.future.getStreamingResponseObject(); } catch (IOException e) { throw new RuntimeException(e); } } return this.response; } @Override public Iterator getCookies() { return getResponse().getCookies(); } @Override public String getHeader(String name) { return getResponse().getHeader(name); } @Override public Iterator<String> getHeaderNames() { return getResponse().getHeaderNames(); } @Override public HttpStatusEnum getRequestStatus() { return getResponse().getRequestStatus(); } @Override public int getStatusCode() { return getResponse().getStatusCode(); } @Override public InputStream getContentStream() { return this.future.getResponseContentStream(); } @Override public byte[] getRawData() { try { ByteBuffer byteBuffer = BufferUtil .readInputStream(getContentStream()); return byteBuffer.array(); } catch (IOException e) { throw new RuntimeException(e); } } @Override public void deallocateContent() { // do *NOT* replace this - should only be called for raw mode getResponse().deallocateContent(); } @Override public boolean isGzipped() { return getResponse().isGzipped(); } /** * Blocking method - reads all the response data and returns the body. * * @see org.ebayopensource.turmeric.runtime.sif.impl.transport.http.HTTPSyncAsyncClientTransport.ResponseAvatar#getBody() */ @Override public String getBody() { return getResponse().getBody(); } } }