https://github.com/gildas/go-request
Helper for sending requests to HTTP/REST services
https://github.com/gildas/go-request
go golang http-client http-request request
Last synced: 10 months ago
JSON representation
Helper for sending requests to HTTP/REST services
- Host: GitHub
- URL: https://github.com/gildas/go-request
- Owner: gildas
- License: mit
- Created: 2019-11-24T22:05:09.000Z (about 6 years ago)
- Default Branch: dev
- Last Pushed: 2024-11-14T15:30:58.000Z (about 1 year ago)
- Last Synced: 2024-11-14T16:34:25.933Z (about 1 year ago)
- Topics: go, golang, http-client, http-request, request
- Language: Go
- Size: 264 KB
- Stars: 3
- Watchers: 3
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# go-request

[](https://pkg.go.dev/github.com/gildas/go-request)
[](https://github.com/gildas/go-request/blob/master/LICENSE)
[](https://goreportcard.com/report/github.com/gildas/go-request)

[](https://github.com/gildas/go-request/actions/workflows/test.yml)
[](https://codecov.io/gh/gildas/go-request/branch/master)

[](https://github.com/gildas/go-request/actions/workflows/test.yml)
[](https://codecov.io/gh/gildas/go-request/branch/dev)
A Package to send requests to HTTP/REST services.
## Usage
The main func allows to send HTTP request to REST servers and takes care of payloads, JSON, result collection.
Examples:
```go
res, err := request.Send(&request.Options{
URL: myURL,
}, nil)
if err != nil {
return err
}
data := struct{Data string}{}
err := res.UnmarshalContentJSON(&data)
```
Here we send an HTTP GET request and unmarshal the response.
It is also possible to let `request.Send` do the unmarshal for us:
```go
data := struct{Data string}{}
_, err := request.Send(&request.Options{
URL: myURL,
}, &data)
```
In that case, the returned `Content`'s data is an empty byte array. Its other properties are valid (like the size, mime type, etc)
You can also download data directly to an `io.Writer`:
```go
writer, err := os.Create(filepath.Join("tmp", "data"))
defer writer.Close()
res, err := request.Send(&request.Options{
URL: serverURL,
}, writer)
log.Infof("Downloaded %d bytes", res.Length)
```
In that case, the returned `Content`'s data is an empty byte array. Its other properties are valid (like the size, mime type, etc)
Authorization can be stored in the `Options.Authorization`:
```go
payload := struct{Key string}{}
res, err := request.Send(&request.Options{
URL: myURL,
Authorization: request.BasicAuthorization("user", "password"),
}, nil)
```
or, with a Bearer Token:
```go
payload := struct{Key string}{}
res, err := request.Send(&request.Options{
URL: myURL,
Authorization: request.BearerAuthorization("myTokenABCD"),
}, nil)
```
Objects can be sent as payloads:
```go
payload := struct{Key string}{}
res, err := request.Send(&request.Options{
URL: myURL,
Payload: payload,
}, nil)
```
A payload will induce an HTTP POST unless mentioned.
So, to send an `HTTP PUT`, simply write:
```go
payload := struct{Key string}{}
res, err := request.Send(&request.Options{
Method: http.MethodPut,
URL: myURL,
Payload: payload,
}, nil)
```
To send an x-www-form, use a `map` in the payload:
```go
res, err := request.Send(&request.Options{
Method: http.MethodPut,
URL: myURL,
Payload: map[string]string{
"ID": "1234",
"Kind": "stuff,"
},
}, nil)
```
To send a multipart form with an attachment, use a `map`, an attachment, and one of the key must start with `>`:
```go
attachment := io.Open("/path/to/file")
res, err := request.Send(&request.Options{
Method: http.MethodPut,
URL: myURL,
Payload: map[string]string{
"ID": "1234",
"Kind": "stuff,"
">file": "image.png",
},
Attachment: attachment,
}, nil)
```
The file name and its key will be written in the `multipart/form-data`'s `Content-Disposition` header as: `form-data; name="file"; filename="image.png"`.
To send the request again when receiving a Service Unavailable (`Attempts` and `Timeout` are optional):
```go
res, err := request.Send(&request.Options{
URL: myURL,
RetryableStatusCodes: []int{http.StatusServiceUnavailable},
Attempts: 10,
Timeout: 2 * time.Second,
}, nil)
```
By default, upon receiving a retryable status code, `Send` will use am exponential backoff algorithm to retry the request. By default, it will wait for 3 seconds before retrying for 5 minutes, then 9 seconds between 5 and 10 minutes, then 27 seconds between 10 and 15 minutes, etc.
Connection errors like EOF, connection reset, etc. are also retried `Options.Attempts` times. The default is 5 attempts and the code waits for `Options.InterAttemptDelay` (Default: 3s).
You can change the delay and the backoff factor like this:
```go
res, err := request.Send(&request.Options{
URL: myURL,
InterAttemptDelay: 5 * time.Second,
InterAttemptBackoffInterval: 2 * time.Minute,
}, nil)
```
You can also not use the backoff algorithm and use the `Retry-After` header instead:
```go
res, err := request.Send(&request.Options{
URL: myURL,
// ...
InterAttemptUseRetryAfter: true,
}, nil)
```
When sending requests to upload data streams, you can provide an `io.Writer` to write the progress to:
```go
import "github.com/schollz/progressbar/v3"
reader, err := os.Open(pathToFile)
defer reader.Close()
stat, err := reader.Stat()
bar := progressbar.DefaultBytes(stat.Size(), "Uploading")
res, err := request.Send(&request.Options{
Method: http.MethodPost,
URL: serverURL,
Payload: reader,
ProgressWriter: bar,
}, reader)
```
If the progress `io.Writer` is also an `io.Closer`, it will be closed at the end of the `request.Send()`.
When sending requests to download data streams, you can provide an `io.Writer` to write the progress to:
```go
import "github.com/schollz/progressbar/v3"
writer, err := os.Create(pathToFile)
defer writer.Close()
bar := progressbar.DefaultBytes(-1, "Downloading") // will use a spinner
res, err := request.Send(&request.Options{
URL: serverURL,
ProgressWriter: bar,
}, writer)
```
Again, if the progress `io.Writer` is also an `io.Closer`, it will be closed at the end of the `request.Send()`.
if you provide a `request.Options.ProgressSetMax` func or if the `io.Writer` is a `request.ProgressBarMaxSetter` or a `request.ProgressBarMaxChanger`, `request.Send` will call it to set the maximum value of the progress bar from the response `Content-Length`:
```go
import "github.com/schollz/progressbar/v3"
writer, err := os.Create(pathToFile)
defer writer.Close()
bar := progressbar.DefaultBytes(1, "Downloading") // use a temporary max value
res, err := request.Send(&request.Options{
URL: serverURL,
ProgressWriter: bar,
}, writer)
```
```go
import "github.com/cheggaaa/pb/v3"
writer, err := os.Create(pathToFile)
defer writer.Close()
bar := pb.StartNew(1) // use a temporary max value
res, err := request.Send(&request.Options{
URL: serverURL,
ProgressWriter: bar,
ProgressSetMaxFunc: func(max int64) { bar.SetTotal64(max) },
}, writer)
```
**Notes:**
- if the PayloadType is not mentioned, it is calculated when processing the Payload.
- if the payload is a `ContentReader` or a `Content`, it is used directly.
- if the payload is a `map[string]xxx` where *xxx* is not `string`, the `fmt.Stringer` is used whenever possible to get the string version of the values.
- if the payload is a struct or a pointer to struct, the body is sent as `application/json` and marshaled.
- if the payload is an array or a slice, the body is sent as `application/json` and marshaled.
- The option `Logger` can be used to let the `request` library log to a `gildas/go-logger`. By default, it logs to a `NilStream` (see github.com/gildas/go-logger).
- When using a logger, you can control how much of the Request/Response Body is logged with the options `RequestBodyLogSize`/`ResponseBodyLogSize`. By default they are set to 2048 bytes. If you do not want to log them, set the options to *-1*.
- `Send()` makes 5 attempts by default to reach the given URL. If option `RetryableStatusCodes` is given, it will attempt the request again when it receives an HTTP Status Code in the given list. If it is not given, the default list is `[]int{http.StatusServiceUnavailable, http.StatusGatewayTimeout, http.StatusBadGateway, http.StatusRequestTimeout, http.StatusTooManyRequests}`.
- The default timeout for `Send()` is 1 second.
**TODO:**
- Support other kinds of `map` in the payload, like `map[string]int`, etc.
- Maybe have an interface for the Payload to allow users to provide the logic of building the payload themselves. (`type PayloadBuilder interface { BuildPayload() *ContentReader}`?!?)