hopsworks-master/hopsworks-api/src/main/java/com/predic8/membrane/servlet/embedded/RStudioMembraneServlet.java
/*
 * This file is part of Hopsworks
 * Copyright (C) 2018, Logical Clocks AB. All rights reserved

 * Hopsworks is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * Hopsworks is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.predic8.membrane.servlet.embedded;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.predic8.membrane.core.Router;
import com.predic8.membrane.core.RuleManager;
import com.predic8.membrane.core.rules.ProxyRule;
import com.predic8.membrane.core.rules.ProxyRuleKey;
import com.predic8.membrane.core.rules.ServiceProxy;
import com.predic8.membrane.core.rules.ServiceProxyKey;
import io.hops.hopsworks.common.util.Ip;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;

/**
 * This embeds Membrane as a servlet.
 */
@SuppressWarnings({"serial"})
public class RStudioMembraneServlet extends HttpServlet {

  private static final long serialVersionUID = 1L;
  private static final Log logger = LogFactory.getLog(RStudioMembraneServlet.class);

  @Override
  public void init(ServletConfig config) throws ServletException {
  }

  @Override
  public void destroy() {
  }

  @Override
  protected void service(HttpServletRequest req, HttpServletResponse resp)
          throws ServletException, IOException {
    String queryString = req.getQueryString() == null ? "" : "?" + req.
            getQueryString();

    Router router;

// For websockets, the following paths are used by JupyterHub:
//  /(user/[^/]*)/(api/kernels/[^/]+/channels|terminals/websocket)/?
// forward to ws(s)://servername:port_number
//<LocationMatch "/mypath/(user/[^/]*)/(api/kernels/[^/]+/channels|terminals/websocket)(.*)">
//    ProxyPassMatch ws://localhost:8999/mypath/$1/$2$3
//    ProxyPassReverse ws://localhost:8999 # this may be superfluous
//</LocationMatch>
//        ProxyPass /api/kernels/ ws://192.168.254.23:8888/api/kernels/
//        ProxyPassReverse /api/kernels/ http://192.168.254.23:8888/api/kernels/
    List<NameValuePair> pairs;
    try {
      //note: HttpClient 4.2 lets you parse the string without building the URI
      pairs = URLEncodedUtils.parse(new URI(queryString), "UTF-8");
    } catch (URISyntaxException e) {
      throw new ServletException("Unexpected URI parsing error on "
              + queryString, e);
    }
    LinkedHashMap<String, String> params = new LinkedHashMap<>();
    for (NameValuePair pair : pairs) {
      params.put(pair.getName(), pair.getValue());
    }

    String externalIp = Ip.getHost(req.getRequestURL().toString());

    StringBuffer urlBuf = new StringBuffer("http://localhost:");

    String ctxPath = req.getRequestURI();

    int x = ctxPath.indexOf("/rstudio");
    int firstSlash = ctxPath.indexOf('/', x + 1);
    int secondSlash = ctxPath.indexOf('/', firstSlash + 1);
    String portString = ctxPath.substring(firstSlash + 1, secondSlash);
    Integer targetPort;
    try {
      targetPort = Integer.parseInt(portString);
    } catch (NumberFormatException ex) {
      logger.error("Invalid target port in the URL: " + portString);
      return;
    }
    urlBuf.append(portString);

    String newTargetUri = urlBuf.toString() + req.getRequestURI();

    StringBuilder newQueryBuf = new StringBuilder();
    newQueryBuf.append(newTargetUri);
    newQueryBuf.append(queryString);

    URI targetUriObj = null;
    try {
      targetUriObj = new URI(newQueryBuf.toString());
    } catch (Exception e) {
      throw new ServletException("Rewritten targetUri is invalid: "
              + newTargetUri, e);
    }
    ServiceProxy sp = new ServiceProxy(
            new ServiceProxyKey(
                    externalIp, "*", "*", -1),
            "localhost", targetPort);
//    ServiceProxy sp = new ServiceProxy(
//            new ServiceProxyKey(
//                    externalIp, "*", "*", -1),
//            "localhost", targetPort);
    sp.setTargetURL(newQueryBuf.toString());
    // only set external hostname in case admin console is used
    try {
      router = new HopsRouter();
      router.add(sp);
      router.init();
      ProxyRule proxy = new ProxyRule(new ProxyRuleKey(-1));
      router.getRuleManager().addProxy(proxy,
              RuleManager.RuleDefinitionSource.MANUAL);
      router.getRuleManager().addProxy(sp,
              RuleManager.RuleDefinitionSource.MANUAL);
      new HopsServletHandler(req, resp, router.getTransport(),
              targetUriObj).run();
    } catch (Exception ex) {
      Logger.getLogger(RStudioMembraneServlet.class.getName()).log(Level.SEVERE, null,
              ex);
    }

  }

}
