How to add X509Certificate to okhttp request? - https

I have installed my_client_cert.p12 to Android.
And then I can use this to get the X509Certificate.
try {
val cert = getCert()
} catch (e: Exception) {
val keyChainAliasCallback =
KeyChainAliasCallback { s ->
val cert = getCert()
}
KeyChain.choosePrivateKeyAlias(
this,
keyChainAliasCallback,
null,
null,
null,
-1,
"my_client_cert"
)
}
fun getCert(): X509Certificate? {
try {
val chain = KeyChain.getCertificateChain(applicationContext, "my_client_cert")
if (chain == null || chain.size == 0) {
return null
}
for (certificate in chain!!) {
return certificate
}
} catch (e: Exception) {
throw e
}
return null
}
I can also use this to set the my_client_cert to okhttp if I put the my_client_cert.p12 to res/raw
val keyStore = KeyStore.getInstance("PKCS12");
val fis = resources.openRawResource(R.raw.my_client_cert)
keyStore.load(fis, "password".toCharArray());
val kmf = KeyManagerFactory.getInstance("X509")
kmf.init(keyStore, "password".toCharArray())
val keyManagers = kmf.keyManagers
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagers, trustAllCerts, SecureRandom())
val builder = OkHttpClient.Builder()
builder.sslSocketFactory(
sslContext.getSocketFactory(),
trustAllCerts[0] as X509TrustManager
)
builder.hostnameVerifier { hostname, session -> true }
val request = Request.Builder()
.url("https://1.2.3.4/clientauth")
.build()
val client: OkHttpClient = builder.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
}
})
var trustAllCerts = arrayOf<TrustManager>(
object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
}
)
But I don't want to copy the cert into APK, how can I set X509Certificate to okhttp?

Related

Ktor: Bearer token injected by Dagger Hilt fails at first

Calls made by Ktor fails first most of the time and then use the refreshTokens to get a token and re-try the calls..
This is not acceptable as I see a LOT of Unauthenticated calls, therefore most calls I get is made 2x, this effectively almost double my network call count.
The ONLY time the calls is authenticated properly is when the previous call was made to the same end point.
Question:
How can I improve this situation so that the token is provided ONCE to all Ktor end-points and
until the token is replaced (via valid login)
I use androidx.datastore:datastore-preferences to store my token and settingsRepository.getAuthToken() correctly retreive it
My DI
I use Dagger Hilt to inject my Bearer token this way
#Provides
#Singleton
fun provideBearerAuthProvider(
settingsRepository: SettingsRepository
) = BearerAuthProvider(
realm = null,
loadTokens = {
val token = settingsRepository.getAuthToken()
if (token == null) {
BearerTokens(accessToken = "fake", refreshToken = "")
} else {
BearerTokens(accessToken = token, refreshToken = "")
}
},
refreshTokens = {
val token = settingsRepository.requireAuthToken()
token?.let { BearerTokens(accessToken = it, refreshToken = "") }
},
sendWithoutRequestCallback = { httpRequestBuilder ->
httpRequestBuilder.url.host == USER_LOGIN
}
).also { bearerAuthProvider ->
settingsRepository.addOnClearListener(bearerAuthProvider::clearToken)
}
#Provides
#Singleton
fun provideApiClient(
bearerAuthProvider: BearerAuthProvider,
) = HttpClient(Android) {
install(Logging) {
logger = Logger.ANDROID
LogLevel.ALL
}
install(ContentNegotiation) {
gson()
}
install(Auth) {
providers += bearerAuthProvider
}
}
My ApiService
This is where the HttpClient is injected
abstract class BaseApiService(
protected val httpClient: HttpClient,
protected val baseUrl: String,
) {
protected suspend inline fun <reified T> get(endpoint: String, block: HttpRequestBuilder.() -> Unit = {}): T {
return httpClient.get(urlString = baseUrl + endpoint, block = block).body()
}
protected suspend inline fun <reified T> post(endpoint: String, block: HttpRequestBuilder.() -> Unit): T {
return httpClient.post(urlString = baseUrl + endpoint, block = block).body()
}
protected suspend inline fun <reified T> put(endpoint: String, block: HttpRequestBuilder.() -> Unit = {}): T {
return httpClient.put(urlString = baseUrl + endpoint, block = block).body()
}
protected suspend inline fun <reified T> patch(endpoint: String, block: HttpRequestBuilder.() -> Unit): T {
return httpClient.patch(urlString = baseUrl + endpoint, block = block).body()
}
protected suspend inline fun <reified T> delete(endpoint: String, block: HttpRequestBuilder.() -> Unit = {}): T {
return httpClient.delete(urlString = baseUrl + endpoint, block = block).body()
}
}
UserApiService
class UserApiService #Inject constructor(
httpClient: HttpClient
) : BaseApiService(httpClient, BASE_URL) {
suspend fun authenticate(): BasicApiResponse<Unit> =
get(endpoint = USER_AUTHENTICATE)
suspend fun login(loginRequest: LoginRequest): BasicApiResponse<AuthApiResponse> =
post(endpoint = USER_LOGIN) {
contentType(ContentType.Application.Json)
setBody(loginRequest)
}
suspend fun updateUserProfile(updateProfileRequest: UpdateProfileRequest): BasicApiResponse<UserApiResponse> =
patch(endpoint = USER) {
contentType(ContentType.Application.Json)
setBody(updateProfileRequest)
}
}
Notes:
I use data classes passing variables to the json payloads e.g.
data class LoginRequest(
val email: String? = null,
val password: String
)
In above code I also included the sendWithoutRequestCallback endpoint
sendWithoutRequestCallback = { httpRequestBuilder ->
httpRequestBuilder.url.host == USER_LOGIN
}

