如何将Netty的通道池映射用作Jax RS客户端的ConnectorProvider

时间:2018-07-17 08:57:03

标签: jersey jax-rs netty

我浪费了数小时来尝试使用netty的频道池映射和jax rs客户端来解决问题。

我以球衣本身的netty连接器为灵感,但将netty的频道与netty的频道池地图交换了。

https://jersey.github.io/apidocs/2.27/jersey/org/glassfish/jersey/netty/connector/NettyConnectorProvider.html

我的问题是在自定义SimpleChannelInboundHandler中需要引用。但是,根据netty创建通道池映射的方式的设计,我无法通过我的自定义ChannelPoolHandler传递引用,因为一旦池映射创建了池,通道池处理程序的构造函数就不会再运行。

这是它获取池并检出通道以发出HTTP请求的方法。

    @Override
public Future<?> apply(ClientRequest request, AsyncConnectorCallback callback) {
    final CompletableFuture<Object> completableFuture = new CompletableFuture<>();

    try{
        HttpRequest httpRequest = buildHttpRequest(request);

        // guard against prematurely closed channel
        final GenericFutureListener<io.netty.util.concurrent.Future<? super Void>> closeListener =
                future -> {
                    if (!completableFuture.isDone()) {
                        completableFuture.completeExceptionally(new IOException("Channel closed."));
                    }
                };

        try {
            ClientRequestDTO clientRequestDTO = new ClientRequestDTO(NettyChannelPoolConnector.this, request, completableFuture, callback);
            dtoMap.putIfAbsent(request.getUri(), clientRequestDTO);

            // Retrieves a channel pool for the given host
            FixedChannelPool pool = this.poolMap.get(clientRequestDTO);

            // Acquire a new channel from the pool
            io.netty.util.concurrent.Future<Channel> f = pool.acquire();

            f.addListener((FutureListener<Channel>) futureWrite -> {
                //Succeeded with acquiring a channel
                if (futureWrite.isSuccess()) {
                    Channel channel = futureWrite.getNow();

                    channel.closeFuture().addListener(closeListener);

                    try {
                        if(request.hasEntity()) {
                            channel.writeAndFlush(httpRequest);

                            final JerseyChunkedInput jerseyChunkedInput = new JerseyChunkedInput(channel);
                            request.setStreamProvider(contentLength -> jerseyChunkedInput);

                            if(HttpUtil.isTransferEncodingChunked(httpRequest)) {
                                channel.write(jerseyChunkedInput);
                            } else {
                                channel.write(jerseyChunkedInput);
                            }

                            executorService.execute(() -> {
                                channel.closeFuture().removeListener(closeListener);

                                try {
                                    request.writeEntity();
                                } catch (IOException ex) {
                                    callback.failure(ex);
                                    completableFuture.completeExceptionally(ex);
                                }
                            });
                            channel.flush();
                        } else {
                            channel.closeFuture().removeListener(closeListener);
                            channel.writeAndFlush(httpRequest);
                        }

                    } catch (Exception ex) {
                        System.err.println("Failed to sync and flush http request" + ex.getLocalizedMessage());
                    }
                    pool.release(channel);
                }
            });
        } catch (NullPointerException ex) {
            System.err.println("Failed to acquire socket from pool " + ex.getLocalizedMessage());
        }

    } catch (Exception ex) {
        completableFuture.completeExceptionally(ex);
        return completableFuture;
    }
    return completableFuture;
}

这是我的ChannelPoolHandler

