funnel_http 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.golangci.yml +21 -0
- data/.rspec +3 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +87 -0
- data/Rakefile +56 -0
- data/Steepfile +31 -0
- data/benchmark/README.md +32 -0
- data/benchmark/benchmark.rb +61 -0
- data/benchmark/compose.yml +8 -0
- data/benchmark/html/index.html +4 -0
- data/benchmark/nginx.conf +25 -0
- data/ext/funnel_http/extconf.rb +11 -0
- data/ext/funnel_http/funnel_http.c +2 -0
- data/ext/funnel_http/funnel_http.go +164 -0
- data/ext/funnel_http/funnel_http.h +6 -0
- data/ext/funnel_http/go.mod +27 -0
- data/ext/funnel_http/go.sum +78 -0
- data/ext/funnel_http/run_requests.go +77 -0
- data/ext/funnel_http/run_requests_test.go +120 -0
- data/lib/funnel_http/client.rb +112 -0
- data/lib/funnel_http/ext.rb +6 -0
- data/lib/funnel_http/version.rb +5 -0
- data/lib/funnel_http.rb +14 -0
- data/rbs_collection.lock.yaml +68 -0
- data/rbs_collection.yaml +24 -0
- data/sig/funnel_http/client.rbs +21 -0
- data/sig/funnel_http/ext.rbs +11 -0
- data/sig/funnel_http.rbs +31 -0
- metadata +261 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
2
|
+
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
3
|
+
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
4
|
+
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
5
|
+
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
6
|
+
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
7
|
+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
8
|
+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
9
|
+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
10
|
+
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
11
|
+
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
12
|
+
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
13
|
+
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
14
|
+
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
15
|
+
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
16
|
+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
17
|
+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
18
|
+
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
|
19
|
+
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
20
|
+
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
21
|
+
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
22
|
+
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
23
|
+
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
24
|
+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
25
|
+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
26
|
+
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
|
27
|
+
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
|
28
|
+
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
29
|
+
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
30
|
+
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
31
|
+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
32
|
+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
33
|
+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
34
|
+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
35
|
+
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
36
|
+
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
37
|
+
github.com/ruby-go-gem/go-gem-wrapper v0.5.1 h1:TFGH/eOJl0uYzYMwrcLbZxXNQiQbQjCq/VLXyRk1HRM=
|
38
|
+
github.com/ruby-go-gem/go-gem-wrapper v0.5.1/go.mod h1:k2k+LziSCMxNYP4J9/9v90xdU6zlU1DJpJDTU6oJhHE=
|
39
|
+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
40
|
+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
41
|
+
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
42
|
+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
43
|
+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
44
|
+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
45
|
+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
46
|
+
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
47
|
+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
48
|
+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
49
|
+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
50
|
+
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
51
|
+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
52
|
+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
53
|
+
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
54
|
+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
55
|
+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
56
|
+
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
57
|
+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
58
|
+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
59
|
+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
60
|
+
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
61
|
+
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
62
|
+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
63
|
+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
64
|
+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
65
|
+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
66
|
+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
67
|
+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
68
|
+
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
69
|
+
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
70
|
+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
71
|
+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
72
|
+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
73
|
+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
74
|
+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
75
|
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
76
|
+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
77
|
+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
78
|
+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
@@ -0,0 +1,77 @@
|
|
1
|
+
package main
|
2
|
+
|
3
|
+
import (
|
4
|
+
"bytes"
|
5
|
+
"github.com/cockroachdb/errors"
|
6
|
+
"golang.org/x/sync/errgroup"
|
7
|
+
"io"
|
8
|
+
"net/http"
|
9
|
+
)
|
10
|
+
|
11
|
+
// Request is proxy between CRuby and Go
|
12
|
+
type Request struct {
|
13
|
+
Method string
|
14
|
+
URL string
|
15
|
+
Header map[string][]string
|
16
|
+
}
|
17
|
+
|
18
|
+
// Response is proxy between CRuby and Go
|
19
|
+
type Response struct {
|
20
|
+
StatusCode int
|
21
|
+
Header map[string][]string
|
22
|
+
Body []byte
|
23
|
+
}
|
24
|
+
|
25
|
+
// RunRequests perform HTTP requests in parallel
|
26
|
+
func RunRequests(httpClient *http.Client, requests []Request) ([]Response, error) {
|
27
|
+
g := new(errgroup.Group)
|
28
|
+
responses := make([]Response, len(requests))
|
29
|
+
|
30
|
+
for i, request := range requests {
|
31
|
+
// https://golang.org/doc/faq#closures_and_goroutines
|
32
|
+
i := i
|
33
|
+
request := request
|
34
|
+
|
35
|
+
g.Go(func() error {
|
36
|
+
var body []byte
|
37
|
+
httpReq, err := http.NewRequest(request.Method, request.URL, bytes.NewBuffer(body))
|
38
|
+
if err != nil {
|
39
|
+
return errors.WithStack(err)
|
40
|
+
}
|
41
|
+
|
42
|
+
for key, values := range request.Header {
|
43
|
+
for _, value := range values {
|
44
|
+
httpReq.Header.Add(key, value)
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
httpResp, err := httpClient.Do(httpReq)
|
49
|
+
if err != nil {
|
50
|
+
return errors.WithStack(err)
|
51
|
+
}
|
52
|
+
defer httpResp.Body.Close()
|
53
|
+
|
54
|
+
responses[i].StatusCode = httpResp.StatusCode
|
55
|
+
|
56
|
+
buf, err := io.ReadAll(httpResp.Body)
|
57
|
+
if err != nil {
|
58
|
+
return errors.WithStack(err)
|
59
|
+
}
|
60
|
+
|
61
|
+
responses[i].Body = buf
|
62
|
+
responses[i].Header = map[string][]string{}
|
63
|
+
|
64
|
+
for key, values := range httpResp.Header {
|
65
|
+
responses[i].Header[key] = append(responses[i].Header[key], values...)
|
66
|
+
}
|
67
|
+
|
68
|
+
return nil
|
69
|
+
})
|
70
|
+
}
|
71
|
+
|
72
|
+
if err := g.Wait(); err != nil {
|
73
|
+
return []Response{}, errors.WithStack(err)
|
74
|
+
}
|
75
|
+
|
76
|
+
return responses, nil
|
77
|
+
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
package main_test
|
2
|
+
|
3
|
+
import (
|
4
|
+
"github.com/jarcoal/httpmock"
|
5
|
+
"github.com/stretchr/testify/assert"
|
6
|
+
"github.com/sue445/funnel_http"
|
7
|
+
"net/http"
|
8
|
+
"testing"
|
9
|
+
)
|
10
|
+
|
11
|
+
func TestRunRequests(t *testing.T) {
|
12
|
+
httpmock.Activate()
|
13
|
+
t.Cleanup(httpmock.DeactivateAndReset)
|
14
|
+
|
15
|
+
httpmock.RegisterResponder("GET", "http://example.com/1",
|
16
|
+
func(req *http.Request) (*http.Response, error) {
|
17
|
+
resp := httpmock.NewStringResponse(200, "GET http://example.com/1")
|
18
|
+
|
19
|
+
resp.Header.Set("Content-Type", "text/plain")
|
20
|
+
|
21
|
+
for key, values := range req.Header {
|
22
|
+
for _, value := range values {
|
23
|
+
resp.Header.Add(key, value)
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
return resp, nil
|
28
|
+
})
|
29
|
+
|
30
|
+
httpmock.RegisterResponder("GET", "http://example.com/2",
|
31
|
+
func(req *http.Request) (*http.Response, error) {
|
32
|
+
resp := httpmock.NewStringResponse(200, "GET http://example.com/2")
|
33
|
+
|
34
|
+
resp.Header.Set("Content-Type", "text/plain")
|
35
|
+
|
36
|
+
for key, values := range req.Header {
|
37
|
+
for _, value := range values {
|
38
|
+
resp.Header.Add(key, value)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
return resp, nil
|
43
|
+
})
|
44
|
+
|
45
|
+
tests := []struct {
|
46
|
+
name string
|
47
|
+
requests []main.Request
|
48
|
+
expected []main.Response
|
49
|
+
}{
|
50
|
+
{
|
51
|
+
name: "1 request",
|
52
|
+
requests: []main.Request{
|
53
|
+
{
|
54
|
+
Method: "GET",
|
55
|
+
URL: "http://example.com/1",
|
56
|
+
Header: map[string][]string{
|
57
|
+
"X-My-Request-Header": {"a", "b"},
|
58
|
+
},
|
59
|
+
},
|
60
|
+
},
|
61
|
+
expected: []main.Response{
|
62
|
+
{
|
63
|
+
StatusCode: 200,
|
64
|
+
Body: []byte("GET http://example.com/1"),
|
65
|
+
Header: map[string][]string{
|
66
|
+
"Content-Type": {"text/plain"},
|
67
|
+
"X-My-Request-Header": {"a", "b"},
|
68
|
+
},
|
69
|
+
},
|
70
|
+
},
|
71
|
+
},
|
72
|
+
{
|
73
|
+
name: "multiple requests",
|
74
|
+
requests: []main.Request{
|
75
|
+
{
|
76
|
+
Method: "GET",
|
77
|
+
URL: "http://example.com/1",
|
78
|
+
Header: map[string][]string{
|
79
|
+
"X-My-Request-Header": {"a", "b"},
|
80
|
+
},
|
81
|
+
},
|
82
|
+
{
|
83
|
+
Method: "GET",
|
84
|
+
URL: "http://example.com/2",
|
85
|
+
Header: map[string][]string{
|
86
|
+
"X-My-Request-Header": {"c", "d"},
|
87
|
+
},
|
88
|
+
},
|
89
|
+
},
|
90
|
+
expected: []main.Response{
|
91
|
+
{
|
92
|
+
StatusCode: 200,
|
93
|
+
Body: []byte("GET http://example.com/1"),
|
94
|
+
Header: map[string][]string{
|
95
|
+
"Content-Type": {"text/plain"},
|
96
|
+
"X-My-Request-Header": {"a", "b"},
|
97
|
+
},
|
98
|
+
},
|
99
|
+
{
|
100
|
+
StatusCode: 200,
|
101
|
+
Body: []byte("GET http://example.com/2"),
|
102
|
+
Header: map[string][]string{
|
103
|
+
"Content-Type": {"text/plain"},
|
104
|
+
"X-My-Request-Header": {"c", "d"},
|
105
|
+
},
|
106
|
+
},
|
107
|
+
},
|
108
|
+
},
|
109
|
+
}
|
110
|
+
|
111
|
+
httpClient := http.Client{}
|
112
|
+
for _, tt := range tests {
|
113
|
+
t.Run(tt.name, func(t *testing.T) {
|
114
|
+
actual, err := main.RunRequests(&httpClient, tt.requests)
|
115
|
+
if assert.NoError(t, err) {
|
116
|
+
assert.Equal(t, tt.expected, actual)
|
117
|
+
}
|
118
|
+
})
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FunnelHttp
|
4
|
+
class Client
|
5
|
+
# @!attribute default_request_header
|
6
|
+
# @return [Hash{String => String, Array<String>}]
|
7
|
+
attr_accessor :default_request_header
|
8
|
+
|
9
|
+
# @param default_request_header [Hash{String => String, Array<String>}]
|
10
|
+
def initialize(default_request_header: {})
|
11
|
+
@default_request_header = {"User-Agent" => USER_AGENT}.merge(default_request_header)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add header to {default_request_header}
|
15
|
+
# @param name [String] Header name
|
16
|
+
# @param value [String, Array<String>] Header value
|
17
|
+
#
|
18
|
+
# @return [Hash{String => String, Array<String>}] {default_request_header} after adding header
|
19
|
+
def add_default_request_header(name, value)
|
20
|
+
default_request_header.merge!(name => value)
|
21
|
+
end
|
22
|
+
|
23
|
+
# perform HTTP requests in parallel
|
24
|
+
#
|
25
|
+
# @overload perform(requests)
|
26
|
+
# @param requests [Array<Hash{Symbol => Object}>] `Array` of following `Hash`
|
27
|
+
# @option requests :method [String, Symbol] **[required]** Request method (e.g. `:get`, `"POST"`)
|
28
|
+
# @option requests :url [String] **[required]** Request url
|
29
|
+
# @option requests :header [Hash{String => String, Array<String>}, nil] Request header
|
30
|
+
#
|
31
|
+
# @overload perform(request)
|
32
|
+
# @param request [Hash{Symbol => Object}]
|
33
|
+
# @option request :method [String, Symbol] **[required]** Request method (e.g. `:get`, `"POST"`)
|
34
|
+
# @option request :url [String] **[required]** Request url
|
35
|
+
# @option request :header [Hash{String => String, Array<String>}, nil] Request header
|
36
|
+
#
|
37
|
+
# @return [Array<Hash<Symbol => Object>>] `Array` of following `Hash`
|
38
|
+
# @return [Integer] `:status_code`
|
39
|
+
# @return [String] `:body` Response body
|
40
|
+
# @return [Hash{String => Array<String>}] `:header` Response header
|
41
|
+
def perform(requests)
|
42
|
+
ext_client.run_requests(normalize_requests(requests))
|
43
|
+
end
|
44
|
+
|
45
|
+
# @overload normalize_requests(requests)
|
46
|
+
# @param requests [Array<Hash{Symbol => Object}>] `Array` of following `Hash`
|
47
|
+
# @option requests :method [String, Symbol] **[required]** Request method (e.g. `:get`, `"POST"`)
|
48
|
+
# @option requests :url [String] **[required]** Request url
|
49
|
+
# @option requests :header [Hash{String => String, Array<String>}, nil] Request header
|
50
|
+
#
|
51
|
+
# @overload normalize_requests(request)
|
52
|
+
# @param request [Hash{Symbol => Object}]
|
53
|
+
# @option request :method [String, Symbol] **[required]** Request method (e.g. `:get`, `"POST"`)
|
54
|
+
# @option request :url [String] **[required]** Request url
|
55
|
+
# @option request :header [Hash{String => String, Array<String>}, nil] Request header
|
56
|
+
#
|
57
|
+
# @return [Array<Hash{Symbol => Object}>] `Array` of following `Hash`
|
58
|
+
# @return [String] `:method` Request method (e.g. `"POST"`)
|
59
|
+
# @return [String] `:url` Request url
|
60
|
+
# @return [Hash{String => Array<String>}] `:header` Request header
|
61
|
+
def normalize_requests(arg)
|
62
|
+
requests =
|
63
|
+
case arg
|
64
|
+
when Array
|
65
|
+
arg
|
66
|
+
when Hash
|
67
|
+
[arg]
|
68
|
+
else
|
69
|
+
raise ArgumentError, "#{arg} must be Array or Hash"
|
70
|
+
end
|
71
|
+
|
72
|
+
requests.map do |request|
|
73
|
+
raise ArgumentError, "#{arg} contains something other than Hash" unless request.is_a?(Hash)
|
74
|
+
|
75
|
+
raise ArgumentError, "#{arg} key does not contain all :method and :url" if !request.key?(:method) || !request.key?(:url)
|
76
|
+
|
77
|
+
{
|
78
|
+
url: request[:url].to_s,
|
79
|
+
method: request[:method].to_s.upcase,
|
80
|
+
header: normalize_header(request[:header]),
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# @param header [Hash{String => String, Array<String>}, nil] Request header
|
88
|
+
# @return [Hash{String => Array<String>}] Request header
|
89
|
+
def normalize_header(header)
|
90
|
+
full_header =
|
91
|
+
if header
|
92
|
+
default_request_header.dup.merge(header)
|
93
|
+
else
|
94
|
+
default_request_header.dup
|
95
|
+
end
|
96
|
+
|
97
|
+
# Workaround for Ruby::UnannotatedEmptyCollection on steep 1.9.0+
|
98
|
+
result = {} #: strict_header
|
99
|
+
|
100
|
+
full_header.each_with_object(result) do |(k, v), hash|
|
101
|
+
# FIXME: Fails `steep check` when use Array(v)...
|
102
|
+
# hash[k] = Array(v)
|
103
|
+
hash[k] = v.is_a?(Array) ? v : Array(v)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [FunnelHttp::Ext::Client]
|
108
|
+
def ext_client
|
109
|
+
@ext_client ||= FunnelHttp::Ext::Client.new
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/funnel_http.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "funnel_http/version"
|
4
|
+
require_relative "funnel_http/client"
|
5
|
+
require_relative "funnel_http/ext"
|
6
|
+
require_relative "funnel_http/funnel_http"
|
7
|
+
|
8
|
+
module FunnelHttp
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
USER_AGENT = "funnel_http/#{FunnelHttp::VERSION} (+https://github.com/sue445/funnel_http)"
|
12
|
+
|
13
|
+
# Your code goes here...
|
14
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
---
|
2
|
+
path: ".gem_rbs_collection"
|
3
|
+
gems:
|
4
|
+
- name: base64
|
5
|
+
version: '0'
|
6
|
+
source:
|
7
|
+
type: stdlib
|
8
|
+
- name: cgi
|
9
|
+
version: '0'
|
10
|
+
source:
|
11
|
+
type: stdlib
|
12
|
+
- name: diff-lcs
|
13
|
+
version: '1.5'
|
14
|
+
source:
|
15
|
+
type: git
|
16
|
+
name: ruby/gem_rbs_collection
|
17
|
+
revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145
|
18
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
19
|
+
repo_dir: gems
|
20
|
+
- name: fileutils
|
21
|
+
version: '0'
|
22
|
+
source:
|
23
|
+
type: stdlib
|
24
|
+
- name: logger
|
25
|
+
version: '0'
|
26
|
+
source:
|
27
|
+
type: stdlib
|
28
|
+
- name: monitor
|
29
|
+
version: '0'
|
30
|
+
source:
|
31
|
+
type: stdlib
|
32
|
+
- name: rack
|
33
|
+
version: '2.2'
|
34
|
+
source:
|
35
|
+
type: git
|
36
|
+
name: ruby/gem_rbs_collection
|
37
|
+
revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145
|
38
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
39
|
+
repo_dir: gems
|
40
|
+
- name: rake
|
41
|
+
version: '13.0'
|
42
|
+
source:
|
43
|
+
type: git
|
44
|
+
name: ruby/gem_rbs_collection
|
45
|
+
revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145
|
46
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
47
|
+
repo_dir: gems
|
48
|
+
- name: sinatra
|
49
|
+
version: '4.0'
|
50
|
+
source:
|
51
|
+
type: git
|
52
|
+
name: ruby/gem_rbs_collection
|
53
|
+
revision: 0a6ea105a0afc7eaee4494585a7775f47eea6145
|
54
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
55
|
+
repo_dir: gems
|
56
|
+
- name: stringio
|
57
|
+
version: '0'
|
58
|
+
source:
|
59
|
+
type: stdlib
|
60
|
+
- name: tempfile
|
61
|
+
version: '0'
|
62
|
+
source:
|
63
|
+
type: stdlib
|
64
|
+
- name: uri
|
65
|
+
version: '0'
|
66
|
+
source:
|
67
|
+
type: stdlib
|
68
|
+
gemfile_lock_path: Gemfile.lock
|
data/rbs_collection.yaml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Download sources
|
2
|
+
sources:
|
3
|
+
- type: git
|
4
|
+
name: ruby/gem_rbs_collection
|
5
|
+
remote: https://github.com/ruby/gem_rbs_collection.git
|
6
|
+
revision: main
|
7
|
+
repo_dir: gems
|
8
|
+
|
9
|
+
# You can specify local directories as sources also.
|
10
|
+
# - type: local
|
11
|
+
# path: path/to/your/local/repository
|
12
|
+
|
13
|
+
# A directory to install the downloaded RBSs
|
14
|
+
path: .gem_rbs_collection
|
15
|
+
|
16
|
+
gems:
|
17
|
+
- name: rbs
|
18
|
+
ignore: true
|
19
|
+
- name: steep
|
20
|
+
ignore: true
|
21
|
+
- name: yard
|
22
|
+
ignore: true
|
23
|
+
- name: funnel_http
|
24
|
+
ignore: true
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module FunnelHttp
|
2
|
+
class Client
|
3
|
+
@ext_client: FunnelHttp::Ext::Client
|
4
|
+
|
5
|
+
attr_accessor default_request_header: fuzzy_header
|
6
|
+
|
7
|
+
def initialize: (?default_request_header: fuzzy_header) -> void
|
8
|
+
|
9
|
+
def add_default_request_header: (String name, String | Array[String] value) -> fuzzy_header
|
10
|
+
|
11
|
+
def perform: (fuzzy_request | Array[fuzzy_request] requests) -> Array[response]
|
12
|
+
|
13
|
+
def normalize_requests: (fuzzy_request | Array[fuzzy_request] arg) -> Array[strict_request]
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def normalize_header: (fuzzy_header? header) -> strict_header
|
18
|
+
|
19
|
+
def ext_client: () -> FunnelHttp::Ext::Client
|
20
|
+
end
|
21
|
+
end
|
data/sig/funnel_http.rbs
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module FunnelHttp
|
2
|
+
VERSION: String
|
3
|
+
USER_AGENT: String
|
4
|
+
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
type fuzzy_header = Hash[String, String | Array[String]]
|
9
|
+
|
10
|
+
type fuzzy_request = {
|
11
|
+
method: String | Symbol,
|
12
|
+
url: String,
|
13
|
+
header: fuzzy_header?
|
14
|
+
}
|
15
|
+
|
16
|
+
type strict_header = Hash[String, Array[String]]
|
17
|
+
|
18
|
+
type strict_request = {
|
19
|
+
method: String,
|
20
|
+
url: String,
|
21
|
+
header: strict_header
|
22
|
+
}
|
23
|
+
|
24
|
+
type response = {
|
25
|
+
status_code: Integer,
|
26
|
+
body: String,
|
27
|
+
header: strict_header
|
28
|
+
}
|
29
|
+
|
30
|
+
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
31
|
+
end
|