Skip to content

Commit

Permalink
fix: Support 0 length messages (#34)
Browse files Browse the repository at this point in the history
* Support 0 length messages, which are valid

* Update contributing.md

* Add specs

* linter

Co-authored-by: James Shkolnik <[email protected]>
  • Loading branch information
shkolnik and shkolnik authored Aug 26, 2020
1 parent 492e303 commit 4486cbe
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 33 deletions.
75 changes: 71 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,75 @@ Use the `rake` command to run the entire test suite. This runs on docker due to

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

### Commit message format
### Pull Request title/description format

Please follow semantic release formatting
https://semantic-release.gitbook.io/semantic-release/#commit-message-format
https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#type
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**:

```commit
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
```

The **header** is mandatory and the **scope** of the header is optional.

The **footer** can contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages).

#### Revert

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.

#### Type

The type must be one of the following:

| Type | Description |
|--------------|-------------------------------------------------------------------------------------------------------------|
| **build** | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) |
| **ci** | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
| **docs** | Documentation only changes |
| **feat** | A new feature |
| **fix** | A bug fix |
| **perf** | A code change that improves performance |
| **refactor** | A code change that neither fixes a bug nor adds a feature |
| **style** | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) |
| **test** | Adding missing tests or correcting existing tests |
| **chore** | Any other change that does not affect the published code (e.g. tool changes, config changes) |

#### Subject

The subject contains succinct description of the change:

- use the imperative, present tense: "change" not "changed" nor "changes"
- no dot (.) at the end

#### Body
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
The body should include the motivation for the change and contrast this with previous behavior.

#### Footer
The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.

**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.

#### Examples

```commit
`fix(pencil): stop graphite breaking when too much pressure applied`
```

```commit
`feat(pencil): add 'graphiteWidth' option`
Fix #42
```

