HTTP Binding refuses to connect ("java.net.URI.getHost() is null")

Hi all,

I am rather new to OpenHAB, so please forgive me when I might not see something very obvious.

I wanted to create a binding to query the status of a small self-made controller that basically returns a power and a temperature. The controller is in use by other home automation tools for years now. When I open the controller’s URL “http://192.168.2.20/shelly” in Firefox I just see the decoded JSON data. So the controller itself answeres properly. So I do not suspect any malfunction there.

But openHAB refuses to get data from it. I just get this error message:

|19:43:06.292|ERROR|org.openhab.core.internal.common.AbstractInvocationHandler|An error occurred while calling method ‘ThingHandler.initialize()’ on ‘org.openhab.binding.http.internal.HttpThingHandler@174b4ed’: Cannot invoke String.equalsIgnoreCase(String) because the return value of java.net.URI.getHost() is null|
|19:43:06.304|ERROR|org.openhab.core.thing.internal.ThingManagerImpl|Exception occurred while initializing handler of thing ‘http:url:f203a2ef74’: Cannot invoke String.equalsIgnoreCase(String) because the return value of java.net.URI.getHost() is null|

What could be triggering this problem?

This is the code for the binding:

UID: http:url:f203a2ef74
label: HTTP Warmwasserkontrollator
thingTypeUID: http:url
configuration:
  authMode: BASIC
  ignoreSSLErrors: true
  baseURL: http://192.168.2.20/shelly
  password: x
  delay: 0
  stateMethod: GET
  refresh: 30
  commandMethod: GET
  contentType: application/json
  timeout: 3000
  bufferSize: 2048
  username: x
channels:
  - id: last-failure
    channelTypeUID: http:request-date-time
    label: Last Failure
    configuration: {}
  - id: last-success
    channelTypeUID: http:request-date-time
    label: Last Success
    configuration: {}
  - id: temperature
    channelTypeUID: http:string
    label: temperature
    description: ""
    configuration:
      mode: READONLY
      stateTransformation:
        - JSONPATH:$.ext_temperature.tC
  - id: power
    channelTypeUID: http:string
    label: Power
    description: ""
    configuration:
      mode: READONLY
      stateTransformation:
        - JSONPATH:$.meters.power

Welcom to our community!

You are not creating a binding, but a Thing using the Http Binding.
Can you show us the json result of the device please.

yes true, I am creating a thing by using a binding…

this is the answer of my controller:
{ “meters”:[{“power”:0.00}],“ext_temperature”:[{“tC”:59}],“relays”:[{“ison”:0}] }

Curious why this happens. Copied your thing config from above and I see no errors. Of course I get no data but no exception. Please perform following steps to gather more information:

  • enable TRACE information for HTTP binding. This will show more detailed logs
  • your json result contains JSON Arrays which are not reflected in the current JSONPATH transformation. Needs to be JSONPATH:$.ext_temperature[0].tC and JSONPATH:$.meters[0].power
  • finally which OH version you’re running on? Is it 4.x or already 5?

Have you installed the JSON-Transformation ?

1 Like

It looks to me like this happens when in the authentication handling while trying to initialize the Thing. The logged Exception is hard to read since it’s not in “code fences”, but it doesn’t seem complete to me. I’d expect to find more information, including line numbers etc.

I think I’ve found where the Excetion is most likely thrown, that is in org.eclipse.jetty.client.util.AbstractAuthentication.matchesURI():

    public static boolean matchesURI(URI uri1, URI uri2)
    {
        String scheme = uri1.getScheme();
        if (scheme.equalsIgnoreCase(uri2.getScheme()))
        {
            if (uri1.getHost().equalsIgnoreCase(uri2.getHost()))
            {
                // Handle default HTTP ports.
                int thisPort = HttpClient.normalizePort(scheme, uri1.getPort());
                int thatPort = HttpClient.normalizePort(scheme, uri2.getPort());
                if (thisPort == thatPort)
                {
                    // Use decoded URI paths.
                    return uri2.getPath().startsWith(uri1.getPath());
                }
            }
        }
        return false;
    }

…on the line where the hosts are compared. Why one of them are null at this point is much harder to figure out, it doesn’t immediately look like something that can happen (although obviously it does). I think a bit more log info from TRACE logging, pasted in code fences, might be helpful.

To answer all the questions:
weymann : Trace is enabled now. See attachment for the log I got…
weymann : fixing the JSONPATH transformation did not help
weymann : its OH 4.3.5
fibu-freak: yes, JSON-Transformation is installed
Nadahar : interesting. My home-brew controller implements a very simplistic server. It has no authentication and maybe gets confused when the client is trying to authenticate anyway.

