Skip to content

Commit

Permalink
Support multi-value HTTP headers
Browse files Browse the repository at this point in the history
Support multi-value HTTP headers in:
HTTP Ingress and Egress
Frag and Defrag
Frame
Clients

Tests for HTTP package

Documentation
  • Loading branch information
bw19 committed May 24, 2024
1 parent 2861f79 commit 151dca3
Show file tree
Hide file tree
Showing 33 changed files with 390 additions and 139 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ Review each of the major project packages to get oriented in the code structure:
* [env](docs/structure/env.md) - Manages the loading of environment variables
* [errors](docs/structure/errors.md) - An enhancement of the standard `errors` package
* [examples](docs/structure/examples.md) - Demo microservices
* [frame](docs/structure/frame.md) - A utility for type-safe manipulation of the HTTP control headers used by the framework
* [frame](docs/structure/frame.md) - A utility for type-safe manipulation of the HTTP control headers used by the framework
* [httpx](docs/structure/httpx.md) ✨ - Various HTTP utilities
* [log](docs/structure/log.md) - Fields for attaching data to log messages
* [lru](docs/structure/lru.md) - An LRU with with limits on age and weight
* [mtr](docs/structure/mtr.md) - Metrics collectors
* [openapi](docs/structure/openapi.md) - Supports the generation of OpenAPI documents
* [pub](docs/structure/pub.md) - Options for publishing requests
* [rand](docs/structure/rand.md) - A utility for generating random numbers
* [service](docs/structure/service.md) - Definitions of interfaces of microservices
* [service](docs/structure/service.md) - Interface definitions of microservices
* [sub](docs/structure/sub.md) - Options for subscribing to handle requests
* [trc](docs/structure/trc.md) - Options for creating tracing spans
* [timex](docs/structure/timex.md) - Enhancement of the standard `time.Time`
Expand Down
4 changes: 2 additions & 2 deletions codegen/bundle/api/clients-gen.webs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (_c *Client) {{ .Name }}{{ if ne .Method "*" }}_Do{{ end }}(ctx context.Con
if err != nil {
return nil, errors.Trace(err)
}
res, err = _c.svc.Request(ctx, pub.Method(r.Method), pub.URL(url), pub.CopyHeaders(r), pub.Body(r.Body))
res, err = _c.svc.Request(ctx, pub.Method(r.Method), pub.URL(url), pub.CopyHeaders(r.Header), pub.Body(r.Body))
if err != nil {
return nil, err // No trace
}
Expand Down Expand Up @@ -207,6 +207,6 @@ func (_c *MulticastClient) {{ .Name }}{{ if ne .Method "*" }}_Do{{ end }}(ctx co
if err != nil {
return _c.errChan(errors.Trace(err))
}
return _c.svc.Publish(ctx, pub.Method(r.Method), pub.URL(url), pub.CopyHeaders(r), pub.Body(r.Body))
return _c.svc.Publish(ctx, pub.Method(r.Method), pub.URL(url), pub.CopyHeaders(r.Header), pub.Body(r.Body))
}
{{ end }}
4 changes: 2 additions & 2 deletions codegen/bundle/integration-gen_test.webs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (tc *{{ .Name }}TestCase) StatusCode(statusCode int) *{{ .Name }}TestCase {
return tc
}

// BodyContains asserts no error and that the response contains the string or byte array value.
// BodyContains asserts no error and that the response body contains the string or byte array value.
func (tc *{{ .Name }}TestCase) BodyContains(value any) *{{ .Name }}TestCase {
if assert.NoError(tc.t, tc.err) {
body := tc.res.Body.(*httpx.BodyReader).Bytes()
Expand All @@ -42,7 +42,7 @@ func (tc *{{ .Name }}TestCase) BodyContains(value any) *{{ .Name }}TestCase {
return tc
}

// BodyNotContains asserts no error and that the response does not contain the string or byte array value.
// BodyNotContains asserts no error and that the response body does not contain the string or byte array value.
func (tc *{{ .Name }}TestCase) BodyNotContains(value any) *{{ .Name }}TestCase {
if assert.NoError(tc.t, tc.err) {
body := tc.res.Body.(*httpx.BodyReader).Bytes()
Expand Down
4 changes: 2 additions & 2 deletions coreservices/httpegress/httpegressapi/clients-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coreservices/httpegress/integration-gen_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions coreservices/httpegress/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func TestHttpegress_Do(t *testing.T) {

// Echo
req, err := http.NewRequest(http.MethodPut, "http://127.0.0.1:5050/echo", bytes.NewReader([]byte("Lorem Ipsum")))
req.Header["Multi-Value"] = []string{"Foo", "Bar"}
assert.NoError(t, err)
req.Header.Set("Content-Type", "text/plain")

Expand All @@ -154,6 +155,8 @@ func TestHttpegress_Do(t *testing.T) {
assert.Contains(t, string(raw), "Host: 127.0.0.1:5050\r\n")
assert.Contains(t, string(raw), "User-Agent: Go-http-client")
assert.Contains(t, string(raw), "Content-Type: text/plain\r\n")
assert.Contains(t, string(raw), "Multi-Value: Foo\r\n")
assert.Contains(t, string(raw), "Multi-Value: Bar\r\n")
assert.Contains(t, string(raw), "\r\n\r\nLorem Ipsum")
}

Expand Down
12 changes: 6 additions & 6 deletions coreservices/httpegress/version-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 42 additions & 12 deletions coreservices/httpingress/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/microbus-io/fabric/coreservices/httpingress/httpingressapi"
"github.com/microbus-io/fabric/errors"
"github.com/microbus-io/fabric/frame"
"github.com/microbus-io/fabric/httpx"
"github.com/microbus-io/fabric/pub"
"github.com/microbus-io/fabric/rand"
)
Expand All @@ -42,23 +43,14 @@ func Initialize() error {
pub.URL("https:/" + strings.TrimPrefix(r.URL.RequestURI(), "/serve")),
pub.Body(r.Body),
pub.Unicast(),
}
for h := range r.Header {
options = append(options, pub.Header(h, r.Header.Get(h)))
pub.CopyHeaders(r.Header),
}
res, err := middleware.Request(r.Context(), options...)
if err != nil {
return errors.Trace(err)
}
for h := range res.Header {
w.Header()[h] = res.Header[h]
}
w.WriteHeader(res.StatusCode)
_, err = io.Copy(w, res.Body)
if err != nil {
return errors.Trace(err)
}
return nil
err = httpx.Copy(w, res)
return errors.Trace(err)
})

// Include all downstream microservices in the testing app
Expand Down Expand Up @@ -624,3 +616,41 @@ func TestHttpingress_OnChangedBlockedPaths(t *testing.T) {
func TestHttpingress_OnChangedServerLanguages(t *testing.T) {
t.Skip() // Not tested
}

func TestHttpingress_MultiValueHeaders(t *testing.T) {
t.Parallel()

con := connector.New("multi.value.headers")
con.Subscribe("GET", "ok", func(w http.ResponseWriter, r *http.Request) error {
if assert.Len(t, r.Header["Multi-Value"], 3) {
assert.Equal(t, "Send 1", r.Header["Multi-Value"][0])
assert.Equal(t, "Send 2", r.Header["Multi-Value"][1])
assert.Equal(t, "Send 3", r.Header["Multi-Value"][2])
}
w.Header()["Multi-Value"] = []string{
"Return 1",
"Return 2",
}
return nil
})
App.Join(con)
err := con.Startup()
assert.NoError(t, err)
defer con.Shutdown()

client := http.Client{} // Timeout: time.Second * 2}
req, err := http.NewRequest("GET", "http://localhost:4040/multi.value.headers/ok", nil)
assert.NoError(t, err)
req.Header["Multi-Value"] = []string{
"Send 1",
"Send 2",
"Send 3",
}
res, err := client.Do(req)
if assert.NoError(t, err) {
if assert.Len(t, res.Header["Multi-Value"], 2) {
assert.Equal(t, "Return 1", res.Header["Multi-Value"][0])
assert.Equal(t, "Return 2", res.Header["Multi-Value"][1])
}
}
}
2 changes: 1 addition & 1 deletion coreservices/httpingress/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ func (svc *Service) serveHTTP(w http.ResponseWriter, r *http.Request) error {
pub.URL(internalURL),
pub.Body(body),
pub.Unicast(),
pub.CopyHeaders(r), // Copy non-internal headers
pub.CopyHeaders(r.Header), // Copy non-internal headers
pub.ContentLength(len(body)), // Overwrite the Content-Length header
pub.Header("Traceparent", ""), // Disallowed header
pub.Header("Tracestate", ""), // Disallowed header
Expand Down
12 changes: 6 additions & 6 deletions coreservices/httpingress/version-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coreservices/metrics/integration-gen_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coreservices/metrics/metricsapi/clients-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions coreservices/metrics/version-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coreservices/openapiportal/openapiportalapi/clients-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions coreservices/openapiportal/version-gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions docs/structure/httpx.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,14 @@ Microbus-Time-Budget: 19999
7SLujUrm4W99YLUp
```

`QArgs` is a simplification of the standard `url.Values` and can be used to encode query strings in a single easily-readable statement:

```go
u := "https://example.com?" + httpx.QArgs{
"foo": "bar",
"count": 5,
}.Encode()
```

`NewRequest`, `MustNewRequest`, `NewRequestWithContext` and `MustNewRequestWithContext` are wrappers over the standard `http.NewRequest` that take in a body of `any` type rather than just a `[]byte` array. `SetRequestBody` is responsible for interpreting the body as the correct type. The `Must-` versions panic instead of returning an error, allowing single statement construction.
Loading

0 comments on commit 151dca3

Please sign in to comment.