Java Firefox 代理 CONNECT 方法请求未触发 HTTPHandler

Java HTTPHandler not triggered on Firefox proxy CONNECT method request

问题:

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

Note: If you do not want to deal with this, Jetty seems to not have this problem with their HTTPServer class (even when not using their ProxyHTTP implementation ConnectHandler). Grizzly however seemed to have the same issue as the default Java implementation, not surprising since they were both made by Oracle.

解释与难点:

Note: I include this to provided a detailed explanation of what I believe to be the issue.

我最近一直在尝试为 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

显着的区别是所谓的 'request target'(有关详细信息,请参阅此 Mozilla article)。具体来说,正常的 GET 请求是 'absolute path',而 CONNECT 请求目标是 'authority form'。

在此 we can see the way to register a HTTPHandler is server.createContext("/test", new MyHandler()), which according to the Oracle documentation 中使用演示服务器必须以“/”开头。但是,因为 'authority form' 开头不包含“/”,所以永远不会触发句柄

Note: This is still somewhat speculation as Oracle's code is closed source, but it was confirmed by sending the same TCP request to the server with the same CONNECT request above, but one time with a leading '/' (so '/www.google.com' instead of 'www.google.com'). With the leading '/' the handler was triggered.

综上所述,我的问题是,

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

Note: Other than writing an new HTTP Server from scratch, or using another library. In my testing Jetty did trigger the HTTPHandler. Grizzly did not (and, in my opinion, has some code quality issues).

您可以在下面查看源代码link

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() returns null 如下图所示

ContextList class 具有以下上下文方法

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 的处理程序或某种默认的 catch all 处理程序。正如我检查过的,当前代码不支持此功能。所有这些 class 都没有标记 public,所以你不能从这些继承并让它工作。

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

如此简短的回答是,使用 Oracle 的原始 com.sun.net.httpserver.HttpServer 无法做到这一点。我检查了 JDK 1.8 而不是 1.9,但我怀疑这些 classes 在 1.9

中会有很大变化

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

1。从 OpenJDK

修改 class ContextList

复制classContextList from JDK 8 (or look for 9) into a new project. The raw file is here.

现在修改 class,插入这个由 "snip/snap" 标记的小代码片段:

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。为自己的项目使用修改

将演示 class 更改为 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。在启动 classpath

时使用修改后的 class 启动 JVM

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

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