Code Monkey home page Code Monkey logo

Comments (6)

kratosmy avatar kratosmy commented on May 20, 2024

Hi, @ikhoon, could I try this issue?

from armeria.

jrhee17 avatar jrhee17 commented on May 20, 2024

Hi sorry, we're doing an internal open source sprint and this issue has been assigned 😅 Let us assign labels on such issues so that it is clear which issues are already being worked on 🙇

from armeria.

kratosmy avatar kratosmy commented on May 20, 2024

Hi sorry, we're doing an internal open source sprint and this issue has been assigned 😅 Let us assign labels on such issues so that it is clear which issues are already being worked on 🙇

sure, so what kind of label could I take on?

from armeria.

jrhee17 avatar jrhee17 commented on May 20, 2024

Armeria provides a HTTP client WebClient, which is able to send HTTP requests.

Usually, a user provides a String format URI and WebClient needs to parse the different parts of the URI to determine the correct protocol, endpoint, and various headers.

  URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

  hier-part   = "//" authority path-abempty
              / path-absolute
              / path-rootless
              / path-empty

ref: https://www.rfc-editor.org/rfc/rfc3986.html#section-3

Amongst the different URI components, the authority determines 1) Where to send the request 2) What to set to the Host or :authority header. Within Armeria, we 1) strip the userinfo 2) separate the host and port 3) and validate the host and port values.

authority = [ userinfo "@" ] host [ ":" port ]

ref: https://www.rfc-editor.org/rfc/rfc3986.html#section-3.2

However, the validation/parsing logic of the authority is fragmented across the code base:

  1. public static Endpoint parse(String authority) {
    requireNonNull(authority, "authority");
    checkArgument(!authority.isEmpty(), "authority is empty");
    return cache.get(authority, key -> {
    if (key.charAt(key.length() - 1) == ':') {
    // HostAndPort.fromString() does not validate an authority that ends with ':' such as "0.0.0.0:"
    throw new IllegalArgumentException("Missing port number: " + key);
    }
    final HostAndPort hostAndPort = HostAndPort.fromString(removeUserInfo(key)).withDefaultPort(0);
    return create(hostAndPort.getHost(), hostAndPort.getPort(), true);
    });
    }
  2. // The connection will be established with the IP address but `host` set to the `Endpoint`
    // could be used for SNI. It would make users send HTTPS requests with CSLB or configure a reverse
    // proxy based on an authority.
    final String host = HostAndPort.fromString(removeUserInfo(authority)).getHost();
    if (!NetUtil.isValidIpV4Address(host) && !NetUtil.isValidIpV6Address(host)) {
    endpoint = endpoint.withHost(host);
    }
  3. schemeAndAuthority = normalizeSchemeAndAuthority(scheme, authority);

The objective of this issue is to unify the validation/parsing logic above

To get jump-started with the current code base, it's probably best one checks from unit tests how the above logic works.
Here are some code snippets which should get one started with how the Armeria public APIs interact with the above logic.

i.e. When sending the request from WebClient

class WebClientTest {

    @RegisterExtension
    static final ServerExtension server = new ServerExtension() {
        @Override
        protected void configure(ServerBuilder sb) throws Exception {
            sb.service("/", (ctx, req) -> HttpResponse.of(200));
        }
    };

    @Test
    void test1() {
        final AggregatedHttpResponse res = WebClient.of().blocking().get(server.httpUri().toString());
        assertThat(res.status().code()).isEqualTo(200);
    }

    @Test
    void test2() {
        final HttpRequest req = HttpRequest.builder()
                                           .header(HttpHeaderNames.SCHEME, "http")
                                           .header(HttpHeaderNames.AUTHORITY, server.httpUri().getRawAuthority())
                                           .get("/")
                                           .build();
        final AggregatedHttpResponse res = WebClient.of().blocking().execute(req);
        assertThat(res.status().code()).isEqualTo(200);
    }

    @Test
    void test3() {
        final AggregatedHttpResponse res = WebClient.of(server.httpUri()).blocking().get("/");
        assertThat(res.status().code()).isEqualTo(200);
    }

    @Test
    void test4() {
        final Endpoint endpoint = Endpoint.of(server.httpUri().getRawAuthority());
        final AggregatedHttpResponse res = WebClient.of(SessionProtocol.HTTP, endpoint).blocking().get("/");
        assertThat(res.status().code()).isEqualTo(200);
    }
}

i.e. When altering headers from decorators

    @Test
    void test5() {
        final AggregatedHttpResponse res = server
                .blockingWebClient(cb -> cb.decorator((delegate, ctx, req) -> {
                    ctx.addAdditionalRequestHeader(HttpHeaderNames.AUTHORITY, "my-authority:8080");
                    assertThat(ctx.host()).isEqualTo("my-authority");
                    return delegate.execute(ctx, req);
                }))
                .get("/");
        assertThat(res.status().code()).isEqualTo(200);
    }

i.e. When parsing an arbitrary target string (this method is used in RedirectingClient/gRPC clients)

    @Test
    void test6() {
        final RequestTarget requestTarget = RequestTarget.forClient("http://my-authority:8080/hello");
        assertThat(requestTarget.authority()).isEqualTo("my-authority:8080");
    }

Afterwards, I recommend that we add a public method in ArmeriaHttpUtil and try to unify the above logic. The skeletal API would probably look something like the following:

public static ... parseAuthority(...) {
  ...
}

from armeria.

jrhee17 avatar jrhee17 commented on May 20, 2024

sure, so what kind of label could I take on?

Let me add a sprint label to all affected issues

from armeria.

jrhee17 avatar jrhee17 commented on May 20, 2024

Also note the following bullet points from the initial report:

  • Always strip the user info part from authority before validation and
  • Always use new URI(String) to parse an authority string; and
  • Make sure we move this logic into ArmeriaHttpUtil so it is shared by DefaultRequestTarget, Endpoint and DefaultClientRequestContext.

For starters, we may want to make this the initial implementation and trying to run the build. We could work on fixing each test case as we move forward.

from armeria.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.