Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ServiceOptions and @ServiceOption #5574

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
44f1946
Add HttpServiceOptions and HttpServiceOption
seonWKim Apr 14, 2024
277ebaf
Rename
seonWKim Apr 14, 2024
d65a265
Configure below options in the constructor of `ServiceConfigBuilder`
seonWKim Apr 27, 2024
44fbf28
Add TODO
seonWKim Apr 27, 2024
da9c92d
Apply comments
seonWKim May 11, 2024
993847f
Merge branch 'main' into feature/ISSUE-5071-add-ServiceOptions-to-Ser…
seonWKim May 11, 2024
9748985
Nit
seonWKim May 11, 2024
89d318b
Nit
seonWKim May 11, 2024
93a37e9
Apply suggestions from code review
seonWKim May 17, 2024
a15ef81
Apply comments
seonWKim May 18, 2024
d269621
Set fallback options if fallbaskService is used
seonWKim May 22, 2024
6412b4e
Merge branch 'main' into feature/ISSUE-5071-add-ServiceOptions-to-Ser…
jrhee17 May 29, 2024
1607bfa
Apply suggestions from code review
seonWKim May 31, 2024
25d8085
Merge branch 'feature/ISSUE-5071-add-ServiceOptions-to-Service' of ht…
seonWKim May 31, 2024
a415102
Apply default option in WebSocketService
seonWKim Jun 4, 2024
1e18ba5
Move HttpServiceOption to 'armeria.server' and use virtual host setti…
ikhoon Jun 4, 2024
aa6f7c4
remove DEFAULT_OPTIONS in WebSocketService
ikhoon Jun 4, 2024
2e7bb57
Add testing code for websocket
ikhoon Jun 4, 2024
6ea57cf
lint
ikhoon Jun 4, 2024
d8b8924
Remove 'Http' prefix
ikhoon Jun 5, 2024
8578e42
Merge branch 'main' into feature/ISSUE-5071-add-ServiceOptions-to-Ser…
ikhoon Jun 5, 2024
a1775a6
Merge branch 'main' into feature/ISSUE-5071-add-ServiceOptions-to-Ser…
minwoox Jun 10, 2024
0eb3cc9
serviceOptions not nullable for WebSocketService
minwoox Jun 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
import com.linecorp.armeria.internal.server.annotation.AnnotatedValueResolver.AggregationType;
import com.linecorp.armeria.internal.server.annotation.AnnotatedValueResolver.ResolverContext;
import com.linecorp.armeria.server.HttpService;
import com.linecorp.armeria.server.HttpServiceOptions;
import com.linecorp.armeria.server.HttpServiceOptionsBuilder;
import com.linecorp.armeria.server.Route;
import com.linecorp.armeria.server.RoutingContext;
import com.linecorp.armeria.server.ServiceRequestContext;
Expand All @@ -68,6 +70,7 @@
import com.linecorp.armeria.server.annotation.ExceptionVerbosity;
import com.linecorp.armeria.server.annotation.FallthroughException;
import com.linecorp.armeria.server.annotation.HttpResult;
import com.linecorp.armeria.server.annotation.HttpServiceOption;
import com.linecorp.armeria.server.annotation.Path;
import com.linecorp.armeria.server.annotation.ResponseConverterFunction;
import com.linecorp.armeria.server.annotation.ServiceName;
Expand Down Expand Up @@ -111,6 +114,8 @@ final class DefaultAnnotatedService implements AnnotatedService {
@Nullable
private final String name;

private final HttpServiceOptions options;

DefaultAnnotatedService(Object object, Method method,
int overloadId, List<AnnotatedValueResolver> resolvers,
List<ExceptionHandlerFunction> exceptionHandlers,
Expand Down Expand Up @@ -176,6 +181,16 @@ final class DefaultAnnotatedService implements AnnotatedService {
this.method.setAccessible(true);
// following must be called only after method.setAccessible(true)
methodHandle = asMethodHandle(method, object);

HttpServiceOption httpServiceOption = AnnotationUtil.findFirst(method, HttpServiceOption.class);
if (httpServiceOption == null) {
httpServiceOption = AnnotationUtil.findFirst(object.getClass(), HttpServiceOption.class);
}
if (httpServiceOption != null) {
options = buildHttpServiceOptions(httpServiceOption);
} else {
options = HttpServiceOptions.of();
}
}

private static Type getActualReturnType(Method method) {
Expand Down Expand Up @@ -221,6 +236,20 @@ private static void warnIfHttpResponseArgumentExists(Type returnType,
}
}

private HttpServiceOptions buildHttpServiceOptions(HttpServiceOption httpServiceOption) {
seonWKim marked this conversation as resolved.
Show resolved Hide resolved
final HttpServiceOptionsBuilder builder = HttpServiceOptions.builder();
if (httpServiceOption.requestTimeoutMillis() >= 0) {
builder.requestTimeoutMillis(httpServiceOption.requestTimeoutMillis());
}
if (httpServiceOption.maxRequestLength() >= 0) {
builder.maxRequestLength(httpServiceOption.maxRequestLength());
}
if (httpServiceOption.requestAutoAbortDelayMillis() >= 0) {
builder.requestAutoAbortDelayMillis(httpServiceOption.requestAutoAbortDelayMillis());
}
return builder.build();
}

@Override
public String name() {
return name;
Expand Down Expand Up @@ -486,6 +515,11 @@ public ExchangeType exchangeType(RoutingContext routingContext) {
}
}

@Override
public HttpServiceOptions options() {
return options;
}

/**
* An {@link ExceptionHandlerFunction} which wraps a list of {@link ExceptionHandlerFunction}s.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@
import com.linecorp.armeria.common.stream.StreamMessage;
import com.linecorp.armeria.common.websocket.WebSocket;
import com.linecorp.armeria.internal.common.websocket.WebSocketFrameEncoder;
import com.linecorp.armeria.internal.common.websocket.WebSocketUtil;
import com.linecorp.armeria.internal.common.websocket.WebSocketWrapper;
import com.linecorp.armeria.server.HttpService;
import com.linecorp.armeria.server.HttpServiceOptions;
import com.linecorp.armeria.server.ServiceConfig;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.websocket.WebSocketProtocolHandler;
Expand Down Expand Up @@ -87,6 +89,13 @@ public final class DefaultWebSocketService implements WebSocketService, WebSocke
// Server-side encoder do not mask the payloads.
private static final WebSocketFrameEncoder encoder = WebSocketFrameEncoder.of(false);

private static final HttpServiceOptions DEFAULT_OPTIONS = HttpServiceOptions
.builder()
.requestTimeoutMillis(WebSocketUtil.DEFAULT_REQUEST_RESPONSE_TIMEOUT_MILLIS)
.maxRequestLength(WebSocketUtil.DEFAULT_MAX_REQUEST_RESPONSE_LENGTH)
.requestAutoAbortDelayMillis(WebSocketUtil.DEFAULT_REQUEST_AUTO_ABORT_DELAY_MILLIS)
.build();

private final WebSocketServiceHandler handler;
@Nullable
private final HttpService fallbackService;
Expand Down Expand Up @@ -397,4 +406,9 @@ public HttpResponse encode(ServiceRequestContext ctx, WebSocket out) {
public WebSocketProtocolHandler protocolHandler() {
return this;
}

@Override
public HttpServiceOptions options() {
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
return DEFAULT_OPTIONS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,12 @@ default HttpService decorate(DecoratingHttpServiceFunction function) {
default ExchangeType exchangeType(RoutingContext routingContext) {
return ExchangeType.BIDI_STREAMING;
}

/**
* Returns the {@link HttpServiceOptions} of this {@link HttpService}.
*/
@UnstableApi
default HttpServiceOptions options() {
return HttpServiceOptions.of();
}
}
111 changes: 111 additions & 0 deletions core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 2024 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.server;

import java.util.Objects;

import com.google.common.base.MoreObjects;

import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.annotation.UnstableApi;

/**
* Options for configuring an {@link HttpService}.
* You can override the default options by implementing {@link HttpService#options()}.
*/
@UnstableApi
public final class HttpServiceOptions {
private static final HttpServiceOptions DEFAULT_OPTIONS = builder().build();

/**
* Returns the default {@link HttpServiceOptions}.
*/
public static HttpServiceOptions of() {
return DEFAULT_OPTIONS;
}

/**
* Returns a new {@link HttpServiceOptionsBuilder}.
*/
public static HttpServiceOptionsBuilder builder() {
return new HttpServiceOptionsBuilder();
}

private final long requestTimeoutMillis;
private final long maxRequestLength;
private final long requestAutoAbortDelayMillis;

HttpServiceOptions(long requestTimeoutMillis, long maxRequestLength, long requestAutoAbortDelayMillis) {
this.requestTimeoutMillis = requestTimeoutMillis;
this.maxRequestLength = maxRequestLength;
this.requestAutoAbortDelayMillis = requestAutoAbortDelayMillis;
}

/**
* Returns the server-side timeout of a request in milliseconds. {@code -1} if not set.
*/
public long requestTimeoutMillis() {
return requestTimeoutMillis;
}

/**
* Returns the server-side maximum length of a request. {@code -1} if not set.
*/
public long maxRequestLength() {
return maxRequestLength;
}

/**
* Returns the amount of time to wait before aborting an {@link HttpRequest} when its corresponding
* {@link HttpResponse} is complete. {@code -1} if not set.
*/
public long requestAutoAbortDelayMillis() {
return requestAutoAbortDelayMillis;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;

Check warning on line 84 in core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java#L84

Added line #L84 was not covered by tests
}
if (o == null || getClass() != o.getClass()) {
return false;

Check warning on line 87 in core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java#L87

Added line #L87 was not covered by tests
}

final HttpServiceOptions that = (HttpServiceOptions) o;

Check warning on line 90 in core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java#L90

Added line #L90 was not covered by tests

return requestTimeoutMillis == that.requestTimeoutMillis &&
maxRequestLength == that.maxRequestLength &&
requestAutoAbortDelayMillis == that.requestAutoAbortDelayMillis;
}

@Override
public int hashCode() {
return Objects.hash(requestTimeoutMillis, maxRequestLength, requestAutoAbortDelayMillis);

Check warning on line 99 in core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java#L99

Added line #L99 was not covered by tests
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("requestTimeoutMillis", requestTimeoutMillis)
.add("maxRequestLength", maxRequestLength)
.add("requestAutoAbortDelayMillis", requestAutoAbortDelayMillis)
.toString();

Check warning on line 108 in core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/com/linecorp/armeria/server/HttpServiceOptions.java#L104-L108

Added lines #L104 - L108 were not covered by tests
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 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.server;

import static com.google.common.base.Preconditions.checkArgument;

import com.linecorp.armeria.common.HttpRequest;
import com.linecorp.armeria.common.HttpResponse;

/**
* Creates a new {@link HttpServiceOptions} with the specified parameters.
*/
public final class HttpServiceOptionsBuilder {
seonWKim marked this conversation as resolved.
Show resolved Hide resolved
private long requestTimeoutMillis = -1;
private long maxRequestLength = -1;
private long requestAutoAbortDelayMillis = -1;

seonWKim marked this conversation as resolved.
Show resolved Hide resolved
HttpServiceOptionsBuilder() {}

/**
* Returns the server-side timeout of a request in milliseconds.
*/
public HttpServiceOptionsBuilder requestTimeoutMillis(long requestTimeoutMillis) {
checkArgument(requestTimeoutMillis >= 0, "requestTimeoutMillis: %s (expected: >= 0)",
requestTimeoutMillis);
this.requestTimeoutMillis = requestTimeoutMillis;
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
return this;
}

/**
* Returns the server-side maximum length of a request.
*/
public HttpServiceOptionsBuilder maxRequestLength(long maxRequestLength) {
checkArgument(maxRequestLength >= 0, "maxRequestLength: %s (expected: >= 0)", maxRequestLength);
this.maxRequestLength = maxRequestLength;
return this;
}

/**
* Sets the amount of time to wait before aborting an {@link HttpRequest} when its corresponding
* {@link HttpResponse} is complete.
*/
public HttpServiceOptionsBuilder requestAutoAbortDelayMillis(long requestAutoAbortDelayMillis) {
checkArgument(requestAutoAbortDelayMillis >= 0, "requestAutoAbortDelayMillis: %s (expected: >= 0)",
requestAutoAbortDelayMillis);
this.requestAutoAbortDelayMillis = requestAutoAbortDelayMillis;
return this;
}

/**
* Returns a newly created {@link HttpServiceOptions} based on the properties of this builder.
*/
public HttpServiceOptions build() {
return new HttpServiceOptions(requestTimeoutMillis, maxRequestLength, requestAutoAbortDelayMillis);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,13 @@
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpHeaders;
import com.linecorp.armeria.common.HttpHeadersBuilder;
import com.linecorp.armeria.common.RequestId;
import com.linecorp.armeria.common.SuccessFunction;
import com.linecorp.armeria.common.annotation.Nullable;
import com.linecorp.armeria.common.util.BlockingTaskExecutor;
import com.linecorp.armeria.common.util.EventLoopGroups;
import com.linecorp.armeria.internal.common.websocket.WebSocketUtil;
import com.linecorp.armeria.internal.server.websocket.DefaultWebSocketService;
import com.linecorp.armeria.server.logging.AccessLogWriter;

import io.netty.channel.EventLoopGroup;
Expand Down Expand Up @@ -91,6 +88,17 @@ final class ServiceConfigBuilder implements ServiceConfigSetters {
ServiceConfigBuilder(Route route, String contextPath, HttpService service) {
this.route = requireNonNull(route, "route").withPrefix(contextPath);
this.service = requireNonNull(service, "service");

final HttpServiceOptions options = service.options();
if (options.requestTimeoutMillis() != -1) {
requestTimeoutMillis = options.requestTimeoutMillis();
}
if (options.maxRequestLength() != -1) {
maxRequestLength = options.maxRequestLength();
}
if (options.requestAutoAbortDelayMillis() != -1) {
requestAutoAbortDelayMillis = options.requestAutoAbortDelayMillis();
}
}

void addMappedRoute(Route mappedRoute) {
Expand Down Expand Up @@ -343,34 +351,13 @@ ServiceConfig build(ServiceNaming defaultServiceNaming,
unloggedExceptionsReporter);
}

final boolean webSocket = service.as(DefaultWebSocketService.class) != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning up!

final long requestTimeoutMillis;
if (this.requestTimeoutMillis != null) {
requestTimeoutMillis = this.requestTimeoutMillis;
} else if (!webSocket || defaultRequestTimeoutMillis != Flags.defaultRequestTimeoutMillis()) {
requestTimeoutMillis = defaultRequestTimeoutMillis;
} else {
requestTimeoutMillis = WebSocketUtil.DEFAULT_REQUEST_RESPONSE_TIMEOUT_MILLIS;
}

final long maxRequestLength;
if (this.maxRequestLength != null) {
maxRequestLength = this.maxRequestLength;
} else if (!webSocket || defaultMaxRequestLength != Flags.defaultMaxRequestLength()) {
maxRequestLength = defaultMaxRequestLength;
} else {
maxRequestLength = WebSocketUtil.DEFAULT_MAX_REQUEST_RESPONSE_LENGTH;
}

final long requestAutoAbortDelayMillis;
if (this.requestAutoAbortDelayMillis != null) {
requestAutoAbortDelayMillis = this.requestAutoAbortDelayMillis;
} else if (!webSocket ||
defaultRequestAutoAbortDelayMillis != Flags.defaultRequestAutoAbortDelayMillis()) {
requestAutoAbortDelayMillis = defaultRequestAutoAbortDelayMillis;
} else {
requestAutoAbortDelayMillis = WebSocketUtil.DEFAULT_REQUEST_AUTO_ABORT_DELAY_MILLIS;
}
final long requestTimeoutMillis = this.requestTimeoutMillis != null ? this.requestTimeoutMillis
: defaultRequestTimeoutMillis;
final long maxRequestLength = this.maxRequestLength != null ? this.maxRequestLength
: defaultMaxRequestLength;
final long requestAutoAbortDelayMillis =
this.requestAutoAbortDelayMillis != null ? this.requestAutoAbortDelayMillis
: defaultRequestAutoAbortDelayMillis;

final Supplier<AutoCloseable> mergedContextHook = mergeHooks(contextHook, this.contextHook);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,9 @@ protected SimpleDecoratingHttpService(HttpService delegate) {
public ExchangeType exchangeType(RoutingContext routingContext) {
return ((HttpService) unwrap()).exchangeType(routingContext);
}

@Override
public HttpServiceOptions options() {
return ((HttpService) unwrap()).options();
}
}