use of io.micronaut.core.io.buffer.ByteBuffer in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method buildExchangeStreamPublisher.
/**
* @param request The request
* @param errorType The error type
* @param <I> The input type
* @return A {@link Function}
*/
protected <I> Function<URI, Publisher<HttpResponse<ByteBuffer<?>>>> buildExchangeStreamPublisher(@NonNull io.micronaut.http.HttpRequest<I> request, @NonNull Argument<?> errorType) {
final io.micronaut.http.HttpRequest<Object> parentRequest = ServerRequestContext.currentRequest().orElse(null);
return requestURI -> {
Flux<io.micronaut.http.HttpResponse<Object>> streamResponsePublisher = Flux.from(buildStreamExchange(parentRequest, request, requestURI, errorType));
return streamResponsePublisher.switchMap(response -> {
StreamedHttpResponse streamedHttpResponse = NettyHttpResponseBuilder.toStreamResponse(response);
Flux<HttpContent> httpContentReactiveSequence = Flux.from(streamedHttpResponse);
return httpContentReactiveSequence.filter(message -> !(message.content() instanceof EmptyByteBuf)).map(message -> {
ByteBuf byteBuf = message.content();
if (log.isTraceEnabled()) {
log.trace("HTTP Client Streaming Response Received Chunk (length: {}) for Request: {} {}", byteBuf.readableBytes(), request.getMethodName(), request.getUri());
traceBody("Response", byteBuf);
}
ByteBuffer<?> byteBuffer = byteBufferFactory.wrap(byteBuf);
NettyStreamedHttpResponse<ByteBuffer<?>> thisResponse = new NettyStreamedHttpResponse<>(streamedHttpResponse, response.status());
thisResponse.setBody(byteBuffer);
return (HttpResponse<ByteBuffer<?>>) new HttpResponseWrapper<>(thisResponse);
});
}).doOnTerminate(() -> {
final Object o = request.getAttribute(NettyClientHttpRequest.CHANNEL).orElse(null);
if (o instanceof Channel) {
final Channel c = (Channel) o;
if (c.isOpen()) {
c.close();
}
}
});
};
}
use of io.micronaut.core.io.buffer.ByteBuffer 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.ByteBuffer 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.core.io.buffer.ByteBuffer in project micronaut-core by micronaut-projects.
the class FullNettyClientHttpResponse method getBody.
@SuppressWarnings("unchecked")
@Override
public <T> Optional<T> getBody(Argument<T> type) {
if (type == null) {
return Optional.empty();
}
Class<T> javaType = type.getType();
if (javaType == void.class) {
return Optional.empty();
}
if (javaType == ByteBuffer.class) {
return Optional.of((T) byteBufferFactory.wrap(nettyHttpResponse.content()));
}
if (javaType == ByteBuf.class) {
return Optional.of((T) (nettyHttpResponse.content()));
}
if (javaType == byte[].class && bodyBytes != null) {
return Optional.of((T) (bodyBytes));
}
Optional<T> result = convertedBodies.computeIfAbsent(type, argument -> {
final boolean isOptional = argument.getType() == Optional.class;
final Argument finalArgument = isOptional ? argument.getFirstTypeVariable().orElse(argument) : argument;
Optional<T> converted;
try {
if (bodyBytes != null) {
return convertBytes(bodyBytes, finalArgument);
}
Optional<B> existing = getBody();
if (existing.isPresent()) {
converted = getBody().flatMap(b -> {
if (b instanceof ByteBuffer) {
ByteBuf bytebuf = (ByteBuf) ((ByteBuffer) b).asNativeBuffer();
return convertByteBuf(bytebuf, finalArgument);
} else {
final Optional opt = ConversionService.SHARED.convert(b, ConversionContext.of(finalArgument));
if (!opt.isPresent()) {
ByteBuf content = nettyHttpResponse.content();
return convertByteBuf(content, finalArgument);
}
return opt;
}
});
} else {
ByteBuf content = nettyHttpResponse.content();
converted = convertByteBuf(content, finalArgument);
}
} catch (RuntimeException e) {
if (status.getCode() < 400) {
throw e;
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Error decoding HTTP error response body: " + e.getMessage(), e);
}
converted = Optional.empty();
}
}
if (isOptional) {
return Optional.of(converted);
} else {
return converted;
}
});
if (LOG.isTraceEnabled() && !result.isPresent()) {
LOG.trace("Unable to convert response body to target type {}", javaType);
}
return result;
}
use of io.micronaut.core.io.buffer.ByteBuffer in project micronaut-core by micronaut-projects.
the class ServiceHttpClientFactory method healthCheckStarter.
/**
* Creates a {@link ApplicationEventListener} that listens to {@link ServerStartupEvent} for each configured HTTP client
* in order to register a health check if necessary.
*
* @param configuration The configuration
* @param instanceList The instance list
* @return The event listener
*/
@EachBean(ServiceHttpClientConfiguration.class)
@Requires(condition = ServiceHttpClientCondition.class)
ApplicationEventListener<ServerStartupEvent> healthCheckStarter(@Parameter ServiceHttpClientConfiguration configuration, @Parameter StaticServiceInstanceList instanceList) {
if (configuration.isHealthCheck()) {
return event -> {
final List<URI> originalURLs = configuration.getUrls();
Collection<URI> loadBalancedURIs = instanceList.getLoadBalancedURIs();
final HttpClient httpClient = clientFactory.get().getClient(configuration.getHttpVersion(), configuration.getServiceId(), configuration.getPath().orElse(null));
final Duration initialDelay = configuration.getHealthCheckInterval();
Duration delay = configuration.getHealthCheckInterval();
taskScheduler.scheduleWithFixedDelay(initialDelay, delay, () -> Flux.fromIterable(originalURLs).flatMap(originalURI -> {
URI healthCheckURI = originalURI.resolve(configuration.getHealthCheckUri());
return Flux.from(httpClient.exchange(HttpRequest.GET(healthCheckURI))).onErrorResume(throwable -> {
if (throwable instanceof HttpClientResponseException) {
HttpClientResponseException responseException = (HttpClientResponseException) throwable;
return Flux.just((HttpResponse<ByteBuffer>) responseException.getResponse());
}
return Flux.just(HttpResponse.serverError());
}).map(response -> Collections.singletonMap(originalURI, response.getStatus()));
}).subscribe(uriToStatusMap -> {
Map.Entry<URI, HttpStatus> entry = uriToStatusMap.entrySet().iterator().next();
URI uri = entry.getKey();
HttpStatus status = entry.getValue();
if (status.getCode() >= 300) {
loadBalancedURIs.remove(uri);
} else if (!loadBalancedURIs.contains(uri)) {
loadBalancedURIs.add(uri);
}
}));
};
}
throw new DisabledBeanException("HTTP Client Health Check not enabled");
}
Aggregations