All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.nats.client.support.NatsUri Maven / Gradle / Ivy

There is a newer version: 2.20.5
Show newest version
// Copyright 2023 The NATS Authors
// Licensed 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 io.nats.client.support;

import io.nats.client.Options;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.regex.Pattern;

import static io.nats.client.support.NatsConstants.*;

public class NatsUri {
    private static final int NO_PORT = -1;
    private static final String UNABLE_TO_PARSE = "Unable to parse URI string.";
    private static final String UNSUPPORTED_SCHEME = "Unsupported NATS URI scheme.";
    private static final String URI_E_ALLOW_TRY_PREFIXED = "Illegal character in scheme name at index";
    private static final Pattern IPV4_RE = Pattern.compile("(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])");
    private static final String COLON_SLASH_SLASH = "://";

    public static NatsUri DEFAULT_NATS_URI = new NatsUri();

    private final URI uri;
    private boolean isSecure;
    private boolean isWebsocket;
    private boolean hostIsIpAddress;

    public URI getUri() {
        return uri;
    }

    public String getScheme() {
        return uri.getScheme();
    }

    public String getHost() {
        return uri.getHost();
    }

    public int getPort() {
        return uri.getPort();
    }

    public String getUserInfo() {
        return uri.getUserInfo();
    }

    public boolean isSecure() {
        return isSecure;
    }

    public boolean isWebsocket() {
        return isWebsocket;
    }

    public boolean hostIsIpAddress() {
        return hostIsIpAddress;
    }

    public NatsUri reHost(String newHost) throws URISyntaxException {
        String newUrl = (uri.getRawUserInfo() == null)
            ? uri.getScheme() + "://" + newHost + ":" + uri.getPort()
            : uri.getScheme() + "://" + uri.getRawUserInfo() + "@" + newHost + ":" + uri.getPort();
        return new NatsUri(newUrl, uri.getScheme());
    }

    @Override
    public String toString() {
        return uri.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if (this == o) return true;
        if (o instanceof NatsUri) {
            o = ((NatsUri)o).uri;
        }
        return uri.equals(o);
    }

    @Override
    public int hashCode() {
        return uri.hashCode();
    }

    public NatsUri() {
        try {
            uri = new URI(Options.DEFAULT_URL);
        } catch (URISyntaxException e) {
            // seriously, this better not happen!
            throw new RuntimeException(e);
        }
        postConstruct();
    }

    public NatsUri(URI uri) throws URISyntaxException {
        this(uri.toString(), null);
    }

    public NatsUri(String url) throws URISyntaxException {
        this(url, null);
    }