```commit
perf(pencil): remove graphiteWidth option`
BREAKING CHANGE: The graphiteWidth option has been removed.
The default graphite width of 10mm is always used for performance reasons.
```
3 changes: 1 addition & 2 deletions lib/grpc_web/message_framing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ def pack_frames(frames)
def unpack_frames(content)
frames = []
remaining_content = content

until remaining_content.empty?
msg_length = remaining_content[1..4].unpack1('N')
raise 'Invalid message length' if msg_length <= 0

frame_end = 5 + msg_length
frames << ::GRPCWeb::MessageFrame.new(
remaining_content[0].bytes[0],
Expand Down
8 changes: 8 additions & 0 deletions spec/integration/envoy_server_ruby_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ def say_hello(_request, _metadata = nil)
end
end

context 'for a method with empty request and response protos' do
subject(:response) { client.say_nothing }

it 'returns the expected response from the service' do
expect(response).to eq(EmptyResponse.new)
end
end

context 'for a network error' do
let(:client_url) { 'http://envoy:8081' }

Expand Down
25 changes: 25 additions & 0 deletions spec/integration/ruby_server_js_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
let(:js_script) do
<<-EOF
var helloService = new #{js_client_class}('http://#{server.host}:#{server.port}', null, null);
#{js_grpc_call}
EOF
end

# JS snippet to make the gRPC-Web call
let(:js_grpc_call) do
<<-EOF
var x = new HelloRequest();
x.setName('#{name}');
window.grpcResponse = null;
Expand Down Expand Up @@ -82,6 +89,24 @@ def say_hello(_request, _metadata = nil)
expect(js_error).to include('code' => 2, 'message' => 'RuntimeError: Some random error')
end
end

context 'for a method with empty request and response protos' do
let(:js_grpc_call) do
<<-EOF
var x = new EmptyRequest();
window.grpcResponse = null;
helloService.sayNothing(x, {}, function(err, response){
window.grpcError = err;
window.grpcResponse = response;
});
EOF
end

it 'returns the expected response from the service' do
perform_request
expect(response).to eq({})
end
end
end

context 'with application/grpc-web-text format' do
Expand Down
12 changes: 11 additions & 1 deletion spec/integration/ruby_server_nodejs_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@
'node',
node_client,
server_url,
name,
grpc_method,
basic_username,
basic_password,
name,
].join(' ')
end
let(:result) { JSON.parse(json_result) }

let(:basic_password) { 'supersecretpassword' }
let(:basic_username) { 'supermanuser' }
let(:service) { TestHelloService }
let(:grpc_method) { 'SayHello' }
let(:rack_app) do
app = TestGRPCWebApp.build(service)
app.use Rack::Auth::Basic do |username, password|
Expand Down Expand Up @@ -70,4 +72,12 @@ def say_hello(_request, _metadata = nil)
)
end
end

context 'with empty request and response protos' do
let(:grpc_method) { 'SayNothing' }

it 'returns the expected response from the service' do
expect(result['response']).to eq({})
end
end
end
8 changes: 8 additions & 0 deletions spec/integration/ruby_server_ruby_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ def say_hello(_request, _metadata = nil)
end
end

context 'for a method with empty request and response protos' do
subject(:response) { client.say_nothing }

it 'returns the expected response from the service' do
expect(response).to eq(EmptyResponse.new)
end
end

context 'for a network error' do
let(:client_url) do
"http://#{basic_username}:#{basic_password}@#{server.host}:#{server.port + 1}"
Expand Down
3 changes: 2 additions & 1 deletion spec/js-client-src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// It must be compiled with webpack to generate spec/js-client/main.js

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

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

window.HelloRequest = HelloRequest;
window.EmptyRequest = EmptyRequest;
window.HelloServiceClientWeb = HelloClientWeb.HelloServiceClient;
window.HelloServiceClientWebText = HelloClientWebText.HelloServiceClient;
42 changes: 29 additions & 13 deletions spec/node-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import { grpc } from "@improbable-eng/grpc-web";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";

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

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

// Usage: node client.js http://server:port nameParam username password
const serverUrl = process.argv[2];
const helloName = process.argv[3];
const grpcMethod = process.argv[3];
const username = process.argv[4];
const password = process.argv[5];
const helloName = process.argv[6];

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

const req = new HelloRequest();
req.setName(helloName);

client.sayHello(req, headers, (err, resp) => {
var result = {
response: resp && resp.toObject(),
error: err && err.metadata && err.metadata.headersMap
}
// Emit response and/or error as JSON so it can be parsed from Ruby
console.log(JSON.stringify(result));
});
if (grpcMethod == 'SayHello') {
const req = new HelloRequest();
req.setName(helloName);
client.sayHello(req, headers, (err, resp) => {
var result = {
response: resp && resp.toObject(),
error: err && err.metadata && err.metadata.headersMap
}
// Emit response and/or error as JSON so it can be parsed from Ruby
console.log(JSON.stringify(result));
});
}
else if (grpcMethod == 'SayNothing') {
const req = new EmptyRequest();
client.sayNothing(req, headers, (err, resp) => {
var result = {
response: resp && resp.toObject(),
error: err && err.metadata && err.metadata.headersMap
}
// Emit response and/or error as JSON so it can be parsed from Ruby
console.log(JSON.stringify(result));
});
}
else {
console.log(`Unknown gRPC method ${grpcMethod}`);
}
11 changes: 10 additions & 1 deletion spec/pb-src/hello.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ message HelloResponse {
string message = 1;
}

message EmptyRequest {

}

message EmptyResponse {

}

service HelloService {
rpc SayHello(HelloRequest) returns (HelloResponse);
rpc SayHello (HelloRequest) returns (HelloResponse);
rpc SayNothing (EmptyRequest) returns (EmptyResponse);
}
4 changes: 4 additions & 0 deletions spec/support/test_hello_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ class TestHelloService < HelloService::Service
def say_hello(request, _call = nil)
HelloResponse.new(message: "Hello #{request.name}")
end

def say_nothing(_request, _call = nil)
EmptyResponse.new
end
end
13 changes: 2 additions & 11 deletions spec/unit/grpc_web/message_framing_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
[
::GRPCWeb::MessageFrame.header_frame('data in the header'),
::GRPCWeb::MessageFrame.payload_frame("data in the \u1f61d first frame"),
::GRPCWeb::MessageFrame.payload_frame(''), # 0 length frame
::GRPCWeb::MessageFrame.payload_frame('data in the second frame'),
]
end
let(:packed_frames) do
string = "\x80\x00\x00\x00\x12data in the header" \
"\x00\x00\x00\x00\x1Cdata in the \u1f61d first frame" \
"\x00\x00\x00\x00\x00" + # 0 length frame
"\x00\x00\x00\x00\x18data in the second frame"
string.b # treat string as a byte string
end
Expand All @@ -27,17 +29,6 @@
subject(:unpack) { described_class.unpack_frames(packed_frames) }

it { is_expected.to eq unpacked_frames }

context 'when the message length is invalid' do
let(:packed_frames) do
string = "\x80\x00\x00\x00\x00data in the header"
string.b # treat string as a byte string
end

it 'raises an error' do
expect { unpack }.to raise_error(StandardError, 'Invalid message length')
end
end
end

describe 'pack and unpack frames' do
Expand Down

0 comments on commit 4486cbe

Please sign in to comment.