Yes, your configuration seems to indicate authentication:

authMode: BASIC

Maybe you should turn that off then?

1 Like

Hi All,
finally I found the problem! I looked for my test hardware that can be connected via USB to see a log of what’s going on on my controller. I tested it by accessing it through Firefox and it showed the incoming connection on the console. The I changed the IP address in OpenHAB to match the test hardware rather than the real thing. What I saw astonished me: I did not see any single connection attempt to the test hardware. So the error happens BEFORE the connection is even established. then I went back into the settings of the HTTP binding and unticked “ignore SSL errors” which is changeing the corresponding code line to:

ignoreSSLErrors: false

After that openHAB connected to my controller and could read the parameters correctly!

So thanks to all for trying to find the trouble. Great community! Thanks!!!
:heart_eyes: Andi

@Nadahar : it is not possible to turn it off… :face_with_diagonal_mouth:

Ok, that sounds like a strange design (of the HTTP binding).

This sounds like some kind of bug in the binding if you ask me. Maybe it’s failing if you have “ignore SSL errors” enabled, but use http: instead of https: for example. But, that shouldn’t matter. The “ignore SSL errors” option shouldn’t impact anything unless and until an SSL error actually exists.

I found this in HttpThingHandler:

            if (!config.username.isEmpty() || !config.password.isEmpty()) {
                switch (config.authMode) {
                    case BASIC_PREEMPTIVE:
                        config.headers.add("Authorization=Basic " + Base64.getEncoder()
                                .encodeToString((config.username + ":" + config.password).getBytes()));
                        logger.debug("Preemptive Basic Authentication configured for thing '{}'", thing.getUID());
                        break;
                    case TOKEN:
                        if (!config.password.isEmpty()) {
                            config.headers.add("Authorization=Bearer " + config.password);
                            logger.debug("Token/Bearer Authentication configured for thing '{}'", thing.getUID());
                        } else {
                            logger.warn("Token/Bearer Authentication configured for thing '{}' but token is empty!",
                                    thing.getUID());
                        }
                        break;
                    case BASIC:
                        authStore.addAuthentication(new BasicAuthentication(uri, Authentication.ANY_REALM,
                                config.username, config.password));
                        logger.debug("Basic Authentication configured for thing '{}'", thing.getUID());
                        break;
                    case DIGEST:
                        authStore.addAuthentication(new DigestAuthentication(uri, Authentication.ANY_REALM,
                                config.username, config.password));
                        logger.debug("Digest Authentication configured for thing '{}'", thing.getUID());
                        break;
                    default:
                        logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode,
                                thing.getUID());
                }
            } else {
                logger.debug("No authentication configured for thing '{}'", thing.getUID());
            }

The logic seems to imply that the way to active “no authentication” is to not configure a username or password. I’d rather see that there was an explicit NONE option here:

public enum HttpAuthMode {
    BASIC_PREEMPTIVE,
    BASIC,
    DIGEST,
    TOKEN
}

It’s just not intuitive, IMO, that the way you disable authentication is to provide a blank username and password. It’s not strange if you know it, but it’s not something I’d guess.

The only difference I can see that this option does in the code at a glance, is to decide which of these two HTTP clients that are used:

        this.secureClient = new HttpClient(new SslContextFactory.Client());
        this.insecureClient = new HttpClient(new SslContextFactory.Client(true));

The true parameter for insecureClient simply means “trust all”. So, it’s hard to understand exactly how this is related to the problem you experienced.

I tried that and it does not allow me not to set a user/password.

anyway: I briefly read through the relevant RFCs: Authentication is something initiated by the server. So in case the server does not ask for it (like in my case), the client should not bother.

What doesn’t allow that? The HTTP binding? That’s very strange, as that seems to be the intended way to avoid authentication.

That might be, but I’d say that the configuration would be less confusing for the user if there is no authentication in use, to be able to specify that, instead of having to use some “fake auth config” that doesn’t apply anyway. Also, from looking at the code, this doesn’t seem to be entirely how it works. For both BASIC_PREEMPTIVE and TOKEN the authentication header is set in the request before first contact with the server. For BASIC and DIGEST, the authentication is just “primed” so that the client have them, probably if requested by the server.

However, the error you experienced, seems to happen before any if this. I can’t be sure without actually debugging it, but most likely the error happens when the binding is trying to clear a authentication cache before setting the “correct” authentication. It’s mysterious to me exactly how this clearing of the authentication cache and the SSL trust setting conspire to cause the problem.