{"id":21865224,"url":"https://github.com/gildas/go-request","last_synced_at":"2025-04-14T21:04:17.150Z","repository":{"id":62156215,"uuid":"223817122","full_name":"gildas/go-request","owner":"gildas","description":"Helper for sending requests to HTTP/REST services","archived":false,"fork":false,"pushed_at":"2024-11-14T15:30:58.000Z","size":270,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"dev","last_synced_at":"2024-11-14T16:34:25.933Z","etag":null,"topics":["go","golang","http-client","http-request","request"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gildas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-24T22:05:09.000Z","updated_at":"2024-11-14T15:31:02.000Z","dependencies_parsed_at":"2023-12-29T09:33:34.575Z","dependency_job_id":"be9ddb4f-96c0-4b7a-b4dd-523085c1387e","html_url":"https://github.com/gildas/go-request","commit_stats":null,"previous_names":[],"tags_count":59,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-request","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-request/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-request/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gildas%2Fgo-request/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gildas","download_url":"https://codeload.github.com/gildas/go-request/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":226856957,"owners_count":17693016,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","http-client","http-request","request"],"created_at":"2024-11-28T04:15:13.516Z","updated_at":"2024-11-28T04:15:14.120Z","avatar_url":"https://github.com/gildas.png","language":"Go","readme":"# go-request\n\n![GoVersion](https://img.shields.io/github/go-mod/go-version/gildas/go-request)\n[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go\u0026logoColor=white\u0026style=flat-square)](https://pkg.go.dev/github.com/gildas/go-request)\n[![License](https://img.shields.io/github/license/gildas/go-request)](https://github.com/gildas/go-request/blob/master/LICENSE)\n[![Report](https://goreportcard.com/badge/github.com/gildas/go-request)](https://goreportcard.com/report/github.com/gildas/go-request)  \n\n![master](https://img.shields.io/badge/branch-master-informational)\n[![Test](https://github.com/gildas/go-request/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/gildas/go-request/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/gildas/go-request/branch/master/graph/badge.svg?token=gFCzS9b7Mu)](https://codecov.io/gh/gildas/go-request/branch/master)\n\n![dev](https://img.shields.io/badge/branch-dev-informational)\n[![Test](https://github.com/gildas/go-request/actions/workflows/test.yml/badge.svg?branch=dev)](https://github.com/gildas/go-request/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/gildas/go-request/branch/dev/graph/badge.svg?token=gFCzS9b7Mu)](https://codecov.io/gh/gildas/go-request/branch/dev)\n\nA Package to send requests to HTTP/REST services.\n\n## Usage\n\nThe main func allows to send HTTP request to REST servers and takes care of payloads, JSON, result collection.\n\nExamples:\n\n```go\nres, err := request.Send(\u0026request.Options{\n    URL: myURL,\n}, nil)\nif err != nil {\n    return err\n}\ndata := struct{Data string}{}\nerr := res.UnmarshalContentJSON(\u0026data)\n```\n\nHere we send an HTTP GET request and unmarshal the response.\n\nIt is also possible to let `request.Send` do the unmarshal for us:\n\n```go\ndata := struct{Data string}{}\n_, err := request.Send(\u0026request.Options{\n    URL: myURL,\n}, \u0026data)\n```\n\nIn that case, the returned `Content`'s data is an empty byte array. Its other properties are valid (like the size, mime type, etc)\n\nYou can also download data directly to an `io.Writer`:\n\n```go\nwriter, err := os.Create(filepath.Join(\"tmp\", \"data\"))\ndefer writer.Close()\nres, err := request.Send(\u0026request.Options{\n  URL: serverURL,\n}, writer)\nlog.Infof(\"Downloaded %d bytes\", res.Length)\n```\n\nIn that case, the returned `Content`'s data is an empty byte array. Its other properties are valid (like the size, mime type, etc)\n\nAuthorization can be stored in the `Options.Authorization`:\n\n```go\npayload := struct{Key string}{}\nres, err := request.Send(\u0026request.Options{\n    URL:           myURL,\n    Authorization: request.BasicAuthorization(\"user\", \"password\"),\n}, nil)\n```\n\nor, with a Bearer Token:  \n\n```go\npayload := struct{Key string}{}\nres, err := request.Send(\u0026request.Options{\n    URL:           myURL,\n    Authorization: request.BearerAuthorization(\"myTokenABCD\"),\n}, nil)\n```\n\nObjects can be sent as payloads:\n\n```go\npayload := struct{Key string}{}\nres, err := request.Send(\u0026request.Options{\n    URL:     myURL,\n    Payload: payload,\n}, nil)\n```\n\nA payload will induce an HTTP POST unless mentioned.\n\nSo, to send an `HTTP PUT`, simply write:\n\n```go\npayload := struct{Key string}{}\nres, err := request.Send(\u0026request.Options{\n    Method:  http.MethodPut,\n    URL:     myURL,\n    Payload: payload,\n}, nil)\n```\n\nTo send an x-www-form, use a `map` in the payload:  \n\n```go\nres, err := request.Send(\u0026request.Options{\n    Method:  http.MethodPut,\n    URL:     myURL,\n    Payload: map[string]string{\n        \"ID\":   \"1234\",\n        \"Kind\": \"stuff,\"\n    },\n}, nil)\n```\n\nTo send a multipart form with an attachment, use a `map`, an attachment, and one of the key must start with `\u003e`:  \n\n```go\nattachment := io.Open(\"/path/to/file\")\nres, err := request.Send(\u0026request.Options{\n    Method:  http.MethodPut,\n    URL:     myURL,\n    Payload: map[string]string{\n        \"ID\":    \"1234\",\n        \"Kind\":  \"stuff,\"\n        \"\u003efile\": \"image.png\",\n    },\n    Attachment: attachment,\n}, nil)\n```\n\nThe 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\"`.\n\nTo send the request again when receiving a Service Unavailable (`Attempts` and `Timeout` are optional):  \n\n```go\nres, err := request.Send(\u0026request.Options{\n    URL:                  myURL,\n    RetryableStatusCodes: []int{http.StatusServiceUnavailable},\n    Attempts:             10,\n    Timeout:              2 * time.Second,\n}, nil)\n```\n\nBy 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.\n\nConnection 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).\n\nYou can change the delay and the backoff factor like this:\n\n```go\nres, err := request.Send(\u0026request.Options{\n    URL:                         myURL,\n    InterAttemptDelay:           5 * time.Second,\n    InterAttemptBackoffInterval: 2 * time.Minute,\n}, nil)\n```\n\nYou can also not use the backoff algorithm and use the `Retry-After` header instead:\n\n```go\nres, err := request.Send(\u0026request.Options{\n    URL:                       myURL,\n    // ...\n    InterAttemptUseRetryAfter: true,\n}, nil)\n```\n\nWhen sending requests to upload data streams, you can provide an `io.Writer` to write the progress to:\n\n```go\nimport \"github.com/schollz/progressbar/v3\"\n\nreader, err := os.Open(pathToFile)\ndefer reader.Close()\nstat, err := reader.Stat()\nbar := progressbar.DefaultBytes(stat.Size(), \"Uploading\")\nres, err := request.Send(\u0026request.Options{\n  Method:         http.MethodPost,\n  URL:            serverURL,\n  Payload:        reader,\n  ProgressWriter: bar,\n}, reader)\n```\n\nIf the progress `io.Writer` is also an `io.Closer`, it will be closed at the end of the `request.Send()`.\n\nWhen sending requests to download data streams, you can provide an `io.Writer` to write the progress to:\n\n```go\nimport \"github.com/schollz/progressbar/v3\"\n\nwriter, err := os.Create(pathToFile)\ndefer writer.Close()\nbar := progressbar.DefaultBytes(-1, \"Downloading\") // will use a spinner\nres, err := request.Send(\u0026request.Options{\n  URL:            serverURL,\n  ProgressWriter: bar,\n}, writer)\n```\n\nAgain, if the progress `io.Writer` is also an `io.Closer`, it will be closed at the end of the `request.Send()`.\n\nif 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`:\n\n```go\nimport \"github.com/schollz/progressbar/v3\"\n\nwriter, err := os.Create(pathToFile)\ndefer writer.Close()\nbar := progressbar.DefaultBytes(1, \"Downloading\") // use a temporary max value\nres, err := request.Send(\u0026request.Options{\n  URL:            serverURL,\n  ProgressWriter: bar,\n}, writer)\n```\n\n```go\nimport \"github.com/cheggaaa/pb/v3\"\n\nwriter, err := os.Create(pathToFile)\ndefer writer.Close()\nbar := pb.StartNew(1) // use a temporary max value\nres, err := request.Send(\u0026request.Options{\n  URL:                serverURL,\n  ProgressWriter:     bar,\n  ProgressSetMaxFunc: func(max int64) { bar.SetTotal64(max) },\n}, writer)\n```\n\n**Notes:**  \n\n- if the PayloadType is not mentioned, it is calculated when processing the Payload.\n- if the payload is a `ContentReader` or a `Content`, it is used directly.\n- 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.\n- if the payload is a struct or a pointer to struct, the body is sent as `application/json` and marshaled.\n- if the payload is an array or a slice, the body is sent as `application/json` and marshaled.\n- 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).\n- 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*.\n- `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}`.\n- The default timeout for `Send()` is 1 second.\n\n**TODO:**  \n\n- Support other kinds of `map` in the payload, like `map[string]int`, etc.\n- Maybe have an interface for the Payload to allow users to provide the logic of building the payload themselves. (`type PayloadBuilder interface { BuildPayload() *ContentReader}`?!?)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgildas%2Fgo-request","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgildas%2Fgo-request","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgildas%2Fgo-request/lists"}