How to proxy/mirror a Websocket connection url in

2019-09-01 23:35发布

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?

1条回答
老娘就宠你
2楼-- · 2019-09-02 00:13

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:

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

Bitnami's default configuration is so good that no other changes were needed.

查看更多
登录 后发表回答