use of io.micronaut.core.io.buffer.ByteBufferFactory 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.core.io.buffer.ByteBufferFactory in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method addFullHttpResponseHandler.
@SuppressWarnings("MagicNumber")
private <O, E> void addFullHttpResponseHandler(io.micronaut.http.HttpRequest<?> request, Channel channel, boolean secure, ChannelPool channelPool, FluxSink<io.micronaut.http.HttpResponse<O>> emitter, Argument<O> bodyType, Argument<E> errorType) {
ChannelPipeline pipeline = channel.pipeline();
final SimpleChannelInboundHandler<FullHttpResponse> newHandler = new SimpleChannelInboundHandlerInstrumented<FullHttpResponse>(false) {
AtomicBoolean complete = new AtomicBoolean(false);
boolean keepAlive = true;
@Override
public boolean acceptInboundMessage(Object msg) {
return msg instanceof FullHttpResponse && (secure || !discardH2cStream((HttpMessage) msg));
}
@Override
protected void channelReadInstrumented(ChannelHandlerContext channelHandlerContext, FullHttpResponse fullResponse) {
try {
HttpResponseStatus status = fullResponse.status();
int statusCode = status.code();
HttpStatus httpStatus;
try {
httpStatus = HttpStatus.valueOf(statusCode);
} catch (IllegalArgumentException e) {
if (complete.compareAndSet(false, true)) {
if (!emitter.isCancelled()) {
emitter.error(e);
}
} else if (log.isWarnEnabled()) {
log.warn("Unsupported http status after handler completed: " + e.getMessage(), e);
}
return;
}
try {
HttpHeaders headers = fullResponse.headers();
if (log.isDebugEnabled()) {
log.debug("Received response {} from {}", status.code(), request.getUri());
}
if (log.isTraceEnabled()) {
traceHeaders(headers);
traceBody("Response", fullResponse.content());
}
// it is a redirect
if (statusCode > 300 && statusCode < 400 && configuration.isFollowRedirects() && headers.contains(HttpHeaderNames.LOCATION)) {
String location = headers.get(HttpHeaderNames.LOCATION);
final MutableHttpRequest<Object> redirectRequest;
if (statusCode == 307) {
redirectRequest = io.micronaut.http.HttpRequest.create(request.getMethod(), location);
request.getBody().ifPresent(redirectRequest::body);
} else {
redirectRequest = io.micronaut.http.HttpRequest.GET(location);
}
setRedirectHeaders(request, redirectRequest);
Flux<io.micronaut.http.HttpResponse<O>> redirectExchange = Flux.from(resolveRedirectURI(request, redirectRequest)).switchMap(buildExchangePublisher(request, redirectRequest, bodyType, errorType));
redirectExchange.defaultIfEmpty(io.micronaut.http.HttpResponse.notFound()).subscribe(oHttpResponse -> {
if (bodyType == null || !bodyType.isVoid()) {
emitter.next(oHttpResponse);
}
emitter.complete();
}, throwable -> {
if (!emitter.isCancelled()) {
emitter.error(throwable);
}
});
return;
}
if (statusCode == HttpStatus.NO_CONTENT.getCode()) {
// normalize the NO_CONTENT header, since http content aggregator adds it even if not present in the response
headers.remove(HttpHeaderNames.CONTENT_LENGTH);
}
boolean convertBodyWithBodyType = statusCode < 400 || (!DefaultHttpClient.this.configuration.isExceptionOnErrorStatus() && bodyType.equalsType(errorType));
FullNettyClientHttpResponse<O> response = new FullNettyClientHttpResponse<>(fullResponse, httpStatus, mediaTypeCodecRegistry, byteBufferFactory, bodyType, convertBodyWithBodyType);
if (complete.compareAndSet(false, true)) {
if (convertBodyWithBodyType) {
if (bodyType == null || !bodyType.isVoid()) {
emitter.next(response);
}
response.onComplete();
emitter.complete();
} else {
// error flow
try {
HttpClientResponseException clientError;
if (errorType != null && errorType != HttpClient.DEFAULT_ERROR_TYPE) {
clientError = new HttpClientResponseException(status.reasonPhrase(), null, response, new HttpClientErrorDecoder() {
@Override
public Argument<?> getErrorType(MediaType mediaType) {
return errorType;
}
});
} else {
clientError = new HttpClientResponseException(status.reasonPhrase(), response);
}
try {
if (!emitter.isCancelled()) {
emitter.error(clientError);
}
} finally {
response.onComplete();
}
} catch (Throwable t) {
if (t instanceof HttpClientResponseException) {
try {
if (!emitter.isCancelled()) {
emitter.error(t);
}
} finally {
response.onComplete();
}
} else {
response.onComplete();
FullNettyClientHttpResponse<Object> errorResponse = new FullNettyClientHttpResponse<>(fullResponse, httpStatus, mediaTypeCodecRegistry, byteBufferFactory, null, false);
errorResponse.onComplete();
HttpClientResponseException clientResponseError = new HttpClientResponseException("Error decoding HTTP error response body: " + t.getMessage(), t, errorResponse, null);
if (!emitter.isCancelled()) {
emitter.error(clientResponseError);
}
}
}
}
}
} catch (Throwable t) {
if (complete.compareAndSet(false, true)) {
if (t instanceof HttpClientResponseException) {
if (!emitter.isCancelled()) {
emitter.error(t);
}
} else {
FullNettyClientHttpResponse<Object> response = new FullNettyClientHttpResponse<>(fullResponse, httpStatus, mediaTypeCodecRegistry, byteBufferFactory, null, false);
HttpClientResponseException clientResponseError = new HttpClientResponseException("Error decoding HTTP response body: " + t.getMessage(), t, response, new HttpClientErrorDecoder() {
@Override
public Argument<?> getErrorType(MediaType mediaType) {
return errorType;
}
});
try {
if (!emitter.isCancelled()) {
emitter.error(clientResponseError);
}
} finally {
response.onComplete();
}
}
} else {
if (log.isWarnEnabled()) {
log.warn("Exception fired after handler completed: " + t.getMessage(), t);
}
}
}
} finally {
if (fullResponse.refCnt() > 0) {
try {
ReferenceCountUtil.release(fullResponse);
} catch (Throwable e) {
if (log.isDebugEnabled()) {
log.debug("Failed to release response: {}", fullResponse);
}
}
}
if (!HttpUtil.isKeepAlive(fullResponse)) {
keepAlive = false;
}
pipeline.remove(this);
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
if (channelPool != null) {
if (readTimeoutMillis != null) {
ctx.pipeline().remove(ChannelPipelineCustomizer.HANDLER_READ_TIMEOUT);
}
final Channel ch = ctx.channel();
if (!keepAlive) {
ch.closeFuture().addListener((future -> channelPool.release(ch)));
} else {
channelPool.release(ch);
}
} else {
// just close it to prevent any future reads without a handler registered
ctx.close();
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
if (readTimeoutMillis != null) {
if (httpVersion == io.micronaut.http.HttpVersion.HTTP_2_0) {
Http2SettingsHandler settingsHandler = (Http2SettingsHandler) ctx.pipeline().get(HANDLER_HTTP2_SETTINGS);
if (settingsHandler != null) {
addInstrumentedListener(settingsHandler.promise, future -> {
if (future.isSuccess()) {
pipeline.addBefore(ChannelPipelineCustomizer.HANDLER_HTTP2_CONNECTION, ChannelPipelineCustomizer.HANDLER_READ_TIMEOUT, new ReadTimeoutHandler(readTimeoutMillis, TimeUnit.MILLISECONDS));
}
});
} else {
pipeline.addBefore(ChannelPipelineCustomizer.HANDLER_HTTP2_CONNECTION, ChannelPipelineCustomizer.HANDLER_READ_TIMEOUT, new ReadTimeoutHandler(readTimeoutMillis, TimeUnit.MILLISECONDS));
}
} else {
pipeline.addBefore(ChannelPipelineCustomizer.HANDLER_HTTP_CLIENT_CODEC, ChannelPipelineCustomizer.HANDLER_READ_TIMEOUT, new ReadTimeoutHandler(readTimeoutMillis, TimeUnit.MILLISECONDS));
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
try {
if (complete.compareAndSet(false, true)) {
String message = cause.getMessage();
if (message == null) {
message = cause.getClass().getSimpleName();
}
if (log.isTraceEnabled()) {
log.trace("HTTP Client exception ({}) occurred for request : {} {}", message, request.getMethodName(), request.getUri());
}
if (cause instanceof TooLongFrameException) {
if (!emitter.isCancelled()) {
emitter.error(new ContentLengthExceededException(configuration.getMaxContentLength()));
}
} else if (cause instanceof io.netty.handler.timeout.ReadTimeoutException) {
if (!emitter.isCancelled()) {
emitter.error(ReadTimeoutException.TIMEOUT_EXCEPTION);
}
} else {
if (!emitter.isCancelled()) {
emitter.error(new HttpClientException("Error occurred reading HTTP response: " + message, cause));
}
}
}
} finally {
keepAlive = false;
pipeline.remove(this);
}
}
};
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_MICRONAUT_FULL_HTTP_RESPONSE, newHandler);
}
Aggregations