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

Allow empty body to be valid for jrpc.HandleCall/Deserialize; allow method override in client #623

Merged
merged 2 commits into from Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Expand Up @@ -52,13 +52,13 @@ You can install from source:
The [releases](https://github.com/fortio/fortio/releases) page has binaries for many OS/architecture combinations (see assets).

```shell
curl -L https://github.com/fortio/fortio/releases/download/v1.37.0/fortio-linux_amd64-1.37.0.tgz \
curl -L https://github.com/fortio/fortio/releases/download/v1.37.1/fortio-linux_amd64-1.37.1.tgz \
| sudo tar -C / -xvzpf -
# or the debian package
wget https://github.com/fortio/fortio/releases/download/v1.37.0/fortio_1.37.0_amd64.deb
dpkg -i fortio_1.37.0_amd64.deb
wget https://github.com/fortio/fortio/releases/download/v1.37.1/fortio_1.37.1_amd64.deb
dpkg -i fortio_1.37.1_amd64.deb
# or the rpm
rpm -i https://github.com/fortio/fortio/releases/download/v1.37.0/fortio-1.37.0-1.x86_64.rpm
rpm -i https://github.com/fortio/fortio/releases/download/v1.37.1/fortio-1.37.1-1.x86_64.rpm
# and more, see assets in release page
```

Expand All @@ -68,7 +68,7 @@ On a MacOS you can also install Fortio using [Homebrew](https://brew.sh/):
brew install fortio
```

On Windows, download https://github.com/fortio/fortio/releases/download/v1.37.0/fortio_win_1.37.0.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt:
On Windows, download https://github.com/fortio/fortio/releases/download/v1.37.1/fortio_win_1.37.1.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt:
```
fortio.exe server
```
Expand Down Expand Up @@ -116,7 +116,7 @@ Full list of command line flags (`fortio help`):
<details>
<!-- use release/updateFlags.sh to update this section -->
<pre>
Φορτίο 1.37.0 usage:
Φορτίο 1.37.1 usage:
fortio command [flags] target
where command is one of: load (load testing), server (starts ui, rest api,
http-echo, redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only
Expand Down
21 changes: 18 additions & 3 deletions jrpc/jrpcClient.go
Expand Up @@ -71,9 +71,13 @@ type FetchError struct {

// Destination is the URL and optional additional headers.
type Destination struct {
URL string
URL string
// Default is nil, which means no additional headers.
Headers *http.Header
// Default is 0 which means use global timeout.
Timeout time.Duration
// Default is "" which will use POST if there is a payload and GET otherwise.
Method string
}

func (fe *FetchError) Error() string {
Expand Down Expand Up @@ -129,6 +133,10 @@ func Serialize(obj interface{}) ([]byte, error) {
// Deserialize deserializes json as a new object of desired type.
func Deserialize[Q any](bytes []byte) (*Q, error) {
var result Q
if len(bytes) == 0 {
// Allow empty body to be deserialized as empty object.
return &result, nil
}
err := json.Unmarshal(bytes, &result)
return &result, err // Will return zero object, not nil upon error
}
Expand Down Expand Up @@ -184,10 +192,17 @@ func Send(dest *Destination, jsonPayload []byte) (int, []byte, error) {
var req *http.Request
var err error
var res []byte
method := dest.Method
if len(jsonPayload) > 0 {
req, err = http.NewRequestWithContext(ctx, http.MethodPost, dest.URL, bytes.NewReader(jsonPayload))
if method == "" {
method = http.MethodPost
}
req, err = http.NewRequestWithContext(ctx, method, dest.URL, bytes.NewReader(jsonPayload))
} else {
req, err = http.NewRequestWithContext(ctx, http.MethodGet, dest.URL, nil)
if method == "" {
method = http.MethodGet
}
req, err = http.NewRequestWithContext(ctx, method, dest.URL, nil)
}
if err != nil {
return -1, res, err
Expand Down
11 changes: 4 additions & 7 deletions jrpc/jrpcServer.go
Expand Up @@ -83,14 +83,11 @@ func ReplyError(w http.ResponseWriter, extraMsg string, err error) error {

// HandleCall deserializes the expected type from the request body.
// Sample usage code:
// ```
//
// req, err := jrpc.HandleCall[Request](w, r)
// if err != nil {
// _ = jrpc.ReplyError(w, "request error", err)
// }
//
// ```.
// req, err := jrpc.HandleCall[Request](w, r)
// if err != nil {
// _ = jrpc.ReplyError(w, "request error", err)
// }
func HandleCall[Q any](w http.ResponseWriter, r *http.Request) (*Q, error) {
data, err := io.ReadAll(r.Body)
if err != nil {
Expand Down
14 changes: 13 additions & 1 deletion jrpc/jrpc_test.go
Expand Up @@ -143,6 +143,18 @@ func TestJPRC(t *testing.T) {
if res.ConcatenatedStrings != "abcd" {
t.Errorf("response doesn't contain expected string: %+v", res)
}
// OK case: empty POST
dest := &jrpc.Destination{
URL: url,
Method: http.MethodPost, // force post (default is get when no payload)
}
res, err = jrpc.Fetch[Response](dest, []byte{})
if err != nil {
t.Errorf("failed Fetch with POST and empty body: %v", err)
}
if res.Error {
t.Errorf("response unexpectedly marked as failed: %+v", res)
}
// Error cases
// Empty request, using FetchBytes()
code, bytes, err := jrpc.FetchBytes(jrpc.NewDestination(url))
Expand Down Expand Up @@ -278,7 +290,7 @@ func TestJPRC(t *testing.T) {
if err == nil {
t.Errorf("error expected %v", res)
}
expected = "deserialization error, code 500: unexpected end of JSON input (raw reply: )"
expected = "non ok http result, code 500: <nil> (raw reply: )"
if err.Error() != expected {
t.Errorf("error string expected %q, got %q, %+v", expected, err.Error(), res)
}
Expand Down