    public NatsUri(String url, String defaultScheme) throws URISyntaxException {
    /*
        test string --> result of new URI(String)

        [1] provide protocol and try again
        1.2.3.4:4222 --> Illegal character in scheme name at index 0: 1.2.3.4:4222

        [2] throw exception
        proto:// --> Expected authority at index

        [3] null scheme, non-empty path? provide protocol and try again
        host    --> scheme:'null', host:'null', up:'null', port:-1, path:'host'
        1.2.3.4 --> scheme:'null', host:'null', up:'null', port:-1, path:'1.2.3.4'

        [4] has scheme but null host/path, provide protocol and try again
        x:p@host         --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
        x:[email protected]      --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
        x:4222           --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
        x:p@host:4222    --> scheme:'x', host:'null', up:'null', port:-1, path:'null'
        x:[email protected]:4222 --> scheme:'x', host:'null', up:'null', port:-1, path:'null'

        [5] has scheme, null host, non-null path, make/throw
        proto://u:p@      --> scheme:'proto', host:'null', up:'null', port:-1, path:''
        proto://:4222     --> scheme:'proto', host:'null', up:'null', port:-1, path:''
        proto://u:p@:4222 --> scheme:'proto', host:'null', up:'null', port:-1, path:''

        [6] has scheme and host just needs port
        proto://host        --> scheme:'proto', host:'host', up:'null', port:-1, path:''
        proto://u:p@host    --> scheme:'proto', host:'host', up:'u:p', port:-1, path:''
        proto://1.2.3.4     --> scheme:'proto', host:'1.2.3.4', up:'null', port:-1, path:''
        proto://u:[email protected] --> scheme:'proto', host:'1.2.3.4', up:'u:p', port:-1, path:''

        [7] has scheme, host and port
        proto://host:4222        --> scheme:'proto', host:'host', up:'null', port:4222, path:''
        proto://u:p@host:4222    --> scheme:'proto', host:'host', up:'u:p', port:4222, path:''
        proto://1.2.3.4:4222     --> scheme:'proto', host:'1.2.3.4', up:'null', port:4222, path:''
        proto://u:[email protected]:4222 --> scheme:'proto', host:'1.2.3.4', up:'u:p', port:4222, path:''
     */

        String prefix;
        if (defaultScheme == null) {
            prefix = NATS_PROTOCOL_SLASH_SLASH;
        }
        else {
            prefix = defaultScheme.toLowerCase();
            if (!prefix.endsWith(COLON_SLASH_SLASH)) {
                prefix += COLON_SLASH_SLASH;
            }
        }

        Helper helper = parse(url, true, prefix);
        String scheme = helper.uri.getScheme();
        String path = helper.uri.getPath();
        if (scheme == null) {
            if (path != null) {
                // [3]
                helper = tryPrefixed(helper, prefix);
                scheme = helper.uri.getScheme();
                path = helper.uri.getPath();
            }
            else {
                // [X] not in the examples so don't know what to do, we are done
                throw new URISyntaxException(url, UNABLE_TO_PARSE);
            }
        }

        String host = helper.uri.getHost();
        if (host == null) {
            if (path == null) {
                // [4]
                helper = tryPrefixed(helper, prefix);
                scheme = helper.uri.getScheme();
                host = helper.uri.getHost();
            }
            else {
                // [5]
                throw new URISyntaxException(url, UNABLE_TO_PARSE);
            }
        }

        if (host == null) {
            // if these aren't here by now, nothing we can do
            throw new URISyntaxException(url, UNABLE_TO_PARSE);
        }

        String lower = scheme.toLowerCase();
        if (!KNOWN_PROTOCOLS.contains(lower)) {
            throw new URISyntaxException(url, UNSUPPORTED_SCHEME);
        }
        if (!lower.equals(scheme)) {
            helper.url = helper.url.replace(scheme, lower);
        }

        if (helper.uri.getPort() == NO_PORT) {
            // [6]
            uri = new URI(helper.url + ":" + DEFAULT_PORT);
        }
        else {
            uri = new URI(helper.url);
        }

        postConstruct();
    }

    private void postConstruct() {
        String s = uri.getScheme().toLowerCase();
        isSecure = SECURE_PROTOCOLS.contains(s);
        isWebsocket = WSS_PROTOCOLS.contains(s);
        s = uri.getHost();
        hostIsIpAddress = IPV4_RE.matcher(s).matches() || s.startsWith("[") && s.endsWith("]");
    }

    static class Helper {
        String url;
        URI uri;
    }

    private Helper tryPrefixed(Helper helper, String prefix) throws URISyntaxException {
        return parse(prefix + helper.url, false, prefix);
    }

    private Helper parse(String inUrl, boolean allowTryPrefixed, String prefix) throws URISyntaxException {
        Helper helper = new Helper();
        try {
            helper.url = inUrl.trim();
            helper.uri = new URI(helper.url);
            return helper;
        }
        catch (URISyntaxException e) {
            if (allowTryPrefixed && e.getMessage().contains(URI_E_ALLOW_TRY_PREFIXED)) {
                // [4]
                return tryPrefixed(helper, prefix);
            }
            else {
                // [5]
                throw e;
            }
        }
    }

    public static String join(String delimiter, List uris) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < uris.size(); i++) {
            if (i > 0) {
                sb.append(delimiter);
            }
            sb.append(uris.get(i).toString());
        }
        return sb.toString();
    }

    private String equivalentComparable() {
        return uri.getHost().toLowerCase() + uri.getPort();
    }

    public boolean equivalent(NatsUri other) {
        return equivalentComparable().compareTo(other.equivalentComparable()) == 0;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy