Skip to content

Commit 8a41c82

Browse files
committed
Go version fixes and Client additions
1 parent bfccf55 commit 8a41c82

File tree

4 files changed

+123
-5
lines changed

4 files changed

+123
-5
lines changed

client.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,60 @@
11
package httpx
22

33
import (
4+
"net"
45
"net/http"
6+
"runtime"
57
"time"
68
)
79

8-
// Doer is an interface for http.Client.
9-
type Doer interface {
10+
// Doer is a backward compatible definition for [Client].
11+
type Doer = Client
12+
13+
// Client is an interface for [http.Client].
14+
type Client interface {
1015
Do(req *http.Request) (*http.Response, error)
1116
}
1217

13-
// NewClient return http.Client with a sane default.
18+
// NewClient returns [http.Client] with a sane defaults and non-shared [http.Transport].
19+
// See [NewTransport] for more info.
1420
func NewClient() *http.Client {
1521
return &http.Client{
16-
Timeout: 5 * time.Second,
22+
Transport: NewTransport(),
23+
Timeout: 5 * time.Second,
24+
}
25+
}
26+
27+
// NewPooledClient returns [http.Client] which will be used for the same host(s).
28+
func NewPooledClient() *http.Client {
29+
return &http.Client{
30+
Transport: NewPooledTransport(),
31+
Timeout: 5 * time.Second,
32+
}
33+
}
34+
35+
// NewTransport returns [http.Transport] with idle connections and keepalives disabled.
36+
func NewTransport() *http.Transport {
37+
transport := NewPooledTransport()
38+
transport.DisableKeepAlives = true
39+
transport.MaxIdleConnsPerHost = -1
40+
return transport
41+
}
42+
43+
// NewPooledTransport returns [http.Transport] which will be used for the same host(s).
44+
func NewPooledTransport() *http.Transport {
45+
transport := &http.Transport{
46+
Proxy: http.ProxyFromEnvironment,
47+
DialContext: (&net.Dialer{
48+
Timeout: 30 * time.Second,
49+
KeepAlive: 30 * time.Second,
50+
DualStack: true,
51+
}).DialContext,
52+
TLSHandshakeTimeout: 10 * time.Second,
53+
MaxIdleConns: 100,
54+
MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1,
55+
IdleConnTimeout: 90 * time.Second,
56+
ExpectContinueTimeout: 1 * time.Second,
57+
ForceAttemptHTTP2: true,
1758
}
59+
return transport
1860
}

content.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package httpx
2+
3+
import (
4+
"path/filepath"
5+
)
6+
7+
// ContentTypeByExt or 'application/octet-stream' if not found.
8+
func ContentTypeByExt(file string) string {
9+
if typ := ctypes[filepath.Ext(file)]; typ != "" {
10+
return typ
11+
}
12+
return ctypes[".bin"]
13+
}
14+
15+
var ctypes = map[string]string{
16+
".avif": "image/avif",
17+
".bin": "application/octet-stream",
18+
".css": "text/css; charset=utf-8",
19+
".csv": "text/csv; charset=utf-8",
20+
".gif": "image/gif",
21+
".htm": "text/html; charset=utf-8",
22+
".html": "text/html; charset=utf-8",
23+
".ico": "image/png",
24+
".jpeg": "image/jpeg",
25+
".jpg": "image/jpeg",
26+
".js": "text/javascript; charset=utf-8",
27+
".json": "application/json; charset=utf-8",
28+
".pdf": "application/pdf",
29+
".png": "image/png",
30+
".svg": "image/svg+xml",
31+
".txt": "text/plain; charset=utf-8",
32+
".webp": "image/webp",
33+
".xml": "application/xml",
34+
".zip": "application/zip",
35+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module github.com/cristalhq/httpx
22

3-
go 1.16
3+
go 1.24

request.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
11
package httpx
22

33
import (
4+
"cmp"
45
"context"
56
"fmt"
67
"io"
78
"net/http"
9+
"net/netip"
10+
"net/url"
11+
"strings"
812
)
913

1014
var NoBody = http.NoBody
1115

16+
var NilRequest = &http.Request{
17+
Method: "STUB",
18+
URL: &url.URL{Path: "/"},
19+
Header: http.Header{
20+
"X-Real-Ip": []string{"0.0.0.0"},
21+
},
22+
}
23+
1224
// NewRequest returns a new http.Request.
1325
func NewRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) {
1426
return http.NewRequestWithContext(ctx, method, url, body)
@@ -99,3 +111,32 @@ func MustPutRequest(ctx context.Context, url string, body io.Reader) *http.Reque
99111
func Bearer(token string) string {
100112
return "Bearer " + token
101113
}
114+
115+
// RequestIP from the headers if possible.
116+
func RequestIP(r *http.Request) netip.Addr {
117+
ip := cmp.Or(
118+
r.Header.Get("Cf-Connecting-Ip"),
119+
r.Header.Get("True-Client-IP"),
120+
r.Header.Get("X-Real-Ip"),
121+
)
122+
123+
if ip == "" {
124+
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
125+
i := strings.Index(xff, ",")
126+
if i == -1 {
127+
i = len(xff)
128+
}
129+
ip = xff[:i]
130+
} else {
131+
ip = r.RemoteAddr
132+
}
133+
}
134+
135+
addr, err := netip.ParseAddr(ip)
136+
if err == nil {
137+
return addr
138+
}
139+
140+
ap, _ := netip.ParseAddrPort(ip)
141+
return ap.Addr()
142+
}

0 commit comments

Comments
 (0)