一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Springboot實現VNC的反向代理功能

Springboot實現VNC的反向代理功能

2022-01-24 01:08茈情苛侍 Java教程

這篇文章主要介紹了Springboot實現VNC的反向代理,搭建過程也很簡單,通過注冊bean攔截指定URL路徑進行自定義操作,具體實例代碼跟隨小編一起看看需要的朋友可以參考下

背景

? 用戶需要通過前端HTML頁面的noVNC(noVNC是什么?)客戶端連接底層VNC Server服務端,為了防止VNC Server的IP暴露,因此需要做一層代理。正常情況下使用Nginx、Apache等都可以搞定,但是由于項目架構的一些問題,暫時不能再加一臺反向代理服務器,所以決定寫一個單獨的模塊實現反向代理的功能。

? 在網上和Github上找了一下,使用了HTTP-Proxy-Servlet,引入該依賴搭建一個Spring Boot項目。

 

搭建

引入代理的依賴

<dependency>
  <groupId>org.mitre.dsmiley.httpproxy</groupId>
  <artifactId>smiley-http-proxy-servlet</artifactId>
  <version>1.12</version>
</dependency>

通過注冊bean攔截指定URL路徑進行自定義操作

@Configuration
public class ProxyServletConfiguration {

  // 攔截所有請求交給下面的VNCProxyServlet去處理
  private final static String SERVLET_URL = "/*";

  @Bean
  public ServletRegistrationBean<VNCProxyServlet> servletServletRegistrationBean() {
      ServletRegistrationBean<VNCProxyServlet> servletRegistrationBean = new ServletRegistrationBean<>(new VNCProxyServlet(), SERVLET_URL);
      //設置網址以及參數
      Map<String, String> params = ImmutableMap.of(
              "targetUri", "null", //這里寫null是因為targetUri是在自定義的VNCProxyServlet類中動態傳入的,而且這里必須要有值
              ProxyServlet.P_LOG, "true",
              ProxyServlet.P_PRESERVEHOST,"true",
              ProxyServlet.P_PRESERVECOOKIES,"true"
      );
      servletRegistrationBean.setInitParameters(params);
      return servletRegistrationBean;
  }

}

這里遇到的坑:

