Search code examples
protocol-buffersgrpcesp8266grpc-javananopb

HTTP/2 client preface string missing or corrupt for C client gRPC using HTTPClient


I am getting "HTTP/2 client preface string missing or corrupt."

My thoughts are that it has to do with the headers not being set correctly. It is likely the implementation of WifiClient/WifiSecureClient. I've been thinking about this for over several weeks and I'm stuck. Any advice?

[Updated: Answer below]

The client was generated using the nanopb protocol buffer compiler:

protoc --plugin=protoc-gen-nanopb=~/grpc/nanopb/generator/protoc-gen-nanopb --nanopb_out=. helloworld.proto

Arduino client:

DHT dht(DHTPIN, DHTTYPE);

WiFiClient client;
//WiFiClientSecure client;

void setup() {
  Serial.setDebugOutput(true);
  Serial.begin(115200);
  delay(10);

  WiFi.begin("<SSID>", "<My Password>");
  delay(3000);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("WIFI connection failed, reconnecting...");
    delay(2000);
  }

  Serial.print("WiFi connected, ");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  Serial.println("Starting DHT11 sensor...");
  dht.begin();
}


void loop() {
    Serial.print("connecting to ");
    Serial.println(addr);
    
//    client.setInsecure(); 
  if (!client.connect(addr, port)) {
    Serial.println(addr);
    Serial.println(port);
    Serial.println("connection failed");
    Serial.println("wait 5 sec to reconnect...");
    delay(5000);
    return;
  }

  Serial.println("reading humidity/temp...");
  float hum = dht.readHumidity();
  float tmp = dht.readTemperature(true);

  Serial.println(hum);
  Serial.println(tmp);

  if (isnan(hum) || isnan(tmp)) {
    Serial.println("failed to read sensor data");
    delay(2000);
    return;
  }

  float hiCel = dht.computeHeatIndex(tmp, hum, true);

  helloworld_TempEvent temp = helloworld_TempEvent_init_zero;
  temp.deviceId = 1;
  temp.eventId = 0;
  temp.humidity = hum;
  temp.tempCel = tmp;
  temp.heatIdxCel = hiCel;

  sendTemp(temp);
  delay(1000);
}

void sendTemp(helloworld_TempEvent e) {
  uint8_t buffer[128];
  pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));

  if (!pb_encode(&stream, helloworld_TempEvent_fields, &e)) {
    Serial.println("failed to encode temp proto");
    Serial.println(PB_GET_ERROR(&stream));
    return;
  }
  
  Serial.print("sending temp...   ");
  Serial.println(e.tempCel);
  client.write(buffer, stream.bytes_written);
}

The server was generated using the standard java protocol buffer compiler. The only thing I changed was adding a TempEvent (below).

... (helloworld template stuff) ...

// The request message containing temperatures
message TempEvent {
    int32 deviceId = 1;
    int32 eventId = 2;
    float humidity = 3;
    float tempCel = 4;
    float heatIdxCel = 5;
}

The sample java client works without any issues. Where my problem lies is the simple client using nanopb on an ESP8266-01 wifi module which is sending the data using gRPC.

public class Server {

    // Doesn't work
    public static void main(String[] args) throws IOException, InterruptedException {
        io.grpc.Server server = ServerBuilder
                .forPort(8080)
                .addService(new HelloServiceImpl()).build();
        server.start();
        server.awaitTermination();
    }

    // Works just fine
    public static void main(String[] args) throws IOException, InterruptedException {

        try (ServerSocket server = new ServerSocket(8080)) {
            System.out.println("Server accepting connections on port " + server.getLocalPort());
            TemperatureClient tempClient = new TemperatureClient();
            while(true) {

                Socket client = server.accept();
                System.out.println("Client connected using remote port " + client.getPort());

                final Thread t = new Thread(() -> {
                    try {
                        TempEvent p = TempEvent.parseFrom(client.getInputStream());
                        float i = p.getTempCel();
                        System.out.println("TEMP " + i);
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                });
                t.start();
            }
        }
    }

The client is able to hit the server:

Nov 29, 2021 5:49:30 PM io.grpc.netty.shaded.io.grpc.netty.NettyServerTransport notifyTerminated
INFO: Transport failed
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 080c10641d0000d84125e17aa0422de4459e42
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:108)
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:306)
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239)
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438)
    at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
    at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
    at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.grpc.netty.shaded.io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.grpc.netty.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.grpc.netty.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:831)

To debug this, I wanted to first see if I could use grpcurl, but I get this:

localhost@pro ~ % grpcurl -plaintext localhost:50051 list
Failed to list services: server does not support the reflection API

localhost@pro ~ % grpcurl -insecure localhost:50051 list
Failed to dial target host "localhost:50051": tls: first record does not look like a TLS handshake

I started looking into the implementation for WifiClient.h used in my implementation code, but does anyone have any ideas on a simple way to test this without digging into everything? I was thinking this should be super simple... but it is turning out to be much more entailed to generate a simple client than I thought. I feel like I am missing something here.

From other forums on here: "The client and server aren't agreeing. Typically this is because one is plaintext and the other using TLS. But it can also be due to HTTP/1 vs HTTP/2 in certain environments."

After looking at the Go Lang implementation, I just tried using WiFiClientSecure client.setInsecure(); // didn't work and the hex dump is below.

17:36:33.030 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] OUTBOUND SETTINGS: ack=false settings={MAX_CONCURRENT_STREAMS=2147483647, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192}
17:36:33.031 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] OUTBOUND WINDOW_UPDATE: streamId=0 windowSizeIncrement=983041
17:36:33.063 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] OUTBOUND GO_AWAY: lastStreamId=2147483647 errorCode=1 length=126 bytes=485454502f3220636c69656e74207072656661636520737472696e67206d697373696e67206f7220636f72727570742e204865782064756d7020666f72207265...
17:36:33.064 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler - [id: 0x41b96938, L:/192.168.0.23:8080 - R:/192.168.0.24:61587] Sent GOAWAY: lastStreamId '2147483647', errorCode '1', debugData 'HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 16030100d4010000d00303000000005c2f03aae7147c5f36'. Forcing shutdown of the connection.
Dec 10, 2021 5:36:33 PM io.grpc.netty.shaded.io.grpc.netty.NettyServerTransport notifyTerminated
INFO: Transport failed
io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception: HTTP/2 client preface string missing or corrupt. Hex dump for received bytes: 16030100d4010000d00303000000005c2f03aae7147c5f36
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2Exception.connectionError(Http2Exception.java:108)
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.readClientPrefaceString(Http2ConnectionHandler.java:306)
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler$PrefaceDecoder.decode(Http2ConnectionHandler.java:239)
    at io.grpc.netty.shaded.io.netty.handler.codec.http2.Http2ConnectionHandler.decode(Http2ConnectionHandler.java:438)
    at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:508)
    at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:447)
    at io.grpc.netty.shaded.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.grpc.netty.shaded.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.grpc.netty.shaded.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.grpc.netty.shaded.io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
    at io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.grpc.netty.shaded.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.grpc.netty.shaded.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.grpc.netty.shaded.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:831)

Solution

  • WiFiClient client;
    if (!client.connect(addr, port)) {
    

    This forms a basic TCP connection. However, gRPC is a complex protocol based on HTTP/2. Currently you are just writing raw protobuf messages to a TCP socket, which can work for communication but is certainly not what a gRPC server is expecting.

    Nanopb does not have gRPC support in itself. There is a third-party project adding it, but it is currently unmaintained.