Azure / azure-notificationhubs-java-backend

Azure Notification Hubs SDK for Java
https://docs.microsoft.com/en-us/azure/notification-hubs/
Apache License 2.0
35 stars 49 forks source link

[BUG] Notification Hubs incompatible with Spring? java.lang.RuntimeException: org.apache.hc.core5.http.ParseException: Invalid protocol version; error at offset 0: <[0x2e][0x2f][0x30][0x31][0x32][0x33][0x34][0x35][0x36][0x37][0x38][0x39][0x3a][0x3b][0x3c][0x3d][0x3e]> #159

Open optimojordan opened 1 year ago

optimojordan commented 1 year ago

Describe the bug When attempting to send a simple notification using com.windowsazure:Notification-Hubs-java-sdk:1.0.3 using a brand new, empty project that does nothing else, it appears that if Spring is also a dependency in the build file, this exception occurs when we attempt to send the notification.

Exception or Stack Trace java.lang.RuntimeException: org.apache.hc.core5.http.ParseException: Invalid protocol version; error at offset 0: <[0x2e][0x2f][0x30][0x31][0x32][0x33][0x34][0x35][0x36][0x37][0x38][0x39][0x3a][0x3b][0x3c][0x3d][0x3e]> at com.windowsazure.messaging.SyncCallback.failed(SyncCallback.java:53) at com.windowsazure.messaging.NotificationHubsService$1.failed(NotificationHubsService.java:78) at org.apache.hc.core5.concurrent.BasicFuture.failed(BasicFuture.java:138) at org.apache.hc.core5.concurrent.ComplexFuture.failed(ComplexFuture.java:86) at org.apache.hc.client5.http.impl.async.InternalAbstractHttpAsyncClient$1$3.failed(InternalAbstractHttpAsyncClient.java:345) at org.apache.hc.client5.http.impl.async.AsyncRedirectExec$1.failed(AsyncRedirectExec.java:246) at org.apache.hc.client5.http.impl.async.AsyncHttpRequestRetryExec$1.failed(AsyncHttpRequestRetryExec.java:167) at org.apache.hc.client5.http.impl.async.AsyncProtocolExec$1.failed(AsyncProtocolExec.java:281) at org.apache.hc.client5.http.impl.async.HttpAsyncMainClientExec$1.failed(HttpAsyncMainClientExec.java:124) at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamHandler.failed(ClientHttp1StreamHandler.java:295) at org.apache.hc.core5.http.impl.nio.ClientHttp1StreamDuplexer.terminate(ClientHttp1StreamDuplexer.java:193) at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.shutdownSession(AbstractHttp1StreamDuplexer.java:164) at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onException(AbstractHttp1StreamDuplexer.java:404) at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.inputReady(AbstractHttp1IOEventHandler.java:66) at org.apache.hc.core5.http.impl.nio.ClientHttp1IOEventHandler.inputReady(ClientHttp1IOEventHandler.java:41) at org.apache.hc.core5.reactor.ssl.SSLIOSession.decryptData(SSLIOSession.java:575) at org.apache.hc.core5.reactor.ssl.SSLIOSession.access$400(SSLIOSession.java:72) at org.apache.hc.core5.reactor.ssl.SSLIOSession$1.inputReady(SSLIOSession.java:172) at org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:133) at org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:51) at org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:178) at org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:127) at org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:85) at org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: org.apache.hc.core5.http.ParseException: Invalid protocol version; error at offset 0: <[0x2e][0x2f][0x30][0x31][0x32][0x33][0x34][0x35][0x36][0x37][0x38][0x39][0x3a][0x3b][0x3c][0x3d][0x3e]> at org.apache.hc.core5.http.message.BasicLineParser.parseProtocolVersion(BasicLineParser.java:110) at org.apache.hc.core5.http.message.BasicLineParser.parseStatusLine(BasicLineParser.java:181) at org.apache.hc.core5.http.impl.nio.DefaultHttpResponseParser.createMessage(DefaultHttpResponseParser.java:83) at org.apache.hc.core5.http.impl.nio.DefaultHttpResponseParser.createMessage(DefaultHttpResponseParser.java:44) at org.apache.hc.core5.http.impl.nio.AbstractMessageParser.parseHeadLine(AbstractMessageParser.java:115) at org.apache.hc.core5.http.impl.nio.AbstractMessageParser.parse(AbstractMessageParser.java:167) at org.apache.hc.core5.http.impl.nio.AbstractMessageParser.parse(AbstractMessageParser.java:51) at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.parseMessageHead(AbstractHttp1StreamDuplexer.java:256) at org.apache.hc.core5.http.impl.nio.AbstractHttp1StreamDuplexer.onInput(AbstractHttp1StreamDuplexer.java:287) at org.apache.hc.core5.http.impl.nio.AbstractHttp1IOEventHandler.inputReady(AbstractHttp1IOEventHandler.java:64) ... 11 more

