我试图创建一个Netty(4.1)POC,它可以将h2c(没有TLS的HTTP2)帧转发到h2c服务器上——即本质上创建一个Netty h2c代理服务。Wireshark显示Netty发送帧,h2c服务器应答(例如响应头和数据),尽管我在Netty内部接收/处理响应HTTP帧时遇到了一些问题。
作为一个起点,我已经调整了多路复用。服务器示例(io.netty.example.http2.helloworld.multiplex.server
)以便在HelloWorldHttp2Handler
中,我连接到远程节点,而不是使用伪消息进行响应:
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel remoteChannel = null;
// create or retrieve the remote channel (one to one mapping) associated with this incoming (client) channel
synchronized (lock) {
if (!ctx.channel().hasAttr(remoteChannelKey)) {
remoteChannel = this.connectToRemoteBlocking(ctx.channel());
ctx.channel().attr(remoteChannelKey).set(remoteChannel);
} else {
remoteChannel = ctx.channel().attr(remoteChannelKey).get();
}
}
if (msg instanceof Http2HeadersFrame) {
onHeadersRead(remoteChannel, (Http2HeadersFrame) msg);
} else if (msg instanceof Http2DataFrame) {
final Http2DataFrame data = (Http2DataFrame) msg;
onDataRead(remoteChannel, (Http2DataFrame) msg);
send(ctx.channel(), new DefaultHttp2WindowUpdateFrame(data.initialFlowControlledBytes()).stream(data.stream()));
} else {
super.channelRead(ctx, msg);
}
}
private void send(Channel remoteChannel, Http2Frame frame) {
remoteChannel.writeAndFlush(frame).addListener(new GenericFutureListener() {
@Override
public void operationComplete(Future future) throws Exception {
if (!future.isSuccess()) {
future.cause().printStackTrace();
}
}
});
}
/**
* If receive a frame with end-of-stream set, send a pre-canned response.
*/
private void onDataRead(Channel remoteChannel, Http2DataFrame data) throws Exception {
if (data.isEndStream()) {
send(remoteChannel, data);
} else {
// We do not send back the response to the remote-peer, so we need to release it.
data.release();
}
}
/**
* If receive a frame with end-of-stream set, send a pre-canned response.
*/
private void onHeadersRead(Channel remoteChannel, Http2HeadersFrame headers)
throws Exception {
if (headers.isEndStream()) {
send(remoteChannel, headers);
}
}
private Channel connectToRemoteBlocking(Channel clientChannel) {
try {
Bootstrap b = new Bootstrap();
b.group(new NioEventLoopGroup());
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.remoteAddress("localhost", H2C_SERVER_PORT);
b.handler(new Http2ClientInitializer());
final Channel channel = b.connect().syncUninterruptibly().channel();
channel.config().setAutoRead(true);
channel.attr(clientChannelKey).set(clientChannel);
return channel;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
初始化通道管道时(在http2clientializer
中),如果执行以下操作:
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(Http2MultiplexCodecBuilder.forClient(new Http2OutboundClientHandler()).frameLogger(TESTLOGGER).build());
ch.pipeline().addLast(new UserEventLogger());
}
然后我可以看到帧被正确地转发在Wireshark和h2c服务器回复与头和帧数据,但Netty回复与GOAway[INTERNAL_ERROR]由于:
14:23:09.324[nioEventLoopGroup-3-1]警告i.n.通道。DefaultChannelPipeline-触发了exceptionCaught()事件,该事件到达管道的尾部。这通常意味着管道中的最后一个处理程序没有处理异常。JAVAlang.IllegalStateException:io上标识符1所需的流对象。内蒂。处理程序。编解码器。http2。Http2FrameCodec$FrameListener。io上的requireStream(Http2FrameCodec.java:587)。内蒂。处理程序。编解码器。http2。Http2FrameCodec$FrameListener。io上的OnHeaderRead(Http2FrameCodec.java:550)。内蒂。处理程序。编解码器。http2。Http2FrameCodec$FrameListener。OnHeaderRead(Http2FrameCodec.java:543)。。。
如果我尝试使用http2客户端示例中的管道配置,例如:
@Override
public void initChannel(SocketChannel ch) throws Exception {
final Http2Connection connection = new DefaultHttp2Connection(false);
ch.pipeline().addLast(
new Http2ConnectionHandlerBuilder()
.connection(connection)
.frameLogger(TESTLOGGER)
.frameListener(new DelegatingDecompressorFrameListener(connection, new InboundHttp2ToHttpAdapterBuilder(connection)
.maxContentLength(maxContentLength)
.propagateSettings(true)
.build() ))
.build());
}
然后我得到:
JAVAlang.UnsupportedOperationException:不支持的消息类型:io处的DefaultHttp2HeaderFrame(应为ByteBuf,FileRegion)。内蒂。频道尼奥。抽象技术通道。io上的filterOutboundMessage(AbstractNioByteChannel.java:283)。内蒂。频道AbstractChannel$AbstractSafe。在io上编写(AbstractChannel.java:882)。内蒂。频道DefaultChannelPipeline$HeadContext。写入(DefaultChannelPipeline.java:1365)
如果我然后添加一个HTTP2帧编解码器(Http2MultiplexCodec
或Http2FrameCodec
):
@Override
public void initChannel(SocketChannel ch) throws Exception {
final Http2Connection connection = new DefaultHttp2Connection(false);
ch.pipeline().addLast(
new Http2ConnectionHandlerBuilder()
.connection(connection)
.frameLogger(TESTLOGGER)
.frameListener(new DelegatingDecompressorFrameListener(connection, new InboundHttp2ToHttpAdapterBuilder(connection)
.maxContentLength(maxContentLength)
.propagateSettings(true)
.build() ))
.build());
ch.pipeline().addLast(Http2MultiplexCodecBuilder.forClient(new Http2OutboundClientHandler()).frameLogger(TESTLOGGER).build());
}
然后Netty发送两个连接前言帧,导致h2c服务器拒绝GOAway[PROTOCOL_ERROR]:
因此,这就是我遇到的问题所在-即,配置远程通道管道,使其能够无误地发送Http2Frame
对象,但在收到响应后,也会在Netty内接收/处理它们。
有人有什么想法/建议吗?
我最终得到了这个工作;下面的Github问题包含一些有用的代码/信息:
我需要进一步研究一些注意事项,尽管该方法的要点是您需要将通道包装在一个Http2StreamChannel中,这意味着我的connectToRemoteBlocking()
方法的结尾是:
private Http2StreamChannel connectToRemoteBlocking(Channel clientChannel) {
try {
Bootstrap b = new Bootstrap();
b.group(new NioEventLoopGroup()); // TODO reuse existing event loop
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.remoteAddress("localhost", H2C_SERVER_PORT);
b.handler(new Http2ClientInitializer());
final Channel channel = b.connect().syncUninterruptibly().channel();
channel.config().setAutoRead(true);
channel.attr(clientChannelKey).set(clientChannel);
// TODO make more robust, see example at https://github.com/netty/netty/issues/8692
final Http2StreamChannelBootstrap bs = new Http2StreamChannelBootstrap(channel);
final Http2StreamChannel http2Stream = bs.open().syncUninterruptibly().get();
http2Stream.attr(clientChannelKey).set(clientChannel);
http2Stream.pipeline().addLast(new Http2OutboundClientHandler()); // will read: DefaultHttp2HeadersFrame, DefaultHttp2DataFrame
return http2Stream;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
然后,为了防止“标识符所需的流对象:1”错误(本质上是说:'此(客户端)HTTP2请求是新的,那么为什么我们有此特定流?'——因为我们隐式地重用了来自最初收到的“服务器”请求的流对象),在以下位置转发数据时,我们需要更改为使用远程通道的流:
private void onHeadersRead(Http2StreamChannel remoteChannel, Http2HeadersFrame headers) throws Exception {
if (headers.isEndStream()) {
headers.stream(remoteChannel.stream());
send(remoteChannel, headers);
}
}
然后,配置的通道入站处理程序(由于其使用,我称之为Http2OutboundClientHandler
)将以正常方式接收传入的HTTP2帧:
@Sharable
public class Http2OutboundClientHandler extends SimpleChannelInboundHandler<Http2Frame> {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
cause.printStackTrace();
ctx.close();
}
@Override
public void channelRead0(ChannelHandlerContext ctx, Http2Frame msg) throws Exception {
System.out.println("Http2OutboundClientHandler Http2Frame Type: " + msg.getClass().toString());
}
}
我有一个小Clojure应用程序,它使用http工具包向服务器发送一些http post请求。我希望通过代理P路由https POST请求,即,我希望流量像应用程序一样- (这是因为目标主机X基于IP限制访问) 这可能吗? 此外,该应用程序运行在ubuntu服务器上,是否有可能通过系统级配置使http工具包使用代理服务器?但我更喜欢其他过程不受影响。
在 Laravel 中,大多数采用 .env 文件管理,深度配置都采用 config/*.php 配置。 在 ThinkSNS+ 这一现象并没有发生改变,我们也推崇使用 Laravel 方式进行管理配置,但是有时候我们往往需要后期配置, 但是后期配置不可能期望用户去修改配置文件。这很危险,用户大多都没有技术能力。 所以,在 ThinkSNS+ 在 Laravel 配置的基础上增加了一份更加灵活的配
Swoole框架提供了配置文件管理的功能。配置可以直接用数组的方式进行访问,底层会自动加载配置文件目录下的$key.php文件。配置文件必须在末尾return $array返回配置的信息。 使用方法 //在控制器中 $this->config['user']; //其他位置 Swoole::$php->config['user']; 设置配置文件路径 Swoole\Config::setPath
San CLI 的配置文件为san.config.js,该文件放在项目的根目录下,当执行 San CLI 命令时,San CLI 会自动读取san.config.js的内容,在项目创建后,首先需要修改san.config.js来配置文件打包等选项。San CLI UI 将配置的每一项转换为表单,同时将配置项的语义直观的展示在页面,无需查找文档,直接在San CLI UI中修改表单,保存后即可看到s
local settings 作为一个开源项目,我们在这方面做得并不是特别好——当然是有意如此的。不过,这里我们还是做一些简单的介绍。对于我们的项目来说,我们需要一些额外的配置,如我们的数据库中的DATABASES、DEFAULT_AUTHENTICATION_CLASSES、CORS_ORIGIN_ALLOW_ALL、SECRET_KEY应该在不同的环境中都有不同的配置。 我们可以一个创建loc
基本上,我需要配置CI/CD与比特桶源代码到ECS容器。我想使用CodePipline部署新的ECR映像到ECS。 目前,AWS CodePipline中没有将bitbucket指定为源的选项。然而,我已经设法用webhooks配置了CodeBuild,这样它就可以构建docker文件,并在每次推送发布分支时将其推送到ECR。 我想将ECR配置为CodePipline中的“源”阶段,并将其部署到现