Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protocol error (promised bytes vs received bytes) when using web / fetch client #13

Open
benridley opened this issue Mar 5, 2025 · 7 comments
Assignees

Comments

@benridley
Copy link

benridley commented Mar 5, 2025

Describe the bug

I've been building a Flutter app that uses ConnectRPC. Everything was working fine on the MacOS build, but when I tried building and running on the web I started getting protocol errors.

To Reproduce

When I try and call my server endpoint, I get: Protocol error: promised 361 bytes, received 408.

The exception itself is coming from length checking in the toBytes function in lib/src/protocol/sink.dart. I tried removing the length checks, and this does seem to resolve the problem.

Here's the relevant protobuf definitions, I'm calling the login service:

service TimesheetService {
  rpc Login(LoginRequest) returns (LoginResponse);
...
message LoginResponse {
  User user = 1;
  AuthToken auth_token = 2;
}
...
message User {
  string id = 1;  // UUID
  string email = 2;
  UserRole role = 3;
  string name = 4;
  optional string myob_employee_id = 5;
}

Now, here's where I instantiate the client in my codebase:

// This function is imported by timesheet_service.dart when compiled for web
RemoteTimesheetServiceImpl createRemoteTimesheetService(
    {required String baseUrl}) {
  return RemoteTimesheetServiceImpl(
    baseUrl: baseUrl,
    createTransport: (baseUrl) => protocol.Transport(
      baseUrl: baseUrl,
      codec: const JsonCodec(),
      httpClient: web.createHttpClient(),
    ),
  );
}

Interestingly, this doesn't happen for all responses. If I mistype the password, I receive the message back from the server saying the user was not found, so no protocol error there.

Environment (please complete the following information):

  • connectrpc version: 0.3.0
  • Dart SDK version: 3.5.4
  • Flutter SDK version: 3.24.5
  • Platform/Device and version: web
@benridley benridley added the bug Something isn't working label Mar 5, 2025
@srikrsna-buf
Copy link
Member

Hey! Can you tell me the protocol you are using?

@srikrsna-buf
Copy link
Member

Can you also tell me the Content-Length and Content-Encoding headers?

@benridley
Copy link
Author

Hey @srikrsna-buf, thanks for the quick response.

Can you tell me the protocol you are using?

I'm using Connect.

Can you also tell me the Content-Length and Content-Encoding headers?

Sure. When I make the request that breaks, here's the headers:

HTTP/1.1 200 OK
Accept-Encoding: gzip
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:56620
Access-Control-Expose-Headers: Grpc-Status, Grpc-Message, Grpc-Status-Details-Bin
Content-Encoding: gzip
Content-Type: application/proto
Vary: Origin
Date: Thu, 06 Mar 2025 10:52:21 GMT
Content-Length: 319

And the message back is:

protocol error: promised 319 bytes, received 309

@srikrsna-buf
Copy link
Member

That's odd there's a check to remove the content-length header if the content-encoding is set, because fetch implementations decode the body but don't change the header.

// Browsers and other fetch environments decompress the response,
// but retain the original content-encoding and content-length headers.
//
// https://github.com/wintercg/fetch/issues/23
if (resHeader.contains('content-encoding')) {
resHeader.remove('content-encoding');
resHeader.remove('content-length');
}

I'll reproduce and identify a fix, thanks for the detailed report.

@benridley
Copy link
Author

That's odd there's a check to remove the content-length header if the content-encoding is set, because fetch implementations decode the body but don't change the header.

connect-dart/packages/connect/lib/src/web.dart

Lines 70 to 77 in c5dfa2e

// Browsers and other fetch environments decompress the response,
// but retain the original content-encoding and content-length headers.
//
// wintercg/fetch#23
if (resHeader.contains('content-encoding')) {
resHeader.remove('content-encoding');
resHeader.remove('content-length');
}
I'll reproduce and identify a fix, thanks for the detailed report.

Thanks @srikrsna-buf, appreciate you jumping on this! I would have contributed a PR myself but I'm out my depth here.

I did spot that. I tried removing it, and also tried tweaking it to be case insensitive etc... It didn't seem to have any impact.

I've been testing my app the past few days with the exceptions removed (literally just deleted the checks), and it seems fine - which suggests the toBytes implementation doesn't actually rely on the content-length header for anything meaningful. Hope that helps!

@jakegibson
Copy link

I am also experiencing this but for only one out two services. Can also confirm it is only only with the web transport.

@srikrsna-buf
Copy link
Member

It took me some time to figure this one out as I was unable to replicate with a regular setup, I should've looked at the response headers more closely. Can you add Content-Encoding to Access-Control-Expose-Headers list. I'll add a note regarding this to the docs.

@srikrsna-buf srikrsna-buf removed the bug Something isn't working label Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants