/* * 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: * * http://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.client; import static java.util.Objects.requireNonNull; import com.google.common.net.HostAndPort; import com.linecorp.armeria.client.endpoint.EndpointGroupRegistry; /** * A remote endpoint that refers to a single host or a group of multiple hosts. * * <p>A host endpoint has {@link #host()} and optional {@link #port()} and it can be represented as * {@code "<host>"} or {@code "<host>:<port>"} in the authority part of a URI. * * <p>A group endpoint has {@link #groupName()} and it can be represented as {@code "group:<groupName>"} * in the authority part of a URI. It can be resolved into a host endpoint with {@link #resolve()}. */ public final class Endpoint { /** * Parse the authority part of a URI. The authority part may have one of the following formats: * <ul> * <li>{@code "group:<groupName>"} for a group endpoint</li> * <li>{@code "<host>:<port>"} for a host endpoint</li> * <li>{@code "<host>"} for a host endpoint with no port number specified</li> * </ul> */ public static Endpoint parse(String authority) { requireNonNull(authority, "authority"); if (authority.startsWith("group:")) { return ofGroup(authority.substring(6)); } final HostAndPort parsed = HostAndPort.fromString(authority).withDefaultPort(0); return new Endpoint(parsed.getHost(), parsed.getPort(), 1000); } /** * Creates a new group {@link Endpoint}. */ public static Endpoint ofGroup(String name) { requireNonNull(name, "name"); return new Endpoint(name); } /** * Creates a new host {@link Endpoint}. */ public static Endpoint of(String host, int port) { return of(host, port, 1000); } /** * Creates a new host {@link Endpoint} with unspecified port number. */ public static Endpoint of(String host) { return new Endpoint(host, 0, 1000); } // TODO(trustin): Remove weight and make Endpoint a pure endpoint representation. // We could specify an additional attributes such as weight/priority // when adding an Endpoint to an EndpointGroup. /** * Creates a new host {@link Endpoint}. */ public static Endpoint of(String host, int port, int weight) { requireNonNull(host, "host"); validatePort("port", port); validateWeight(weight); return new Endpoint(host, port, weight); } private final String groupName; private final String host; private final int port; private final int weight; private String authority; private Endpoint(String groupName) { this.groupName = groupName; host = null; port = 0; weight = 0; } private Endpoint(String host, int port, int weight) { this.host = host; this.port = port; this.weight = weight; groupName = null; } /** * Returns {@code true} if this endpoint refers to a group. */ public boolean isGroup() { return groupName != null; } /** * Resolves this endpoint into a host endpoint. * * @return the {@link Endpoint} resolved by {@link EndpointGroupRegistry}. * {@code this} if this endpoint is already a host endpoint. */ public Endpoint resolve() { if (isGroup()) { return EndpointGroupRegistry.selectNode(groupName); } else { return this; } } /** * Returns the group name of this endpoint. * * @throws IllegalStateException if this endpoint is not a group endpoint */ public String groupName() { ensureGroup(); return groupName; } /** * Returns the host name of this endpoint. * * @throws IllegalStateException if this endpoint is not a host endpoint */ public String host() { ensureSingle(); return host; } /** * Returns the port number of this endpoint. * * @throws IllegalStateException if this endpoint is not a host endpoint or * this endpoint does not have its port specified. */ public int port() { ensureSingle(); if (port == 0) { throw new IllegalStateException("port not specified"); } return port; } /** * Returns the port number of this endpoint. * * @param defaultPort the default port number to use when this endpoint does not have its port specified * * @throws IllegalStateException if this endpoint is not a host endpoint */ public int port(int defaultPort) { ensureSingle(); validatePort("defaultPort", defaultPort); return port != 0 ? port : defaultPort; } /** * Returns a new host endpoint with the specified default port number. * * @return the new endpoint whose port is {@code defaultPort} if this endpoint does not have its port * specified. {@code this} if this endpoint already has its port specified. * * @throws IllegalStateException if this endpoint is not a host endpoint */ public Endpoint withDefaultPort(int defaultPort) { ensureSingle(); validatePort("defaultPort", defaultPort); return port != 0 ? this : new Endpoint(host(), defaultPort, weight()); } /** * Returns a new host endpoint with the specified weight. * * @return the new endpoint with the specified weight. {@code this} if this endpoint has the same weight. * * @throws IllegalStateException if this endpoint is not a host endpoint */ public Endpoint withWeight(int weight) { ensureSingle(); validateWeight(weight); return this.weight == weight ? this : new Endpoint(host(), port, weight); } /** * Returns the weight of this endpoint. */ public int weight() { ensureSingle(); return weight; } /** * Converts this endpoint into the authority part of a URI. * * @return the authority string */ public String authority() { String authority = this.authority; if (authority != null) { return authority; } if (isGroup()) { authority = "group:" + groupName; } else if (port != 0) { authority = host() + ':' + port; } else { authority = host(); } return this.authority = authority; } private void ensureGroup() { if (!isGroup()) { throw new IllegalStateException("not a group endpoint"); } } private void ensureSingle() { if (isGroup()) { throw new IllegalStateException("not a host:port endpoint"); } } private static void validatePort(String name, int port) { if (port <= 0 || port > 65535) { throw new IllegalArgumentException(name + ": " + port + " (expected: 1-65535)"); } } private static void validateWeight(int weight) { if (weight <= 0) { throw new IllegalArgumentException("weight: " + weight + " (expected: > 0)"); } } @Override public String toString() { return "Endpoint(" + authority() + '/' + weight + ')'; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Endpoint)) { return false; } final Endpoint that = (Endpoint) obj; return authority().equals(that.authority()) && weight() == that.weight(); } @Override public int hashCode() { return authority().hashCode(); } }