public class SimpleChannelPoolHandler implements ChannelPoolHandler {
private ClientRequestDTO clientRequestDTO;

private boolean ssl;
private URI uri;
private int port;

SimpleChannelPoolHandler(URI uri) {

    this.uri = uri;
    if(uri != null) {
        this.port = uri.getPort() != -1 ? uri.getPort() : "https".equals(uri.getScheme()) ? 443 : 80;
        ssl = "https".equalsIgnoreCase(uri.getScheme());
    }
}

@Override
public void channelReleased(Channel ch) throws Exception {
    System.out.println("Channel released: " + ch.toString());
}

@Override
public void channelAcquired(Channel ch) throws Exception {
    System.out.println("Channel acquired: " + ch.toString());
}

@Override
public void channelCreated(Channel ch) throws Exception {
    System.out.println("Channel created: " + ch.toString());

    int readTimeout = Integer.parseInt(ApplicationEnvironment.getInstance().get("READ_TIMEOUT"));

    SocketChannelConfig channelConfig = (SocketChannelConfig) ch.config();
    channelConfig.setConnectTimeoutMillis(2000);

    ChannelPipeline channelPipeline = ch.pipeline();

    if(ssl) {
        SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        channelPipeline.addLast("ssl", sslContext.newHandler(ch.alloc(), uri.getHost(), this.port));
    }

    channelPipeline.addLast("client codec", new HttpClientCodec());
    channelPipeline.addLast("chunked content writer",new ChunkedWriteHandler());
    channelPipeline.addLast("content decompressor", new HttpContentDecompressor());
    channelPipeline.addLast("read timeout", new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
    channelPipeline.addLast("business logic", new JerseyNettyClientHandler(this.uri));
}

}

这是我的SimpleInboundHandler

public class JerseyNettyClientHandler extends SimpleChannelInboundHandler<HttpObject> {

private final NettyChannelPoolConnector nettyChannelPoolConnector;
private final LinkedBlockingDeque<InputStream> isList = new LinkedBlockingDeque<>();

private final AsyncConnectorCallback asyncConnectorCallback;
private final ClientRequest jerseyRequest;
private final CompletableFuture future;

public JerseyNettyClientHandler(ClientRequestDto clientRequestDTO) {
    this.nettyChannelPoolConnector = clientRequestDTO.getNettyChannelPoolConnector();

    ClientRequestDTO cdto = clientRequestDTO.getNettyChannelPoolConnector().getDtoMap().get(clientRequestDTO.getClientRequest());

    this.asyncConnectorCallback = cdto.getCallback();
    this.jerseyRequest = cdto.getClientRequest();
    this.future = cdto.getFuture();
}

@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
    if(msg instanceof HttpResponse) {

        final HttpResponse httpResponse = (HttpResponse) msg;

        final ClientResponse response = new ClientResponse(new Response.StatusType() {
            @Override
            public int getStatusCode() {
                return httpResponse.status().code();
            }

            @Override
            public Response.Status.Family getFamily() {
                return Response.Status.Family.familyOf(httpResponse.status().code());
            }

            @Override
            public String getReasonPhrase() {
                return httpResponse.status().reasonPhrase();
            }
        }, jerseyRequest);

        for (Map.Entry<String, String> entry : httpResponse.headers().entries()) {
            response.getHeaders().add(entry.getKey(), entry.getValue());
        }

        if((httpResponse.headers().contains(HttpHeaderNames.CONTENT_LENGTH) && HttpUtil.getContentLength(httpResponse) > 0) || HttpUtil.isTransferEncodingChunked(httpResponse)) {
            ctx.channel().closeFuture().addListener(future -> isList.add(NettyInputStream.END_OF_INPUT_ERROR));
            response.setEntityStream(new NettyInputStream(isList));
        } else {
            response.setEntityStream(new InputStream() {
                @Override
                public int read() {
                    return -1;
                }
            });
        }

        if(asyncConnectorCallback != null) {
            nettyChannelPoolConnector.executorService.execute(() -> {
                asyncConnectorCallback.response(response);
                future.complete(response);
            });
        }
    }

    if(msg instanceof HttpContent) {
        HttpContent content = (HttpContent) msg;

        ByteBuf byteContent = content.content();

        if(byteContent.isReadable()) {
            byte[] bytes = new byte[byteContent.readableBytes()];
            byteContent.getBytes(byteContent.readerIndex(), bytes);
            isList.add(new ByteArrayInputStream(bytes));
        }
    }

    if(msg instanceof LastHttpContent) {
        isList.add(NettyInputStream.END_OF_INPUT);
    }
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    if(asyncConnectorCallback != null) {
        nettyChannelPoolConnector.executorService.execute(() -> asyncConnectorCallback.failure(cause));
    }
    future.completeExceptionally(cause);
    isList.add(NettyInputStream.END_OF_INPUT_ERROR);
}

如第一个代码块所示,需要传递给SimpleChannelInboundHandler的引用是打包到ClientRequestDTO中的内容。

1 个答案:

答案 0 :(得分:0)

我不确定这不是经过测试的代码。但这可以通过以下代码来实现。

        SimpleChannelPool sPool = poolMap.get(Req.getAddress());
        Future<Channel> f = sPool.acquire();
        f.get().pipeline().addLast("inbound", new NettyClientInBoundHandler(Req, jbContext, ReportData));
        f.addListener(new NettyClientFutureListener(this.Req, sPool));

其中Req,jbContext,ReportData可以作为InboundHandler()的输入数据。