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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

seonWKim
Copy link
Contributor

@seonWKim seonWKim commented Apr 7, 2024

Motivation:

Allow users to set default options for a specific service

Modifications:

  • Add ServiceOptions and @ServiceOption annotation to allow users specify default options

Result:

Users are able to set service specific options like below

final HttpService httpService = new HttpService() {
    ...

    @Override
    public ServiceOptions options() {
        return defaultServiceOptions;
    }
};

or use annotation

  final class MyService {

      @ServiceOption(requestTimeoutMillis = 11111, maxRequestLength = 1111,
              requestAutoAbortDelayMillis = 111)
      @Get("/test1")
      public HttpResponse test() {
          return HttpResponse.of("OK");
      }
   ...
}

@seonWKim
Copy link
Contributor Author

seonWKim commented Apr 7, 2024

I think we need to decide what settings the ServiceOptions should support. For now, it only supports three settings which are:

  • requestTimeoutMillis
  • maxRequestLength
  • requestAutoAbortDelayMillis

@seonWKim
Copy link
Contributor Author

seonWKim commented Apr 7, 2024

Should we use the name ServiceOptions which is used to configure HttpService?
Because HttpService extends Service<HttpRequest, HttpResponse> it might be better to use HttpServiceOptions for HttpService rather than ServiceOptions. 🤔

@seonWKim seonWKim marked this pull request as draft April 7, 2024 22:38
@ikhoon
Copy link
Contributor

ikhoon commented Apr 11, 2024

A service can be wrapped with a decorator. If the decorator does not override ServiceOptions, it should delegate options().

public abstract class SimpleDecoratingHttpService ... {
    @Override
    public ServiceOptions options() {
        return ((HttpService) unwrap()).options();
    }
}

@ikhoon
Copy link
Contributor

ikhoon commented Apr 11, 2024

it might be better to use HttpServiceOptions for HttpService rather than ServiceOptions

It may be better. Let use HttpServiceOptions for now. Other folks may have different opinions though.

@seonWKim
Copy link
Contributor Author

It may be better. Let use HttpServiceOptions for now. Other folks may have different opinions though.

I've renamed the class into HttpServiceOptions.

@seonWKim seonWKim marked this pull request as ready for review April 13, 2024 10:04
Copy link

codecov bot commented Apr 13, 2024

Codecov Report

Attention: Patch coverage is 79.01235% with 17 lines in your changes are missing coverage. Please review.

Project coverage is 74.11%. Comparing base (b8eb810) to head (a15ef81).
Report is 208 commits behind head on main.

Files Patch % Lines
...om/linecorp/armeria/server/HttpServiceOptions.java 47.82% 12 Missing ⚠️
...corp/armeria/server/HttpServiceOptionsBuilder.java 78.57% 0 Missing and 3 partials ⚠️
...nal/server/annotation/DefaultAnnotatedService.java 86.66% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5574      +/-   ##
============================================
+ Coverage     73.95%   74.11%   +0.15%     
- Complexity    20115    21328    +1213     
============================================
  Files          1730     1855     +125     
  Lines         74161    78816    +4655     
  Branches       9465    10069     +604     
============================================
+ Hits          54847    58413    +3566     
- Misses        14837    15691     +854     
- Partials       4477     4712     +235     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@seonWKim seonWKim force-pushed the feature/ISSUE-5071-add-ServiceOptions-to-Service branch 2 times, most recently from 2cbba00 to 81e8d09 Compare April 14, 2024 05:12
@seonWKim seonWKim force-pushed the feature/ISSUE-5071-add-ServiceOptions-to-Service branch from 81e8d09 to 44f1946 Compare April 14, 2024 05:17
@seonWKim
Copy link
Contributor Author

There were some conflicts with my origin and upstream, so I'd reset the commits and did a force push 😓

- OPTIONS -> DEFAULT OPTIONS
- WEBSOCKET_DEFAULT -> WEB_SOCKET_DEFAULT_OPTIONS
- requestTimeoutMillis
- maxRequestLength
- requestAutoAbortDelayMillis
@seonWKim seonWKim force-pushed the feature/ISSUE-5071-add-ServiceOptions-to-Service branch from 9f73765 to d65a265 Compare April 27, 2024 01:03
Comment on lines 35 to 40
private static final HttpServiceOptions WEB_SOCKET_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();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we move this to WebSocketService? I don't see we need to expose websocket() here.

