2

I have a Shiny app that I want to embed into a page of my Java 8 webserver that is hosted on Amazon AWS. (Note: I say "embed" because most pages in the webserver share a common sidebar & footer - this is applied automatically to most views, such that the .jsp files only have to provide html for the real substance of the page).

The Shiny app listens on 3305 and the webserver hosts to :8080 on localhost ([server ip]/subject/index.html works fine to get webpages via other machines).

Embedding the Shiny app works fine for localhost via a shiny.jsp containing <iframe src="http://localhost:3305">, but when viewed from another machine, the page looks for a shiny server only on that other machine, when it should be looking on the host machine. Leaving off the "http://" results in the Shiny app never getting a request and the page staying blank, and exchanging "localhost" with "127.0.0.1" results in no noticeable changes.

The server uses Spring, JSTL and Apache Tomcat 7.0 .

I cannot simply port forward the Shiny app to make it visible outside the server, because the app displays confidential information. In a .jsp, this is not a concern, as the iframe can be wrapped in a <security:authorize> tag (the website requires login to access any pages in the first place).

The question is, what is the simplest way to embed the Shiny app into a .jsp page such that it remains interactive for the user but is secure (cannot be directly accessed with no authorization from outside the server) and requires no additional logins?

M. Zoller
  • 65
  • 9
  • As discussed below, I think some sort of proxy or transparent passthrough allowing me to direct requests from rootserve/shiny-proxy to localhost:3305 could be a convenient solution, as an – M. Zoller Oct 11 '18 at 21:12

2 Answers2

0

You can expose a proxy API from your webserver to retrieve content of Shiny app. You can then use iframe to display content of shiny app using proxy as src.

ViralPanda
  • 83
  • 8
  • I've seen suggestions to do this but I was trying to avoid outside tools... I'm testing whether https://cloud.spring.io/spring-cloud-gateway/1.0.x/multi/multi__building_a_gateway_using_spring_mvc.html will work now. – M. Zoller Oct 09 '18 at 19:11
  • I was not able to get ProxyExchange to work as in the last link, I think because I am not using Spring Boot. The specific error was something like "No default constructor found for ProxyExchange" during the injection/autowiring attempt. – M. Zoller Oct 11 '18 at 21:02
  • I'm now looking into https://stackoverflow.com/questions/14726082/spring-mvc-rest-service-redirect-forward-proxy. I've only been able to add the HTML to the iframe as weird text with a bunch of \n's so far, OR (using .getBody() and returning String) as a single quoted string in a
     tag, which I'm hoping might have something to do with "expected Document but transfered as application/json". I'm hoping something like https://stackoverflow.com/questions/21613416/why-resttemplate-get-response-is-in-json-when-should-be-in-xml might help.
    – M. Zoller Oct 11 '18 at 21:04
  • correction: the Javascript console warning is "Resource interpreted as Document but transfered with MIME type application/json" – M. Zoller Oct 15 '18 at 19:23
  • So the better solution was to return ResponseEntity after all, and mvc:annotation-driven fixed the weird formatting and headers. Now the only problem is requests of the format ws://localhost:8080/project/shiny-proxy/websocket/ need to go to ws://localhost:3305/websocket/ to establish a WebSocket connection, but I can't find any way of making restTemplate.exchange work for a URI like that. I've confirmed that attempting to access http://localhost:3305/websocket/ results in a hard "Not found" html page, but that http://localhost:3305 does successfully access ws://localhost:3305/websocket/. – M. Zoller Oct 16 '18 at 01:42
  • `Request URL: ws://localhost:3305/websocket/ Request Method: GET Status Code: 101 Switching Protocols Connection: Upgrade Content-Length: 0 Sec-WebSocket-Accept: [snip] Upgrade: websocket` – M. Zoller Oct 16 '18 at 01:42
  • The RestTemplate.exchange approach is continued in https://stackoverflow.com/questions/52880670/how-to-proxy-mirror-a-websocket-connection-url-in-spring-mvc, but it may or may not work out. – M. Zoller Oct 19 '18 at 17:49
0

A la spring mvc rest service redirect / forward / proxy, a Controller mirrors the Shiny app, so that we retain access control:

import java.net.URI;
import java.util.Arrays;    
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;

@Controller
public class ShinyController
{
    private static final RestTemplate restTemplate = new RestTemplate();

    @RequestMapping(path = "/shiny-proxy/**")
    public ResponseEntity<String> mirrorRest(@RequestBody(required = false) String body,
            HttpMethod method, HttpServletRequest request) throws URISyntaxException {
        String path = StringUtils.removeStart(
            request.getRequestURI(), "/project/shiny-proxy");
        URI uri = new URI(request.getScheme(), null, "localhost", 3305,
                          path, request.getQueryString(), null);

        HttpHeaders headers = new HttpHeaders();
        if (path.endsWith(".css.map")) {
            headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        }
        HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
        return restTemplate.exchange(uri, method, httpEntity, String.class);
    }
}

This handles the Shiny page's HTML and all of its resources. In a JSP view,

<iframe src="shiny-proxy/">

Now we just need to handle the ws://[shiny url]/websocket/ request.

In web.xml, prevent the webserver from handling it via:

<servlet-mapping>
  <servlet-name>default</servlet-name>
  <url-pattern>/project/shiny-proxy/websocket/</url-pattern>
</servlet-mapping>

Then, enable Include conf/extra/httpd-vhosts.conf in /opt/bitnami/apache2/conf/httpd.conf,

and set httpd-vhosts.conf's content to:

<VirtualHost *:80>
  RewriteEngine on
  RewriteRule /project/shiny-proxy/websocket/(.*) ws://localhost:3305/websocket/$1 [P,L]
</VirtualHost>

so... turned out to be quite simple.

M. Zoller
  • 65
  • 9