| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF 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 flex.messaging.services; |
| |
| import flex.management.runtime.messaging.services.HTTPProxyServiceControl; |
| import flex.management.runtime.messaging.services.http.HTTPProxyDestinationControl; |
| import flex.messaging.Destination; |
| import flex.messaging.FlexRemoteCredentials; |
| import flex.messaging.MessageBroker; |
| import flex.messaging.MessageException; |
| import flex.messaging.FlexContext; |
| import flex.messaging.messages.HTTPMessage; |
| import flex.messaging.messages.Message; |
| import flex.messaging.messages.SOAPMessage; |
| import flex.messaging.services.http.HTTPProxyDestination; |
| import flex.messaging.services.http.proxy.ProxyException; |
| import flex.messaging.util.SettingsReplaceUtil; |
| import flex.messaging.util.StringUtils; |
| import flex.messaging.log.LogCategories; |
| import flex.messaging.log.Log; |
| |
| import java.util.List; |
| import javax.servlet.http.HttpServletRequest; |
| |
| /** |
| * The HttpProxyService replaces the Flex 1.5 Proxy. It decouples |
| * the details of how the client contacts the message broker |
| * by accepting an HTTPMessage type which can be sent over |
| * any channel. |
| * |
| * @see flex.messaging.messages.HTTPMessage |
| */ |
| public class HTTPProxyService extends AbstractService { |
| /** |
| * Log category for <code>HTTPProxyService</code>. |
| */ |
| public static final String LOG_CATEGORY = LogCategories.SERVICE_HTTP; |
| |
| // Errors |
| private static final int DOT_DOT_NOT_ALLOWED = 10700; |
| private static final int MULTIPLE_DOMAIN_PORT = 10701; |
| private static final int DYNAMIC_NOT_CONFIGURED = 10702; |
| |
| private HTTPProxyServiceControl controller; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructs an unmanaged <code>HTTPProxyService</code>. |
| */ |
| public HTTPProxyService() { |
| this(false); |
| } |
| |
| /** |
| * Constructs a <code>HTTPProxyService</code> with the indicated management. |
| * |
| * @param enableManagement <code>true</code> if the <code>HTTPProxyService</code> |
| * is manageable; otherwise <code>false</code>. |
| */ |
| public HTTPProxyService(boolean enableManagement) { |
| super(enableManagement); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Public Getters and Setters for HTTPProxyService properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Creates a <code>HTTPProxyDestination</code> instance, sets its id, |
| * sets it manageable if the <code>AbstractService</code> that created it is |
| * manageable, and sets its <code>Service</code> to the <code>AbstractService</code> |
| * that created it. |
| * |
| * @param id The id of the <code>HTTPProxyDestination</code>. |
| * @return The <code>Destination</code> instanced created. |
| */ |
| @Override |
| public Destination createDestination(String id) { |
| HTTPProxyDestination destination = new HTTPProxyDestination(); |
| destination.setId(id); |
| destination.setManaged(isManaged()); |
| destination.setService(this); |
| |
| return destination; |
| } |
| |
| /** |
| * Casts the <code>Destination</code> into <code>HTTPProxyDestination</code> |
| * and calls super.addDestination. |
| * |
| * @param destination The <code>Destination</code> instance to be added. |
| */ |
| @Override |
| public void addDestination(Destination destination) { |
| HTTPProxyDestination proxyDestination = (HTTPProxyDestination) destination; |
| super.addDestination(proxyDestination); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Other Public APIs |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Processes messages of type <code>HTTPMessage</code> by invoking the |
| * requested destination's adapter. |
| * |
| * @param msg The message sent by the MessageBroker. |
| * @return The result of the service. |
| */ |
| @Override |
| public Object serviceMessage(Message msg) { |
| if (!(msg instanceof HTTPMessage)) { |
| // The 'HTTPProxy' Service can only process messages of type 'HTTPMessage'. |
| ServiceException e = new ServiceException(); |
| e.setMessage(UNKNOWN_MESSAGE_TYPE, new Object[]{"HTTPProxy", "HTTPMessage"}); |
| throw e; |
| } |
| |
| HTTPMessage message = (HTTPMessage) msg; |
| |
| String destination = message.getDestination(); |
| HTTPProxyDestination dest = (HTTPProxyDestination) destinations.get(destination); |
| |
| //use the remote settings if the message didn't specify them |
| FlexRemoteCredentials remoteCredentials = |
| FlexContext.getFlexSession().getRemoteCredentials(getId(), destination); |
| if (remoteCredentials != null) { |
| message.setRemoteUsername(remoteCredentials.getUsername()); |
| message.setRemotePassword((String) remoteCredentials.getCredentials()); |
| } else if (dest.getRemoteUsername() != null && dest.getRemotePassword() != null) { |
| message.setRemoteUsername(dest.getRemoteUsername()); |
| message.setRemotePassword(dest.getRemotePassword()); |
| } |
| |
| ServiceAdapter adapter = dest.getAdapter(); |
| |
| Object result; |
| |
| if (message instanceof SOAPMessage) { |
| result = invokeSoap(adapter, (SOAPMessage) message, dest); |
| } else { |
| result = invokeHttp(adapter, message, dest); |
| } |
| |
| if (Log.isDebug()) { |
| String debugResult = |
| StringUtils.prettifyString(String.valueOf(result)); |
| Log.getLogger(getLogCategory()).debug |
| ("HTTP request: " + |
| message + StringUtils.NEWLINE + |
| " response: " + StringUtils.NEWLINE + |
| debugResult + StringUtils.NEWLINE); |
| } |
| |
| return result; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Protected/private APIs |
| // |
| //-------------------------------------------------------------------------- |
| |
| protected Object invokeSoap(ServiceAdapter adapter, SOAPMessage message, HTTPProxyDestination destination) { |
| if (isManaged()) { |
| HTTPProxyDestinationControl destinationControl = (HTTPProxyDestinationControl) destination.getControl(); |
| if (destinationControl != null) |
| destinationControl.incrementInvokeSOAPCount(); |
| } |
| |
| String dynamicUrl = message.getUrl(); |
| |
| String contextPath = null; |
| String serverName = null; |
| String serverPort = null; |
| String protocol = null; |
| HttpServletRequest req = FlexContext.getHttpRequest(); |
| if (req != null) { |
| contextPath = req.getContextPath(); |
| protocol = req.getScheme(); |
| serverName = req.getServerName(); |
| int port = req.getServerPort(); |
| if (port != 0) { |
| serverPort = Integer.valueOf(req.getServerPort()).toString(); |
| } |
| } |
| |
| if (dynamicUrl != null && dynamicUrl.length() > 0) { |
| checkUrl(dynamicUrl, contextPath, destination, serverName, serverPort, protocol, message.getRemoteUsername() != null); |
| } else { |
| //TODO: QUESTION: Pete Support default soap endpoints? |
| //String url = settings.getParsedDefaultUrl(contextPath); |
| //message.setUrl(url); |
| |
| // FIXME: Need a better error here! |
| throw new MessageException("A SOAP endpoint was not provided."); |
| } |
| |
| return adapter.invoke(message); |
| } |
| |
| protected void checkUrl(String url, String contextPath, HTTPProxyDestination destination, String serverName, |
| String serverPort, String serverProtocol, boolean authSupplied) { |
| String originalUrl = url; |
| |
| String defaultUrl = destination.getParsedDefaultUrl(contextPath, serverName, serverPort, serverProtocol); |
| List dynamicUrls = destination.getParsedDynamicUrls(contextPath); |
| |
| //If we find ".." in a URL provided by the client, someone's likely |
| //trying to trick us. Ask them to do it another way if so. |
| |
| int i = url.indexOf("/.."); |
| while (i != -1) { |
| if (i == (url.length() - 3) || url.charAt(i + 3) == '/') { |
| throw new ProxyException(DOT_DOT_NOT_ALLOWED); |
| } |
| i = url.indexOf("/..", i + 1); |
| } |
| |
| //Next, check if the URL is exactly the default URL |
| url = url.toLowerCase(); |
| |
| // In IPv6, update to long form, if required. |
| url = SettingsReplaceUtil.updateIPv6(url); |
| |
| if (defaultUrl != null && defaultUrl.equalsIgnoreCase(url)) |
| return; |
| |
| char[] urlChars = url.toCharArray(); |
| |
| // Next, check that the URL matches a dynamic URL pattern |
| for (i = 0; i < dynamicUrls.size(); i++) { |
| char[] pattern = (char[]) dynamicUrls.get(i); |
| boolean matches = StringUtils.findMatchWithWildcard(urlChars, pattern); |
| |
| if (matches) { |
| if (!authSupplied || destination.allowsDynamicAuthentication()) |
| return; |
| throw new ProxyException(MULTIPLE_DOMAIN_PORT); |
| } |
| } |
| |
| ProxyException exception = new ProxyException(); |
| exception.setMessage |
| (DYNAMIC_NOT_CONFIGURED, new Object[]{originalUrl, destination.getId()}); |
| throw exception; |
| } |
| |
| /** |
| * Returns the log category of the <code>HTTPProxyService</code>. |
| * |
| * @return The log category of the component. |
| */ |
| @Override |
| protected String getLogCategory() { |
| return LOG_CATEGORY; |
| } |
| |
| protected Object invokeHttp(ServiceAdapter adapter, HTTPMessage message, HTTPProxyDestination destination) { |
| if (isManaged()) { |
| HTTPProxyDestinationControl destinationControl = (HTTPProxyDestinationControl) destination.getControl(); |
| if (destinationControl != null) |
| destinationControl.incrementInvokeHTTPCount(); |
| } |
| |
| String dynamicUrl = message.getUrl(); |
| |
| String contextPath = null; |
| String serverName = null; |
| String serverPort = null; |
| String protocol = null; |
| HttpServletRequest req = FlexContext.getHttpRequest(); |
| |
| if (req != null) { |
| contextPath = req.getContextPath(); |
| protocol = req.getScheme(); |
| serverName = req.getServerName(); |
| int port = req.getServerPort(); |
| if (port != 0) { |
| serverPort = Integer.toString(req.getServerPort()); |
| } |
| } |
| |
| if (dynamicUrl != null && !"".equals(dynamicUrl)) { |
| checkUrl(dynamicUrl, contextPath, destination, serverName, serverPort, protocol, message.getRemoteUsername() != null); |
| } else { |
| String url = destination.getParsedDefaultUrl(contextPath, serverName, serverPort, protocol); |
| message.setUrl(url); |
| } |
| |
| return adapter.invoke(message); |
| } |
| |
| /** |
| * This method is invoked to allow the <code>HTTPProxyService</code> to instantiate and register its |
| * MBean control. |
| * |
| * @param broker The <code>MessageBroker</code> to pass to the <code>HTTPProxyServiceControl</code> constructor. |
| */ |
| @Override |
| protected void setupServiceControl(MessageBroker broker) { |
| controller = new HTTPProxyServiceControl(this, broker.getControl()); |
| controller.register(); |
| setControl(controller); |
| } |
| } |