diff --git a/api/interface.go b/api/interface.go index a408fe1..a9222e5 100644 --- a/api/interface.go +++ b/api/interface.go @@ -2,6 +2,7 @@ package aurestclientapi import ( "context" + "io" "net/http" "time" ) @@ -18,6 +19,14 @@ type ParsedResponse struct { Time time.Time } +// CustomRequestBody allows you the greatest amount of control over the request by directly supplying the +// body io.Reader, the length, and the content type header. +type CustomRequestBody struct { + BodyReader io.Reader // Tip: &bytes.Buffer{} implements io.Reader + BodyLength int + ContentType string +} + // Client is a utility class representing a http client. // // We provide multiple stackable implementations, typical stacking order is @@ -104,4 +113,4 @@ type CacheKeyFunction func(ctx context.Context, method string, url string, reque // // Not all parameters will always be set. For example, the latency is only known at the request logging level, // and the request/response body size is only known while that is being processed. -type MetricsCallbackFunction func(ctx context.Context, method string, url string, status int, err error, latency time.Duration, size int) \ No newline at end of file +type MetricsCallbackFunction func(ctx context.Context, method string, url string, status int, err error, latency time.Duration, size int) diff --git a/example/fullstack/fullstack.go b/example/fullstack/fullstack.go index 5164364..ec37f30 100644 --- a/example/fullstack/fullstack.go +++ b/example/fullstack/fullstack.go @@ -1,6 +1,7 @@ package examplefullstack import ( + "bytes" "context" aulogging "github.com/StephanHCB/go-autumn-logging" aurestclientapi "github.com/StephanHCB/go-autumn-restclient/api" @@ -11,6 +12,8 @@ import ( aurestrecorder "github.com/StephanHCB/go-autumn-restclient/implementation/recorder" aurestlogging "github.com/StephanHCB/go-autumn-restclient/implementation/requestlogging" aurestretry "github.com/StephanHCB/go-autumn-restclient/implementation/retry" + "io" + "mime/multipart" "net/http" "time" ) @@ -112,3 +115,70 @@ func testingExample() { // also assert on the recording what requests were made _ = aurestcapture.GetRecording(requestCaptureClient) } + +func fileUploadExample() { + fileContents := &bytes.Buffer{} // here you'd read the file, or open a reader for the file + + // construct custom response body + + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + part, _ := writer.CreateFormFile("file", "filename.txt") + _, _ = io.Copy(part, fileContents) + _ = writer.Close() + + request := aurestclientapi.CustomRequestBody{ + BodyReader: body, + BodyLength: body.Len(), + ContentType: writer.FormDataContentType(), + } + + // assumes you set up logging by importing one of the go-autumn-logging-xxx dependencies + // + // for this example, let's set up a logger that does nothing, so we don't pull in these dependencies here + // + // This of course makes the requestLoggingClient not work. + aulogging.SetupNoLoggerForTesting() + + // ----- setup (can be done once during application startup) ---- + + // 1. set up http client + var timeout time.Duration = 0 + var customCACert []byte = nil + var requestManipulator aurestclientapi.RequestManipulatorCallback = nil + + httpClient, _ := auresthttpclient.New(timeout, customCACert, requestManipulator) + + // 2. recording (for use with 1a) + recorderClient := aurestrecorder.New(httpClient) + + // 3. request logging + requestLoggingClient := aurestlogging.New(recorderClient) + + // 4. circuit breaker (see https://github.com/StephanHCB/go-autumn-restclient-circuitbreaker) + // not included here because it has extra dependencies + + // 5. retry + var repeatCount uint8 = 2 // 0 means only try once (but then why use this at all?) + var condition aurestclientapi.RetryConditionCallback = func(ctx context.Context, response *aurestclientapi.ParsedResponse, err error) bool { + return response.Status == http.StatusRequestTimeout + } + var beforeRetry aurestclientapi.BeforeRetryCallback = nil + + retryingClient := aurestretry.New(requestLoggingClient, repeatCount, condition, beforeRetry) + + // ----- now make a request ----- + + bodyDto := make(map[string]interface{}) + + response := aurestclientapi.ParsedResponse{ + Body: &bodyDto, + } + err := retryingClient.Perform(context.Background(), http.MethodPost, "https://some.rest.api/fileupload", request, &response) + if err != nil { + return + } + + // now bodyDto is filled with the response and response.Status and response.Header are also set. + +} diff --git a/implementation/httpclient/httpclient.go b/implementation/httpclient/httpclient.go index 9fe5977..7991ae6 100644 --- a/implementation/httpclient/httpclient.go +++ b/implementation/httpclient/httpclient.go @@ -170,6 +170,9 @@ func (c *HttpClientImpl) requestBodyReader(requestBody interface{}) (io.Reader, if requestBody == nil { return nil, 0, "", nil } + if asCustom, ok := requestBody.(aurestclientapi.CustomRequestBody); ok { + return asCustom.BodyReader, asCustom.BodyLength, asCustom.ContentType, nil + } if asString, ok := requestBody.(string); ok { return strings.NewReader(asString), len(asString), aurestclientapi.ContentTypeApplicationJson, nil }