? 剛開始其實是準備在已有的一個模塊中加上這個代理功能,因為可以指定攔截的路徑,比如只攔截請求路徑為/proxy/*的,然后交給自定義的Servlet去代理,后來寫好測試時,發現代理過去后代理目標主頁一片空白,看了控制臺的Network后,主頁確實是返回200且加載正常,但是由主頁發起的js、css和img等靜態資源狀態碼都為404。

Springboot實現VNC的反向代理功能

Springboot實現VNC的反向代理功能

? 當時以為是代碼的問題,后來發現靜態資源都是相對路徑的有問題,如果前端的靜態資源是引入第三方的,比如從CDN中引入Vue.js則不會出現問題,都可以正常的被代理。既然狀態碼是404,那肯定是找不到這個資源,看了一下發現如果在靜態資源的路徑前加上指定攔截的路徑/proxy/就可以被正常代理。此時才明白,因為訪問首頁的路徑中帶/proxy/是在地址欄主動輸入的,所以請求到后臺,后臺Servlet攔截發現路徑中帶/proxy/,把該請求交給自定義的代理Servlet去處理然后返回。而主頁上的js、css等靜態資源發起請求的路徑是不會帶/proxy/*的,因此不會走到代理Servlet,并且代理模塊中也沒有相應資源路徑,所以就理所應當的返回了404。

? 為此還專門在GitHub上問了一下作者,作者也是回復說這并不是這個代理模塊該做的事,最好是前端處理,或者讓前端使用絕對路徑。附上地址(Discussions

Springboot實現VNC的反向代理功能

? 最后就是決定單獨拉出來寫一個Spring Boot項目做這個代理功能模塊,直接代理/*,這樣所有請求到這個模塊的都會被代理。

自定義Servlet實現動態代理目標地址

// VNCProxyServlet繼承了ProxyServlet 重寫了service方法 在方法中添加自定義操作 從請求地址中動態獲取
@Override
  protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException {
      // 獲取請求地址
      String targetUri = servletRequest.getRequestURL().toString();
		
      // 正則取請求地址中的參數 參數是放在域名中的
      Matcher matcher = DOMAIN_REX.matcher(targetUri);
      if(!matcher.find()){
          // 自定義的異常
          throw new GenericException("從域名中獲取vmId異常!");
      }
      // 取域名中的第一個 eg: http://vmId.xxx.cn得 [vmId,xxx,cn] 得 vmId
      Long vmId = Long.valueOf(matcher.group().split("\\.")[0]);
      
      // eg:業務邏輯根據vmId去拿 targetUri
  	targetUri = vmService.getTargetUrl(vmId);
	
      if (StringUtils.isEmpty(targetUri)) {
          throw new GenericException("代理路徑不正確,請確認路徑");
      }

      // 設置Url
      if (servletRequest.getAttribute(ATTR_TARGET_URI) == null) {
          servletRequest.setAttribute(ATTR_TARGET_URI, targetUri);
      }

      // 設置Host
      if (servletRequest.getAttribute(ATTR_TARGET_HOST) == null) {
          URL trueUrl = URLUtil.url(targetUri);
          servletRequest.setAttribute(ATTR_TARGET_HOST, new HttpHost(trueUrl.getHost(), trueUrl.getPort(), trueUrl.getProtocol()));
      }
		// 下面大部分都是父類的源碼 沒有需要特別修改的地方
      String method = servletRequest.getMethod();
      // 替換多余路徑
      String proxyRequestUri = this.rewriteUrlFromRequest(servletRequest);

      HttpRequest proxyRequest;
      if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null ||
              servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) {
          proxyRequest = new BasicHttpRequest(method, proxyRequestUri);
      } else {
          proxyRequest = this.newProxyRequestWithEntity(method, proxyRequestUri, servletRequest);
      }

      this.copyRequestHeaders(servletRequest, proxyRequest);
      setXForwardedForHeader(servletRequest, proxyRequest);
      HttpResponse proxyResponse = null;

      try {
          // Execute the request
          proxyResponse = this.doExecute(servletRequest, servletResponse, proxyRequest);

          // Process the response
          int statusCode = proxyResponse.getStatusLine().getStatusCode();

          // "reason phrase" is deprecated but it's the only way to pass
          servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase());

          // copying response headers to make sure SESSIONID or other Cookie which comes from remote server
          // will be saved in client when the proxied url was redirected to another one.
          copyResponseHeaders(proxyResponse, servletRequest, servletResponse);

          if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) {
              servletResponse.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0);
          } else {
              copyResponseEntity(proxyResponse, servletResponse, proxyRequest, servletRequest);
          }
      } catch (Exception e) {
          handleRequestException(proxyRequest, proxyResponse, e);
      } finally {
          if (proxyResponse != null) {
              EntityUtils.consumeQuietly(proxyResponse.getEntity());
          }
      }
  }

? 這里主要列出關鍵部分,因為方案還沒有完結。

 

問題

? 本以為這樣就成功了,但是測試之后發現頁面和靜態資源都代理過去了,但是有一個websocket請求失敗了。像noVNC這種網頁版的黑窗口,早就該想到肯定是用websocket這種長鏈接的請求進行交互的。后來去搜了一下這個叫websockify的請求,就是最開始介紹noVNC博客中介紹的:

? 瀏覽器不支持VNC,所以不能直接連接VNC,但是可以使用代理,使用noVNC通過WebSocket建立連接,而VNC Server不支持WebSocket,所以需要開啟Websockify代理來做WebSocket和TCP Socket之間的轉換,這個代理在noVNC的目錄里,叫做websockify。

? 此時項目是能夠攔截到websockify這個請求的,但是由于servlet把這個請求當成普通的請求去代理到目標服務器,這樣是無法成功的,所以要做的就是類似實現一個websocket的反向代理,搜了一下的話發現例子不是很多,大多都是在前端做的,前端作為客戶端與服務端建立websocket連接,但目前的狀況很明顯是需要這個代理模塊既做websocket服務端與web端建立連接,再作為websocket客戶端與VNC 服務端建立連接,然后進行交互傳遞通信。

? 后面也找到了這篇通過noVNC和websockify連接到QEMU/KVM,然后總結一下從用戶發出請求到得到響應的流程:

PC Chrome(客戶端) => noVNC Server(noVNC端) => websockify(websocket轉TCP Socket) => VNC Server(VNC服務端) => websockify(TCP Socket轉websocket) => noVNC Server(noVNC端)=> PC Chrome(客戶端)

用戶使用PC Chrome瀏覽器請求 noVNC端(因為無法直接訪問VNC Server端,VNC Server是不支持Websocket連接),經由websockify將websocket轉為TCP Socket請求到VNC服務端,返回TCP響應,經由websockify轉換為websocket返回給客戶端瀏覽器,這樣來進行交互。整個過程 websockify 代理器是關鍵,noVNC 可以被放在瀏覽器端。

1.noVNC網頁端與代理模塊建立websocket通信

@Configuration
public class WebSocketConfig {
  @Bean
  public ServerEndpointExporter serverEndpointExporter() {
      return new ServerEndpointExporter();
  }
}
@ServerEndpoint("/websockify")
@Component
public class WebSocketServer {

  /**
   * 連接建立成功調用的方法
   */
  @OnOpen
  public void onOpen(Session session) {
      logger.info("open...");
  }

  /**
   * 連接關閉調用的方法
   */
  @OnClose
  public void onClose() {
      logger.info("close...");
  }

  /**
   * 收到客戶端消息后調用的方法
   */
  @OnMessage
  public void onMessage(String message, Session session) {
      logger.info(message);
  }

  /**
   * 發生錯誤調用的方法
   */
  @OnError
  public void onError(Session session, Throwable error) {
      logger.error("用戶錯誤原因:"+error.getMessage());
      error.printStackTrace();
  }

}

