Skip to content

Commit

Permalink
Allow empty body to be valid for jrpc.HandleCall/Deserialize; allow m…
Browse files Browse the repository at this point in the history
…ethod override in client (#623)

* Allow and empty body to be valid for HandleCall/Deserialize; allow call/send method to be overridden
  • Loading branch information
ldemailly committed Sep 13, 2022
1 parent 0d61b4f commit 0fa8982
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 17 deletions.
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

0 comments on commit 0fa8982

Please sign in to comment.