Throw exception to parent method in webclient

Is there is a way to throw the exception to parent method in webclient. I am not able to throw the exception to parent method getStores(). What is the way to throw the exception to parent method in below code.
For example:
#Override
public Mono<Stores> getStores(String id) {
Mono<Stores> stores = null;
try {
WebClientRequest<Stores> webClientRequest = new WebClientRequest<>();
webClientRequest.setContentType("application/json");
webClientRequest.setEndPoint("http://localhost:8090/api/stores/eee");
webClientRequest.setPathParam(id);
stores= webClient.getAsynchronousWebClient(webClientRequest, Stores.class);
} catch(MtampWebClientException e) {
System.out.println("Hello");
}
return stores;
}
public <T> Mono<K> getAsynchronousWebClient(WebClientRequest<T> webClientRequest, Class<K> clazz) {
ResponseSpec retrieve = performWebRequest(webClientRequest);
return retrieve.bodyToMono(clazz).doOnNext(response -> webClientResponse(webClientRequest, response));
}
private <T> ResponseSpec performWebRequest(WebClientRequest<T> webClientRequest) {
WebClient client = webClientBuilder();
LinkedMultiValueMap<String, String> map = setHeaders(webClientRequest);
Consumer<HttpHeaders> consumer = it -> it.addAll(map);
ResponseSpec retrieve;
if (null != webClientRequest.getPathParam()) {
retrieve = client.get().uri(webClientRequest.getEndPoint() + "/" + webClientRequest.getPathParam())
.headers(consumer).retrieve();
} else if (null != webClientRequest.getQueryParam()) {
LinkedMultiValueMap<String, String> queryMap = new LinkedMultiValueMap<>();
webClientRequest.getQueryParam().entrySet().stream().forEach(e -> queryMap.add(e.getKey(), e.getValue()));
retrieve = client.get()
.uri(uriBuilder -> uriBuilder.path(webClientRequest.getEndPoint()).queryParams(queryMap).build())
.headers(consumer).retrieve();
} else {
retrieve = client.get().uri(webClientRequest.getEndPoint()).headers(consumer).retrieve();
}
return retrieve;
}
private WebClient webClientBuilder() {
HttpClient httpClient = HttpClient.create().option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.responseTimeout(Duration.ofMillis(5000))
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)));
return WebClient.builder().filter(ExchangeFilterFunction.ofResponseProcessor(this::errorHandler))
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
public Mono<ClientResponse> errorHandler(ClientResponse clientResponse) {
if (clientResponse.statusCode().is5xxServerError()) {
return clientResponse.bodyToMono(String.class).flatMap(errorBody -> Mono
.error(new MtampWebClientException(clientResponse.statusCode().toString(), errorBody)));
} else if (clientResponse.statusCode().is4xxClientError()) {
return clientResponse.bodyToMono(String.class).flatMap(errorBody -> Mono
.error(new MtampWebClientException(clientResponse.statusCode().toString(), errorBody)));
} else {
return Mono.just(clientResponse);
}
}

