I've almost completed How can I embed a server-side Shiny app into a JSP page without exposing the app elsewhere, but the final part of this solution avenue has me really stuck.
(Basic background: this is a Spring MVC Java webserver, on the same machine as a Shiny app. I'd like to avoid moving to Spring Boot if possible.)
The code is successfully mirroring all the Shiny content from localhost:3305
on to localhost:8080/project/shiny-proxy
, EXCEPT for a websocket connection URL: ws://localhost:8080/project/shiny-proxy/websocket/
needs to map to ws://localhost:3305/websocket/
. http://localhost:3305/websocket/
returns a hard-coded "Not found" HTML page when I visit it in Chrome, so requesting using the http://
prefix isn't likely to work via any method.
Note that I'm hoping that if I can get a connection established between the client and ws://localhost:3305/websocket/
, the Java code won't need to handle any of the Websocket messages between them.
Here's the controller code so far, based on https://stackoverflow.com/a/23736527/7376471:
private static final RestTemplate restTemplate = new RestTemplate(
/* specific HttpRequestFactory here */);
@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");
boolean websocket = false;
URI uri;
if (path.endsWith("/websocket/")) {
websocket = true;
// restore the ws:// that the request URL started with after Spring MVC makes it http://
uri = new URI("ws", null, "localhost", 3305, path, request.getQueryString(), null);
} else {
uri = new URI(request.getScheme(), null, "localhost", 3305, path, request.getQueryString(), null);
}
if (websocket) {
System.out.println(request.getRequestURL().toString());
System.out.println(request.getRequestURI());
System.out.println(path);
System.out.println(uri);
}
HttpHeaders headers = new HttpHeaders();
if (path.endsWith(".css.map")) { // special handling for unusual content types from Shiny
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
}
HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> ret = null;
try {
ret = restTemplate.exchange(uri, method, httpEntity, String.class);
} catch (Exception e) {
System.out.println(request.getRequestURL().toString());
System.out.println(request.getRequestURI());
System.out.println(path);
System.out.println(uri);
e.printStackTrace();
}
if (websocket) {
System.out.println("ResponseEntity headers: " + ret.getHeaders());
}
return ret;
}
but I can't find any HttpRequestFactory
that works with ws://
URLs (many of them convert URI's to java.net.URLs, which don't support ws://, and the others I've tested fail to handle ws:// in the reflective code) and even if I could, I'm not sure that returning a ResponseEntity
would even work for ws connections.
So my question is, is there ANY way to get the controller to correctly establish a websocket connection between client and localhost:3305 given a ws:// request URL, or should I abandon the RestTemplate idea and try a configured proxy like Nginx instead?
Turned out the solution using a configured proxy in my case was incredibly simple: enable
Include conf/extra/httpd-vhosts.conf
in/opt/bitnami/apache2/conf/httpd.conf
,and set httpd-vhosts.conf's content to:
Bitnami's default configuration is so good that no other changes were needed.