Skip to content

Commit

Permalink
[jrpc] Add support for optional custom allowed http status codes and …
Browse files Browse the repository at this point in the history
…custom TLS options (#817)

* Add support for optional custom allowed http status codes and custom TLS options

* also have a provision to change the client entirely

* bump release and add pointer to multicurl
  • Loading branch information
ldemailly committed Aug 7, 2023
1 parent 252ae63 commit 1b14793
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 11 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- 1.58.0 -->
<!-- 1.59.0 -->
# Fortio

[![Awesome Go](https://fortio.org/mentioned-badge.svg)](https://github.com/avelino/awesome-go#networking)
Expand Down Expand Up @@ -60,13 +60,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.58.0/fortio-linux_amd64-1.58.0.tgz \
curl -L https://github.com/fortio/fortio/releases/download/v1.59.0/fortio-linux_amd64-1.59.0.tgz \
| sudo tar -C / -xvzpf -
# or the debian package
wget https://github.com/fortio/fortio/releases/download/v1.58.0/fortio_1.58.0_amd64.deb
dpkg -i fortio_1.58.0_amd64.deb
wget https://github.com/fortio/fortio/releases/download/v1.59.0/fortio_1.59.0_amd64.deb
dpkg -i fortio_1.59.0_amd64.deb
# or the rpm
rpm -i https://github.com/fortio/fortio/releases/download/v1.58.0/fortio-1.58.0-1.x86_64.rpm
rpm -i https://github.com/fortio/fortio/releases/download/v1.59.0/fortio-1.59.0-1.x86_64.rpm
# and more, see assets in release page
```

Expand All @@ -76,7 +76,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.58.0/fortio_win_1.58.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.59.0/fortio_win_1.59.0.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt:
```
fortio.exe server
```
Expand Down Expand Up @@ -128,7 +128,7 @@ Full list of command line flags (`fortio help`):
<!-- use release/updateFlags.sh to update this section -->
<pre>
<!-- USAGE_START -->
Φορτίο 1.58.0 usage:
Φορτίο 1.59.0 usage:
fortio command [flags] target
where command is one of: load (load testing), server (starts ui, rest api,
http-echo, redirect, proxies, tcp-echo, udp-echo and grpc ping servers),
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
fortio.org/dflag v1.5.3
fortio.org/log v1.10.0
fortio.org/scli v1.11.0
fortio.org/sets v1.0.3
fortio.org/testscript v0.3.1
fortio.org/version v1.0.2
github.com/golang/protobuf v1.5.3
Expand All @@ -27,7 +28,6 @@ require (
//)

require (
fortio.org/sets v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b // indirect
golang.org/x/sys v0.11.0 // indirect
Expand Down
32 changes: 29 additions & 3 deletions jrpc/jrpcClient.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ package jrpc // import "fortio.org/fortio/jrpc"
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
Expand All @@ -35,6 +36,7 @@ import (
"time"

"fortio.org/fortio/version"
"fortio.org/sets"
)

// Client side and common code.
Expand Down Expand Up @@ -72,6 +74,8 @@ type FetchError struct {
}

// Destination is the URL and optional additional headers.
// Depending on your needs consider also https://pkg.go.dev/fortio.org/multicurl/mc#MultiCurl
// and its configuration https://pkg.go.dev/fortio.org/multicurl/mc#Config object.
type Destination struct {
URL string
// Default is nil, which means no additional headers.
Expand All @@ -86,6 +90,13 @@ type Destination struct {
Context context.Context
// ClientTrace to use if set.
ClientTrace *httptrace.ClientTrace
// TLSConfig to use if set. This is ignored if HTTPClient is set.
// Otherwise that setting this implies a new http.Client each call where this is set.
TLSConfig *tls.Config
// Ok codes. If nil (default) then 200, 201, 202 are ok.
OkCodes sets.Set[int]
// Only use this if all the options above are not enough. Defaults to http.DefaultClient.
Client *http.Client
}

func (d *Destination) GetContext() context.Context {
Expand Down Expand Up @@ -170,8 +181,13 @@ func Fetch[Q any](url *Destination, bytes []byte) (*Q, error) {
if err != nil {
return nil, err
}
// 200, 201, 202 are ok
ok := (code >= http.StatusOK && code <= http.StatusAccepted)
var ok bool
if url.OkCodes != nil {
ok = url.OkCodes.Has(code)
} else {
// Default is 200, 201, 202 are ok
ok = (code >= http.StatusOK && code <= http.StatusAccepted)
}
result, err := Deserialize[Q](bytes)
if err != nil {
if ok {
Expand Down Expand Up @@ -233,8 +249,18 @@ func Send(dest *Destination, jsonPayload []byte) (int, []byte, error) {
}
SetHeaderIfMissing(req.Header, "Accept", "application/json")
SetHeaderIfMissing(req.Header, UserAgentHeader, UserAgent)
var client *http.Client
if dest.Client != nil {
client = dest.Client
} else if dest.TLSConfig != nil {
transport := http.DefaultTransport.(*http.Transport).Clone() // Let it crash/panic if somehow DefaultTransport is not a Transport
transport.TLSClientConfig = dest.TLSConfig
client = &http.Client{Transport: transport}
} else {
client = http.DefaultClient
}
var resp *http.Response
resp, err = http.DefaultClient.Do(req)
resp, err = client.Do(req)
if err != nil {
return -1, res, err
}
Expand Down
53 changes: 53 additions & 0 deletions jrpc/jrpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package jrpc_test

import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
Expand All @@ -29,6 +30,7 @@ import (
"fortio.org/assert"
"fortio.org/fortio/fhttp"
"fortio.org/fortio/jrpc"
"fortio.org/sets"
)

func TestDebugSummary(t *testing.T) {
Expand Down Expand Up @@ -520,3 +522,54 @@ func TestContext(t *testing.T) {
t.Errorf("expected newCtx, got %v", ctx)
}
}

// Semi copied from fhttp/https_server_test.go - to avoid circular dependencies
// TODO move these path to shared test package.
var (
// Generated from "make cert".
svrCrt = "../cert-tmp/server.crt"
svrKey = "../cert-tmp/server.key"
tlsOptions = &fhttp.TLSOptions{Cert: svrCrt, Key: svrKey}
)

func TestTLS(t *testing.T) {
// log.SetLogLevel(log.Debug)
m, a := fhttp.ServeTLS("0", "", tlsOptions)
if m == nil || a == nil {
t.Errorf("Failed to create server %v %v", m, a)
}
url := fmt.Sprintf("https://localhost:%d/", a.(*net.TCPAddr).Port)
d1 := &jrpc.Destination{URL: url}
_, err := jrpc.Get[struct{}](d1)
if err == nil {
t.Errorf("expected error, got nil - should have complained about TLS")
}
d1.TLSConfig = &tls.Config{InsecureSkipVerify: true}
_, err = jrpc.Get[struct{}](d1)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
// If we change the client, we should be back to errors again
d1.Client = &http.Client{}
_, err = jrpc.Get[struct{}](d1)
if err == nil {
t.Errorf("expected error, got nil - should have complained about TLS when passing a new client")
}
d1.Client = nil
d1.URL += "?status=417" // tea pot
_, err = jrpc.Get[struct{}](d1)
if err == nil {
t.Errorf("expected error, got nil - should have complained about return code 417")
}
d1.OkCodes = sets.New(233, 417)
_, err = jrpc.Get[struct{}](d1)
if err != nil {
t.Errorf("expected no error, got %v", err)
}
// 200 should error again
d1.URL = url
_, err = jrpc.Get[struct{}](d1)
if err == nil {
t.Errorf("expected error, got nil - should have complained about return code 200")
}
}

0 comments on commit 1b14793

Please sign in to comment.