use of io.micronaut.http.MutableHttpRequest 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.MutableHttpRequest in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method streamRequestThroughChannel.
private void streamRequestThroughChannel(io.micronaut.http.HttpRequest<?> parentRequest, AtomicReference<io.micronaut.http.HttpRequest> requestWrapper, FluxSink emitter, Channel channel, boolean failOnError) throws HttpPostRequestEncoder.ErrorDataEncoderException {
io.micronaut.http.HttpRequest<?> finalRequest = requestWrapper.get();
URI requestURI = finalRequest.getUri();
NettyRequestWriter requestWriter = prepareRequest(finalRequest, requestURI, emitter, false);
HttpRequest nettyRequest = requestWriter.getNettyRequest();
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_MICRONAUT_HTTP_RESPONSE_FULL, new SimpleChannelInboundHandlerInstrumented<FullHttpResponse>() {
final AtomicBoolean received = new AtomicBoolean(false);
@Override
public boolean acceptInboundMessage(Object msg) {
return msg instanceof FullHttpResponse;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (received.compareAndSet(false, true)) {
emitter.error(cause);
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent && received.compareAndSet(false, true)) {
// closed to idle ste
emitter.error(ReadTimeoutException.TIMEOUT_EXCEPTION);
}
}
@Override
protected void channelReadInstrumented(ChannelHandlerContext ctx, FullHttpResponse msg) {
if (received.compareAndSet(false, true)) {
HttpResponseStatus status = msg.status();
int statusCode = status.code();
HttpStatus httpStatus;
try {
httpStatus = HttpStatus.valueOf(statusCode);
} catch (IllegalArgumentException e) {
emitter.error(e);
return;
}
Publisher<HttpContent> bodyPublisher;
if (msg.content() instanceof EmptyByteBuf) {
bodyPublisher = Publishers.empty();
} else {
bodyPublisher = Publishers.just(new DefaultLastHttpContent(msg.content()));
}
DefaultStreamedHttpResponse nettyResponse = new DefaultStreamedHttpResponse(msg.protocolVersion(), msg.status(), msg.headers(), bodyPublisher);
NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(nettyResponse, httpStatus);
HttpHeaders headers = msg.headers();
if (log.isTraceEnabled()) {
log.trace("HTTP Client Streaming Response Received ({}) for Request: {} {}", msg.status(), nettyRequest.method().name(), nettyRequest.uri());
traceHeaders(headers);
}
boolean errorStatus = statusCode >= 400;
if (errorStatus && failOnError) {
emitter.error(new HttpClientResponseException(response.getStatus().getReason(), response));
} else {
emitter.next(response);
emitter.complete();
}
}
}
});
pipeline.addLast(ChannelPipelineCustomizer.HANDLER_MICRONAUT_HTTP_RESPONSE_STREAM, new SimpleChannelInboundHandlerInstrumented<StreamedHttpResponse>() {
final AtomicBoolean received = new AtomicBoolean(false);
@Override
public boolean acceptInboundMessage(Object msg) {
return msg instanceof StreamedHttpResponse;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (received.compareAndSet(false, true)) {
emitter.error(cause);
}
}
@Override
protected void channelReadInstrumented(ChannelHandlerContext ctx, StreamedHttpResponse msg) {
if (received.compareAndSet(false, true)) {
HttpResponseStatus status = msg.status();
int statusCode = status.code();
HttpStatus httpStatus;
try {
httpStatus = HttpStatus.valueOf(statusCode);
} catch (IllegalArgumentException e) {
emitter.error(e);
return;
}
NettyStreamedHttpResponse response = new NettyStreamedHttpResponse(msg, httpStatus);
HttpHeaders headers = msg.headers();
if (log.isTraceEnabled()) {
log.trace("HTTP Client Streaming Response Received ({}) for Request: {} {}", msg.status(), nettyRequest.method().name(), nettyRequest.uri());
traceHeaders(headers);
}
if (statusCode > 300 && statusCode < 400 && configuration.isFollowRedirects() && headers.contains(HttpHeaderNames.LOCATION)) {
String location = headers.get(HttpHeaderNames.LOCATION);
Flux<io.micronaut.http.HttpResponse<Object>> redirectedExchange;
try {
MutableHttpRequest<Object> redirectRequest;
if (statusCode == 307) {
redirectRequest = io.micronaut.http.HttpRequest.create(finalRequest.getMethod(), location);
finalRequest.getBody().ifPresent(redirectRequest::body);
} else {
redirectRequest = io.micronaut.http.HttpRequest.GET(location);
}
setRedirectHeaders(nettyRequest, redirectRequest);
redirectedExchange = Flux.from(resolveRedirectURI(parentRequest, redirectRequest)).flatMap(uri -> buildStreamExchange(parentRequest, redirectRequest, uri, null));
// noinspection SubscriberImplementation
redirectedExchange.subscribe(new Subscriber<io.micronaut.http.HttpResponse<Object>>() {
Subscription sub;
@Override
public void onSubscribe(Subscription s) {
s.request(1);
this.sub = s;
}
@Override
public void onNext(io.micronaut.http.HttpResponse<Object> objectHttpResponse) {
emitter.next(objectHttpResponse);
}
@Override
public void onError(Throwable t) {
emitter.error(t);
}
@Override
public void onComplete() {
emitter.complete();
}
});
} catch (Exception e) {
emitter.error(e);
}
} else {
boolean errorStatus = statusCode >= 400;
if (errorStatus && failOnError) {
emitter.error(new HttpClientResponseException(response.getStatus().getReason(), response));
} else {
emitter.next(response);
emitter.complete();
}
}
}
}
});
if (log.isDebugEnabled()) {
debugRequest(requestURI, nettyRequest);
}
if (log.isTraceEnabled()) {
traceRequest(requestWrapper.get(), nettyRequest);
}
requestWriter.writeAndClose(channel, null, emitter);
}
use of io.micronaut.http.MutableHttpRequest in project micronaut-core by micronaut-projects.
the class DefaultHttpClient method applyFilterToResponsePublisher.
/**
* @param parentRequest The parent request
* @param request The request
* @param requestURI The URI of the request
* @param requestWrapper The request wrapper
* @param responsePublisher The response publisher
* @param <I> The input type
* @param <O> The output type
* @return The {@link Publisher} for the response
*/
protected <I, O> Publisher<io.micronaut.http.HttpResponse<O>> applyFilterToResponsePublisher(io.micronaut.http.HttpRequest<?> parentRequest, io.micronaut.http.HttpRequest<I> request, URI requestURI, AtomicReference<io.micronaut.http.HttpRequest> requestWrapper, Publisher<io.micronaut.http.HttpResponse<O>> responsePublisher) {
if (request instanceof MutableHttpRequest) {
((MutableHttpRequest<I>) request).uri(requestURI);
List<HttpClientFilter> filters = filterResolver.resolveFilters(request, clientFilterEntries);
if (parentRequest != null) {
filters.add(new ClientServerContextFilter(parentRequest));
}
OrderUtil.reverseSort(filters);
Publisher<io.micronaut.http.HttpResponse<O>> finalResponsePublisher = responsePublisher;
filters.add((req, chain) -> finalResponsePublisher);
ClientFilterChain filterChain = buildChain(requestWrapper, filters);
if (parentRequest != null) {
responsePublisher = ServerRequestContext.with(parentRequest, (Supplier<Publisher<io.micronaut.http.HttpResponse<O>>>) () -> {
try {
return Flux.from((Publisher<io.micronaut.http.HttpResponse<O>>) filters.get(0).doFilter(request, filterChain)).contextWrite(ctx -> ctx.put(ServerRequestContext.KEY, parentRequest));
} catch (Throwable t) {
return Flux.error(t);
}
});
} else {
try {
responsePublisher = (Publisher<io.micronaut.http.HttpResponse<O>>) filters.get(0).doFilter(request, filterChain);
} catch (Throwable t) {
responsePublisher = Flux.error(t);
}
}
}
return responsePublisher;
}
use of io.micronaut.http.MutableHttpRequest 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.MutableHttpRequest 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;
});
}
Aggregations