【问题标题】:Java HTTPHandler not triggered on Firefox proxy CONNECT method requestFirefox 代理 CONNECT 方法请求未触发 Java HTTPHandler
【发布时间】:2018-02-13 00:15:42
【问题描述】:

问题:

有没有办法通过 Oracle HTTPServer class 支持 CONNECT HTTP 请求?

注意:如果不想处理这个,Jetty 好像没有 他们的 HTTPServer 类的这个问题(即使不使用他们的 ProxyHTTP 实现ConnectHandler)。然而灰熊似乎 与默认 Java 实现有相同的问题,而不是 令人惊讶,因为它们都是由 Oracle 制造的。

解释和难度:

注意:我包含此内容是为了详细解释我认为是什么问题。

我最近一直在尝试为 Firefox 上的安全 (HTTPS) 连接创建 HTTP 代理。完成此操作的方式是CONNECT method (IETF RFC 2817)。来自 Firefox 的 CONNECT 请求示例如下(在请求 Google 主页时发生)。

CONNECT www.google.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:58.0) Gecko/20100101 Firefox/58.0
Proxy-Connection: keep-alive
Connection: keep-alive
Host: www.google.com:443

这与普通的 GET 请求不同。

GET /index HTTP/1.1
Host: 127.0.0.1:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:58.0) Gecko/20100101 Firefox/58.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1

显着的区别是所谓的“请求目标”(有关详细信息,请参阅此Mozilla article)。具体来说,正常的 GET 请求是“绝对路径”,而 CONNECT 请求目标是“权限形式”。

使用StackOverflow answer (simple Java HTTPServer implementation)中的演示服务器,我们可以看到注册HTTPHandler的方式是server.createContext("/test", new MyHandler()),根据Oracle documentation必须以'/开头'。然而,因为“授权形式”在开始时不包含“/”,所以永远不会触发句柄

注意:这仍然是一些猜测,因为 Oracle 的代码已关闭 源,但它通过发送相同的 TCP 请求到 服务器具有与上述相同的 CONNECT 请求,但有一次使用 前导“/”(所以“/www.google.com”而不是“www.google.com”)。和 触发处理程序的前导“/”。

说了这么多,我的问题是这样的,

有没有办法解决这个问题,同时仍然使用 Java 的 Oracle 版本中包含的库?

注意:除了从头开始编写一个新的 HTTP 服务器,或者使用 另一个图书馆。在我的测试中,Jetty 确实触发了 HTTPHandler。 Grizzly 没有(而且在我看来,存在一些代码质量问题)。

【问题讨论】:

  • JDK 的 HttpServer,虽然不是官方支持的 API 的一部分,但不是封闭源代码,请参阅 herethere,也许探索源代码可以帮助您的努力。
  • @HuguesM。谢谢,我确实认为它是封闭源代码。我的错。

标签: java http http-proxy


【解决方案1】:

您可以在以下链接查看源代码

https://github.com/akashche/java-1.8.0-openjdk-1.8.0.151-2.b12.fc28.ppc64le

所以下面是这里播放的几个文件

  • /sun/net/httpserver/ServerImpl.java
  • /sun/net/httpserver/ContextList.java

当 http 服务器尝试获取 CONNECT 请求的路径时

ctx = contexts.findContext (protocol, uri.getPath());

它有uri 作为www.google.com:443uri.getPath() 返回null 如下截图所示

ContextList 类有以下上下文方法

public synchronized HttpContextImpl createContext (String path, HttpHandler handler) {
    if (handler == null || path == null) {
        throw new NullPointerException ("null handler, or path parameter");
    }
    HttpContextImpl context = new HttpContextImpl (protocol, path, handler, this);
    contexts.add (context);
    logger.config ("context created: " + path);
    return context;
}

public synchronized HttpContextImpl createContext (String path) {
    if (path == null) {
        throw new NullPointerException ("null path parameter");
    }
    HttpContextImpl context = new HttpContextImpl (protocol, path, null, this);
    contexts.add (context);
    logger.config ("context created: " + path);
    return context;
}

两者都检查路径 null 并且不允许您设置具有空路径的上下文。这意味着我们永远无法让上下文在这种情况下工作。要使CONNECT 请求正常工作,我们需要能够注册一个处理程序,其路径为null 或某种默认的捕获所有处理程序。正如我检查过的,当前代码不支持此功能。而且所有这些类都没有标记为公共的,所以你不能从这些继承并让它工作。

但既然你有源代码,如果你真的想让它工作,你必须复制这两个文件并做一些修改才能工作。

如此简短的回答是,使用 Oracle 的原始 com.sun.net.httpserver.HttpServer 是不可能做到这一点的。我检查了 JDK 1.8 而不是 1.9,但我怀疑 1.9 中这些类会有很大变化

