com.linecorp.armeria.common.HttpResponse Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of armeria-shaded Show documentation
Show all versions of armeria-shaded Show documentation
Asynchronous HTTP/2 RPC/REST client/server library built on top of Java 8, Netty, Thrift and GRPC (armeria-shaded)
/*
* Copyright 2016 LINE Corporation
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.linecorp.armeria.common;
import static com.linecorp.armeria.internal.ArmeriaHttpUtil.isContentAlwaysEmpty;
import static com.linecorp.armeria.internal.ArmeriaHttpUtil.isContentAlwaysEmptyWithValidation;
import static java.util.Objects.requireNonNull;
import java.nio.charset.StandardCharsets;
import java.util.Formatter;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.reactivestreams.Publisher;
import com.linecorp.armeria.common.FixedHttpResponse.OneElementFixedHttpResponse;
import com.linecorp.armeria.common.FixedHttpResponse.RegularFixedHttpResponse;
import com.linecorp.armeria.common.FixedHttpResponse.TwoElementFixedHttpResponse;
import com.linecorp.armeria.common.stream.StreamMessage;
import com.linecorp.armeria.common.util.Exceptions;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.concurrent.EventExecutor;
/**
* A streamed HTTP/2 {@link Response}.
*/
public interface HttpResponse extends Response, StreamMessage {
// Note: Ensure we provide the same set of `of()` methods with the `of()` methods of
// AggregatedHttpMessage for consistency.
/**
* Creates a new HTTP response that can stream an arbitrary number of {@link HttpObject} to the client.
* The first object written must be of type {@link HttpHeaders}.
*/
static HttpResponseWriter streaming() {
return new DefaultHttpResponse();
}
/**
* Creates a new HTTP response that delegates to the {@link HttpResponse} produced by the specified
* {@link CompletionStage}. If the specified {@link CompletionStage} fails, the returned response will be
* closed with the same cause as well.
*/
static HttpResponse from(CompletionStage extends HttpResponse> stage) {
requireNonNull(stage, "stage");
final DeferredHttpResponse res = new DeferredHttpResponse();
stage.whenComplete((delegate, thrown) -> {
if (thrown != null) {
res.close(Exceptions.peel(thrown));
} else if (delegate == null) {
res.close(new NullPointerException("delegate stage produced a null response: " + stage));
} else {
res.delegate(delegate);
}
});
return res;
}
/**
* Creates a new HTTP response of the specified {@code statusCode} and closes the stream if the
* {@link HttpStatusClass} is not {@linkplain HttpStatusClass#INFORMATIONAL informational} (1xx).
*/
static HttpResponse of(int statusCode) {
return of(HttpStatus.valueOf(statusCode));
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream if the
* {@link HttpStatusClass} is not {@linkplain HttpStatusClass#INFORMATIONAL informational} (1xx).
*/
static HttpResponse of(HttpStatus status) {
requireNonNull(status, "status");
if (status.codeClass() == HttpStatusClass.INFORMATIONAL) {
final HttpResponseWriter res = streaming();
res.write(HttpHeaders.of(status));
return res;
} else if (isContentAlwaysEmpty(status)) {
return new OneElementFixedHttpResponse(HttpHeaders.of(status));
} else {
return of(status, MediaType.PLAIN_TEXT_UTF_8, status.toHttpData());
}
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream.
*
* @param mediaType the {@link MediaType} of the response content
* @param content the content of the response
*/
static HttpResponse of(HttpStatus status, MediaType mediaType, String content) {
return of(status, mediaType, content.getBytes(mediaType.charset().orElse(StandardCharsets.UTF_8)));
}
/**
* Creates a new HTTP response of OK status with the content as UTF_8 and closes the stream.
*
* @param content the content of the response
*/
static HttpResponse of(String content) {
return of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, content);
}
/**
* Creates a new HTTP response of OK status with the content as UTF_8 and closes the stream.
* The content of the response is formatted by {@link String#format(Locale, String, Object...)} with
* {@linkplain Locale#ENGLISH English locale}.
*
* @param format {@linkplain Formatter the format string} of the response content
* @param args the arguments referenced by the format specifiers in the format string
*/
static HttpResponse of(String format, Object... args) {
return of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, format, args);
}
/**
* Creates a new HTTP response of OK status with the content and closes the stream.
*
* @param mediaType the {@link MediaType} of the response content
* @param content the content of the response
*/
static HttpResponse of(MediaType mediaType, String content) {
return of(HttpStatus.OK, mediaType, content);
}
/**
* Creates a new HTTP response of OK status with the content and closes the stream.
* The content of the response is formatted by {@link String#format(Locale, String, Object...)} with
* {@linkplain Locale#ENGLISH English locale}.
*
* @param mediaType the {@link MediaType} of the response content
* @param format {@linkplain Formatter the format string} of the response content
* @param args the arguments referenced by the format specifiers in the format string
*/
static HttpResponse of(MediaType mediaType, String format, Object... args) {
return of(HttpStatus.OK, mediaType, format, args);
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream.
* The content of the response is formatted by {@link String#format(Locale, String, Object...)} with
* {@linkplain Locale#ENGLISH English locale}.
*
* @param mediaType the {@link MediaType} of the response content
* @param format {@linkplain Formatter the format string} of the response content
* @param args the arguments referenced by the format specifiers in the format string
*/
static HttpResponse of(HttpStatus status, MediaType mediaType, String format, Object... args) {
return of(status,
mediaType,
String.format(Locale.ENGLISH, format, args).getBytes(
mediaType.charset().orElse(StandardCharsets.UTF_8)));
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream.
*
* @param mediaType the {@link MediaType} of the response content
* @param content the content of the response
*/
static HttpResponse of(HttpStatus status, MediaType mediaType, byte[] content) {
return of(status, mediaType, HttpData.of(content));
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream.
*
* @param mediaType the {@link MediaType} of the response content
* @param content the content of the response
* @param offset the start offset of {@code content}
* @param length the length of {@code content}
*/
static HttpResponse of(HttpStatus status, MediaType mediaType, byte[] content, int offset, int length) {
return of(status, mediaType, HttpData.of(content, offset, length));
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream.
*
* @param mediaType the {@link MediaType} of the response content
* @param content the content of the response
*/
static HttpResponse of(HttpStatus status, MediaType mediaType, HttpData content) {
return of(status, mediaType, content, HttpHeaders.EMPTY_HEADERS);
}
/**
* Creates a new HTTP response of the specified {@link HttpStatus} and closes the stream.
*
* @param mediaType the {@link MediaType} of the response content
* @param content the content of the response
* @param trailingHeaders the trailing HTTP headers
*/
static HttpResponse of(HttpStatus status, MediaType mediaType, HttpData content,
HttpHeaders trailingHeaders) {
requireNonNull(status, "status");
requireNonNull(mediaType, "mediaType");
requireNonNull(content, "content");
final HttpHeaders headers =
HttpHeaders.of(status)
.contentType(mediaType)
.setInt(HttpHeaderNames.CONTENT_LENGTH, content.length());
return of(headers, content, trailingHeaders);
}
/**
* Creates a new HTTP response of the specified headers.
*/
static HttpResponse of(HttpHeaders headers) {
return of(headers, HttpData.EMPTY_DATA);
}
/**
* Creates a new HTTP response of the specified headers and content.
*/
static HttpResponse of(HttpHeaders headers, HttpData content) {
return of(headers, content, HttpHeaders.EMPTY_HEADERS);
}
/**
* Creates a new HTTP response of the specified objects and closes the stream.
*/
static HttpResponse of(HttpHeaders headers, HttpData content, HttpHeaders trailingHeaders) {
requireNonNull(headers, "headers");
requireNonNull(content, "content");
requireNonNull(trailingHeaders, "trailingHeaders");
final HttpStatus status = headers.status();
// From the section 8.1.2.4 of RFC 7540:
//// For HTTP/2 responses, a single :status pseudo-header field is defined that carries the HTTP status
//// code field (see [RFC7231], Section 6). This pseudo-header field MUST be included in all responses;
//// otherwise, the response is malformed (Section 8.1.2.6).
if (status == null) {
throw new IllegalStateException("not a response (missing :status)");
}
if (isContentAlwaysEmptyWithValidation(status, content, trailingHeaders)) {
ReferenceCountUtil.safeRelease(content);
return new OneElementFixedHttpResponse(headers);
} else if (!content.isEmpty()) {
if (trailingHeaders.isEmpty()) {
return new TwoElementFixedHttpResponse(headers, content);
} else {
return new RegularFixedHttpResponse(headers, content, trailingHeaders);
}
} else if (!trailingHeaders.isEmpty()) {
return new TwoElementFixedHttpResponse(headers, trailingHeaders);
} else {
return new OneElementFixedHttpResponse(headers);
}
}
/**
* Creates a new HTTP response of the specified objects and closes the stream.
*/
static HttpResponse of(HttpObject... objs) {
requireNonNull(objs, "objs");
for (int i = 0; i < objs.length; i++) {
if (objs[i] == null) {
throw new NullPointerException("objs[" + i + "] is null");
}
}
return new RegularFixedHttpResponse(objs);
}
/**
* Converts the {@link AggregatedHttpMessage} into a new complete {@link HttpResponse}.
*/
static HttpResponse of(AggregatedHttpMessage res) {
requireNonNull(res, "res");
final List informationals = res.informationals();
final HttpHeaders headers = res.headers();
final HttpData content = res.content();
final HttpHeaders trailingHeaders = res.trailingHeaders();
if (informationals.isEmpty()) {
return of(headers, content, trailingHeaders);
}
final int numObjects = informationals.size() +
1 /* headers */ +
(!content.isEmpty() ? 1 : 0) +
(!trailingHeaders.isEmpty() ? 1 : 0);
final HttpObject[] objs = new HttpObject[numObjects];
int writerIndex = 0;
for (HttpHeaders informational : informationals) {
objs[writerIndex++] = informational;
}
objs[writerIndex++] = headers;
if (!content.isEmpty()) {
objs[writerIndex++] = content;
}
if (!trailingHeaders.isEmpty()) {
objs[writerIndex] = trailingHeaders;
}
return new RegularFixedHttpResponse(objs);
}
/**
* Creates a new HTTP response whose stream is produced from an existing {@link Publisher}.
*/
static HttpResponse of(Publisher extends HttpObject> publisher) {
return new PublisherBasedHttpResponse(publisher);
}
/**
* Creates a new failed HTTP response.
*/
static HttpResponse ofFailure(Throwable cause) {
final HttpResponseWriter res = streaming();
res.close(cause);
return res;
}
/**
* Creates a new failed HTTP response.
*
* @deprecated Use {@link #ofFailure(Throwable)} instead.
*/
@Deprecated
static HttpResponse ofFailed(Throwable cause) {
return ofFailure(cause);
}
@Override
default CompletableFuture closeFuture() {
return completionFuture();
}
@Override
CompletableFuture completionFuture();
/**
* Aggregates this response. The returned {@link CompletableFuture} will be notified when the content and
* the trailing headers of the response are received fully.
*/
default CompletableFuture aggregate() {
final CompletableFuture future = new CompletableFuture<>();
final HttpResponseAggregator aggregator = new HttpResponseAggregator(future, null);
completionFuture().whenComplete(aggregator);
subscribe(aggregator);
return future;
}
/**
* Aggregates this response. The returned {@link CompletableFuture} will be notified when the content and
* the trailing headers of the response are received fully.
*/
default CompletableFuture aggregate(EventExecutor executor) {
final CompletableFuture future = new CompletableFuture<>();
final HttpResponseAggregator aggregator = new HttpResponseAggregator(future, null);
completionFuture().whenCompleteAsync(aggregator, executor);
subscribe(aggregator, executor);
return future;
}
/**
* Aggregates this response. The returned {@link CompletableFuture} will be notified when the content and
* the trailing headers of the response are received fully. {@link AggregatedHttpMessage#content()} will
* return a pooled object, and the caller must ensure to release it. If you don't know what this means,
* use {@link HttpResponse#aggregate()}.
*/
default CompletableFuture aggregateWithPooledObjects(ByteBufAllocator alloc) {
requireNonNull(alloc, "alloc");
final CompletableFuture future = new CompletableFuture<>();
final HttpResponseAggregator aggregator = new HttpResponseAggregator(future, alloc);
completionFuture().whenComplete(aggregator);
subscribe(aggregator, true);
return future;
}
/**
* Aggregates this response. The returned {@link CompletableFuture} will be notified when the content and
* the trailing headers of the request is received fully. {@link AggregatedHttpMessage#content()} will
* return a pooled object, and the caller must ensure to release it. If you don't know what this means,
* use {@link HttpResponse#aggregate()}.
*/
default CompletableFuture aggregateWithPooledObjects(
EventExecutor executor, ByteBufAllocator alloc) {
requireNonNull(executor, "executor");
requireNonNull(alloc, "alloc");
final CompletableFuture future = new CompletableFuture<>();
final HttpResponseAggregator aggregator = new HttpResponseAggregator(future, alloc);
completionFuture().whenCompleteAsync(aggregator, executor);
subscribe(aggregator, executor, true);
return future;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy