use of io.micronaut.http.HttpStatus in project micronaut-core by micronaut-projects.
the class RouteExecutor method createResponseForBody.
private Flux<MutableHttpResponse<?>> createResponseForBody(HttpRequest<?> request, Object body, RouteInfo<?> routeInfo) {
return Flux.<MutableHttpResponse<?>>defer(() -> {
Mono<MutableHttpResponse<?>> outgoingResponse;
if (body == null) {
if (routeInfo.isVoid()) {
MutableHttpResponse<Object> data = forStatus(routeInfo);
if (HttpMethod.permitsRequestBody(request.getMethod())) {
data.header(HttpHeaders.CONTENT_LENGTH, "0");
}
outgoingResponse = Mono.just(data);
} else {
outgoingResponse = Mono.just(newNotFoundError(request));
}
} else {
HttpStatus defaultHttpStatus = routeInfo.isErrorRoute() ? HttpStatus.INTERNAL_SERVER_ERROR : HttpStatus.OK;
boolean isReactive = routeInfo.isAsyncOrReactive() || Publishers.isConvertibleToPublisher(body);
if (isReactive) {
Class<?> bodyClass = body.getClass();
boolean isSingle = isSingle(routeInfo, bodyClass);
boolean isCompletable = !isSingle && routeInfo.isVoid() && Publishers.isCompletable(bodyClass);
if (isSingle || isCompletable) {
// full response case
Publisher<Object> publisher = Publishers.convertPublisher(body, Publisher.class);
Supplier<MutableHttpResponse<?>> emptyResponse = () -> {
MutableHttpResponse<?> singleResponse;
if (isCompletable || routeInfo.isVoid()) {
singleResponse = forStatus(routeInfo, HttpStatus.OK).header(HttpHeaders.CONTENT_LENGTH, "0");
} else {
singleResponse = newNotFoundError(request);
}
return singleResponse;
};
return Flux.from(publisher).flatMap(o -> {
MutableHttpResponse<?> singleResponse;
if (o instanceof Optional) {
Optional optional = (Optional) o;
if (optional.isPresent()) {
o = ((Optional<?>) o).get();
} else {
return Flux.just(emptyResponse.get());
}
}
if (o instanceof HttpResponse) {
singleResponse = toMutableResponse((HttpResponse<?>) o);
final Argument<?> bodyArgument = // Mono
routeInfo.getReturnType().getFirstTypeVariable().orElse(// HttpResponse
Argument.OBJECT_ARGUMENT).getFirstTypeVariable().orElse(// Mono
Argument.OBJECT_ARGUMENT);
if (bodyArgument.isAsyncOrReactive()) {
return processPublisherBody(request, singleResponse, routeInfo);
}
} else if (o instanceof HttpStatus) {
singleResponse = forStatus(routeInfo, (HttpStatus) o);
} else {
singleResponse = forStatus(routeInfo, defaultHttpStatus).body(o);
}
return Flux.just(singleResponse);
}).switchIfEmpty(Mono.fromSupplier(emptyResponse));
} else {
// streaming case
Argument<?> typeArgument = routeInfo.getReturnType().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
if (HttpResponse.class.isAssignableFrom(typeArgument.getType())) {
// a response stream
Publisher<HttpResponse<?>> bodyPublisher = Publishers.convertPublisher(body, Publisher.class);
Flux<MutableHttpResponse<?>> response = Flux.from(bodyPublisher).map(this::toMutableResponse);
Argument<?> bodyArgument = typeArgument.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
if (bodyArgument.isAsyncOrReactive()) {
return response.flatMap((resp) -> processPublisherBody(request, resp, routeInfo));
}
return response;
} else {
MutableHttpResponse<?> response = forStatus(routeInfo, defaultHttpStatus).body(body);
return processPublisherBody(request, response, routeInfo);
}
}
}
// now we have the raw result, transform it as necessary
if (body instanceof HttpStatus) {
outgoingResponse = Mono.just(HttpResponse.status((HttpStatus) body));
} else {
if (routeInfo.isSuspended()) {
boolean isKotlinFunctionReturnTypeUnit = routeInfo instanceof MethodBasedRouteMatch && isKotlinFunctionReturnTypeUnit(((MethodBasedRouteMatch) routeInfo).getExecutableMethod());
final Supplier<CompletableFuture<?>> supplier = ContinuationArgumentBinder.extractContinuationCompletableFutureSupplier(request);
if (isKotlinCoroutineSuspended(body)) {
return Mono.fromCompletionStage(supplier).<MutableHttpResponse<?>>flatMap(obj -> {
MutableHttpResponse<?> response;
if (obj instanceof HttpResponse) {
response = toMutableResponse((HttpResponse<?>) obj);
final Argument<?> bodyArgument = routeInfo.getReturnType().getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
if (bodyArgument.isAsyncOrReactive()) {
return processPublisherBody(request, response, routeInfo);
}
} else {
response = forStatus(routeInfo, defaultHttpStatus);
if (!isKotlinFunctionReturnTypeUnit) {
response = response.body(obj);
}
}
return Mono.just(response);
}).switchIfEmpty(createNotFoundErrorResponsePublisher(request));
} else {
Object suspendedBody;
if (isKotlinFunctionReturnTypeUnit) {
suspendedBody = Mono.empty();
} else {
suspendedBody = body;
}
outgoingResponse = toMutableResponse(request, routeInfo, defaultHttpStatus, suspendedBody);
}
} else {
outgoingResponse = toMutableResponse(request, routeInfo, defaultHttpStatus, body);
}
}
}
// for head request we never emit the body
if (request != null && request.getMethod().equals(HttpMethod.HEAD)) {
outgoingResponse = outgoingResponse.map(r -> {
final Object o = r.getBody().orElse(null);
if (o instanceof ReferenceCounted) {
((ReferenceCounted) o).release();
}
r.body(null);
return r;
});
}
return outgoingResponse;
}).doOnNext((response) -> {
applyConfiguredHeaders(response.getHeaders());
if (routeInfo instanceof RouteMatch) {
response.setAttribute(HttpAttributes.ROUTE_MATCH, routeInfo);
}
response.setAttribute(HttpAttributes.ROUTE_INFO, routeInfo);
});
}
use of io.micronaut.http.HttpStatus in project micronaut-core by micronaut-projects.
the class RouteExecutor method toMutableResponse.
private MutableHttpResponse<?> toMutableResponse(HttpResponse<?> message) {
MutableHttpResponse<?> mutableHttpResponse;
if (message instanceof MutableHttpResponse) {
mutableHttpResponse = (MutableHttpResponse<?>) message;
} else {
HttpStatus httpStatus = message.status();
mutableHttpResponse = HttpResponse.status(httpStatus, httpStatus.getReason());
mutableHttpResponse.body(message.body());
message.getHeaders().forEach((name, value) -> {
for (String val : value) {
mutableHttpResponse.header(name, val);
}
});
mutableHttpResponse.getAttributes().putAll(message.getAttributes());
}
return mutableHttpResponse;
}
use of io.micronaut.http.HttpStatus in project micronaut-core by micronaut-projects.
the class RouteExecutor method handleStatusException.
private Publisher<MutableHttpResponse<?>> handleStatusException(HttpRequest<?> request, MutableHttpResponse<?> response) {
HttpStatus status = response.status();
RouteInfo<?> routeInfo = response.getAttribute(HttpAttributes.ROUTE_INFO, RouteInfo.class).orElse(null);
if (status.getCode() >= 400 && routeInfo != null && !routeInfo.isErrorRoute()) {
RouteMatch<Object> statusRoute = findStatusRoute(request, status, routeInfo);
if (statusRoute != null) {
return executeRoute(request, false, Flux.just(statusRoute));
}
}
return Flux.just(response);
}
use of io.micronaut.http.HttpStatus in project micronaut-core by micronaut-projects.
the class RoutingInBoundHandler method writeFinalNettyResponse.
private void writeFinalNettyResponse(MutableHttpResponse<?> message, HttpRequest<?> request, ChannelHandlerContext context) {
HttpStatus httpStatus = message.status();
final io.micronaut.http.HttpVersion httpVersion = request.getHttpVersion();
final boolean isHttp2 = httpVersion == io.micronaut.http.HttpVersion.HTTP_2_0;
boolean decodeError = request instanceof NettyHttpRequest && ((NettyHttpRequest<?>) request).getNativeRequest().decoderResult().isFailure();
final Object body = message.body();
if (body instanceof NettyCustomizableResponseTypeHandlerInvoker) {
// default Connection header if not set explicitly
if (!isHttp2) {
if (!message.getHeaders().contains(HttpHeaders.CONNECTION)) {
if (!decodeError && (httpStatus.getCode() < 500 || serverConfiguration.isKeepAliveOnServerError())) {
message.getHeaders().set(HttpHeaders.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} else {
message.getHeaders().set(HttpHeaders.CONNECTION, HttpHeaderValues.CLOSE);
}
}
}
NettyCustomizableResponseTypeHandlerInvoker handler = (NettyCustomizableResponseTypeHandlerInvoker) body;
message.body(null);
handler.invoke(request, message, context);
} else {
io.netty.handler.codec.http.HttpResponse nettyResponse = NettyHttpResponseBuilder.toHttpResponse(message);
io.netty.handler.codec.http.HttpHeaders nettyHeaders = nettyResponse.headers();
// default Connection header if not set explicitly
if (!isHttp2) {
if (!nettyHeaders.contains(HttpHeaderNames.CONNECTION)) {
boolean expectKeepAlive = nettyResponse.protocolVersion().isKeepAliveDefault() || request.getHeaders().isKeepAlive();
if (!decodeError && (expectKeepAlive || httpStatus.getCode() < 500 || serverConfiguration.isKeepAliveOnServerError())) {
nettyHeaders.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
} else {
nettyHeaders.set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
}
}
}
// default to Transfer-Encoding: chunked if Content-Length not set or not already set
if (!nettyHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) && !nettyHeaders.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
nettyHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
}
// close handled by HttpServerKeepAliveHandler
final NettyHttpRequest<?> nettyHttpRequest = (NettyHttpRequest<?>) request;
if (isHttp2) {
addHttp2StreamHeader(request, nettyResponse);
}
io.netty.handler.codec.http.HttpRequest nativeRequest = nettyHttpRequest.getNativeRequest();
GenericFutureListener<Future<? super Void>> requestCompletor = future -> {
try {
if (!future.isSuccess()) {
final Throwable throwable = future.cause();
if (!(throwable instanceof ClosedChannelException)) {
if (throwable instanceof Http2Exception.StreamException) {
Http2Exception.StreamException se = (Http2Exception.StreamException) throwable;
if (se.error() == Http2Error.STREAM_CLOSED) {
// ignore
return;
}
}
if (LOG.isErrorEnabled()) {
LOG.error("Error writing final response: " + throwable.getMessage(), throwable);
}
}
}
} finally {
cleanupRequest(context, nettyHttpRequest);
context.read();
}
};
if (nativeRequest instanceof StreamedHttpRequest && !((StreamedHttpRequest) nativeRequest).isConsumed()) {
StreamedHttpRequest streamedHttpRequest = (StreamedHttpRequest) nativeRequest;
// We have to clear the buffer of FlowControlHandler before writing the response
// If this is a streamed request and there is still content to consume then subscribe
// and write the buffer is empty.
// noinspection ReactiveStreamsSubscriberImplementation
streamedHttpRequest.subscribe(new Subscriber<HttpContent>() {
private Subscription streamSub;
@Override
public void onSubscribe(Subscription s) {
streamSub = s;
s.request(1);
}
@Override
public void onNext(HttpContent httpContent) {
httpContent.release();
streamSub.request(1);
}
@Override
public void onError(Throwable t) {
syncWriteAndFlushNettyResponse(context, request, nettyResponse, requestCompletor);
}
@Override
public void onComplete() {
syncWriteAndFlushNettyResponse(context, request, nettyResponse, requestCompletor);
}
});
} else {
syncWriteAndFlushNettyResponse(context, request, nettyResponse, requestCompletor);
}
}
}
use of io.micronaut.http.HttpStatus in project micronaut-core by micronaut-projects.
the class RoutingInBoundHandler method channelRead0.
@Override
protected void channelRead0(ChannelHandlerContext ctx, io.micronaut.http.HttpRequest<?> request) {
ctx.channel().config().setAutoRead(false);
io.micronaut.http.HttpMethod httpMethod = request.getMethod();
String requestPath = request.getUri().getPath();
ServerRequestContext.set(request);
if (LOG.isDebugEnabled()) {
LOG.debug("Request {} {}", httpMethod, request.getUri());
}
NettyHttpRequest nettyHttpRequest = (NettyHttpRequest) request;
io.netty.handler.codec.http.HttpRequest nativeRequest = nettyHttpRequest.getNativeRequest();
// handle decoding failure
DecoderResult decoderResult = nativeRequest.decoderResult();
if (decoderResult.isFailure()) {
Throwable cause = decoderResult.cause();
HttpStatus status = cause instanceof TooLongFrameException ? HttpStatus.REQUEST_ENTITY_TOO_LARGE : HttpStatus.BAD_REQUEST;
handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(status), status.getReason());
return;
}
MediaType contentType = request.getContentType().orElse(null);
final String requestMethodName = request.getMethodName();
if (!multipartEnabled && contentType != null && contentType.equals(MediaType.MULTIPART_FORM_DATA_TYPE)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Multipart uploads have been disabled via configuration. Rejected request for URI {}, method {}, and content type {}", request.getUri(), requestMethodName, contentType);
}
handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE), "Content Type [" + contentType + "] not allowed");
return;
}
UriRouteMatch<Object, Object> routeMatch = null;
List<UriRouteMatch<Object, Object>> uriRoutes = router.findAllClosest(request);
if (uriRoutes.size() > 1) {
throw new DuplicateRouteException(requestPath, uriRoutes);
} else if (uriRoutes.size() == 1) {
UriRouteMatch<Object, Object> establishedRoute = uriRoutes.get(0);
request.setAttribute(HttpAttributes.ROUTE, establishedRoute.getRoute());
request.setAttribute(HttpAttributes.ROUTE_MATCH, establishedRoute);
request.setAttribute(HttpAttributes.ROUTE_INFO, establishedRoute);
request.setAttribute(HttpAttributes.URI_TEMPLATE, establishedRoute.getRoute().getUriMatchTemplate().toString());
routeMatch = establishedRoute;
}
RouteMatch<?> route;
if (routeMatch == null) {
// Check if there is a file for the route before returning route not found
Optional<? extends FileCustomizableResponseType> optionalFile = matchFile(requestPath);
if (optionalFile.isPresent()) {
filterAndEncodeResponse(ctx, nettyHttpRequest, Flux.just(HttpResponse.ok(optionalFile.get())));
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("No matching route: {} {}", httpMethod, request.getUri());
}
// if there is no route present try to locate a route that matches a different HTTP method
final List<UriRouteMatch<?, ?>> anyMatchingRoutes = router.findAny(request.getUri().toString(), request).collect(Collectors.toList());
final Collection<MediaType> acceptedTypes = request.accept();
final boolean hasAcceptHeader = CollectionUtils.isNotEmpty(acceptedTypes);
Set<MediaType> acceptableContentTypes = contentType != null ? new HashSet<>(5) : null;
Set<String> allowedMethods = new HashSet<>(5);
Set<MediaType> produceableContentTypes = hasAcceptHeader ? new HashSet<>(5) : null;
for (UriRouteMatch<?, ?> anyRoute : anyMatchingRoutes) {
final String routeMethod = anyRoute.getRoute().getHttpMethodName();
if (!requestMethodName.equals(routeMethod)) {
allowedMethods.add(routeMethod);
}
if (contentType != null && !anyRoute.doesConsume(contentType)) {
acceptableContentTypes.addAll(anyRoute.getRoute().getConsumes());
}
if (hasAcceptHeader && !anyRoute.doesProduce(acceptedTypes)) {
produceableContentTypes.addAll(anyRoute.getRoute().getProduces());
}
}
if (CollectionUtils.isNotEmpty(acceptableContentTypes)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Content type not allowed for URI {}, method {}, and content type {}", request.getUri(), requestMethodName, contentType);
}
handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE), "Content Type [" + contentType + "] not allowed. Allowed types: " + acceptableContentTypes);
return;
}
if (CollectionUtils.isNotEmpty(produceableContentTypes)) {
if (LOG.isDebugEnabled()) {
LOG.debug("Content type not allowed for URI {}, method {}, and content type {}", request.getUri(), requestMethodName, contentType);
}
handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.NOT_ACCEPTABLE), "Specified Accept Types " + acceptedTypes + " not supported. Supported types: " + produceableContentTypes);
return;
}
if (!allowedMethods.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Method not allowed for URI {} and method {}", request.getUri(), requestMethodName);
}
handleStatusError(ctx, nettyHttpRequest, HttpResponse.notAllowedGeneric(allowedMethods), "Method [" + requestMethodName + "] not allowed for URI [" + request.getUri() + "]. Allowed methods: " + allowedMethods);
return;
} else {
handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.NOT_FOUND), "Page Not Found");
}
return;
} else {
route = routeMatch;
}
if (LOG.isTraceEnabled()) {
if (route instanceof MethodBasedRouteMatch) {
LOG.trace("Matched route {} - {} to controller {}", requestMethodName, requestPath, route.getDeclaringType());
} else {
LOG.trace("Matched route {} - {}", requestMethodName, requestPath);
}
}
// all ok proceed to try and execute the route
if (route.isWebSocketRoute()) {
handleStatusError(ctx, nettyHttpRequest, HttpResponse.status(HttpStatus.BAD_REQUEST), "Not a WebSocket request");
} else {
handleRouteMatch(route, nettyHttpRequest, ctx);
}
}
Aggregations