interface WebSocketService {
    @Override
    default HttpServiceOptions options() {
        return DefaultWebSocketService.DEFAULT_OPTIONS;
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Shouldn't we override the options() only in the DefaultWebSocketService? Below test seems to allow DEFAULT_OPTIONS for DefaultWebSocketService only.

@Test
void shouldNotSetDefaultSettings() {
    final ServiceConfig serviceConfig = server.server().serviceConfigs().get(0);
    assertThat(serviceConfig.service().as(DelegatingWebSocketService.class)).isNotNull();
    // The default settings for `WebSocketService` should be applied only to `DefaultWebSocketService`.
    assertThat(serviceConfig.requestTimeoutMillis()).isEqualTo(Flags.defaultRequestTimeoutMillis());
}

@@ -40,6 +40,7 @@ void webSocketServiceDefaultConfigValues() {
assertThat(serviceConfig.requestAutoAbortDelayMillis()).isEqualTo(
WebSocketUtil.DEFAULT_REQUEST_AUTO_ABORT_DELAY_MILLIS);

// TODO: Should we allow default HttpServiceOptions to override the default values from virtualHostTemplate
Copy link
Contributor

Choose a reason for hiding this comment

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

The priority of HttpServiceOptions is between the values in ServiceBindingBuilder and VirtualHostBuilder.

The new priority of configurations will be: (Listed from high priority to low priority)

  • ServiceBindingBuilder (highest)
  • HttpServiceOptions (if exists)
  • VirtualHostBuilder
  • ServerBuilder (lowest)

Based on the new priority, sb.requestAutoAbortDelayMillis(1000) should not override HttpServiceOptions.requestAutoAbortDelayMillis() if requestAutoAbortDelayMillis > -1.

Should we test two scenarios?

  • Make sure that sb.requestAutoAbortDelayMillis(1000) does NOT override serviceConfig.requestAutoAbortDelayMillis() of WebSocketService
  • Make sure that sb.route().requestAutoAbortDelayMillis(1000) overrides WebSocketService.options().requestAutoAbortDelayMillis().

Copy link
Contributor Author

@seonWKim seonWKim May 11, 2024

Choose a reason for hiding this comment

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

I've added above mentioned test scenarios in WebSocketServiceConfigTest#webSocketServiceOptionsPriority.

- Move DEFAULT_OPTIONS of webSocket under DefaultWebSocketService
- Add test to verify priority of the options
@minwoox minwoox added this to the 1.29.0 milestone May 16, 2024
/**
* Returns the {@link HttpServiceOptions} of this {@link HttpService}.
*/
@Nullable
Copy link
Member

Choose a reason for hiding this comment

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

What are your thoughts on making this non-nullable like other methods?

Copy link
Contributor Author

@seonWKim seonWKim May 17, 2024

Choose a reason for hiding this comment

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

Sure, I'll make it as non-nullable as we can identify whether the user has set the value by checking whether the fields are set with values of -1 👍

* Returns the server-side timeout of a request in milliseconds.
*/
public HttpServiceOptionsBuilder requestTimeoutMillis(long requestTimeoutMillis) {
this.requestTimeoutMillis = requestTimeoutMillis;
Copy link
Member

Choose a reason for hiding this comment

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

We need to check the value:

checkArgument(requestTimeoutMillis >= 0,
              "requestTimeoutMillis: %s (expected: >= 0)", requestTimeoutMillis);

After that, we also need to modify these lines:
https://github.com/line/armeria/pull/5574/files#diff-2246967d4d345f650f2b3ec5d5032040fe494cc032094448c9c05c8557665449R188-R190

Copy link
Contributor Author

@seonWKim seonWKim May 17, 2024

Choose a reason for hiding this comment

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

Thank you for checking. I'll add validation logic and applied the changes to the specified code that you mentioned.

import com.linecorp.armeria.server.HttpServiceOptions;

/**
* An annotation used to configure {@link HttpServiceOptions} of a {@link HttpService}.
Copy link
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
* An annotation used to configure {@link HttpServiceOptions} of a {@link HttpService}.
* An annotation used to configure {@link HttpServiceOptions} of an {@link HttpService}.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've also changed a to an in the WrappingTransientHttpService's doc.

* An annotation used to configure {@link HttpServiceOptions} of a {@link HttpService}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
Copy link
Member

Choose a reason for hiding this comment

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

What do you think of also adding ElementType.TYPE?

Copy link
Contributor Author

@seonWKim seonWKim May 17, 2024

Choose a reason for hiding this comment

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

Sure. I'll add support for ElementType.TYPE 🙇

@@ -397,4 +406,9 @@ public HttpResponse encode(ServiceRequestContext ctx, WebSocket out) {
public WebSocketProtocolHandler protocolHandler() {
return this;
}

@Override
public HttpServiceOptions options() {
Copy link
Member

Choose a reason for hiding this comment

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

We also need to add this to GraphqlWebSocketService.

If a WebSocketService fails to upgrade here:


we need to set the HttpServiceOptions from the fallbackService.

@Nullable
HttpService fallbackService();

@Override
default HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
    final WebSocketUpgradeResult upgradeResult = protocolHandler().upgrade(ctx, req);
    if (!upgradeResult.isSuccess()) {
        if (fallbackService() != null) {
            ctx.setRequestTimeoutMillis(fallbackService().options().requestTimeoutMillis());
            ...
        }
        return upgradeResult.fallbackResponse();
    }

    final WebSocket in = protocolHandler().decode(ctx, req);
    final WebSocket out = serve(ctx, in);
    return protocolHandler().encode(ctx, out);
}

Copy link
Contributor Author

@seonWKim seonWKim May 18, 2024

Choose a reason for hiding this comment

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

What do you think of setting the HttpServiceOptions in the DefaultWebSocketService#failOrFallback method like below?

private HttpResponse failOrFallback(ServiceRequestContext ctx, HttpRequest req,
                                    Supplier<HttpResponse> invalidResponse) throws Exception {
    if (fallbackService != null) {
        final HttpServiceOptions options = fallbackService.options(); 
        if (options.requestTimeoutMillis() > 0) {
            ctx.setRequestTimeoutMillis(options.requestTimeoutMillis());
        } 
        ...
        return fallbackService.serve(ctx, req);
    } else {
        return invalidResponse.get();
    }
}

The reason for above suggestion is because we can set HttpServiceOptions while we are creating a fallback response. If we set HttpServiceOption in WebSocketService, handling fallback logic is separated into two parts

  • final WebSocketUpgradeResult upgradeResult = protocolHandler().upgrade(ctx, req); : handles upgrade and create fallback response
  • if (fallbackService() != null) {
      ctx.setRequestTimeoutMillis(fallbackService().options().requestTimeoutMillis());
        ...
    }
    => sets HttpServiceOptions for the fallbackResponse

Copy link
Member

Choose a reason for hiding this comment

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

What do you think of setting the HttpServiceOptions in the DefaultWebSocketService#failOrFallback method like below?

Oops, I missed it. Yeah we should set it in the location. 😉

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

I think we also need to update RouteDecoratingService.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add ServiceOptions to Service
3 participants