/* * Copyright 2015 WSO2, Inc. (http://wso2.com) * * 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 org.wso2.carbon.mediation.transport.handlers; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.xpath.AXIOMXPath; import org.apache.axiom.util.blob.OverflowBlob; import org.apache.axis2.AxisFault; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.AxisOperation; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.Parameter; import org.apache.axis2.util.JavaUtils; import org.apache.axis2.util.XMLUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.HttpInetConnection; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.nio.NHttpServerConnection; import org.apache.http.protocol.HTTP; import org.apache.synapse.transport.nhttp.NHttpConfiguration; import org.apache.synapse.transport.nhttp.NhttpConstants; import org.apache.synapse.transport.passthru.HttpGetRequestProcessor; import org.apache.synapse.transport.passthru.PassThroughConstants; import org.apache.synapse.transport.passthru.ProtocolState; import org.apache.synapse.transport.passthru.SourceContext; import org.apache.synapse.transport.passthru.SourceHandler; import org.jaxen.SimpleNamespaceContext; import org.jaxen.XPath; import org.wso2.carbon.base.ServerConfiguration; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.core.multitenancy.utils.TenantAxisUtils; import org.wso2.carbon.core.transports.CarbonHttpRequest; import org.wso2.carbon.core.transports.CarbonHttpResponse; import org.wso2.carbon.mediation.transport.handlers.utils.RequestProcessorDispatcherUtil; import org.wso2.carbon.utils.ServerConstants; import javax.servlet.ServletException; import javax.xml.namespace.QName; import java.io.IOException; import java.io.OutputStream; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; /** * Get Processor implementation for PassThrough Transport. */ public class PassThroughNHttpGetProcessor implements HttpGetRequestProcessor { private Map<String, org.wso2.carbon.core.transports.HttpGetRequestProcessor> getRequestProcessors = new LinkedHashMap<String, org.wso2.carbon.core.transports.HttpGetRequestProcessor>(); private ConfigurationContext cfgCtx; private SourceHandler sourceHandler; private static final QName ITEM_QN = new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, "Item"); private static final QName CLASS_QN = new QName(ServerConstants.CARBON_SERVER_XML_NAMESPACE, "Class"); private static final String CONTENT_TYPE = "Content-Type"; private static final String TEXT_HTML = "text/html"; private static final Log log = LogFactory.getLog(PassThroughNHttpGetProcessor.class); private void populateGetRequestProcessors() throws AxisFault { try { OMElement docEle = XMLUtils.toOM(ServerConfiguration.getInstance().getDocumentElement()); if (docEle != null) { SimpleNamespaceContext nsCtx = new SimpleNamespaceContext(); nsCtx.addNamespace("wsas", ServerConstants.CARBON_SERVER_XML_NAMESPACE); XPath xp = new AXIOMXPath("//wsas:HttpGetRequestProcessors/wsas:Processor"); xp.setNamespaceContext(nsCtx); List nodeList = xp.selectNodes(docEle); for (Object aNodeList : nodeList) { OMElement processorEle = (OMElement) aNodeList; OMElement itemEle = processorEle.getFirstChildWithName(ITEM_QN); if (itemEle == null) { throw new ServletException("Required element, 'Item' not found!"); } OMElement classEle = processorEle.getFirstChildWithName(CLASS_QN); org.wso2.carbon.core.transports.HttpGetRequestProcessor processor; if (classEle == null) { throw new ServletException("Required element, 'Class' not found!"); } else { processor = (org.wso2.carbon.core.transports.HttpGetRequestProcessor) Class.forName(classEle.getText().trim()).newInstance(); } getRequestProcessors.put(itemEle.getText().trim(), processor); } } } catch (Exception e) { handleException("Error populating GetRequestProcessors", e); } } private void processWithGetProcessor(HttpRequest request, HttpResponse response, String requestUri, String requestUrl, String queryString, String item, OutputStream outputStream, NHttpServerConnection conn) throws Exception { OverflowBlob temporaryData = new OverflowBlob(256, 4048, "_nhttp", ".dat"); try { CarbonHttpRequest carbonHttpRequest = new CarbonHttpRequest( "GET", requestUri, requestUrl); String uri = request.getRequestLine().getUri(); // setting the parameters for nhttp transport int pos = uri.indexOf("?"); if (pos != -1) { StringTokenizer st = new StringTokenizer(uri.substring(pos + 1), "&"); while (st.hasMoreTokens()) { String param = st.nextToken(); pos = param.indexOf("="); if (pos != -1) { carbonHttpRequest.setParameter( param.substring(0, pos), param.substring(pos + 1)); } else { carbonHttpRequest.setParameter(param, null); } } } carbonHttpRequest.setContextPath(cfgCtx.getServiceContextPath()); carbonHttpRequest.setQueryString(queryString); CarbonHttpResponse carbonHttpResponse = new CarbonHttpResponse( temporaryData.getOutputStream()); try { PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(TenantAxisUtils. getTenantDomain(requestUrl), true); (getRequestProcessors.get(item)).process(carbonHttpRequest, carbonHttpResponse, cfgCtx); } finally { PrivilegedCarbonContext.endTenantFlow(); } // adding headers Map<String, String> responseHeaderMap = carbonHttpResponse.getHeaders(); for (Object key : responseHeaderMap.keySet()) { Object value = responseHeaderMap.get(key); response.addHeader(key.toString(), value.toString()); } // setting status code response.setStatusCode(carbonHttpResponse.getStatusCode()); // setting error codes if (carbonHttpResponse.isError()) { if (carbonHttpResponse.getStatusMessage() != null) { response.setStatusLine(response.getProtocolVersion(), carbonHttpResponse.getStatusCode(), carbonHttpResponse.getStatusMessage()); } else { response.setStatusLine(response.getProtocolVersion(), carbonHttpResponse.getStatusCode()); } } if (carbonHttpResponse.isRedirect()) { response.addHeader("Location", carbonHttpResponse.getRedirect()); response.setStatusLine(response.getProtocolVersion(), 302); } SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE); try{ temporaryData.writeTo(outputStream); }catch (Exception e) { e.printStackTrace(); } try { outputStream.flush(); outputStream.close(); } catch (Exception ignored) {} } finally { temporaryData.release(); sourceHandler.commitResponseHideExceptions(conn, response); } } public void init(ConfigurationContext configurationContext, SourceHandler sourceHandler) throws AxisFault { //super.init(configurationContext, sourceHandler); this.cfgCtx = configurationContext; this.sourceHandler = sourceHandler; if (cfgCtx.getProperty("GETRequestProcessorMap") != null) { getRequestProcessors = (Map<String, org.wso2.carbon.core.transports.HttpGetRequestProcessor>) cfgCtx.getProperty("GETRequestProcessorMap"); } else { populateGetRequestProcessors(); } } public void process(HttpRequest request, HttpResponse response, MessageContext messageContext, NHttpServerConnection conn, OutputStream outputStream, boolean b) { boolean isRequestHandled = false; String uri = request.getRequestLine().getUri(); String servicePath = cfgCtx.getServiceContextPath(); if (!servicePath.startsWith("/")) { servicePath = "/" + servicePath; } String serviceName = getServiceName(request); boolean loadBalancer = Boolean.parseBoolean(System.getProperty("wso2.loadbalancer", "false")); // Handle browser request to get favicon while requesting for wsdl if (uri.equals("/favicon.ico")) { response.setStatusCode(HttpStatus.SC_MOVED_PERMANENTLY); response.addHeader("Location", "http://wso2.org/favicon.ico"); SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE); try { outputStream.flush(); outputStream.close(); } catch (Exception ignore) { } sourceHandler.commitResponseHideExceptions(conn, response); isRequestHandled = true ; } else if(uri.startsWith(servicePath) && (serviceName == null || serviceName.length() == 0)){ //check if service listing request is blocked if (isServiceListBlocked(uri)) { response.setStatusCode(HttpStatus.SC_FORBIDDEN); sourceHandler.commitResponseHideExceptions(conn, response); } else { generateServicesList(response, conn, outputStream, servicePath); messageContext.setProperty("WSDL_GEN_HANDLED", true); } try { outputStream.flush(); outputStream.close(); } catch (IOException ignore) { } isRequestHandled = true ; } else { int pos = uri.indexOf('?'); if (pos != -1) { String queryString = uri.substring(pos + 1); String requestUri = uri.substring(0, pos); String requestUrl = uri; if (requestUri.indexOf("://") == -1) { HttpInetConnection inetConn = (HttpInetConnection) conn; String hostName = "localhost"; ServerConfiguration serverConfig = ServerConfiguration.getInstance(); if (serverConfig.getFirstProperty("HostName") != null) { hostName = serverConfig.getFirstProperty("HostName"); } requestUrl = "http://" + hostName + ":" + inetConn.getLocalPort() + requestUri; } String contextPath = cfgCtx.getServiceContextPath(); int beginIndex = -1; if (requestUri.indexOf(contextPath) != -1) { beginIndex = requestUri.indexOf(contextPath) + contextPath.length() + 1; } /** * This reverseProxyMode was introduce to avoid LB exposing it's own services when invoked through rest call. * For a soap call this works well. But for a rest call this does not work as intended. in LB it has to set system property "reverseProxyMode" * */ boolean reverseProxyMode = Boolean.parseBoolean(System.getProperty("reverseProxyMode")); AxisService axisService = null; if (!reverseProxyMode) { if (!(beginIndex < 0 || beginIndex > requestUri.length())) { serviceName = requestUri.substring(beginIndex); axisService = cfgCtx.getAxisConfiguration().getServiceForActivation(serviceName); } if (axisService == null && !loadBalancer && serviceName != null) { // Try to see whether the service is available in a tenant try { //Removing the below code segment due to empty stack exception coming from kernel //Current context does not need to be destroyed at this level. //PrivilegedCarbonContext.destroyCurrentContext(); String tenantDomain = TenantAxisUtils.getTenantDomain(uri); PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); axisService = TenantAxisUtils.getAxisService(serviceName, cfgCtx); } catch (AxisFault axisFault) { log.error("Error while retrieving Axis Service for Service name: "+serviceName, axisFault); } finally { PrivilegedCarbonContext.endTenantFlow(); } } } if (queryString != null) { for (String item : getRequestProcessors.keySet()) { if (queryString.indexOf(item) == 0 && (queryString.equals(item) || queryString.indexOf("&") == item.length() || queryString.indexOf("=") == item.length())) { if (axisService == null) { try { String tenantDomain = TenantAxisUtils.getTenantDomain(uri); PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain, true); //check for APIs since no axis2 service found if (!RequestProcessorDispatcherUtil.isDispatchToApiGetProcessor(requestUri, cfgCtx)) { continue; } } finally { PrivilegedCarbonContext.endTenantFlow(); } } try { processWithGetProcessor(request, response, requestUri, requestUrl, queryString, item, outputStream, conn); messageContext.setProperty("WSDL_GEN_HANDLED", true); } catch (Exception e) { handleBrowserException(response, conn, outputStream, "Error processing request", e); } isRequestHandled = true; break; } } } } } if (!isRequestHandled) { //processGetAndDelete(request, response, messageContext, conn, outputStream, "GET", b); messageContext.setProperty(PassThroughConstants.REST_GET_DELETE_INVOKE, true); } } /** * Generates the services list. * * @param response HttpResponse * @param conn NHttpServerConnection * @param os OutputStream * @param servicePath service path of the service */ protected void generateServicesList(HttpResponse response, NHttpServerConnection conn, OutputStream os, String servicePath) { try { byte[] bytes = getServicesHTML( servicePath.endsWith("/") ? "" : servicePath + "/").getBytes(); response.addHeader(CONTENT_TYPE, TEXT_HTML); SourceContext.updateState(conn, ProtocolState.WSDL_RESPONSE_DONE); sourceHandler.commitResponseHideExceptions(conn, response); os.write(bytes); } catch (IOException e) { handleBrowserException(response, conn, os, "Error generating services list", e); } } /** * Returns the HTML text for the list of services deployed. * This can be delegated to another Class as well * where it will handle more options of GET messages. * * @param prefix to be used for the Service names * @return the HTML to be displayed as a String */ protected String getServicesHTML(String prefix) { Map services = cfgCtx.getAxisConfiguration().getServices(); Hashtable erroneousServices = cfgCtx.getAxisConfiguration().getFaultyServices(); boolean servicesFound = false; StringBuffer resultBuf = new StringBuffer(); resultBuf.append("<html><head><title>Axis2: Services</title></head>" + "<body>"); if ((services != null) && !services.isEmpty()) { servicesFound = true; resultBuf.append("<h2>" + "Deployed services" + "</h2>"); for (Object service : services.values()) { AxisService axisService = (AxisService) service; Parameter isHiddenService = axisService.getParameter( NhttpConstants.HIDDEN_SERVICE_PARAM_NAME); Parameter isAdminService = axisService.getParameter("adminService"); boolean isClientSide = axisService.isClientSide(); boolean isSkippedService = (isHiddenService != null && JavaUtils.isTrueExplicitly(isHiddenService.getValue())) || (isAdminService != null && JavaUtils.isTrueExplicitly(isAdminService.getValue())) || isClientSide; if (axisService.getName().startsWith("__") || isSkippedService) { continue; // skip private services } Iterator iterator = axisService.getOperations(); resultBuf.append("<h3><a href=\"").append(prefix).append(axisService.getName()).append( "?wsdl\">").append(axisService.getName()).append("</a></h3>"); if (iterator.hasNext()) { resultBuf.append("Available operations <ul>"); for (; iterator.hasNext();) { AxisOperation axisOperation = (AxisOperation) iterator.next(); resultBuf.append("<li>").append( axisOperation.getName().getLocalPart()).append("</li>"); } resultBuf.append("</ul>"); } else { resultBuf.append("No operations specified for this service"); } } } if ((erroneousServices != null) && !erroneousServices.isEmpty()) { servicesFound = true; resultBuf.append("<hr><h2><font color=\"blue\">Faulty Services</font></h2>"); Enumeration faultyservices = erroneousServices.keys(); while (faultyservices.hasMoreElements()) { String faultyserviceName = (String) faultyservices.nextElement(); resultBuf.append("<h3><font color=\"blue\">").append( faultyserviceName).append("</font></h3>"); } } if (!servicesFound) { resultBuf.append("<h2>There are no services deployed</h2>"); } resultBuf.append("</body></html>"); return resultBuf.toString(); } /** * Is the incoming URI is requesting service list and http.block_service_list=true in * nhttp.properties * @param incomingURI incoming URI * @return whether to proceed with incomingURI */ protected boolean isServiceListBlocked(String incomingURI) { String isBlocked = NHttpConfiguration.getInstance().isServiceListBlocked(); return (("/services").equals(incomingURI) || ("/services" + "/").equals(incomingURI)) && Boolean.parseBoolean(isBlocked); } /** * Handles browser exception. * * @param response HttpResponse * @param conn NHttpServerConnection * @param os OutputStream * @param msg message * @param e Exception */ protected void handleBrowserException(HttpResponse response, NHttpServerConnection conn, OutputStream os, String msg, Exception e) { if (e == null) { log.error(msg); } else { log.error(msg, e); } if (!response.containsHeader(HTTP.TRANSFER_ENCODING)) { response.setStatusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR); response.setReasonPhrase(msg); //response.addHeader(CONTENT_TYPE, TEXT_HTML); //serverHandler.commitResponseHideExceptions(conn, response); try { os.write(msg.getBytes()); os.close(); } catch (IOException ignore) { } } if (conn != null) { try { conn.shutdown(); } catch (IOException ignore) { } } } /** * Returns the service name. * * @param request HttpRequest * @return service name as a String */ protected String getServiceName(HttpRequest request) { String uri = request.getRequestLine().getUri(); String servicePath = cfgCtx.getServiceContextPath(); if (!servicePath.startsWith("/")) { servicePath = "/" + servicePath; } String serviceName = null; if (uri.startsWith(servicePath)) { serviceName = uri.substring(servicePath.length()); if (serviceName.startsWith("/")) { serviceName = serviceName.substring(1); } if (serviceName.indexOf("?") != -1) { serviceName = serviceName.substring(0, serviceName.indexOf("?")); } } else { // this may be a custom URI String incomingURI = request.getRequestLine().getUri(); Map serviceURIMap = (Map) cfgCtx.getProperty(NhttpConstants.EPR_TO_SERVICE_NAME_MAP); if (serviceURIMap != null) { Set keySet = serviceURIMap.keySet(); for (Object key : keySet) { if (incomingURI.toLowerCase().contains(((String) key).toLowerCase())) { return (String) serviceURIMap.get(key); } } } } if (serviceName != null) { int opnStart = serviceName.indexOf("/"); if (opnStart != -1) { serviceName = serviceName.substring(0, opnStart); } } return serviceName; } public void handleException(String msg, Exception e) throws AxisFault { log.error(msg, e); throw new AxisFault(msg, e); } }