/** Common base for HTTP Destination implementations. */ public abstract class AbstractHTTPDestination extends AbstractMultiplexDestination implements Configurable, Assertor { public static final String HTTP_REQUEST = "HTTP.REQUEST"; public static final String HTTP_RESPONSE = "HTTP.RESPONSE"; public static final String HTTP_CONTEXT = "HTTP.CONTEXT"; public static final String HTTP_CONFIG = "HTTP.CONFIG"; public static final String PROTOCOL_HEADERS_CONTENT_TYPE = Message.CONTENT_TYPE.toLowerCase(); public static final String PARTIAL_RESPONSE = AbstractMultiplexDestination.class.getName() + ".partial.response"; public static final String RESPONSE_COMMITED = "http.response.done"; private static final Logger LOG = LogUtils.getL7dLogger(AbstractHTTPDestination.class); private static final long serialVersionUID = 1L; protected final Bus bus; // Configuration values protected HTTPServerPolicy server; protected String contextMatchStrategy = "stem"; protected boolean fixedParameterOrder; protected boolean multiplexWithAddress; /** * Constructor * * @param b the associated Bus * @param ci the associated conduit initiator * @param ei the endpoint info of the destination * @param dp true for adding the default port if it is missing * @throws IOException */ public AbstractHTTPDestination(Bus b, EndpointInfo ei, boolean dp) throws IOException { super(b, getTargetReference(getAddressValue(ei, dp), b), ei); bus = b; initConfig(); } /** * Cache HTTP headers in message. * * @param message the current message */ protected void setHeaders(Message message) { Map<String, List<String>> requestHeaders = new HashMap<String, List<String>>(); copyRequestHeaders(message, requestHeaders); message.put(Message.PROTOCOL_HEADERS, requestHeaders); if (requestHeaders.containsKey("Authorization")) { List<String> authorizationLines = requestHeaders.get("Authorization"); String credentials = authorizationLines.get(0); String authType = credentials.split(" ")[0]; if ("Basic".equals(authType)) { String authEncoded = credentials.split(" ")[1]; try { String authDecoded = new String(Base64Utility.decode(authEncoded)); String authInfo[] = authDecoded.split(":"); String username = (authInfo.length > 0) ? authInfo[0] : ""; // Below line for systems that blank out password after authentication; // see CXF-1495 for more info String password = (authInfo.length > 1) ? authInfo[1] : ""; AuthorizationPolicy policy = new AuthorizationPolicy(); policy.setUserName(username); policy.setPassword(password); message.put(AuthorizationPolicy.class, policy); } catch (Base64Exception ex) { // ignore, we'll leave things alone. They can try decoding it themselves } } } if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Request Headers: " + requestHeaders.toString()); } } protected void updateResponseHeaders(Message message) { Map<String, List<String>> responseHeaders = CastUtils.cast((Map) message.get(Message.PROTOCOL_HEADERS)); if (responseHeaders == null) { responseHeaders = new HashMap<String, List<String>>(); message.put(Message.PROTOCOL_HEADERS, responseHeaders); } setPolicies(responseHeaders); } /** * @param message the message under consideration * @return true iff the message has been marked as oneway */ protected final boolean isOneWay(Message message) { Exchange ex = message.getExchange(); return ex == null ? false : ex.isOneWay(); } /** * Copy the request headers into the message. * * @param message the current message * @param headers the current set of headers */ protected void copyRequestHeaders(Message message, Map<String, List<String>> headers) { HttpServletRequest req = (HttpServletRequest) message.get(HTTP_REQUEST); // TODO how to deal with the fields for (Enumeration e = req.getHeaderNames(); e.hasMoreElements(); ) { String fname = (String) e.nextElement(); String mappedName = HttpHeaderHelper.getHeaderKey(fname); List<String> values; if (headers.containsKey(mappedName)) { values = headers.get(mappedName); } else { values = new ArrayList<String>(); headers.put(mappedName, values); } for (Enumeration e2 = req.getHeaders(fname); e2.hasMoreElements(); ) { String val = (String) e2.nextElement(); values.add(val); } } headers.put(Message.CONTENT_TYPE, Collections.singletonList(req.getContentType())); } /** * Copy the response headers into the response. * * @param message the current message * @param headers the current set of headers */ protected void copyResponseHeaders(Message message, HttpServletResponse response) { String ct = (String) message.get(Message.CONTENT_TYPE); String enc = (String) message.get(Message.ENCODING); if (null != ct && null != enc && ct.indexOf("charset=") == -1 && !ct.toLowerCase().contains("multipart/related")) { ct = ct + "; charset=" + enc; } Map<?, ?> headers = (Map<?, ?>) message.get(Message.PROTOCOL_HEADERS); if (null != headers) { if (!headers.containsKey(Message.CONTENT_TYPE)) { response.setContentType(ct); } for (Iterator<?> iter = headers.keySet().iterator(); iter.hasNext(); ) { String header = (String) iter.next(); List<?> headerList = (List<?>) headers.get(header); StringBuilder sb = new StringBuilder(); for (int i = 0; i < headerList.size(); i++) { sb.append(headerList.get(i)); if (i + 1 < headerList.size()) { sb.append(','); } } response.addHeader(header, sb.toString()); } } else { response.setContentType(ct); } } protected void setupMessage( Message inMessage, final ServletContext context, final HttpServletRequest req, final HttpServletResponse resp) throws IOException { setupMessage(inMessage, null, context, req, resp); } protected void setupMessage( Message inMessage, final ServletConfig config, final ServletContext context, final HttpServletRequest req, final HttpServletResponse resp) throws IOException { inMessage.setContent(InputStream.class, req.getInputStream()); inMessage.put(HTTP_REQUEST, req); inMessage.put(HTTP_RESPONSE, resp); inMessage.put(HTTP_CONTEXT, context); inMessage.put(HTTP_CONFIG, config); inMessage.put(Message.HTTP_REQUEST_METHOD, req.getMethod()); inMessage.put(Message.REQUEST_URI, req.getRequestURI()); String contextPath = req.getContextPath(); if (contextPath == null) { contextPath = ""; } inMessage.put(Message.PATH_INFO, contextPath + req.getPathInfo()); String contentType = req.getContentType(); String enc = HttpHeaderHelper.findCharset(contentType); if (enc == null) { enc = req.getCharacterEncoding(); } // work around a bug with Jetty which results in the character // encoding not being trimmed correctly. if (enc != null && enc.endsWith("\"")) { enc = enc.substring(0, enc.length() - 1); } if (enc != null || "POST".equals(req.getMethod()) || "PUT".equals(req.getMethod())) { // allow gets/deletes/options to not specify an encoding String normalizedEncoding = HttpHeaderHelper.mapCharset(enc); if (normalizedEncoding == null) { String m = new org.apache.cxf.common.i18n.Message("INVALID_ENCODING_MSG", LOG, enc).toString(); LOG.log(Level.WARNING, m); throw new IOException(m); } inMessage.put(Message.ENCODING, normalizedEncoding); } inMessage.put(Message.QUERY_STRING, req.getQueryString()); inMessage.put(Message.CONTENT_TYPE, contentType); inMessage.put(Message.ACCEPT_CONTENT_TYPE, req.getHeader("Accept")); String basePath = getBasePath(contextPath); if (!StringUtils.isEmpty(basePath)) { inMessage.put(Message.BASE_PATH, basePath); } inMessage.put(Message.FIXED_PARAMETER_ORDER, isFixedParameterOrder()); inMessage.put(Message.ASYNC_POST_RESPONSE_DISPATCH, Boolean.TRUE); inMessage.put( SecurityContext.class, new SecurityContext() { public Principal getUserPrincipal() { return req.getUserPrincipal(); } public boolean isUserInRole(String role) { return req.isUserInRole(role); } }); setHeaders(inMessage); SSLUtils.propogateSecureSession(req, inMessage); } protected String getBasePath(String contextPath) throws IOException { if (StringUtils.isEmpty(endpointInfo.getAddress())) { return ""; } return new URL(endpointInfo.getAddress()).getPath(); } protected static EndpointInfo getAddressValue(EndpointInfo ei) { return getAddressValue(ei, true); } protected static EndpointInfo getAddressValue(EndpointInfo ei, boolean dp) { if (dp) { String eiAddress = ei.getAddress(); if (eiAddress == null) { try { ServerSocket s = new ServerSocket(0); ei.setAddress("http://localhost:" + s.getLocalPort()); s.close(); return ei; } catch (IOException ex) { // problem allocating a random port, go to the default one ei.setAddress("http://localhost"); } } String addr = StringUtils.addDefaultPortIfMissing(ei.getAddress()); if (addr != null) { ei.setAddress(addr); } } return ei; } /** * @param inMessage the incoming message * @return the inbuilt backchannel */ protected Conduit getInbuiltBackChannel(Message inMessage) { HttpServletResponse response = (HttpServletResponse) inMessage.get(HTTP_RESPONSE); return new BackChannelConduit(response); } /** * Mark message as a partial message. * * @param partialResponse the partial response message * @param the decoupled target * @return true iff partial responses are supported */ protected final boolean markPartialResponse( Message partialResponse, EndpointReferenceType decoupledTarget) { // setup the outbound message to for 202 Accepted partialResponse.put(Message.RESPONSE_CODE, HttpURLConnection.HTTP_ACCEPTED); partialResponse.getExchange().put(EndpointReferenceType.class, decoupledTarget); partialResponse.put(PARTIAL_RESPONSE, Boolean.TRUE); return true; } protected boolean isPartialResponse(Message m) { return Boolean.TRUE.equals(m.get(PARTIAL_RESPONSE)); } private void initConfig() { PolicyEngine engine = bus.getExtension(PolicyEngine.class); // for a decoupled endpoint there is no service info if (null != engine && engine.isEnabled() && null != endpointInfo.getService()) { server = PolicyUtils.getServer(engine, endpointInfo, this); } if (null == server) { server = endpointInfo.getTraversedExtensor(new HTTPServerPolicy(), HTTPServerPolicy.class); } } private static List<String> createMutableList(String val) { return new ArrayList<String>(Arrays.asList(new String[] {val})); } void setPolicies(Map<String, List<String>> headers) { HTTPServerPolicy policy = server; if (policy.isSetCacheControl()) { headers.put("Cache-Control", createMutableList(policy.getCacheControl().value())); } if (policy.isSetContentLocation()) { headers.put("Content-Location", createMutableList(policy.getContentLocation())); } if (policy.isSetContentEncoding()) { headers.put("Content-Encoding", createMutableList(policy.getContentEncoding())); } if (policy.isSetContentType()) { headers.put(HttpHeaderHelper.CONTENT_TYPE, createMutableList(policy.getContentType())); } if (policy.isSetServerType()) { headers.put("Server", createMutableList(policy.getServerType())); } if (policy.isSetHonorKeepAlive() && !policy.isHonorKeepAlive()) { headers.put("Connection", createMutableList("close")); } else if (policy.isSetKeepAliveParameters()) { headers.put("Keep-Alive", createMutableList(policy.getKeepAliveParameters())); } /* * TODO - hook up these policies <xs:attribute name="SuppressClientSendErrors" type="xs:boolean" use="optional" default="false"> <xs:attribute name="SuppressClientReceiveErrors" type="xs:boolean" use="optional" default="false"> */ } protected OutputStream flushHeaders(Message outMessage) throws IOException { updateResponseHeaders(outMessage); Object responseObj = outMessage.get(HTTP_RESPONSE); OutputStream responseStream = null; boolean oneWay = isOneWay(outMessage); if (responseObj instanceof HttpServletResponse) { HttpServletResponse response = (HttpServletResponse) responseObj; Integer i = (Integer) outMessage.get(Message.RESPONSE_CODE); if (i != null) { int status = i.intValue(); if (HttpURLConnection.HTTP_INTERNAL_ERROR == i) { Map<Object, Object> pHeaders = CastUtils.cast((Map) outMessage.get(Message.PROTOCOL_HEADERS)); if (null != pHeaders && pHeaders.containsKey(PROTOCOL_HEADERS_CONTENT_TYPE)) { pHeaders.remove(PROTOCOL_HEADERS_CONTENT_TYPE); } } response.setStatus(status); } else if (oneWay) { response.setStatus(HttpURLConnection.HTTP_ACCEPTED); } else { response.setStatus(HttpURLConnection.HTTP_OK); } copyResponseHeaders(outMessage, response); if (oneWay && !isPartialResponse(outMessage)) { response.setContentLength(0); response.flushBuffer(); response.getOutputStream().close(); } else { responseStream = response.getOutputStream(); } } else if (null != responseObj) { String m = (new org.apache.cxf.common.i18n.Message( "UNEXPECTED_RESPONSE_TYPE_MSG", LOG, responseObj.getClass())) .toString(); LOG.log(Level.WARNING, m); throw new IOException(m); } else { String m = (new org.apache.cxf.common.i18n.Message("NULL_RESPONSE_MSG", LOG)).toString(); LOG.log(Level.WARNING, m); throw new IOException(m); } if (oneWay) { outMessage.remove(HTTP_RESPONSE); } return responseStream; } /** Backchannel conduit. */ public class BackChannelConduit extends AbstractDestination.AbstractBackChannelConduit { protected HttpServletResponse response; BackChannelConduit(HttpServletResponse resp) { response = resp; } /** * Send an outbound message, assumed to contain all the name-value mappings of the corresponding * input message (if any). * * @param message the message to be sent. */ public void prepare(Message message) throws IOException { message.put(HTTP_RESPONSE, response); message.setContent(OutputStream.class, new WrappedOutputStream(message, response)); } } /** * Wrapper stream responsible for flushing headers and committing outgoing HTTP-level response. */ private class WrappedOutputStream extends AbstractWrappedOutputStream { protected HttpServletResponse response; private Message outMessage; WrappedOutputStream(Message m, HttpServletResponse resp) { super(); this.outMessage = m; response = resp; } /** * Perform any actions required on stream flush (freeze headers, reset output stream ... etc.) */ protected void onFirstWrite() throws IOException { OutputStream responseStream = flushHeaders(outMessage); if (null != responseStream) { wrappedStream = responseStream; } } /** Perform any actions required on stream closure (handle response etc.) */ public void close() throws IOException { if (wrappedStream == null) { OutputStream responseStream = flushHeaders(outMessage); if (null != responseStream) { wrappedStream = responseStream; } } if (wrappedStream != null) { wrappedStream.close(); response.flushBuffer(); } } public void flush() throws IOException { // ignore until we close // or we'll force chunking and cause all kinds of network packets } } protected boolean contextMatchOnExact() { return "exact".equals(contextMatchStrategy); } public String getBeanName() { String beanName = null; if (endpointInfo.getName() != null) { beanName = endpointInfo.getName().toString() + ".http-destination"; } return beanName; } /* * Implement multiplex via the address URL to avoid the need for ws-a. * Requires contextMatchStrategy of stem. * * @see org.apache.cxf.transport.AbstractMultiplexDestination#getAddressWithId(java.lang.String) */ public EndpointReferenceType getAddressWithId(String id) { EndpointReferenceType ref = null; if (isMultiplexWithAddress()) { String address = EndpointReferenceUtils.getAddress(reference); ref = EndpointReferenceUtils.duplicate(reference); if (address.endsWith("/")) { EndpointReferenceUtils.setAddress(ref, address + id); } else { EndpointReferenceUtils.setAddress(ref, address + "/" + id); } } else { ref = super.getAddressWithId(id); } return ref; } /* * (non-Javadoc) * * @see org.apache.cxf.transport.AbstractMultiplexDestination#getId(java.util.Map) */ public String getId(Map context) { String id = null; if (isMultiplexWithAddress()) { String address = (String) context.get(Message.PATH_INFO); if (null != address) { int afterLastSlashIndex = address.lastIndexOf("/") + 1; if (afterLastSlashIndex > 0 && afterLastSlashIndex < address.length()) { id = address.substring(afterLastSlashIndex); } } else { getLogger() .log( Level.WARNING, new org.apache.cxf.common.i18n.Message("MISSING_PATH_INFO", LOG).toString()); } } else { return super.getId(context); } return id; } public String getContextMatchStrategy() { return contextMatchStrategy; } public void setContextMatchStrategy(String contextMatchStrategy) { this.contextMatchStrategy = contextMatchStrategy; } public boolean isFixedParameterOrder() { return fixedParameterOrder; } public void setFixedParameterOrder(boolean fixedParameterOrder) { this.fixedParameterOrder = fixedParameterOrder; } public boolean isMultiplexWithAddress() { return multiplexWithAddress; } public void setMultiplexWithAddress(boolean multiplexWithAddress) { this.multiplexWithAddress = multiplexWithAddress; } public HTTPServerPolicy getServer() { return server; } public void setServer(HTTPServerPolicy server) { this.server = server; } public void assertMessage(Message message) { PolicyUtils.assertServerPolicy(message, server); } public boolean canAssert(QName type) { return PolicyUtils.HTTPSERVERPOLICY_ASSERTION_QNAME.equals(type); } }
@SuppressWarnings("unchecked") protected void propagateHeadersFromCamelToCxf( Exchange camelExchange, Map<String, Object> camelHeaders, org.apache.cxf.message.Exchange cxfExchange, Map<String, Object> cxfContext) { // get cxf transport headers (if any) from camel exchange // use a treemap to keep ordering and ignore key case Map<String, List<String>> transportHeaders = new TreeMap<String, List<String>>(String.CASE_INSENSITIVE_ORDER); if (camelExchange != null) { Map<String, List<String>> h = CastUtils.cast((Map<?, ?>) camelExchange.getProperty(Message.PROTOCOL_HEADERS)); if (h != null) { transportHeaders.putAll(h); } } Map<String, List<String>> headers = CastUtils.cast((Map<?, ?>) camelHeaders.get(Message.PROTOCOL_HEADERS)); if (headers != null) { transportHeaders.putAll(headers); } DataFormat dataFormat = camelExchange.getProperty(CxfConstants.DATA_FORMAT_PROPERTY, DataFormat.class); for (Map.Entry<String, Object> entry : camelHeaders.entrySet()) { // put response code in request context so it will be copied to CXF message's property if (Message.RESPONSE_CODE.equals(entry.getKey()) || Exchange.HTTP_RESPONSE_CODE.equals(entry.getKey())) { LOG.debug("Propagate to CXF header: {} value: {}", Message.RESPONSE_CODE, entry.getValue()); cxfContext.put(Message.RESPONSE_CODE, entry.getValue()); continue; } // We need to copy the content-type if the dataformat is RAW if (Message.CONTENT_TYPE.equalsIgnoreCase(entry.getKey()) && dataFormat.equals(DataFormat.RAW)) { LOG.debug("Propagate to CXF header: {} value: {}", Message.CONTENT_TYPE, entry.getValue()); cxfContext.put(Message.CONTENT_TYPE, entry.getValue().toString()); continue; } // need to filter the User-Agent ignore the case, as CXF just check the header with // "User-Agent" if (entry.getKey().equalsIgnoreCase("User-Agent")) { List<String> listValue = new ArrayList<String>(); listValue.add(entry.getValue().toString()); transportHeaders.put("User-Agent", listValue); } // this header should be filtered, continue to the next header if (headerFilterStrategy.applyFilterToCamelHeaders( entry.getKey(), entry.getValue(), camelExchange)) { continue; } LOG.debug("Propagate to CXF header: {} value: {}", entry.getKey(), entry.getValue()); // put SOAP/protocol header list in exchange if (Header.HEADER_LIST.equals(entry.getKey())) { List<Header> headerList = (List<Header>) entry.getValue(); for (Header header : headerList) { header.setDirection(Header.Direction.DIRECTION_OUT); LOG.trace( "Propagate SOAP/protocol header: {} : {}", header.getName(), header.getObject()); } // cxfExchange.put(Header.HEADER_LIST, headerList); cxfContext.put(entry.getKey(), headerList); continue; } // things that are not filtered and not specifically copied will be put in transport headers if (entry.getValue() instanceof List) { transportHeaders.put(entry.getKey(), (List<String>) entry.getValue()); } else { List<String> listValue = new ArrayList<String>(); listValue.add(entry.getValue().toString()); transportHeaders.put(entry.getKey(), listValue); } } if (transportHeaders.size() > 0) { cxfContext.put(Message.PROTOCOL_HEADERS, transportHeaders); } else { // no propagated transport headers does really mean no headers, not the ones // from the previous request or response propagated with the invocation context cxfContext.remove(Message.PROTOCOL_HEADERS); } }