To Reproduce 1) Create a simple Spring project with no content 2) Add the following to the build.gradle file:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.0.1'
    id 'io.spring.dependency-management' version '1.1.0'
}

sourceCompatibility = '17'

repositories {
    mavenCentral()
    google()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    testImplementation 'junit:junit:4.13.2'

    // Azure Notification Hub
    implementation 'com.windowsazure:Notification-Hubs-java-sdk:1.0.3'
    testImplementation 'com.windowsazure:Notification-Hubs-java-sdk:1.0.3'
}

tasks.named('test') {
    useJUnitPlatform()
}

// if you omit this you will get the following exception:
// java.lang.IllegalArgumentException: LoggerFactory is not a Logback LoggerContext but Logback is on the classpath. Either remove Logback or the competing implementation (class org.slf4j.impl.SimpleLoggerFactory loaded from ...PATH...). If you are using WebLogic you will need to add 'org.slf4j' to prefer-application-packages in WEB-INF/weblogic.xml: org.slf4j.impl.SimpleLoggerFactory
configurations {
    all {
        exclude group: 'ch.qos.logback', module: 'logback-classic'
    }
}

3) Create a simple unit test to confirm the project can run, e.g.

    @Test
    void testCanIRun() {
        assertEquals(4,2+2);
    }

This test should run and pass.

4) Now create a simple unit test to send a simple notification (see snippet below)

Code Snippet

package com.foo.bar;

import com.windowsazure.messaging.Notification;
import com.windowsazure.messaging.NotificationHub;
import org.junit.jupiter.api.Test;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.Assert.assertEquals;

@SpringBootTest
public class PushnotificationsApplicationTests {

    private static final String ARBITRARY_TOKEN = "TOKENGOESHERE";
    String SANDBOX_CONNECTION_STRING = "CONNSTRINGGOESHERE";
    String SANDBOX_HUB = "HUBGOESHERE";

    private final Logger log = LoggerFactory.getLogger(PushnotificationsApplicationTests.class);
    String prodConnectionString;
    String prodHubName;
    String sandboxConnectionString;
    String sandboxHubName;

    @Test
    void contextLoads() {
    }

    @Test
    void testCanIRun() {
        assertEquals(4,2+2);
    }

    @Test
    public void testSendASimpleNotification() {
        try {

            setSandBoxHubName(SANDBOX_HUB);
            setSandboxConnectionString(SANDBOX_CONNECTION_STRING);
            boolean useSandbox = true;
            boolean result = sendSingleNotification("notification content", ARBITRARY_TOKEN, useSandbox);
            assert(result);
        } catch (Exception e) {
            log.error("Azure Push Notification failed.", e);
        }
    }

    // HELPER METHODS

    public boolean sendSingleNotification(String content, String token, boolean isSandbox) throws Exception {
        try {
            String message = "{\"notification\":{\"title\":\"This is the title\", \"body\": \"this is the body\"}, \"data\": {\"property1\": \"foo\", \"property2\": 42}}";
            NotificationHub hub = getNotificatonHub(isSandbox);
            Notification notification = getNotification(message);
            hub.sendDirectNotification(notification, token);
            return true;
        } catch (Exception e) {
            log.error("Azure Push Notification failed.", e);
        }
        return false;
    }

    private NotificationHub getNotificatonHub(boolean isSandbox) {
        return new NotificationHub(
                isSandbox ? sandboxConnectionString : prodConnectionString,
                isSandbox ? sandboxHubName : prodHubName);
    }

    private Notification getNotification(String content) {
        return Notification.createFcmNotification(content);
    }

    public void setSandBoxHubName(String s) {
        sandboxHubName = s;
    }

    public void setSandboxConnectionString(String s) {
        sandboxConnectionString = s;
    }

}

Expected behavior Expected a notification to be sent to the device whose token corresponds with ARBITRARY_TOKEN

Screenshots If applicable, add screenshots to help explain your problem.

Setup (please complete the following information):

Additional context This was also tried with a simple Android app project and simple notifications succeeded, suggesting that classes involved in the Android gradle plugin(s) or dependencies are possibly superseding the classes that are causing the Invalid Protocol Error in the non-android app.

Information Checklist Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issue as an incomplete report. Please do not provide any private information in this bug report.

krevelen commented 1 year ago

For me upgrading to a newer httpcomponents version helped, since it is more robust against improper server responses, e.g. using Maven:

<properties>
    <httpcomponents.version>5.2.1</httpcomponents.version>
</properties>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents.client5</groupId>
            <artifactId>httpclient5</artifactId>
            <version>${httpcomponents.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents.core5</groupId>
            <artifactId>httpcore5</artifactId>
            <version>${httpcomponents.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents.core5</groupId>
            <artifactId>httpcore5-h2</artifactId>
            <version>${httpcomponents.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents.core5</groupId>
            <artifactId>httpcore5-reactive</artifactId>
            <version>${httpcomponents.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>