问题:
有没有办法用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没有(并且在我看来,有一些代码质量问题)。
答案 0 :(得分:1)
您可以在以下链接中查看源代码
https://github.com/akashche/java-1.8.0-openjdk-1.8.0.151-2.b12.fc28.ppc64le
以下是这里播放的几个文件
当http服务器尝试获取CONNECT
请求的路径
ctx = contexts.findContext (protocol, uri.getPath());
uri
www.google.com:443
和uri.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中会有很大的变化
答案 1 :(得分:1)
值得一提的是 - 让我们称之为手指练习 - 这是一个小小的黑客,它可以让你的CONNECT调用触发你的处理程序。
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
。
更改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();
}
}
}
通过JVM参数将JAR添加到引导类路径(例如,在使用此修改类的项目的IDE运行配置中),以便让JRE在原始类之前找到修改后的类:
java -Xbootclasspath/p:/path/to/my.jar ...
$ 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
。