use of io.micronaut.http.codec.MediaTypeCodecRegistry 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.codec.MediaTypeCodecRegistry in project micronaut-core by micronaut-projects.
the class StreamFunctionExecutor method encode.
/**
* Encode and write to output stream.
*
* @param environment environment
* @param registry local function registry
* @param returnType return type as Class
* @param result result object
* @param output outputstream
* @throws IOException input/output exception
*/
static void encode(Environment environment, LocalFunctionRegistry registry, Class returnType, Object result, OutputStream output) throws IOException {
if (ClassUtils.isJavaLangType(returnType)) {
if (result instanceof Byte) {
output.write((Byte) result);
} else if (result instanceof Boolean) {
output.write(((Boolean) result) ? 1 : 0);
} else if (result instanceof byte[]) {
output.write((byte[]) result);
} else {
byte[] bytes = environment.convert(result.toString(), byte[].class).orElseThrow(() -> new InvocationException("Unable to convert result [" + result + "] for output stream"));
output.write(bytes);
}
} else {
if (result instanceof Writable) {
Writable writable = (Writable) result;
writable.writeTo(output, environment.getProperty(LocalFunctionRegistry.FUNCTION_CHARSET, Charset.class, StandardCharsets.UTF_8));
} else {
Optional<MediaTypeCodec> codec = registry instanceof MediaTypeCodecRegistry ? ((MediaTypeCodecRegistry) registry).findCodec(MediaType.APPLICATION_JSON_TYPE) : Optional.empty();
if (codec.isPresent()) {
codec.get().encode(result, output);
} else {
byte[] bytes = environment.convert(result, byte[].class).orElseThrow(() -> new InvocationException("Unable to convert result [" + result + "] for output stream"));
output.write(bytes);
}
}
}
}
use of io.micronaut.http.codec.MediaTypeCodecRegistry in project micronaut-core by micronaut-projects.
the class DefaultNettyHttpClientRegistry method getClient.
private DefaultHttpClient getClient(ClientKey key, BeanContext beanContext, AnnotationMetadata annotationMetadata) {
return clients.computeIfAbsent(key, clientKey -> {
DefaultHttpClient clientBean = null;
final String clientId = clientKey.clientId;
final Class<?> configurationClass = clientKey.configurationClass;
if (clientId != null) {
clientBean = (DefaultHttpClient) this.beanContext.findBean(HttpClient.class, Qualifiers.byName(clientId)).orElse(null);
}
if (configurationClass != null && !HttpClientConfiguration.class.isAssignableFrom(configurationClass)) {
throw new IllegalStateException("Referenced HTTP client configuration class must be an instance of HttpClientConfiguration for injection point: " + configurationClass);
}
final List<String> filterAnnotations = clientKey.filterAnnotations;
final String path = clientKey.path;
if (clientBean != null && path == null && configurationClass == null && filterAnnotations.isEmpty()) {
return clientBean;
}
LoadBalancer loadBalancer = null;
List<String> clientIdentifiers = null;
final HttpClientConfiguration configuration;
if (configurationClass != null) {
configuration = (HttpClientConfiguration) this.beanContext.getBean(configurationClass);
} else if (clientId != null) {
configuration = this.beanContext.findBean(HttpClientConfiguration.class, Qualifiers.byName(clientId)).orElse(defaultHttpClientConfiguration);
} else {
configuration = defaultHttpClientConfiguration;
}
if (clientId != null) {
loadBalancer = loadBalancerResolver.resolve(clientId).orElseThrow(() -> new HttpClientException("Invalid service reference [" + clientId + "] specified to @Client"));
clientIdentifiers = Collections.singletonList(clientId);
}
String contextPath = null;
if (StringUtils.isNotEmpty(path)) {
contextPath = path;
} else if (StringUtils.isNotEmpty(clientId) && clientId.startsWith("/")) {
contextPath = clientId;
} else {
if (loadBalancer != null) {
contextPath = loadBalancer.getContextPath().orElse(null);
}
}
final DefaultHttpClient client = buildClient(loadBalancer, clientKey.httpVersion, configuration, clientIdentifiers, contextPath, beanContext, annotationMetadata);
final JsonFeatures jsonFeatures = clientKey.jsonFeatures;
if (jsonFeatures != null) {
List<MediaTypeCodec> codecs = new ArrayList<>(2);
MediaTypeCodecRegistry codecRegistry = client.getMediaTypeCodecRegistry();
for (MediaTypeCodec codec : codecRegistry.getCodecs()) {
if (codec instanceof MapperMediaTypeCodec) {
codecs.add(((MapperMediaTypeCodec) codec).cloneWithFeatures(jsonFeatures));
} else {
codecs.add(codec);
}
}
if (!codecRegistry.findCodec(MediaType.APPLICATION_JSON_TYPE).isPresent()) {
codecs.add(createNewJsonCodec(this.beanContext, jsonFeatures));
}
client.setMediaTypeCodecRegistry(MediaTypeCodecRegistry.of(codecs));
}
return client;
});
}
use of io.micronaut.http.codec.MediaTypeCodecRegistry in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method connectWebSocket.
private <T> Flux<T> connectWebSocket(URI uri, MutableHttpRequest<?> request, Class<T> clientEndpointType, WebSocketBean<T> webSocketBean) {
Bootstrap bootstrap = this.bootstrap.clone();
if (webSocketBean == null) {
webSocketBean = webSocketRegistry.getWebSocket(clientEndpointType);
}
WebSocketBean<T> finalWebSocketBean = webSocketBean;
return Flux.create(emitter -> {
SslContext sslContext = buildSslContext(uri);
WebSocketVersion protocolVersion = finalWebSocketBean.getBeanDefinition().enumValue(ClientWebSocket.class, "version", WebSocketVersion.class).orElse(WebSocketVersion.V13);
int maxFramePayloadLength = finalWebSocketBean.messageMethod().map(m -> m.intValue(OnMessage.class, "maxPayloadLength").orElse(65536)).orElse(65536);
String subprotocol = finalWebSocketBean.getBeanDefinition().stringValue(ClientWebSocket.class, "subprotocol").orElse(StringUtils.EMPTY_STRING);
RequestKey requestKey;
try {
requestKey = new RequestKey(uri);
} catch (HttpClientException e) {
emitter.error(e);
return;
}
bootstrap.remoteAddress(requestKey.getHost(), requestKey.getPort());
initBootstrapForProxy(bootstrap, sslContext != null, requestKey.getHost(), requestKey.getPort());
bootstrap.handler(new HttpClientInitializer(sslContext, requestKey.getHost(), requestKey.getPort(), false, false, false, null) {
@Override
protected void addFinalHandler(ChannelPipeline pipeline) {
pipeline.remove(ChannelPipelineCustomizer.HANDLER_HTTP_DECODER);
ReadTimeoutHandler readTimeoutHandler = pipeline.get(ReadTimeoutHandler.class);
if (readTimeoutHandler != null) {
pipeline.remove(readTimeoutHandler);
}
Optional<Duration> readIdleTime = configuration.getReadIdleTimeout();
if (readIdleTime.isPresent()) {
Duration duration = readIdleTime.get();
if (!duration.isNegative()) {
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_IDLE_STATE, new IdleStateHandler(duration.toMillis(), duration.toMillis(), duration.toMillis(), TimeUnit.MILLISECONDS));
}
}
final NettyWebSocketClientHandler webSocketHandler;
try {
String scheme = (sslContext == null) ? "ws" : "wss";
URI webSocketURL = UriBuilder.of(uri).scheme(scheme).host(host).port(port).build();
MutableHttpHeaders headers = request.getHeaders();
HttpHeaders customHeaders = EmptyHttpHeaders.INSTANCE;
if (headers instanceof NettyHttpHeaders) {
customHeaders = ((NettyHttpHeaders) headers).getNettyHeaders();
}
if (StringUtils.isNotEmpty(subprotocol)) {
customHeaders.add("Sec-WebSocket-Protocol", subprotocol);
}
webSocketHandler = new NettyWebSocketClientHandler<>(request, finalWebSocketBean, WebSocketClientHandshakerFactory.newHandshaker(webSocketURL, protocolVersion, subprotocol, true, customHeaders, maxFramePayloadLength), requestBinderRegistry, mediaTypeCodecRegistry, emitter);
pipeline.addLast(WebSocketClientCompressionHandler.INSTANCE);
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_MICRONAUT_WEBSOCKET_CLIENT, webSocketHandler);
} catch (Throwable e) {
emitter.error(new WebSocketSessionException("Error opening WebSocket client session: " + e.getMessage(), e));
}
}
});
addInstrumentedListener(bootstrap.connect(), future -> {
if (!future.isSuccess()) {
emitter.error(future.cause());
}
});
}, FluxSink.OverflowStrategy.ERROR);
}
use of io.micronaut.http.codec.MediaTypeCodecRegistry 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