? 都是很常用的websocket服務端的代碼,唯一要注意的是前端請求'/websockify'地址發起websocket連接時,要注意用ip,尤其是本地,使用localhost會報錯,要使用127.0.0.1。最后測試連接成功,返回狀態碼101,并且消息可以正常接收。noVNC網頁端與代理模塊建立websocket通信完成。

2.代理模塊與VNC Server建立websocket通信

java后臺作為websocket客戶端很少,大多是用Netty去寫的,但是不適合目前的情況,最后還是找到了一個感覺比較合適的

public class MyWebSocketClient {
  public static WebSocketClient mWs;
  public static void main(String[] args) {
      try {
          // 
          String url = "ws://172.28.132.11:8888/websocketify";
          URI uri = new URI(url);
          HashMap<String, String> httpHeadersMap = new HashMap<>();
          httpHeadersMap.put("Sec-WebSocket-Version", "13");
          httpHeadersMap.put("Sec-WebSocket-Key", "YBhzbbwLI83U5EH8Tlutwg==");
          httpHeadersMap.put("Connection","Upgrade");
          httpHeadersMap.put("Upgrade","websocket");
          httpHeadersMap.put("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36");
          httpHeadersMap.put("Cookie","token=8asda2das-84easdac-asdaqwe4-2asda-asdsadas");
          httpHeadersMap.put("Sec-WebSocket-Extensions","permessage-deflate; client_max_window_bits");
          mWs = new WebSocketClient(uri,httpHeadersMap){
              @Override
              public void onOpen(ServerHandshake serverHandshake) {
                  System.out.println("open...");
                  System.out.println(serverHandshake.getHttpStatus());
                  mWs.send("666");
              }

              @Override
              public void onMessage(String s) {
                  System.out.println(s);
              }

              @Override
              public void onClose(int i, String s, boolean b) {
                  System.out.println("close...");
                  System.out.println(i);
                  System.out.println(s);
                  System.out.println(b);
              }

              @Override
              public void onError(Exception e) {
                  System.out.println("發生了錯誤...");
              }
          };
          mWs.connect();
      } catch (Exception e) {
          e.printStackTrace();
      }

  }
}

