Skip to content

Commit b1d2afb

Browse files
committed
Export Connection.Transport for use on Appengine
Some environments, such as Google Appengine, have restrictions on sockets. An http transport can be used with Connection to interface to the custom backend: import ( "appengine/urlfetch" "fmt" "github.com/ncw/swift" ) func handler(w http.ResponseWriter, r *http.Request) { ctx := appengine.NewContext(r) c := swift.Connection{ UserName: "user", ApiKey: "key", AuthUrl: "auth_url", Transport: urlfetch.Transport{Context: ctx}, } err := c.Authenticate() if err != nil { panic(err) } fmt.Fprintf(w, "Authenticate OK") } Fixes ncw#5 Original fix by @vmihailenko
1 parent 8dda9a3 commit b1d2afb

File tree

4 files changed

+87
-24
lines changed

4 files changed

+87
-24
lines changed

compatibility_1_0.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
// Cancel the request - doesn't work under < go 1.1
14-
func cancelRequest(tr *http.Transport, req *http.Request) {
14+
func cancelRequest(transport http.RoundTripper, req *http.Request) {
1515
log.Printf("Tried to cancel a request but couldn't - recompile with go 1.1")
1616
}
1717

compatibility_1_1.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ import (
1010
)
1111

1212
// Cancel the request
13-
func cancelRequest(tr *http.Transport, req *http.Request) {
14-
tr.CancelRequest(req)
13+
func cancelRequest(transport http.RoundTripper, req *http.Request) {
14+
if tr, ok := transport.(interface {
15+
CancelRequest(*http.Request)
16+
}); ok {
17+
tr.CancelRequest(req)
18+
}
1519
}
1620

1721
// Reset a timer

swift.go

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,25 +48,47 @@ const (
4848
// Rackspace v2 https://identity.api.rackspacecloud.com/v2.0
4949
// Memset Memstore UK https://auth.storage.memset.com/v1.0
5050
// Memstore v2 https://auth.storage.memset.com/v2.0
51+
//
52+
// When using Google Appengine you must provide the Connection with an appengine-specific Transport:
53+
//
54+
// import (
55+
// "appengine/urlfetch"
56+
// "fmt"
57+
// "github.com/ncw/swift"
58+
// )
59+
//
60+
// func handler(w http.ResponseWriter, r *http.Request) {
61+
// ctx := appengine.NewContext(r)
62+
// tr := urlfetch.Transport{Context: ctx}
63+
// c := swift.Connection{
64+
// UserName: "user",
65+
// ApiKey: "key",
66+
// AuthUrl: "auth_url",
67+
// Transport: tr,
68+
// }
69+
// _ := c.Authenticate()
70+
// containers, _ := c.ContainerNames(nil)
71+
// fmt.Fprintf(w, "containers: %q", containers)
72+
// }
5173
type Connection struct {
5274
// Parameters - fill these in before calling Authenticate
5375
// They are all optional except UserName, ApiKey and AuthUrl
54-
UserName string // UserName for api
55-
ApiKey string // Key for api access
56-
AuthUrl string // Auth URL
57-
Retries int // Retries on error (default is 3)
58-
UserAgent string // Http User agent (default goswift/1.0)
59-
ConnectTimeout time.Duration // Connect channel timeout (default 10s)
60-
Timeout time.Duration // Data channel timeout (default 60s)
61-
Region string // Region to use eg "LON", "ORD" - default is use first region (V2 auth only)
62-
AuthVersion int // Set to 1 or 2 or leave at 0 for autodetect
63-
Internal bool // Set this to true to use the the internal / service network
64-
Tenant string // Name of the tenant (v2 auth only)
65-
TenantId string // Id of the tenant (v2 auth only)
76+
UserName string // UserName for api
77+
ApiKey string // Key for api access
78+
AuthUrl string // Auth URL
79+
Retries int // Retries on error (default is 3)
80+
UserAgent string // Http User agent (default goswift/1.0)
81+
ConnectTimeout time.Duration // Connect channel timeout (default 10s)
82+
Timeout time.Duration // Data channel timeout (default 60s)
83+
Region string // Region to use eg "LON", "ORD" - default is use first region (V2 auth only)
84+
AuthVersion int // Set to 1 or 2 or leave at 0 for autodetect
85+
Internal bool // Set this to true to use the the internal / service network
86+
Tenant string // Name of the tenant (v2 auth only)
87+
TenantId string // Id of the tenant (v2 auth only)
88+
Transport http.RoundTripper // Optional specialised http.Transport (eg. for Google Appengine)
6689
// These are filled in after Authenticate is called as are the defaults for above
6790
storageUrl string
6891
authToken string
69-
tr *http.Transport
7092
client *http.Client
7193
Auth Authenticator // the current authenticator
7294
}
@@ -191,7 +213,7 @@ func (c *Connection) doTimeoutRequest(timer *time.Timer, req *http.Request) (*ht
191213
return r.resp, r.err
192214
case <-timer.C:
193215
// Kill the connection on timeout so we don't leak sockets or goroutines
194-
cancelRequest(c.tr, req)
216+
cancelRequest(c.Transport, req)
195217
return nil, TimeoutError
196218
}
197219
panic("unreachable") // For Go 1.0
@@ -212,8 +234,8 @@ func (c *Connection) Authenticate() (err error) {
212234
if c.Timeout == 0 {
213235
c.Timeout = 60 * time.Second
214236
}
215-
if c.tr == nil {
216-
c.tr = &http.Transport{
237+
if c.Transport == nil {
238+
c.Transport = &http.Transport{
217239
// TLSClientConfig: &tls.Config{RootCAs: pool},
218240
// DisableCompression: true,
219241
MaxIdleConnsPerHost: 2048,
@@ -222,12 +244,12 @@ func (c *Connection) Authenticate() (err error) {
222244
if c.client == nil {
223245
c.client = &http.Client{
224246
// CheckRedirect: redirectPolicyFunc,
225-
Transport: c.tr,
247+
Transport: c.Transport,
226248
}
227249
}
228250
// Flush the keepalives connection - if we are
229251
// re-authenticating then stuff has gone wrong
230-
c.tr.CloseIdleConnections()
252+
flushKeepaliveConnections(c.Transport)
231253
c.Auth, err = newAuth(c.AuthUrl, c.AuthVersion)
232254
if err != nil {
233255
return err
@@ -245,7 +267,7 @@ func (c *Connection) Authenticate() (err error) {
245267
checkClose(resp.Body, &err)
246268
// Flush the auth connection - we don't want to keep
247269
// it open if keepalives were enabled
248-
c.tr.CloseIdleConnections()
270+
flushKeepaliveConnections(c.Transport)
249271
}()
250272
if err = c.parseHeaders(resp, authErrorMap); err != nil {
251273
return
@@ -262,6 +284,15 @@ func (c *Connection) Authenticate() (err error) {
262284
return nil
263285
}
264286

287+
// flushKeepaliveConnections is called to flush pending requests after an error.
288+
func flushKeepaliveConnections(transport http.RoundTripper) {
289+
if tr, ok := transport.(interface {
290+
CloseIdleConnections()
291+
}); ok {
292+
tr.CloseIdleConnections()
293+
}
294+
}
295+
265296
// UnAuthenticate removes the authentication from the Connection.
266297
func (c *Connection) UnAuthenticate() {
267298
c.storageUrl = ""
@@ -374,7 +405,7 @@ func (c *Connection) Call(targetUrl string, p RequestOpts) (resp *http.Response,
374405
} else {
375406
// Cancel the request on timeout
376407
cancel := func() {
377-
cancelRequest(c.tr, req)
408+
cancelRequest(c.Transport, req)
378409
}
379410
// Wrap resp.Body to make it obey an idle timeout
380411
resp.Body = newTimeoutReader(resp.Body, c.Timeout, cancel)

swift_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"fmt"
1818
"github.com/ncw/swift"
1919
"io"
20+
"net/http"
2021
"os"
2122
"testing"
2223
"time"
@@ -40,7 +41,34 @@ const (
4041
CONTENT_MD5 = "827ccb0eea8a706c4c34a16891f84e7b"
4142
)
4243

43-
// Test functions are run in order - this one must be first!
44+
type someTransport struct{ http.Transport }
45+
46+
func TestTransport(t *testing.T) {
47+
UserName := os.Getenv("SWIFT_API_USER")
48+
ApiKey := os.Getenv("SWIFT_API_KEY")
49+
AuthUrl := os.Getenv("SWIFT_AUTH_URL")
50+
if UserName == "" || ApiKey == "" || AuthUrl == "" {
51+
t.Fatal("SWIFT_API_USER, SWIFT_API_KEY and SWIFT_AUTH_URL not all set")
52+
}
53+
tr := &someTransport{Transport: http.Transport{MaxIdleConnsPerHost: 2048}}
54+
ct := swift.Connection{
55+
UserName: UserName,
56+
ApiKey: ApiKey,
57+
AuthUrl: AuthUrl,
58+
Tenant: os.Getenv("SWIFT_TENANT"),
59+
TenantId: os.Getenv("SWIFT_TENANT_ID"),
60+
Transport: tr,
61+
}
62+
err := ct.Authenticate()
63+
if err != nil {
64+
t.Fatal("Auth failed", err)
65+
}
66+
if !ct.Authenticated() {
67+
t.Fatal("Not authenticated")
68+
}
69+
}
70+
71+
// The following Test functions are run in order - this one must come before the others!
4472
func TestAuthenticate(t *testing.T) {
4573
UserName := os.Getenv("SWIFT_API_USER")
4674
ApiKey := os.Getenv("SWIFT_API_KEY")

0 commit comments

Comments
 (0)