【问题标题】:Java - Replace host in url?Java - 替换网址中的主机?
【发布时间】:2018-03-21 21:30:52
【问题描述】:

在 java 中,我想用新的 Host 替换 url 的 Host 部分,其中主机和 url 都作为字符串提供。

这应该考虑到主机中可能有一个端口,如defined in the RFC

例如,给定以下输入

我应该从正确执行此操作的函数中获得以下输出

有谁知道任何库或例程可以正确替换网址中的Host

编辑:对于我的用例,我希望我的主机替换与 java servlet 的响应相匹配。我通过运行本地 java web 服务器尝试了这一点,然后使用curl -H 'Host:superduper.com:80' 'http://localhost:8000/testurl' 对其进行了测试,并让该端点简单地返回来自request.getRequestURL().toString() 的url,其中请求是HttpServletRequest。它返回了http://superduper.com/testurl,所以它删除了http的默认端口,这也是我努力的目标。

【问题讨论】:

    标签: java


    【解决方案1】:

    Spring 框架提供UriComponentsBuilder。你可以这样使用它:

    import org.springframework.web.util.UriComponentsBuilder;
    
    String initialUri = "http://localhost/me/out?it=5";
    UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(initialUri);
    String modifiedUri = builder.host("myserver").port("20000").toUriString();
    System.out.println(modifiedUri);
    // ==> http://myserver:20000/me/out?it=5
    

    这里您需要在单独的调用中提供主机名和端口以获得正确的编码。

    【讨论】:

      【解决方案2】:

      你使用 java.net.URI 是对的。主机和端口(以及用户/密码,如果存在)统称为 URI 的 authority 组件:

      public static String replaceHostInUrl(String originalURL,
                                            String newAuthority)
      throws URISyntaxException {
      
          URI uri = new URI(originalURL);
          uri = new URI(uri.getScheme().toLowerCase(Locale.US), newAuthority,
              uri.getPath(), uri.getQuery(), uri.getFragment());
      
          return uri.toString();
      }
      

      (URI 的方案是required to be lowercase,所以虽然可以说上面的代码没有完美地保留所有原始 URL 的非授权部分,但大写方案从一开始就不是真正合法的。当然, ,它不会影响 URL 连接的功能。)

      请注意,您的某些测试有误。例如:

      assertEquals("https://super/me/out?it=5", replaceHostInUrl("https://www.test.com:4300/me/out?it=5","super:443")); 
      assertEquals("http://super/me/out?it=5", replaceHostInUrl("http://www.test.com:4300/me/out?it=5","super:80")); 
      

      虽然https://super/me/out?it=5 在功能上与https://super:443/me/out?it=5 相同(因为 https 的默认端口是 443),但如果您在 URI 中指定显式端口,则 URI 在其权限中指定了端口,这就是它应该如何留下来。

      更新:

      如果你想去除一个明确但不必要的端口号,你可以使用 URL.getDefaultPort() 来检查它:

      public static String replaceHostInUrl(String originalURL,
                                            String newAuthority)
      throws URISyntaxException,
             MalformedURLException {
      
          URI uri = new URI(originalURL);
          uri = new URI(uri.getScheme().toLowerCase(Locale.US), newAuthority,
              uri.getPath(), uri.getQuery(), uri.getFragment());
      
          int port = uri.getPort();
          if (port > 0 && port == uri.toURL().getDefaultPort()) {
              uri = new URI(uri.getScheme(), uri.getUserInfo(),
                  uri.getHost(), -1, uri.getPath(),
                  uri.getQuery(), uri.getFragment());
          }
      
          return uri.toString();
      }
      

      【讨论】:

      • 嗯...有趣...我将不得不审查这个...谢谢!还有一个repl of your solution,按照你的建议调整了测试。
      • 用去除默认端口号的代码更新了答案。
      • 谢谢...这是一个updated repl,表明它也适用于原始测试用例!
      • 如果uri.getQuery()包含转义字符,编码信息会丢失
      • @machinarium 确实如此,但对于大多数用途来说可能没问题。据我所知,它似乎只改变了不需要编码的字符。 %20 在往返过程中幸存下来。但是 %33 会变成 3。
      【解决方案3】:

      我很快尝试使用java.net.URIjavax.ws.rs.core.UriBuilderorg.apache.http.client.utils.URIBuilder,但似乎没有一个人知道主机头可能包括一个端口,所以他们都需要一些额外的逻辑。使其正确发生,而端口有时不会被“加倍”,而在其他时候也不会被正确替换。

      由于java.net.URL 不需要任何额外的库,所以我使用了它。我确实知道,如果我在某处使用URL.equals,这可能是个问题,因为它可能会进行 DNS 查找,但我不是,所以我认为这很好,因为这涵盖了我的用例,如伪单元测试所示.

      我整理了这个方法,你可以test it out online here at repl.it

      import java.net.URL;
      import java.net.MalformedURLException;
      
      class Main 
      {
        public static void main(String[] args) 
        {
          testReplaceHostInUrl();
        }
      
        public static void testReplaceHostInUrl()
        {
          assertEquals("http://myserver:20000/me/out?it=5", replaceHostInUrl("http://localhost/me/out?it=5","myserver:20000")); 
          assertEquals("http://myserver:20000/me/out?it=5", replaceHostInUrl("http://localhost:19000/me/out?it=5","myserver:20000")); 
          assertEquals("http://super/me/out?it=5", replaceHostInUrl("http://localhost:19000/me/out?it=5","super")); 
          assertEquals("http://super/me/out?it=5", replaceHostInUrl("http://www.test.com/me/out?it=5","super")); 
          assertEquals("https://myserver:20000/me/out?it=5", replaceHostInUrl("https://localhost/me/out?it=5","myserver:20000")); 
          assertEquals("https://myserver:20000/me/out?it=5", replaceHostInUrl("https://localhost:19000/me/out?it=5","myserver:20000")); 
          assertEquals("https://super/me/out?it=5", replaceHostInUrl("https://www.test.com/me/out?it=5","super")); 
          assertEquals("https://super/me/out?it=5", replaceHostInUrl("https://www.test.com:4300/me/out?it=5","super")); 
          assertEquals("https://super/me/out?it=5", replaceHostInUrl("https://www.test.com:4300/me/out?it=5","super:443")); 
          assertEquals("http://super/me/out?it=5", replaceHostInUrl("http://www.test.com:4300/me/out?it=5","super:80")); 
          assertEquals("http://super:8080/me/out?it=5", replaceHostInUrl("http://www.test.com:80/me/out?it=5","super:8080")); 
          assertEquals("http://super/me/out?it=5&test=5", replaceHostInUrl("http://www.test.com:80/me/out?it=5&test=5","super:80")); 
          assertEquals("https://super:80/me/out?it=5&test=5", replaceHostInUrl("https://www.test.com:80/me/out?it=5&test=5","super:80")); 
          assertEquals("https://super/me/out?it=5&test=5", replaceHostInUrl("https://www.test.com:80/me/out?it=5&test=5","super:443")); 
          assertEquals("http://super:443/me/out?it=5&test=5", replaceHostInUrl("http://www.test.com:443/me/out?it=5&test=5","super:443")); 
          assertEquals("http://super:443/me/out?it=5&test=5", replaceHostInUrl("HTTP://www.test.com:443/me/out?it=5&test=5","super:443")); 
          assertEquals("http://SUPERDUPER:443/ME/OUT?IT=5&TEST=5", replaceHostInUrl("HTTP://WWW.TEST.COM:443/ME/OUT?IT=5&TEST=5","SUPERDUPER:443")); 
          assertEquals("https://SUPERDUPER:23/ME/OUT?IT=5&TEST=5", replaceHostInUrl("HTTPS://WWW.TEST.COM:22/ME/OUT?IT=5&TEST=5","SUPERDUPER:23")); 
          assertEquals(null, replaceHostInUrl(null, null));
        }
      
        public static String replaceHostInUrl(String url, String newHost)
        {
          if (url == null || newHost == null)
          {
            return url;
          }
      
          try
          {
            URL originalURL = new URL(url);
      
            boolean hostHasPort = newHost.indexOf(":") != -1;
            int newPort = originalURL.getPort();
            if (hostHasPort)
            {
              URL hostURL = new URL("http://" + newHost);
              newHost = hostURL.getHost();
              newPort = hostURL.getPort();
            }
            else
            {
              newPort = -1;
            }
      
            // Use implicit port if it's a default port
            boolean isHttps = originalURL.getProtocol().equals("https");
            boolean useDefaultPort = (newPort == 443 && isHttps) || (newPort == 80 && !isHttps);
            newPort = useDefaultPort ? -1 : newPort;
      
            URL newURL = new URL(originalURL.getProtocol(), newHost, newPort, originalURL.getFile());
            String result = newURL.toString();
      
            return result;
          }
          catch (MalformedURLException e)
          {
            throw new RuntimeException("Couldnt replace host in url, originalUrl=" + url + ", newHost=" + newHost);
          }
        }
      
        public static void assertEquals(String expected, String actual)
        {
          if (expected == null && actual == null)
          {
            System.out.println("TEST PASSED, expected:" + expected + ", actual:" + actual);
            return;
          }
      
          if (! expected.equals(actual))
            throw new RuntimeException("Not equal! expected:" + expected + ", actual:" + actual);
      
          System.out.println("TEST PASSED, expected:" + expected + ", actual:" + actual);
        }
      }
      

      【讨论】:

      • 让我印象深刻的是答案和解决方案是在准确同一时间发布的:)
      • @pruntlar 鼓励自己创建一个问题来直接回答它,因为它可以帮助其他有类似问题的人 (SO: self-answer)。
      • 是的,如果我搜索了一些东西,没有找到答案,并且想在某个地方记录它以防我再次需要它,我倾向于这样做。它由 StackOverflow 直接支持,以帮助分享知识和促进讨论。我这样做的原因是因为那里可能有比我更好的答案,如果有,我会改用它们,但目前,这适用于我的用例。谢谢!
      • 我会改进这个问题,我觉得它有点短,如果你没有发布答案,我会倾向于问你尝试了什么,甚至可能关闭它。我想到的一件事是使用正则表达式来替换主机。这显然有缺点/陷阱,但您可以在问题中指出这些要求。
      • @WilliMentzel - 谢谢,但我认为他们需要在那里,因为它有助于验证边缘情况,并使其他人也可以轻松地在在线 java repl 中测试/比较他们的解决方案。跨度>
      【解决方案4】:

      我意识到这是一个很老的问题;但是发布一个更简单的解决方案以防其他人需要它。

      String newUrl = new URIBuilder(URI.create(originalURL)).setHost(newHost).build().toString();
      

      【讨论】:

      • 当你依赖外部依赖做某事的时候,提哪一个比较合适。我猜你在用httpcomponents:httpclientURIBuilder?!
      【解决方案5】:

      我在 RawHTTP 库中添加了一个方法来执行此操作,因此您可以简单地执行此操作:

      URI uri = RawHttp.replaceHost(oldUri, "new-host");
      

      在此提交中添加:https://github.com/renatoathaydes/rawhttp/commit/cbe439f2511f7afcb89b5a0338ed9348517b9163#diff-ff0fec3bc023897ae857b07cc3522366

      欢迎反馈,很快就会发布。

      【讨论】:

        【解决方案6】:

        或者使用一些正则表达式魔法:

        public static String replaceHostInUrl(String url, String newHost) {
            if (url == null || newHost == null) {
                return null;
            }
            String s = url.replaceFirst("(?i)(?<=(https?)://)(www.)?\\w*(.com)?(:\\d*)?", newHost);
            if (s.contains("http://")) {
                s = s.replaceFirst(":80(?=/)", "");
            } else if (s.contains("https://")) {
                s = s.replaceFirst(":443(?=/)", "");
            }
            Matcher m = Pattern.compile("HTTPS?").matcher(s);
            if (m.find()) {
                s = s.replaceFirst(m.group(), m.group().toLowerCase());
            }
            return s;
        }
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-08-27
        • 1970-01-01
        • 1970-01-01
        • 2019-11-17
        相关资源
        最近更新 更多