-
Notifications
You must be signed in to change notification settings - Fork 0
[init] initial implementation #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| <a id="readme-top"></a> | ||
|
|
||
| [![Contributors][contributors-shield]][contributors-url] | ||
| [![Forks][forks-shield]][forks-url] | ||
| [![Stargazers][stars-shield]][stars-url] | ||
| [![Issues][issues-shield]][issues-url] | ||
| [![Apache 2.0 License][license-shield]][license-url] | ||
|
|
||
| <br /> | ||
| <div align="center"> | ||
| <h1>httpfx</h1> | ||
| <p> | ||
| Uber Fx module for <code>net/http.Client</code> with SOCKS5, HTTP proxy, and environment-based proxy support. | ||
| </p> | ||
| <a href="https://github.com/go-core-fx/httpfx/issues/new">Report Bug</a> | ||
| · | ||
| <a href="https://github.com/go-core-fx/httpfx/issues/new">Request Feature</a> | ||
| </div> | ||
|
|
||
| ## Table of Contents | ||
| - [Table of Contents](#table-of-contents) | ||
| - [About The Project](#about-the-project) | ||
| - [Built With](#built-with) | ||
| - [Getting Started](#getting-started) | ||
| - [Prerequisites](#prerequisites) | ||
| - [Installation](#installation) | ||
| - [Usage](#usage) | ||
| - [Module Setup](#module-setup) | ||
| - [Configuration Reference](#configuration-reference) | ||
| - [Factory \& Per-Client Options](#factory--per-client-options) | ||
| - [Available Options](#available-options) | ||
| - [Proxy Examples](#proxy-examples) | ||
| - [Roadmap](#roadmap) | ||
| - [Contributing](#contributing) | ||
| - [License](#license) | ||
| - [Acknowledgments](#acknowledgments) | ||
|
|
||
|
|
||
| --- | ||
|
|
||
| ## About The Project | ||
|
|
||
| `httpfx` is an [Uber Fx](https://uber-go.github.io/fx/) module that provides a configured `*http.Client` and a `Factory` for creating additional client instances. It supports: | ||
|
|
||
| - **SOCKS5 proxy** via `golang.org/x/net/proxy` (`socks5://user:pass@host:port`) | ||
| - **HTTP-level proxy** via `net/http.Transport.Proxy` (`http://proxy:8080`) | ||
| - **Environment-based proxy** via `ALL_PROXY` env var | ||
| - **Per-host proxy bypass** for hosts that should connect directly | ||
| - **Per-client overrides** via functional options on the `Factory` | ||
| - **Transport tuning** — idle connections, timeouts, pool sizes | ||
|
|
||
| ### Built With | ||
|
|
||
| - [](https://go.dev/) | ||
| - [](https://uber-go.github.io/fx/) | ||
| - [](https://pkg.go.dev/golang.org/x/net/proxy) | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| --- | ||
|
|
||
| ## Getting Started | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - Go 1.25+ | ||
| - An application using [Uber Fx](https://uber-go.github.io/fx/) for dependency injection | ||
|
|
||
| ### Installation | ||
|
|
||
| ```sh | ||
| go get github.com/go-core-fx/httpfx@latest | ||
| ``` | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| --- | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Module Setup | ||
|
|
||
| ```go | ||
| import ( | ||
| "time" | ||
|
|
||
| "github.com/go-core-fx/httpfx" | ||
| "go.uber.org/fx" | ||
| ) | ||
|
|
||
| func main() { | ||
| fx.New( | ||
| fx.Provide(func() httpfx.Config { | ||
| return httpfx.Config{ | ||
| ProxyURL: "socks5://127.0.0.1:1080", | ||
| Bypass: "localhost,127.0.0.1", | ||
| Timeout: 30 * time.Second, | ||
| } | ||
| }), | ||
| httpfx.Module(), | ||
| // ... other modules | ||
| ).Run() | ||
| } | ||
| ``` | ||
|
|
||
| The module provides both a default `*http.Client` and a `Factory` for creating additional clients. | ||
|
|
||
| ### Configuration Reference | ||
|
|
||
| | Field | Type | Default | Description | | ||
| | --------------------- | --------------- | ------- | --------------------------------------------------------------------------------- | | ||
| | `ProxyURL` | `string` | `""` | SOCKS5 proxy URL (e.g. `socks5://user:pass@host:port`). Takes highest precedence. | | ||
| | `ProxyFromEnv` | `bool` | `false` | Read proxy from `ALL_PROXY` env var when `ProxyURL` is empty. | | ||
| | `Bypass` | `string` | `""` | Comma-separated hosts to bypass the SOCKS proxy (e.g. `localhost,127.0.0.1`). | | ||
| | `Timeout` | `time.Duration` | `0` | Client-level request timeout. Zero means no timeout. | | ||
| | `MaxIdleConns` | `int` | `0` | Maximum idle (keep-alive) connections. Zero means no limit. | | ||
| | `MaxIdleConnsPerHost` | `int` | `0` | Maximum idle connections per host. Zero means Go default (2). | | ||
| | `IdleConnTimeout` | `time.Duration` | `0` | Maximum time a connection stays idle. Zero means no timeout. | | ||
|
|
||
| **Proxy precedence:** `ProxyURL` → `ProxyFromEnv`. To disable all proxying, clear `ProxyURL` and set `ProxyFromEnv: false`. | ||
|
|
||
| ### Factory & Per-Client Options | ||
|
|
||
| Inject `httpfx.Factory` to create additional clients with shared base config but per-client overrides: | ||
|
|
||
| ```go | ||
| func Handler(f httpfx.Factory) { | ||
| // Default client — uses factory base config | ||
| defaultClient := f.NewClient() | ||
|
|
||
| // Override timeout for a fast endpoint | ||
| apiClient := f.NewClient(httpfx.WithTimeout(5 * time.Second)) | ||
|
|
||
| // Disable proxy for internal service calls | ||
| internalClient := f.NewClient(httpfx.WithProxyURL("")) | ||
|
|
||
| // Custom transport for a specific module | ||
| uploadClient := f.NewClient( | ||
| httpfx.WithTimeout(5 * time.Minute), | ||
| httpfx.WithMaxIdleConns(10), | ||
| ) | ||
| } | ||
| ``` | ||
|
|
||
| #### Available Options | ||
|
|
||
| | Option | Description | | ||
| | ---------------------------- | -------------------------------------- | | ||
| | `WithProxyURL(url)` | Override SOCKS5 proxy URL | | ||
| | `WithProxyFromEnv(v)` | Override env-based proxy flag | | ||
| | `WithBypass(bypass)` | Override proxy bypass list | | ||
| | `WithTimeout(d)` | Override client timeout | | ||
| | `WithMaxIdleConns(n)` | Override max idle connections | | ||
| | `WithMaxIdleConnsPerHost(n)` | Override max idle connections per host | | ||
| | `WithIdleConnTimeout(d)` | Override idle connection timeout | | ||
|
|
||
| ### Proxy Examples | ||
|
|
||
| **SOCKS5 with authentication:** | ||
|
|
||
| ```go | ||
| httpfx.Config{ | ||
| ProxyURL: "socks5://user:pass@127.0.0.1:1080", | ||
| } | ||
| ``` | ||
|
|
||
| **Environment-based (reads `ALL_PROXY`):** | ||
|
|
||
| ```go | ||
| httpfx.Config{ | ||
| ProxyFromEnv: true, | ||
| } | ||
| ``` | ||
|
|
||
| ```sh | ||
| export ALL_PROXY="socks5://127.0.0.1:1080" | ||
| ``` | ||
|
|
||
| **SOCKS5 with bypass for local addresses and CIDR ranges:** | ||
|
|
||
| ```go | ||
| httpfx.Config{ | ||
| ProxyURL: "socks5://127.0.0.1:1080", | ||
| Bypass: "localhost,127.0.0.1,192.168.0.0/16", | ||
| } | ||
| ``` | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| --- | ||
|
|
||
| ## Roadmap | ||
|
|
||
| - [x] SOCKS5 proxy support via `golang.org/x/net/proxy` | ||
| - [x] Environment-based proxy (`ALL_PROXY`) | ||
| - [x] Per-host proxy bypass | ||
| - [x] Factory with per-client functional options | ||
| - [x] Transport tuning (idle connections, timeouts) | ||
| - [ ] TLS configuration options | ||
| - [ ] Proxy authentication header support | ||
|
|
||
| See the [open issues](https://github.com/go-core-fx/httpfx/issues) for a full list of proposed features. | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| --- | ||
|
|
||
| ## Contributing | ||
|
|
||
| Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. | ||
|
|
||
| 1. Fork the Project | ||
| 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) | ||
| 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) | ||
| 4. Push to the Branch (`git push origin feature/AmazingFeature`) | ||
| 5. Open a Pull Request | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| --- | ||
|
|
||
| ## License | ||
|
|
||
| Distributed under the Apache License 2.0. See `LICENSE` for more information. | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| --- | ||
|
|
||
| ## Acknowledgments | ||
|
|
||
| - [Uber Fx](https://uber-go.github.io/fx/) — dependency injection framework | ||
| - [golang.org/x/net/proxy](https://pkg.go.dev/golang.org/x/net/proxy) — SOCKS5 and environment-based proxy dialers | ||
| - [Best-README-Template](https://github.com/othneildrew/Best-README-Template) — README structure | ||
|
|
||
| <p align="right">(<a href="#readme-top">back to top</a>)</p> | ||
|
|
||
| <!-- MARKDOWN LINKS & IMAGES --> | ||
| [contributors-shield]: https://img.shields.io/github/contributors/go-core-fx/httpfx.svg?style=for-the-badge | ||
| [contributors-url]: https://github.com/go-core-fx/httpfx/graphs/contributors | ||
| [forks-shield]: https://img.shields.io/github/forks/go-core-fx/httpfx.svg?style=for-the-badge | ||
| [forks-url]: https://github.com/go-core-fx/httpfx/network/members | ||
| [stars-shield]: https://img.shields.io/github/stars/go-core-fx/httpfx.svg?style=for-the-badge | ||
| [stars-url]: https://github.com/go-core-fx/httpfx/stargazers | ||
| [issues-shield]: https://img.shields.io/github/issues/go-core-fx/httpfx.svg?style=for-the-badge | ||
| [issues-url]: https://github.com/go-core-fx/httpfx/issues | ||
| [license-shield]: https://img.shields.io/github/license/go-core-fx/httpfx.svg?style=for-the-badge | ||
| [license-url]: https://github.com/go-core-fx/httpfx/blob/master/LICENSE | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package httpfx | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "net" | ||
| "net/http" | ||
| "net/url" | ||
| "os" | ||
| "strings" | ||
|
|
||
| "golang.org/x/net/proxy" | ||
| ) | ||
|
|
||
| func newClient(config Config) (*http.Client, error) { | ||
| transport := &http.Transport{ | ||
| MaxIdleConns: config.MaxIdleConns, | ||
| MaxIdleConnsPerHost: config.MaxIdleConnsPerHost, | ||
| IdleConnTimeout: config.IdleConnTimeout, | ||
| } | ||
|
|
||
| if err := applyProxy(transport, config); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &http.Client{ | ||
| Transport: transport, | ||
| Timeout: config.Timeout, | ||
| }, nil | ||
| } | ||
|
|
||
| func applyProxy(transport *http.Transport, config Config) error { | ||
| switch { | ||
| case config.ProxyURL != "": | ||
| return applySOCKSProxy(transport, config.ProxyURL, config.Bypass) | ||
| case config.ProxyFromEnv: | ||
| return applyEnvProxy(transport, config.Bypass) | ||
| default: | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func applySOCKSProxy(transport *http.Transport, rawURL, bypass string) error { | ||
| u, err := url.Parse(rawURL) | ||
| if err != nil { | ||
| return fmt.Errorf("%w", ErrInvalidProxyURL) | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| if u.Hostname() == "" { | ||
| return fmt.Errorf("%w: empty hostname", ErrInvalidProxyURL) | ||
| } | ||
|
|
||
| dialer, err := proxy.FromURL(u, proxy.Direct) | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %w", ErrProxyDialFailed, err) | ||
|
capcom6 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| dialer = applyBypass(dialer, bypass) | ||
| setTransportDialer(transport, dialer) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func applyEnvProxy(transport *http.Transport, bypass string) error { | ||
| hasProxyEnv := os.Getenv("ALL_PROXY") != "" || os.Getenv("all_proxy") != "" | ||
|
|
||
| dialer := proxy.FromEnvironment() | ||
|
|
||
| if dialer == proxy.Direct { | ||
| if hasProxyEnv { | ||
| return fmt.Errorf("%w: proxy environment variable set but invalid", ErrInvalidProxyURL) | ||
| } | ||
| return nil | ||
| } | ||
|
capcom6 marked this conversation as resolved.
|
||
|
|
||
| dialer = applyBypass(dialer, bypass) | ||
| setTransportDialer(transport, dialer) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func applyBypass(dialer proxy.Dialer, bypass string) proxy.Dialer { | ||
| bypass = strings.TrimSpace(bypass) | ||
| if bypass == "" { | ||
| return dialer | ||
| } | ||
|
|
||
| perHost := proxy.NewPerHost(dialer, proxy.Direct) | ||
| perHost.AddFromString(bypass) | ||
|
|
||
| return perHost | ||
| } | ||
|
|
||
| func setTransportDialer(transport *http.Transport, dialer proxy.Dialer) { | ||
| if cd, ok := dialer.(proxy.ContextDialer); ok { | ||
| transport.DialContext = cd.DialContext | ||
| return | ||
| } | ||
|
|
||
| transport.DialContext = func( | ||
| _ context.Context, | ||
| network, | ||
| addr string, | ||
| ) (net.Conn, error) { | ||
| return dialer.Dial(network, addr) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package httpfx | ||
|
|
||
| import "time" | ||
|
|
||
| // Config holds the HTTP client configuration. | ||
| type Config struct { | ||
| // ProxyURL is an explicit SOCKS5 proxy URL (e.g., "socks5://user:pass@host:port"). | ||
| // Empty means no proxy. Takes precedence over ProxyFromEnv. | ||
| ProxyURL string | ||
|
|
||
| // ProxyFromEnv enables reading the proxy from the ALL_PROXY environment variable. | ||
| // Used only when ProxyURL is empty. | ||
| ProxyFromEnv bool | ||
|
|
||
| // Bypass is a comma-separated list of hosts that bypass the proxy | ||
| // (e.g., "localhost,127.0.0.1"). | ||
| Bypass string | ||
|
capcom6 marked this conversation as resolved.
|
||
|
|
||
| // Timeout is the HTTP client-level timeout. Zero means no timeout. | ||
| Timeout time.Duration | ||
|
|
||
| // MaxIdleConns is the maximum number of idle (keep-alive) connections. | ||
| // Zero means no limit. | ||
| MaxIdleConns int | ||
|
|
||
| // MaxIdleConnsPerHost is the maximum idle connections per host. | ||
| // Zero means DefaultMaxIdleConnsPerHost (2). | ||
| MaxIdleConnsPerHost int | ||
|
|
||
| // IdleConnTimeout is the maximum time an idle connection is kept alive. | ||
| // Zero means no timeout. | ||
| IdleConnTimeout time.Duration | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.