I have a problem with the code in ViewModel + Kotlin Coroutines + LiveData

I have a ViewModel
#HiltViewModel
class LoginViewModel #Inject constructor(
private val apiRepository: ApiRepository
) : ViewModel() {
private val account = MutableLiveData<String>("123")
private val password = MutableLiveData<String>("123")
val message: MutableLiveData<String> = MutableLiveData()
var loginResult: LiveData<Resource<UserInfo>> = MutableLiveData()
fun signIn() {
if (TextUtils.isEmpty(account.value)) {
message.postValue("Please enter your account")
return
}
if (TextUtils.isEmpty(password.value)) {
message.postValue("please enter your password")
return
}
// In this code, it doesn’t work. I think it’s because I didn’t observe it.
// Is there any better way to write it here?
loginResult = apiRepository.signIn(account.value!!, password.value!!)
}
fun inputAccount(accountValue: String) {
account.value = accountValue
}
fun inputPassword(passwordValue: String) {
password.value = passwordValue
}
}
This is my interface
#AndroidEntryPoint
class LoginActivity : BaseActivity<ActivityLoginBinding>() {
private val viewModel: LoginViewModel by viewModels()
......
override fun initEvent() {
binding.account.editText!!.addTextChangedListener { viewModel.inputAccount(it.toString()) }
binding.password.editText!!.addTextChangedListener { viewModel.inputPassword(it.toString()) }
binding.signIn.setOnClickListener {
viewModel.signIn()
}
}
override fun setupObservers() {
viewModel.message.observe(this) {
Snackbar.make(binding.root, it, Snackbar.LENGTH_SHORT).show()
}
/**
* There will be no callback here, I know it’s because I’m observing
* `var loginResult: LiveData<Resource<UserInfo>> = MutableLiveData()`
* instead of `apiRepository.signIn(account.value!!, password.value!!)`
* because it was reassigned
*/
viewModel.loginResult.observe(this) {
Log.d("TAG", "setupObservers: $it")
}
}
}
So I adjusted the code a bit
LoginViewModel.signIn
fun signIn(): LiveData<Resource<UserInfo>>? {
if (TextUtils.isEmpty(account.value)) {
message.postValue("Please enter your account")
return null
}
if (TextUtils.isEmpty(password.value)) {
message.postValue("please enter your password")
return null
}
return apiRepository.signIn(account.value!!, password.value!!)
}
LoginActivity.initEvent
override fun initEvent() {
binding.signIn.setOnClickListener {
viewModel.signIn()?.observe(this) {
Log.d("TAG", "setupObservers: $it")
}
}
}
I have checked the official documents of LiveData, and all call livedata{} during initialization. There has been no re-assignment, but if you log in, you cannot directly start the application and request the network.
coroutines doucument
Although I finally achieved my results, I think this is not the best practice, so I want to ask for help!
Supplementary code
ApiRepository
class ApiRepository #Inject constructor(
private val apiService: ApiService
) : BaseRemoteDataSource() {
fun signIn(account: String, password: String) =
getResult { apiService.signIn(account, password) }
}
BaseRemoteDataSource
abstract class BaseRemoteDataSource {
protected fun <T> getResult(call: suspend () -> Response<T>): LiveData<Resource<T>> =
liveData(Dispatchers.IO) {
try {
val response = call.invoke()
if (response.isSuccessful) {
val body = response.body()
if (body != null) emit(Resource.success(body))
} else {
emit(Resource.error<T>(" ${response.code()} ${response.message()}"))
}
} catch (e: Exception) {
emit(Resource.error<T>(e.message ?: e.toString()))
}
}
}
Or i write like this
fun signIn() {
if (TextUtils.isEmpty(account.value)) {
message.postValue("Please enter your account")
return
}
if (TextUtils.isEmpty(password.value)) {
message.postValue("please enter your password")
return
}
viewModelScope.launch {
repository.signIn(account.value, password.value).onEach {
loginResult.value = it
}
}
}
But I think this is not perfect

