在Firefox代理上没有触发Java HTTPHandler CONNECT方法请求

时间:2018-02-13 00:15:42

标签: java http http-proxy

问题:

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

  

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

解释和难度:

  

注意:我将此包含在内,以便详细解释我认为的问题。

我最近一直试图在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()),根据{{ 3}}必须以' /'开头。但是因为“权威形式”和“#39;不包含' /'在开始时句柄永远不会被触发

  

注意:由于Oracle的代码已关闭,因此仍有一些猜测   来源,但它通过发送相同的TCP请求确认   服务器上面有相同的CONNECT请求,但有一次带有   领先' /' (所以' /www.google.com'而不是' www.google.com')。同   领先的' /'处理程序被触发了。

所有这些都说明了我的问题,

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

  

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

2 个答案:

答案 0 :(得分: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,如下面的屏幕截图所示

Context path 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中会有很大的变化

答案 1 :(得分:1)

值得一提的是 - 让我们称之为手指练习 - 这是一个小小的黑客,它可以让你的CONNECT调用触发你的处理程序。

1。从OpenJDK

修改类ContextList

将JDK 8中的类ContextList复制(或查找9)到新项目中。原始文件为here

现在通过插入标记为“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。对您自己的项目使用修改

更改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