Skip to content

Commit 4486cbe

Browse files
authored
fix: Support 0 length messages (#34)
* Support 0 length messages, which are valid * Update contributing.md * Add specs * linter Co-authored-by: James Shkolnik <[email protected]>
1 parent 492e303 commit 4486cbe

11 files changed

+171
-33
lines changed

CONTRIBUTING.md

+71-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,75 @@ Use the `rake` command to run the entire test suite. This runs on docker due to
88

99
Unit tests can be run without docker by running `rspec spec/unit`
1010

11-
### Commit message format
11+
### Pull Request title/description format
1212

13-
Please follow semantic release formatting
14-
https://semantic-release.gitbook.io/semantic-release/#commit-message-format
15-
https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#type
13+
Each Pull Request description consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, a **scope** and a **subject**:
14+
15+
```commit
16+
<type>(<scope>): <subject>
17+
<BLANK LINE>
18+
<body>
19+
<BLANK LINE>
20+
<footer>
21+
```
22+
23+
The **header** is mandatory and the **scope** of the header is optional.
24+
25+
The **footer** can contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages).
26+
27+
#### Revert
28+
29+
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
30+
31+
#### Type
32+
33+
The type must be one of the following:
34+
35+
| Type | Description |
36+
|--------------|-------------------------------------------------------------------------------------------------------------|
37+
| **build** | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
38+
| **ci** | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
39+
| **docs** | Documentation only changes |
40+
| **feat** | A new feature |
41+
| **fix** | A bug fix |
42+
| **perf** | A code change that improves performance |
43+
| **refactor** | A code change that neither fixes a bug nor adds a feature |
44+
| **style** | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) |
45+
| **test** | Adding missing tests or correcting existing tests |
46+
| **chore** | Any other change that does not affect the published code (e.g. tool changes, config changes) |
47+
48+
#### Subject
49+
50+
The subject contains succinct description of the change:
51+
52+
- use the imperative, present tense: "change" not "changed" nor "changes"
53+
- no dot (.) at the end
54+
55+
#### Body
56+
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
57+
The body should include the motivation for the change and contrast this with previous behavior.
58+
59+
#### Footer
60+
The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.
61+
62+
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
63+
64+
#### Examples
65+
66+
```commit
67+
`fix(pencil): stop graphite breaking when too much pressure applied`
68+
```
69+
70+
```commit
71+
`feat(pencil): add 'graphiteWidth' option`
72+
73+
Fix #42
74+
```
75+
76+
```commit
77+
perf(pencil): remove graphiteWidth option`
78+
79+
BREAKING CHANGE: The graphiteWidth option has been removed.
80+
81+
The default graphite width of 10mm is always used for performance reasons.
82+
```

lib/grpc_web/message_framing.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ def pack_frames(frames)
1515
def unpack_frames(content)
1616
frames = []
1717
remaining_content = content
18+
1819
until remaining_content.empty?
1920
msg_length = remaining_content[1..4].unpack1('N')
20-
raise 'Invalid message length' if msg_length <= 0
21-
2221
frame_end = 5 + msg_length
2322
frames << ::GRPCWeb::MessageFrame.new(
2423
remaining_content[0].bytes[0],

spec/integration/envoy_server_ruby_client_spec.rb

+8
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ def say_hello(_request, _metadata = nil)
6363
end
6464
end
6565

66+
context 'for a method with empty request and response protos' do
67+
subject(:response) { client.say_nothing }
68+
69+
it 'returns the expected response from the service' do
70+
expect(response).to eq(EmptyResponse.new)
71+
end
72+
end
73+
6674
context 'for a network error' do
6775
let(:client_url) { 'http://envoy:8081' }
6876

spec/integration/ruby_server_js_client_spec.rb

+25
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414
let(:js_script) do
1515
<<-EOF
1616
var helloService = new #{js_client_class}('http://#{server.host}:#{server.port}', null, null);
17+
#{js_grpc_call}
18+
EOF
19+
end
20+
21+
# JS snippet to make the gRPC-Web call
22+
let(:js_grpc_call) do
23+
<<-EOF
1724
var x = new HelloRequest();
1825
x.setName('#{name}');
1926
window.grpcResponse = null;
@@ -82,6 +89,24 @@ def say_hello(_request, _metadata = nil)
8289
expect(js_error).to include('code' => 2, 'message' => 'RuntimeError: Some random error')
8390
end
8491
end
92+
93+
context 'for a method with empty request and response protos' do
94+
let(:js_grpc_call) do
95+
<<-EOF
96+
var x = new EmptyRequest();
97+
window.grpcResponse = null;
98+
helloService.sayNothing(x, {}, function(err, response){
99+
window.grpcError = err;
100+
window.grpcResponse = response;
101+
});
102+
EOF
103+
end
104+
105+
it 'returns the expected response from the service' do
106+
perform_request
107+
expect(response).to eq({})
108+
end
109+
end
85110
end
86111

87112
context 'with application/grpc-web-text format' do

spec/integration/ruby_server_nodejs_client_spec.rb

+11-1
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,18 @@
1313
'node',
1414
node_client,
1515
server_url,
16-
name,
16+
grpc_method,
1717
basic_username,
1818
basic_password,
19+
name,
1920
].join(' ')
2021
end
2122
let(:result) { JSON.parse(json_result) }
2223

2324
let(:basic_password) { 'supersecretpassword' }
2425
let(:basic_username) { 'supermanuser' }
2526
let(:service) { TestHelloService }
27+
let(:grpc_method) { 'SayHello' }
2628
let(:rack_app) do
2729
app = TestGRPCWebApp.build(service)
2830
app.use Rack::Auth::Basic do |username, password|
@@ -70,4 +72,12 @@ def say_hello(_request, _metadata = nil)
7072
)
7173
end
7274
end
75+
76+
context 'with empty request and response protos' do
77+
let(:grpc_method) { 'SayNothing' }
78+
79+
it 'returns the expected response from the service' do
80+
expect(result['response']).to eq({})
81+
end
82+
end
7383
end

spec/integration/ruby_server_ruby_client_spec.rb

+8
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ def say_hello(_request, _metadata = nil)
6868
end
6969
end
7070

71+
context 'for a method with empty request and response protos' do
72+
subject(:response) { client.say_nothing }
73+
74+
it 'returns the expected response from the service' do
75+
expect(response).to eq(EmptyResponse.new)
76+
end
77+
end
78+
7179
context 'for a network error' do
7280
let(:client_url) do
7381
"http://#{basic_username}:#{basic_password}@#{server.host}:#{server.port + 1}"

spec/js-client-src/client.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// It must be compiled with webpack to generate spec/js-client/main.js
55

6-
const {HelloRequest} = require('pb-grpc-web/hello_pb.js');
6+
const {HelloRequest, EmptyRequest} = require('pb-grpc-web/hello_pb.js');
77

88
// This version of the JS client makes requests as application/grpc-web (binary):
99
const HelloClientWeb = require('pb-grpc-web/hello_grpc_web_pb.js');
@@ -15,5 +15,6 @@ const grpc = {};
1515
grpc.web = require('grpc-web');
1616

1717
window.HelloRequest = HelloRequest;
18+
window.EmptyRequest = EmptyRequest;
1819
window.HelloServiceClientWeb = HelloClientWeb.HelloServiceClient;
1920
window.HelloServiceClientWebText = HelloClientWebText.HelloServiceClient;

spec/node-client/client.ts

+29-13
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import { grpc } from "@improbable-eng/grpc-web";
22
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
33

44
import {HelloServiceClient} from './pb-ts/hello_pb_service';
5-
import {HelloRequest} from './pb-ts/hello_pb';
5+
import {HelloRequest, EmptyRequest} from './pb-ts/hello_pb';
66

77
// Required for grpc-web in a NodeJS environment (vs. browser)
88
grpc.setDefaultTransport(NodeHttpTransport());
99

1010
// Usage: node client.js http://server:port nameParam username password
1111
const serverUrl = process.argv[2];
12-
const helloName = process.argv[3];
12+
const grpcMethod = process.argv[3];
1313
const username = process.argv[4];
1414
const password = process.argv[5];
15+
const helloName = process.argv[6];
1516

1617
const client = new HelloServiceClient(serverUrl);
1718
const headers = new grpc.Metadata();
@@ -21,14 +22,29 @@ if (username && password) {
2122
headers.set("Authorization", `Basic ${encodedCredentials}`);
2223
}
2324

24-
const req = new HelloRequest();
25-
req.setName(helloName);
26-
27-
client.sayHello(req, headers, (err, resp) => {
28-
var result = {
29-
response: resp && resp.toObject(),
30-
error: err && err.metadata && err.metadata.headersMap
31-
}
32-
// Emit response and/or error as JSON so it can be parsed from Ruby
33-
console.log(JSON.stringify(result));
34-
});
25+
if (grpcMethod == 'SayHello') {
26+
const req = new HelloRequest();
27+
req.setName(helloName);
28+
client.sayHello(req, headers, (err, resp) => {
29+
var result = {
30+
response: resp && resp.toObject(),
31+
error: err && err.metadata && err.metadata.headersMap
32+
}
33+
// Emit response and/or error as JSON so it can be parsed from Ruby
34+
console.log(JSON.stringify(result));
35+
});
36+
}
37+
else if (grpcMethod == 'SayNothing') {
38+
const req = new EmptyRequest();
39+
client.sayNothing(req, headers, (err, resp) => {
40+
var result = {
41+
response: resp && resp.toObject(),
42+
error: err && err.metadata && err.metadata.headersMap
43+
}
44+
// Emit response and/or error as JSON so it can be parsed from Ruby
45+
console.log(JSON.stringify(result));
46+
});
47+
}
48+
else {
49+
console.log(`Unknown gRPC method ${grpcMethod}`);
50+
}

spec/pb-src/hello.proto

+10-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ message HelloResponse {
88
string message = 1;
99
}
1010

11+
message EmptyRequest {
12+
13+
}
14+
15+
message EmptyResponse {
16+
17+
}
18+
1119
service HelloService {
12-
rpc SayHello(HelloRequest) returns (HelloResponse);
20+
rpc SayHello (HelloRequest) returns (HelloResponse);
21+
rpc SayNothing (EmptyRequest) returns (EmptyResponse);
1322
}

spec/support/test_hello_service.rb

+4
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ class TestHelloService < HelloService::Service
66
def say_hello(request, _call = nil)
77
HelloResponse.new(message: "Hello #{request.name}")
88
end
9+
10+
def say_nothing(_request, _call = nil)
11+
EmptyResponse.new
12+
end
913
end

spec/unit/grpc_web/message_framing_spec.rb

+2-11
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
[
88
::GRPCWeb::MessageFrame.header_frame('data in the header'),
99
::GRPCWeb::MessageFrame.payload_frame("data in the \u1f61d first frame"),
10+
::GRPCWeb::MessageFrame.payload_frame(''), # 0 length frame
1011
::GRPCWeb::MessageFrame.payload_frame('data in the second frame'),
1112
]
1213
end
1314
let(:packed_frames) do
1415
string = "\x80\x00\x00\x00\x12data in the header" \
1516
"\x00\x00\x00\x00\x1Cdata in the \u1f61d first frame" \
17+
"\x00\x00\x00\x00\x00" + # 0 length frame
1618
"\x00\x00\x00\x00\x18data in the second frame"
1719
string.b # treat string as a byte string
1820
end
@@ -27,17 +29,6 @@
2729
subject(:unpack) { described_class.unpack_frames(packed_frames) }
2830

2931
it { is_expected.to eq unpacked_frames }
30-
31-
context 'when the message length is invalid' do
32-
let(:packed_frames) do
33-
string = "\x80\x00\x00\x00\x00data in the header"
34-
string.b # treat string as a byte string
35-
end
36-
37-
it 'raises an error' do
38-
expect { unpack }.to raise_error(StandardError, 'Invalid message length')
39-
end
40-
end
4132
end
4233

4334
describe 'pack and unpack frames' do

0 commit comments

Comments
 (0)