How to resolve GOOGLE_APPLICATION_CREDENTIALS when running app in test, Spring Boot?

I have a Spring Boot application dependent on Google PubSub. I want to run it with a Google Cloud PubSub emulator. How can I resolve GOOGLE_APPLICATION_CREDENTIALS, so the app will start and consume messages from the local emulator, not an external project?
At the moment, if I set GOOGLE_APPLICATION_CREDENTIALS to dev.json, PubSub doesn't get invoked if I don't set the variable, test crashes. How can I overcome it? I cannot put puzzles together.
NOTE: I am writing an integration test with a full Spring boot startup.
My PubSub implementation:
import com.github.dockerjava.api.exception.DockerClientException
import com.google.api.gax.core.NoCredentialsProvider
import com.google.api.gax.grpc.GrpcTransportChannel
import com.google.api.gax.rpc.FixedTransportChannelProvider
import com.google.api.gax.rpc.TransportChannelProvider
import com.google.cloud.pubsub.v1.*
import com.google.cloud.pubsub.v1.stub.GrpcSubscriberStub
import com.google.cloud.pubsub.v1.stub.SubscriberStubSettings
import com.google.protobuf.ByteString
import com.google.pubsub.v1.*
import com.greenbird.server.contracts.TestServer
import io.grpc.ManagedChannel
import io.grpc.ManagedChannelBuilder
import org.testcontainers.containers.PubSubEmulatorContainer
import org.testcontainers.utility.DockerImageName
import java.util.concurrent.TimeUnit
class PubSubTestServer(private val projectName: ProjectName, private val ports: Array<Int> = arrayOf(8085)) :
TestServer {
constructor(projectId: String): this(ProjectName.of(projectId))
private val projectId = projectName.project
var emulator: PubSubEmulatorContainer = PubSubEmulatorContainer(
DockerImageName.parse("gcr.io/google.com/cloudsdktool/cloud-sdk:latest")
)
private var channels: MutableList<ManagedChannel> = mutableListOf()
private fun channel(): ManagedChannel {
return if (channels.isEmpty()) {
val endpoint = emulator.emulatorEndpoint
val channel = ManagedChannelBuilder
.forTarget(endpoint)
.usePlaintext()
.build()
channels.add(channel)
channel
} else {
channels.first()
}
}
private val channelProvider: TransportChannelProvider
get() {
return FixedTransportChannelProvider
.create(
GrpcTransportChannel.create(channel())
)
}
private val credentialsProvider: NoCredentialsProvider = NoCredentialsProvider.create()
private val topicAdminSettings: TopicAdminSettings
get() {
when {
emulator.isRunning -> {
return buildTopicAdminSettings()
}
else -> {
throw DockerClientException("Topic admin settings attempted to initialize before starting PubSub emulator")
}
}
}
private fun buildTopicAdminSettings(): TopicAdminSettings {
return TopicAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
}
private val subscriptionAdminSettings: SubscriptionAdminSettings
get() {
when {
emulator.isRunning -> {
return buildSubscriptionAdminSettings()
}
else -> {
throw DockerClientException("Subscription admin settings attempted to initialize before starting PubSub emulator")
}
}
}
private fun buildSubscriptionAdminSettings(): SubscriptionAdminSettings {
return SubscriptionAdminSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
}
override fun start() {
emulator.withExposedPorts(*ports).start()
}
override fun stop() {
terminate()
emulator.stop()
}
private fun terminate() {
for (channel in channels) {
channel.shutdownNow()
channel.awaitTermination(5, TimeUnit.SECONDS)
}
}
fun createTopic(topicId: String) {
TopicAdminClient.create(topicAdminSettings).use { topicAdminClient ->
val topicName = TopicName.of(projectId, topicId)
topicAdminClient.createTopic(topicName)
}
}
fun listTopics(): List<String> {
return TopicAdminClient.create(topicAdminSettings)
.listTopics(projectName)
.iterateAll()
.map { it.name }
.toList()
}
fun createSubscription(subscriptionId: String, topicId: String) {
val subscriptionName = ProjectSubscriptionName.of(projectId, subscriptionId)
SubscriptionAdminClient.create(subscriptionAdminSettings).createSubscription(
subscriptionName,
TopicName.of(projectId, topicId),
PushConfig.getDefaultInstance(),
10
)
}
fun listSubscriptions(): List<String> {
return SubscriptionAdminClient.create(subscriptionAdminSettings)
.listSubscriptions(projectName)
.iterateAll()
.map { it.name }
.toList()
}
fun push(topicId: String, message: String) {
val publisher: Publisher = Publisher.newBuilder(TopicName.of(projectId, topicId))
.setChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
val pubsubMessage: PubsubMessage = PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(message)).build()
publisher.publish(pubsubMessage).get()
}
fun poll(size: Int, subscriptionId: String): List<String> {
val subscriberStubSettings: SubscriberStubSettings = SubscriberStubSettings.newBuilder()
.setTransportChannelProvider(channelProvider)
.setCredentialsProvider(credentialsProvider)
.build()
GrpcSubscriberStub.create(subscriberStubSettings).use { subscriber ->
val pullRequest: PullRequest = PullRequest.newBuilder()
.setMaxMessages(size)
.setSubscription(ProjectSubscriptionName.format(projectId, subscriptionId))
.build()
val pullResponse: PullResponse = subscriber.pullCallable().call(pullRequest)
return pullResponse.receivedMessagesList
.map { it.message.data.toStringUtf8() }
.toList()
}
}
}
I couldn't find the answer to my question as it was asked.
I found a workaround for Junit5 with junit-pioneer it's possible to set the env variable to something before the actual test run.
Therefore, the code will be the same as before but annotated with #SetEnvironmentVariable
#SetEnvironmentVariable(key="GOOGLE_APPLICATION_CREDENTIALS", value="dev.json")
class PubSubTestServer {
...
}
JUnit-pioneer: Maven central.

How to use Netty's channel pool map as a ConnectorProvider for a Jax RS client

I have wasted several hours trying to solve a issue with the use of netty's channel pool map and a jax rs client.
I have used jersey's own netty connector as an inspiration but exchanged netty's channel with netty's channel pool map.
https://jersey.github.io/apidocs/2.27/jersey/org/glassfish/jersey/netty/connector/NettyConnectorProvider.html
My problem is that I have references that I need inside my custom SimpleChannelInboundHandler. However by the design of netty's way to create a channel pool map, I can not pass the references through my custom ChannelPoolHandler, because as soon as the pool map has created a pool the constructor of the channel pool handler never runs again.
This is the method where it makes acquires a pool and check out a channel to make a HTTP request.
#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;
}
This is my 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));
}
}
And this is my 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);
}
The references needed to be passed to the SimpleChannelInboundHandler is what is packed into the ClientRequestDTO as seen in the first code block.
I am not sure as it is not a tested code. But it could be achieved by the following code.
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));
where Req, jbContext, ReportData could be input data for InboundHandler().

Resources