// 調用后報錯  直接關閉了連接  狀態碼為1002 
// close...
// 1002
// Invalid status code received: 400 Status line: HTTP/1.1 400 Client must support 'binary' or 'base64' protocol

? 發生錯誤后,發現關鍵地方,客戶端必須支持 binary或base64協議,一番搜索后再Stack Overflow找到了線索,并且是Kanaka(noVNC和websockify的開發者)親自回答的,大概意思就是你需要在構造函數中提供這些協議。

Springboot實現VNC的反向代理功能

? 然后我又在websockify.js的源碼中找到了這個構造,確實需要傳遞一個protocols的數組參數,可是這是前端,并不知道Java如何完成這個操作。

Springboot實現VNC的反向代理功能

后續

? 首先再次感謝開源項目和各位博主大佬的分享,依舊在尋找解決方案......

到此這篇關于Springboot實現VNC的反向代理功能的文章就介紹到這了,更多相關Springboot反向代理內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/yanht/p/15352010.html

延伸 · 閱讀

精彩推薦
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學習使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發現了對于集合操作轉換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關于Java8中S...

    阿杜7482021-02-04
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經有好久沒有升過級了。升級完畢重啟之后,突然發現好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實現搶紅包功能

    Java實現搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java BufferWriter寫文件寫不進去或缺失數據的解決

    Java BufferWriter寫文件寫不進去或缺失數據的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數據的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程xml與Java對象的轉換詳解

    xml與Java對象的轉換詳解

    這篇文章主要介紹了xml與Java對象的轉換詳解的相關資料,需要的朋友可以參考下...

    Java教程網2942020-09-17
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關于小米推送Java代碼,小編覺得內容挺不錯的,現在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩中求8032021-07-12
主站蜘蛛池模板: 男同精品视频免费观看网站 | 亚洲国产精品无码中文字满 | 男神插曲女生动漫完整版动漫 | 91嫩草私人成人亚洲影院 | 欧美一级专区免费大片俄罗斯 | 爽好舒服快想要免费看 | 成人男女啪啪免费观看网站 | 亚洲视频在线免费看 | 日本无卡码一区二区三区 | ova巨公主催眠1在线观看 | 99r视频| 暖暖高清日本在线 | 成人资源影音先锋久久资源网 | 日本春菜花在线中文字幕 | 午夜精品久久久久久 | 极品丝袜老师h系列全文阅读 | 国产一成人精品福利网站 | 久久99r66热这里只有精品 | 99热这里只精品99re66 | 法国老妇性xx在线播放 | 肠交女王magnet | 韩国最新三级网站在线播放 | 99er在线视频| sao虎在线精品永久在线 | 免费福利资源站在线视频 | 青青草原国产视频 | 国产成人综合网 | 顶级尤物极品女神福利视频 | 日本一区三区 | 小柔的性放荡羞辱日记 | bbbxxx乱大交欧美小说 | 91视频国产在线 | 91进入蜜桃臀在线播放 | 国产午夜亚洲精品理论片不卡 | 2019理论韩国理论中文 | 免费被黄网站在观看 | 美女脱一光二净的视频 | 欧美日韩综合一区 | 欧美日韩中文字幕一区二区高清 | 欧美又大又粗又爽视频 | 精品播放|