use of io.micronaut.http.client.exceptions.HttpClientException in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method configureHttp2Ssl.
/**
* Configures HTTP/2 for the channel when SSL is enabled.
*
* @param httpClientInitializer The client initializer
* @param ch The channel
* @param sslCtx The SSL context
* @param host The host
* @param port The port
* @param connectionHandler The connection handler
*/
protected void configureHttp2Ssl(HttpClientInitializer httpClientInitializer, @NonNull SocketChannel ch, @NonNull SslContext sslCtx, String host, int port, HttpToHttp2ConnectionHandler connectionHandler) {
ChannelPipeline pipeline = ch.pipeline();
// Specify Host in SSLContext New Handler to add TLS SNI Extension
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_SSL, sslCtx.newHandler(ch.alloc(), host, port));
// We must wait for the handshake to finish and the protocol to be negotiated before configuring
// the HTTP/2 components of the pipeline.
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_HTTP2_PROTOCOL_NEGOTIATOR, new ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_2) {
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
// the logic to send the request should only be executed once the HTTP/2
// Connection Preface request has been sent. Once the Preface has been sent and
// removed then this handler is removed so we invoke the remaining logic once
// this handler removed
final Consumer<ChannelHandlerContext> contextConsumer = httpClientInitializer.contextConsumer;
if (contextConsumer != null) {
contextConsumer.accept(ctx);
}
}
@Override
protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {
if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
ChannelPipeline p = ctx.pipeline();
if (httpClientInitializer.stream) {
// stream consumer manages backpressure and reads
ctx.channel().config().setAutoRead(false);
}
p.addLast(ChannelPipelineCustomizer.HANDLER_HTTP2_SETTINGS, new Http2SettingsHandler(ch.newPromise()));
httpClientInitializer.addEventStreamHandlerIfNecessary(p);
httpClientInitializer.addFinalHandler(p);
for (ChannelPipelineListener pipelineListener : pipelineListeners) {
pipelineListener.onConnect(p);
}
} else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
ChannelPipeline p = ctx.pipeline();
httpClientInitializer.addHttp1Handlers(p);
} else {
ctx.close();
throw new HttpClientException("Unknown Protocol: " + protocol);
}
}
});
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_HTTP2_CONNECTION, connectionHandler);
}
use of io.micronaut.http.client.exceptions.HttpClientException in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method buildNettyRequest.
/**
* @param request The request
* @param requestURI The URI of the request
* @param requestContentType The request content type
* @param permitsBody Whether permits body
* @param bodyType The body type
* @param onError Called when the body publisher encounters an error
* @param closeChannelAfterWrite Whether to close the channel. For stream requests we don't close the channel until disposed of.
* @return A {@link NettyRequestWriter}
* @throws HttpPostRequestEncoder.ErrorDataEncoderException if there is an encoder exception
*/
protected NettyRequestWriter buildNettyRequest(MutableHttpRequest request, URI requestURI, MediaType requestContentType, boolean permitsBody, @Nullable Argument<?> bodyType, Consumer<? super Throwable> onError, boolean closeChannelAfterWrite) throws HttpPostRequestEncoder.ErrorDataEncoderException {
io.netty.handler.codec.http.HttpRequest nettyRequest;
HttpPostRequestEncoder postRequestEncoder = null;
if (permitsBody) {
Optional body = request.getBody();
boolean hasBody = body.isPresent();
if (requestContentType.equals(MediaType.APPLICATION_FORM_URLENCODED_TYPE) && hasBody) {
Object bodyValue = body.get();
if (bodyValue instanceof CharSequence) {
ByteBuf byteBuf = charSequenceToByteBuf((CharSequence) bodyValue, requestContentType);
request.body(byteBuf);
nettyRequest = NettyHttpRequestBuilder.toHttpRequest(request);
} else {
postRequestEncoder = buildFormDataRequest(request, bodyValue);
nettyRequest = postRequestEncoder.finalizeRequest();
}
} else if (requestContentType.equals(MediaType.MULTIPART_FORM_DATA_TYPE) && hasBody) {
Object bodyValue = body.get();
postRequestEncoder = buildMultipartRequest(request, bodyValue);
nettyRequest = postRequestEncoder.finalizeRequest();
} else {
ByteBuf bodyContent = null;
if (hasBody) {
Object bodyValue = body.get();
if (Publishers.isConvertibleToPublisher(bodyValue)) {
boolean isSingle = Publishers.isSingle(bodyValue.getClass());
Publisher<?> publisher = ConversionService.SHARED.convert(bodyValue, Publisher.class).orElseThrow(() -> new IllegalArgumentException("Unconvertible reactive type: " + bodyValue));
Flux<HttpContent> requestBodyPublisher = Flux.from(publisher).map(o -> {
if (o instanceof CharSequence) {
ByteBuf textChunk = Unpooled.copiedBuffer(((CharSequence) o), requestContentType.getCharset().orElse(StandardCharsets.UTF_8));
if (log.isTraceEnabled()) {
traceChunk(textChunk);
}
return new DefaultHttpContent(textChunk);
} else if (o instanceof ByteBuf) {
ByteBuf byteBuf = (ByteBuf) o;
if (log.isTraceEnabled()) {
log.trace("Sending Bytes Chunk. Length: {}", byteBuf.readableBytes());
}
return new DefaultHttpContent(byteBuf);
} else if (o instanceof byte[]) {
byte[] bodyBytes = (byte[]) o;
if (log.isTraceEnabled()) {
log.trace("Sending Bytes Chunk. Length: {}", bodyBytes.length);
}
return new DefaultHttpContent(Unpooled.wrappedBuffer(bodyBytes));
} else if (o instanceof ByteBuffer) {
ByteBuffer<?> byteBuffer = (ByteBuffer<?>) o;
Object nativeBuffer = byteBuffer.asNativeBuffer();
if (log.isTraceEnabled()) {
log.trace("Sending Bytes Chunk. Length: {}", byteBuffer.readableBytes());
}
if (nativeBuffer instanceof ByteBuf) {
return new DefaultHttpContent((ByteBuf) nativeBuffer);
} else {
return new DefaultHttpContent(Unpooled.wrappedBuffer(byteBuffer.toByteArray()));
}
} else if (mediaTypeCodecRegistry != null) {
Optional<MediaTypeCodec> registeredCodec = mediaTypeCodecRegistry.findCodec(requestContentType);
ByteBuf encoded = registeredCodec.map(codec -> {
if (bodyType != null && bodyType.isInstance(o)) {
return codec.encode((Argument<Object>) bodyType, o, byteBufferFactory).asNativeBuffer();
} else {
return codec.encode(o, byteBufferFactory).asNativeBuffer();
}
}).orElse(null);
if (encoded != null) {
if (log.isTraceEnabled()) {
traceChunk(encoded);
}
return new DefaultHttpContent(encoded);
}
}
throw new CodecException("Cannot encode value [" + o + "]. No possible encoders found");
});
if (!isSingle && MediaType.APPLICATION_JSON_TYPE.equals(requestContentType)) {
requestBodyPublisher = JsonSubscriber.lift(requestBodyPublisher);
}
requestBodyPublisher = requestBodyPublisher.doOnError(onError);
request.body(requestBodyPublisher);
nettyRequest = NettyHttpRequestBuilder.toHttpRequest(request);
try {
nettyRequest.setUri(requestURI.toURL().getFile());
} catch (MalformedURLException e) {
// should never happen
}
return new NettyRequestWriter(requestURI.getScheme(), nettyRequest, null, closeChannelAfterWrite);
} else if (bodyValue instanceof CharSequence) {
bodyContent = charSequenceToByteBuf((CharSequence) bodyValue, requestContentType);
} else if (mediaTypeCodecRegistry != null) {
Optional<MediaTypeCodec> registeredCodec = mediaTypeCodecRegistry.findCodec(requestContentType);
bodyContent = registeredCodec.map(codec -> {
if (bodyType != null && bodyType.isInstance(bodyValue)) {
return codec.encode((Argument<Object>) bodyType, bodyValue, byteBufferFactory).asNativeBuffer();
} else {
return codec.encode(bodyValue, byteBufferFactory).asNativeBuffer();
}
}).orElse(null);
}
if (bodyContent == null) {
bodyContent = ConversionService.SHARED.convert(bodyValue, ByteBuf.class).orElseThrow(() -> new HttpClientException("Body [" + bodyValue + "] cannot be encoded to content type [" + requestContentType + "]. No possible codecs or converters found."));
}
}
request.body(bodyContent);
try {
nettyRequest = NettyHttpRequestBuilder.toHttpRequest(request);
} finally {
// reset body after encoding request in case of retry
request.body(body.orElse(null));
}
}
} else {
nettyRequest = NettyHttpRequestBuilder.toHttpRequest(request);
}
try {
nettyRequest.setUri(requestURI.toURL().getFile());
} catch (MalformedURLException e) {
// should never happen
}
return new NettyRequestWriter(requestURI.getScheme(), nettyRequest, postRequestEncoder, closeChannelAfterWrite);
}
use of io.micronaut.http.client.exceptions.HttpClientException in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method eventStreamOrError.
private <I> Publisher<Event<ByteBuffer<?>>> eventStreamOrError(@NonNull io.micronaut.http.HttpRequest<I> request, @NonNull Argument<?> errorType) {
if (request instanceof MutableHttpRequest) {
((MutableHttpRequest) request).accept(MediaType.TEXT_EVENT_STREAM_TYPE);
}
return Flux.create(emitter -> dataStream(request, errorType).subscribe(new Subscriber<ByteBuffer<?>>() {
private Subscription dataSubscription;
private CurrentEvent currentEvent;
@Override
public void onSubscribe(Subscription s) {
this.dataSubscription = s;
Disposable cancellable = () -> dataSubscription.cancel();
emitter.onCancel(cancellable);
if (!emitter.isCancelled() && emitter.requestedFromDownstream() > 0) {
// request the first chunk
dataSubscription.request(1);
}
}
@Override
public void onNext(ByteBuffer<?> buffer) {
try {
int len = buffer.readableBytes();
// emit the current event
if (len == 0) {
try {
Event event = Event.of(byteBufferFactory.wrap(currentEvent.data)).name(currentEvent.name).retry(currentEvent.retry).id(currentEvent.id);
emitter.next(event);
} finally {
currentEvent = null;
}
} else {
if (currentEvent == null) {
currentEvent = new CurrentEvent();
}
int colonIndex = buffer.indexOf((byte) ':');
// SSE comments start with colon, so skip
if (colonIndex > 0) {
// obtain the type
String type = buffer.slice(0, colonIndex).toString(StandardCharsets.UTF_8).trim();
int fromIndex = colonIndex + 1;
// skip the white space before the actual data
if (buffer.getByte(fromIndex) == ((byte) ' ')) {
fromIndex++;
}
if (fromIndex < len) {
int toIndex = len - fromIndex;
switch(type) {
case "data":
ByteBuffer content = buffer.slice(fromIndex, toIndex);
byte[] d = currentEvent.data;
if (d == null) {
currentEvent.data = content.toByteArray();
} else {
currentEvent.data = ArrayUtils.concat(d, content.toByteArray());
}
break;
case "id":
ByteBuffer id = buffer.slice(fromIndex, toIndex);
currentEvent.id = id.toString(StandardCharsets.UTF_8).trim();
break;
case "event":
ByteBuffer event = buffer.slice(fromIndex, toIndex);
currentEvent.name = event.toString(StandardCharsets.UTF_8).trim();
break;
case "retry":
ByteBuffer retry = buffer.slice(fromIndex, toIndex);
String text = retry.toString(StandardCharsets.UTF_8);
if (!StringUtils.isEmpty(text)) {
Long millis = Long.valueOf(text);
currentEvent.retry = Duration.ofMillis(millis);
}
break;
default:
// ignore message
break;
}
}
}
}
if (emitter.requestedFromDownstream() > 0 && !emitter.isCancelled()) {
dataSubscription.request(1);
}
} catch (Throwable e) {
onError(e);
} finally {
if (buffer instanceof ReferenceCounted) {
((ReferenceCounted) buffer).release();
}
}
}
@Override
public void onError(Throwable t) {
dataSubscription.cancel();
if (t instanceof HttpClientException) {
emitter.error(t);
} else {
emitter.error(new HttpClientException("Error consuming Server Sent Events: " + t.getMessage(), t));
}
}
@Override
public void onComplete() {
emitter.complete();
}
}), FluxSink.OverflowStrategy.BUFFER);
}
use of io.micronaut.http.client.exceptions.HttpClientException in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method proxy.
@Override
public Publisher<MutableHttpResponse<?>> proxy(@NonNull io.micronaut.http.HttpRequest<?> request) {
return Flux.from(resolveRequestURI(request)).flatMap(requestURI -> {
io.micronaut.http.MutableHttpRequest<?> httpRequest = request instanceof MutableHttpRequest ? (io.micronaut.http.MutableHttpRequest<?>) request : request.mutate();
httpRequest.headers(headers -> headers.remove(HttpHeaderNames.HOST));
AtomicReference<io.micronaut.http.HttpRequest> requestWrapper = new AtomicReference<>(httpRequest);
Flux<MutableHttpResponse<Object>> proxyResponsePublisher = Flux.create(emitter -> {
SslContext sslContext = buildSslContext(requestURI);
ChannelFuture channelFuture;
try {
if (httpVersion == io.micronaut.http.HttpVersion.HTTP_2_0) {
channelFuture = doConnect(request, requestURI, sslContext, true, true, channelHandlerContext -> {
try {
final Channel channel = channelHandlerContext.channel();
request.setAttribute(NettyClientHttpRequest.CHANNEL, channel);
streamRequestThroughChannel(request, requestWrapper, emitter, channel, false);
} catch (Throwable e) {
emitter.error(e);
}
});
} else {
channelFuture = doConnect(request, requestURI, sslContext, true, true, null);
addInstrumentedListener(channelFuture, (ChannelFutureListener) f -> {
if (f.isSuccess()) {
Channel channel = f.channel();
request.setAttribute(NettyClientHttpRequest.CHANNEL, channel);
streamRequestThroughChannel(request, requestWrapper, emitter, channel, false);
} else {
Throwable cause = f.cause();
emitter.error(new HttpClientException("Connect error:" + cause.getMessage(), cause));
}
});
}
} catch (HttpClientException e) {
emitter.error(e);
return;
}
Disposable disposable = buildDisposableChannel(channelFuture);
emitter.onDispose(disposable);
emitter.onCancel(disposable::dispose);
}, FluxSink.OverflowStrategy.BUFFER);
// apply filters
// noinspection unchecked
proxyResponsePublisher = Flux.from(applyFilterToResponsePublisher(request, requestWrapper.get(), requestURI, requestWrapper, (Publisher) proxyResponsePublisher));
return proxyResponsePublisher;
});
}
use of io.micronaut.http.client.exceptions.HttpClientException in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method buildStreamExchange.
/**
* @param parentRequest The parent request
* @param request The request
* @param requestURI The request URI
* @param errorType The error type
* @param <I> The input type
* @return A {@link Flux}
*/
@SuppressWarnings("MagicNumber")
protected <I> Publisher<io.micronaut.http.HttpResponse<Object>> buildStreamExchange(@Nullable io.micronaut.http.HttpRequest<?> parentRequest, @NonNull io.micronaut.http.HttpRequest<I> request, @NonNull URI requestURI, @NonNull Argument<?> errorType) {
SslContext sslContext = buildSslContext(requestURI);
AtomicReference<io.micronaut.http.HttpRequest> requestWrapper = new AtomicReference<>(request);
Flux<io.micronaut.http.HttpResponse<Object>> streamResponsePublisher = Flux.create(emitter -> {
ChannelFuture channelFuture;
try {
if (httpVersion == io.micronaut.http.HttpVersion.HTTP_2_0) {
channelFuture = doConnect(request, requestURI, sslContext, true, channelHandlerContext -> {
try {
final Channel channel = channelHandlerContext.channel();
request.setAttribute(NettyClientHttpRequest.CHANNEL, channel);
streamRequestThroughChannel(parentRequest, requestWrapper, emitter, channel, true);
} catch (Throwable e) {
emitter.error(e);
}
});
} else {
channelFuture = doConnect(request, requestURI, sslContext, true, null);
addInstrumentedListener(channelFuture, (ChannelFutureListener) f -> {
if (f.isSuccess()) {
Channel channel = f.channel();
request.setAttribute(NettyClientHttpRequest.CHANNEL, channel);
streamRequestThroughChannel(parentRequest, requestWrapper, emitter, channel, true);
} else {
Throwable cause = f.cause();
emitter.error(new HttpClientException("Connect error:" + cause.getMessage(), cause));
}
});
}
} catch (HttpClientException e) {
emitter.error(e);
return;
}
Disposable disposable = buildDisposableChannel(channelFuture);
emitter.onDispose(disposable);
emitter.onCancel(disposable);
}, FluxSink.OverflowStrategy.BUFFER);
streamResponsePublisher = readBodyOnError(errorType, streamResponsePublisher);
// apply filters
streamResponsePublisher = Flux.from(applyFilterToResponsePublisher(parentRequest, request, requestURI, requestWrapper, streamResponsePublisher));
return streamResponsePublisher.subscribeOn(scheduler);
}
Aggregations