https://github.com/sonirico/withttp
go build http π requests with fluency and wit β¨
https://github.com/sonirico/withttp
builder fluent-api generics golang golang-library hacktoberfest http http-client http-requests https
Last synced: 6 months ago
JSON representation
go build http π requests with fluency and wit β¨
- Host: GitHub
- URL: https://github.com/sonirico/withttp
- Owner: sonirico
- License: mit
- Created: 2022-08-15T21:40:35.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2023-02-15T19:33:41.000Z (almost 3 years ago)
- Last Synced: 2025-03-31T09:04:39.048Z (10 months ago)
- Topics: builder, fluent-api, generics, golang, golang-library, hacktoberfest, http, http-client, http-requests, https
- Language: Go
- Homepage:
- Size: 106 KB
- Stars: 19
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# π― withttp
**Build HTTP requests and parse responses with fluent syntax and wit**
[](https://github.com/sonirico/withttp/actions/workflows/go.yml)
[](https://goreportcard.com/report/github.com/sonirico/withttp)
[](https://godoc.org/github.com/sonirico/withttp)
[](https://opensource.org/licenses/MIT)
[](https://golang.org/dl/)
*A fluent HTTP client library that covers common scenarios while maintaining maximum flexibility*
---
## π Features
- **π Fluent API** - Chain methods for intuitive request building
- **π‘ Multiple HTTP Backends** - Support for `net/http` and `fasthttp`
- **π― Type-Safe Responses** - Generic-based response parsing
- **π Streaming Support** - Stream data from slices, channels, or readers
- **π§ͺ Mock-Friendly** - Built-in mocking capabilities for testing
- **β‘ High Performance** - Optimized for speed and low allocations
## π¦ Installation
```bash
go get github.com/sonirico/withttp
```
## ποΈ Supported HTTP Implementations
| Implementation | Description |
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [net/http](https://pkg.go.dev/net/http) | Go's standard HTTP client |
| [fasthttp](https://pkg.go.dev/github.com/valyala/fasthttp) | High-performance HTTP client |
| Custom Client | Implement the [Client interface](https://github.com/sonirico/withttp/blob/main/endpoint.go#L43) |
> π‘ Missing your preferred HTTP client? [Open an issue](https://github.com/sonirico/withttp/issues/new) and let us know!
## π Table of Contents
- [π― withttp](#-withttp)
- [π Features](#-features)
- [π¦ Installation](#-installation)
- [ποΈ Supported HTTP Implementations](#οΈ-supported-http-implementations)
- [π Table of Contents](#-table-of-contents)
- [π Quick Start](#-quick-start)
- [π‘ Examples](#-examples)
- [RESTful API Queries](#restful-api-queries)
- [Streaming Data](#streaming-data)
- [π Stream from Slice](#-stream-from-slice)
- [π‘ Stream from Channel](#-stream-from-channel)
- [π Stream from Reader](#-stream-from-reader)
- [Multiple Endpoints](#multiple-endpoints)
- [Testing with Mocks](#testing-with-mocks)
- [πΊοΈ Roadmap](#οΈ-roadmap)
- [π€ Contributing](#-contributing)
- [π License](#-license)
- [β Show Your Support](#-show-your-support)
## π Quick Start
```go
package main
import (
"context"
"fmt"
"net/http"
"github.com/sonirico/withttp"
)
type GithubRepo struct {
ID int `json:"id"`
Name string `json:"name"`
URL string `json:"html_url"`
}
func main() {
call := withttp.NewCall[GithubRepo](withttp.Fasthttp()).
URL("https://api.github.com/repos/sonirico/withttp").
Method(http.MethodGet).
Header("User-Agent", "withttp-example/1.0", false).
ParseJSON().
ExpectedStatusCodes(http.StatusOK)
err := call.Call(context.Background())
if err != nil {
panic(err)
}
fmt.Printf("Repository: %s (ID: %d)\n", call.BodyParsed.Name, call.BodyParsed.ID)
}
```
## π‘ Examples
All examples are now available as:
- **Example functions** in the test files - you can run these with `go test -v -run "^Example"`
- **Test functions** for comprehensive testing scenarios
- **Documentation examples** in the code itself
### RESTful API Queries
Click to expand
```go
type GithubRepoInfo struct {
ID int `json:"id"`
URL string `json:"html_url"`
}
func GetRepoInfo(user, repo string) (GithubRepoInfo, error) {
call := withttp.NewCall[GithubRepoInfo](withttp.Fasthttp()).
URL(fmt.Sprintf("https://api.github.com/repos/%s/%s", user, repo)).
Method(http.MethodGet).
Header("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false).
ParseJSON().
ExpectedStatusCodes(http.StatusOK)
err := call.Call(context.Background())
return call.BodyParsed, err
}
func main() {
info, _ := GetRepoInfo("sonirico", "withttp")
log.Println(info)
}
```
### Streaming Data
#### π Stream from Slice
View example
[See test example](https://github.com/sonirico/withttp/blob/main/examples_request_stream_test.go)
```go
type metric struct {
Time time.Time `json:"t"`
Temp float32 `json:"T"`
}
func CreateStream() error {
points := []metric{
{Time: time.Unix(time.Now().Unix()-1, 0), Temp: 39},
{Time: time.Now(), Temp: 40},
}
stream := withttp.Slice[metric](points)
testEndpoint := withttp.NewEndpoint("webhook-site-request-stream-example").
Request(withttp.BaseURL("https://webhook.site/24e84e8f-75cf-4239-828e-8bed244c0afb"))
call := withttp.NewCall[any](withttp.Fasthttp()).
Method(http.MethodPost).
ContentType(withttp.ContentTypeJSONEachRow).
RequestSniffed(func(data []byte, err error) {
fmt.Printf("recv: '%s', err: %v", string(data), err)
}).
RequestStreamBody(withttp.RequestStreamBody[any, metric](stream)).
ExpectedStatusCodes(http.StatusOK)
return call.CallEndpoint(context.Background(), testEndpoint)
}
```
#### π‘ Stream from Channel
View example
[See test example](https://github.com/sonirico/withttp/blob/main/examples_request_stream_test.go)
```go
func CreateStreamChannel() error {
points := make(chan metric, 2)
go func() {
points <- metric{Time: time.Unix(time.Now().Unix()-1, 0), Temp: 39}
points <- metric{Time: time.Now(), Temp: 40}
close(points)
}()
stream := withttp.Channel[metric](points)
testEndpoint := withttp.NewEndpoint("webhook-site-request-stream-example").
Request(withttp.BaseURL("https://webhook.site/24e84e8f-75cf-4239-828e-8bed244c0afb"))
call := withttp.NewCall[any](withttp.Fasthttp()).
Method(http.MethodPost).
ContentType(withttp.ContentTypeJSONEachRow).
RequestSniffed(func(data []byte, err error) {
fmt.Printf("recv: '%s', err: %v", string(data), err)
}).
RequestStreamBody(withttp.RequestStreamBody[any, metric](stream)).
ExpectedStatusCodes(http.StatusOK)
return call.CallEndpoint(context.Background(), testEndpoint)
}
```
#### π Stream from Reader
View example
[See test example](https://github.com/sonirico/withttp/blob/main/examples_request_stream_test.go)
```go
func CreateStreamReader() error {
buf := bytes.NewBuffer(nil)
go func() {
buf.WriteString("{\"t\":\"2022-09-01T00:58:15+02:00\"")
buf.WriteString(",\"T\":39}\n{\"t\":\"2022-09-01T00:59:15+02:00\",\"T\":40}\n")
}()
streamFactory := withttp.NewProxyStreamFactory(1 << 10)
stream := withttp.NewStreamFromReader(buf, streamFactory)
testEndpoint := withttp.NewEndpoint("webhook-site-request-stream-example").
Request(withttp.BaseURL("https://webhook.site/24e84e8f-75cf-4239-828e-8bed244c0afb"))
call := withttp.NewCall[any](withttp.NetHttp()).
Method(http.MethodPost).
RequestSniffed(func(data []byte, err error) {
fmt.Printf("recv: '%s', err: %v", string(data), err)
}).
ContentType(withttp.ContentTypeJSONEachRow).
RequestStreamBody(withttp.RequestStreamBody[any, []byte](stream)).
ExpectedStatusCodes(http.StatusOK)
return call.CallEndpoint(context.Background(), testEndpoint)
}
```
### Multiple Endpoints
Click to expand
Define reusable endpoint configurations for API consistency:
```go
var (
githubApi = withttp.NewEndpoint("GithubAPI").
Request(withttp.BaseURL("https://api.github.com/"))
)
type GithubRepoInfo struct {
ID int `json:"id"`
URL string `json:"html_url"`
}
func GetRepoInfo(user, repo string) (GithubRepoInfo, error) {
call := withttp.NewCall[GithubRepoInfo](withttp.Fasthttp()).
URI(fmt.Sprintf("repos/%s/%s", user, repo)).
Method(http.MethodGet).
Header("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false).
HeaderFunc(func() (key, value string, override bool) {
return "X-Date", time.Now().String(), true
}).
ParseJSON().
ExpectedStatusCodes(http.StatusOK)
err := call.CallEndpoint(context.Background(), githubApi)
return call.BodyParsed, err
}
type GithubCreateIssueResponse struct {
ID int `json:"id"`
URL string `json:"url"`
}
func CreateRepoIssue(user, repo, title, body, assignee string) (GithubCreateIssueResponse, error) {
type payload struct {
Title string `json:"title"`
Body string `json:"body"`
Assignee string `json:"assignee"`
}
p := payload{Title: title, Body: body, Assignee: assignee}
call := withttp.NewCall[GithubCreateIssueResponse](withttp.Fasthttp()).
URI(fmt.Sprintf("repos/%s/%s/issues", user, repo)).
Method(http.MethodPost).
ContentType("application/vnd+github+json").
Body(p).
HeaderFunc(func() (key, value string, override bool) {
return "Authorization", fmt.Sprintf("Bearer %s", "S3cret"), true
}).
ExpectedStatusCodes(http.StatusCreated)
err := call.CallEndpoint(context.Background(), githubApi)
log.Println("req body", string(call.Req.Body()))
return call.BodyParsed, err
}
func main() {
// Fetch repo info
info, _ := GetRepoInfo("sonirico", "withttp")
log.Println(info)
// Create an issue
res, err := CreateRepoIssue("sonirico", "withttp", "test", "This is a test", "sonirico")
log.Println(res, err)
}
```
### Testing with Mocks
Click to expand
Easily test your HTTP calls with built-in mocking:
```go
var (
exchangeListOrders = withttp.NewEndpoint("ListOrders").
Request(withttp.BaseURL("http://example.com")).
Response(
withttp.MockedRes(func(res withttp.Response) {
res.SetBody(io.NopCloser(bytes.NewReader(mockResponse)))
res.SetStatus(http.StatusOK)
}),
)
mockResponse = []byte(strings.TrimSpace(`
{"amount": 234, "pair": "BTC/USDT"}
{"amount": 123, "pair": "ETH/USDT"}`))
)
func main() {
type Order struct {
Amount float64 `json:"amount"`
Pair string `json:"pair"`
}
res := make(chan Order)
call := withttp.NewCall[Order](withttp.Fasthttp()).
URL("https://github.com/").
Method(http.MethodGet).
Header("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false).
ParseJSONEachRowChan(res).
ExpectedStatusCodes(http.StatusOK)
go func() {
for order := range res {
log.Println(order)
}
}()
err := call.CallEndpoint(context.Background(), exchangeListOrders)
if err != nil {
panic(err)
}
}
```
## πΊοΈ Roadmap
| Feature | Status |
| ----------------------------- | ------------- |
| Form-data content type codecs | π In Progress |
| Enhanced auth methods | π Planned |
| XML parsing support | π Planned |
| Tabular data support | π Planned |
| gRPC integration | π€ Considering |
## π€ Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## π License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## β Show Your Support
If this project helped you, please give it a β! It helps others discover the project.
---
**[Documentation](https://godoc.org/github.com/sonirico/withttp)** β’
**[Test Examples](https://github.com/sonirico/withttp/blob/main/)** β’
**[Issues](https://github.com/sonirico/withttp/issues)** β’
**[Discussions](https://github.com/sonirico/withttp/discussions)**
Made with β€οΈ by [sonirico](https://github.com/sonirico)