【讨论】:

  • 不错的分析。 :-) @Zimm3r:我也许可以帮助您修复空路径并通过 AspectJ 将其他内容放在那里,但您似乎不希望在您的类路径中添加任何第三方库。在这种情况下,您将需要 AspectJ 编织器。告诉我你是否有兴趣。但对你来说,只为你的服务器使用 3rd 方库可能会更容易。
  • @kriegaex 不,我只是希望没有 3rd 方库,但如果我要去写 3rd 方库,我还不如使用另一个 HTTP 库。我想到的其他选项是像这样修补私有变量上下文列表stackoverflow.com/questions/32716952/…
  • 另一种选择是复制ContextList 的OpenJDK 源代码(包括包名!)并更改findContext 以便返回一个注册的上下文,其名称为“/SOCKS”,如果路径为空。这样处理程序就会被调用并且可以做任何你喜欢的事情。 但是 丑陋的是,为了覆盖 JRE 类,您需要通过 -Xbootclasspath/p:/my/path 将包含被操作类的类目录或 JAR 附加到 Java 引导类路径中。它工作得很好,但在某种程度上比使用 3rd 方库更糟糕。如果你想知道这个问题的答案,请告诉我。
  • @kriegaex 是的,这些越来越难看。并不是说如果需要就不好,但走第三方路线或编写基本的 http 服务器可能更容易。但如果这些不可用,当然是不错的选择。
【解决方案2】:

对于它的价值 - 让我们称之为手指练习 - 这是一个小技巧,可以让您触发处理程序以进行 CONNECT 调用。

1。从 OpenJDK 修改类ContextList

从 JDK 8(或查找 9)复制 classContextList 到一个新项目中。原始文件是here

现在通过插入这个标记为“snip/snap”的小代码sn-p来修改类:

synchronized HttpContextImpl findContext (String protocol, String path, boolean exact) {
  protocol = protocol.toLowerCase();
  String longest = "";
  HttpContextImpl lc = null;
  for (HttpContextImpl ctx: list) {
    // --- snip -----------------------------
    if (path == null) {
      if (ctx.getPath().equals("/_CONNECT_")) {
        System.out.println("Null path detected, using CONNECT handler");
        return ctx;
      }
      continue;
    }
    // --- snap -----------------------------
    if (!ctx.getProtocol().equals(protocol)) {
      continue;
    }
    // (...)
  return lc;
}

我添加了日志输出,以便在 URL 路径为空的情况下更容易确定是否执行新代码。如果不需要,可以将其删除。

将项目编译成 JAR,例如 my.jar

2。为您自己的项目使用修改

更改 this answer 的演示类,以便注册一个新的虚拟上下文 /_CONNECT_(或您在 JDK 修改中调用的任何名称),用于 CONNECT 调用。

package de.scrum_master.app;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class Application {

  public static void main(String[] args) throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
    server.createContext("/test", new MyHandler());
    server.createContext("/_CONNECT_", new MyHandler());
    server.setExecutor(null); // creates a default executor
    server.start();
  }

  static class MyHandler implements HttpHandler {
    @Override
    public void handle(HttpExchange t) throws IOException {
      String response = "This is the response";
      t.sendResponseHeaders(200, response.length());
      OutputStream os = t.getResponseBody();
      os.write(response.getBytes());
      os.close();
    }
  }

}

3。在引导类路径上使用修改后的类启动 JVM

通过 JVM 参数将 JAR 添加到引导类路径(例如,在使用此修改后的类的项目的 IDE 运行配置中),以便让 JRE 在原始类之前找到修改后的类:

java -Xbootclasspath/p:/path/to/my.jar ...

4。测试你的服务器

$ curl http://localhost:8000/test
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    20  100    20    0     0     20      0  0:00:01 --:--:--  0:00:01   645This is the response

$ curl -p -x http://localhost:8000 https://scrum-master.de
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
curl: (35) error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol

您可以看到/test 上下文和/_CONNECT_ 上下文都有效。当然,后者没有做正确的事情,但这取决于你。目标是触发它,正如您在服务器控制台上看到的那样,它应该为第二个请求打印 Null path detected, using CONNECT handler

【讨论】:

  • 这是一种方法。我从来都不喜欢私有变量的原因之一。 java有这个功能还是很神奇的。
  • 我不明白那个评论。我没有使用私有变量。但也许您与要破解的 JDK 代码中的私有变量有关。问题不是这里的私有变量 - 没有它们会是糟糕的 IMO 并杀死类封装 - 但只是你不能以你需要的方式注入依赖项(例如你自己的 HttpContext 实现),因为这些类不是设计成的以这种方式扩展或覆盖。
  • 是的,我指的是 HttpContext,专门修补了它所比较的​​路径。
  • 那么您对我的解决方案有何经验?它是否可以帮助您实现代理服务器?您是否使用它并取得了进展,或者您是否已经决定使用其他工具?我在我的小练习上花了很长时间,因为我很好奇,所以我很想知道结果。
  • 它确实有效,但是我决定使用套接字和线程从头开始实现我自己的 http 服务器,但有很多限制,主要是我希望它尽可能容易运行,并加载一个新的类路径是困难的。此外,OpenJDK 代码是 GPL 的,因此存在许可证问题。
猜你喜欢
  • 1970-01-01
  • 2016-10-28
  • 1970-01-01
  • 1970-01-01
  • 2011-06-18
  • 2011-03-25
  • 2018-10-18
  • 2015-02-25
  • 2011-09-29
相关资源
最近更新 更多