Merge lp:~wallyworld/gwacl/ensure-all-roles-have-costs into lp:gwacl

Proposed by Ian Booth
Status: Merged
Merged at revision: 240
Proposed branch: lp:~wallyworld/gwacl/ensure-all-roles-have-costs
Merge into: lp:gwacl
Diff against target: 40138 lines (+16771/-16704)
77 files modified
Makefile (+6/-3)
dedent/dedent.go (+38/-38)
dedent/dedent_test.go (+39/-39)
deletedisk.go (+20/-20)
deletedisk_test.go (+70/-70)
endpoints.go (+22/-22)
endpoints_test.go (+87/-87)
example/management/run.go (+246/-246)
example/storage/run.go (+301/-301)
fork/http/chunked.go (+97/-97)
fork/http/client.go (+186/-186)
fork/http/cookie.go (+194/-194)
fork/http/filetransport.go (+65/-65)
fork/http/fs.go (+261/-261)
fork/http/header.go (+27/-27)
fork/http/jar.go (+9/-9)
fork/http/lex.go (+98/-98)
fork/http/request.go (+529/-529)
fork/http/response.go (+169/-169)
fork/http/server.go (+812/-812)
fork/http/sniff.go (+157/-157)
fork/http/status.go (+91/-91)
fork/http/transfer.go (+533/-533)
fork/http/transport.go (+548/-548)
fork/http/triv.go (+80/-80)
fork/tls/alert.go (+53/-53)
fork/tls/cipher_suites.go (+110/-110)
fork/tls/common.go (+208/-208)
fork/tls/conn.go (+671/-671)
fork/tls/generate_cert.go (+56/-56)
fork/tls/handshake_client.go (+328/-328)
fork/tls/handshake_messages.go (+908/-908)
fork/tls/handshake_server.go (+339/-339)
fork/tls/key_agreement.go (+199/-199)
fork/tls/prf.go (+158/-158)
fork/tls/tls.go (+121/-121)
helpers_apiobjects_test.go (+98/-98)
helpers_factory_test.go (+18/-18)
helpers_http_test.go (+24/-24)
helpers_misc_test.go (+10/-10)
httperror.go (+61/-61)
httperror_test.go (+98/-98)
logging/logging.go (+41/-41)
logging/logging_test.go (+22/-22)
management.go (+310/-310)
management_base.go (+386/-386)
management_base_test.go (+947/-947)
management_test.go (+1178/-1178)
master_test.go (+3/-3)
names.go (+71/-71)
names_test.go (+53/-53)
poller.go (+69/-69)
poller_test.go (+141/-141)
random.go (+7/-7)
retry_policy.go (+59/-59)
retry_policy_test.go (+93/-93)
rolesizes.go (+663/-590)
rolesizes_test.go (+31/-31)
shared_signature.go (+48/-48)
shared_signature_test.go (+52/-52)
storage.go (+178/-178)
storage_base.go (+528/-528)
storage_base_test.go (+910/-910)
storage_test.go (+295/-295)
testhelpers_x509dispatch.go (+38/-38)
testing.go (+26/-26)
testing_test.go (+34/-34)
utilities/format (+0/-9)
utils.go (+34/-34)
utils_test.go (+76/-76)
vhd_footer_test.go (+6/-6)
x509dispatcher.go (+110/-110)
x509dispatcher_test.go (+217/-217)
x509session.go (+101/-101)
x509session_test.go (+265/-265)
xmlobjects.go (+509/-509)
xmlobjects_test.go (+1125/-1125)
To merge this branch: bzr merge lp:~wallyworld/gwacl/ensure-all-roles-have-costs
Reviewer Review Type Date Requested Status
Andrew Wilkins (community) Approve
Review via email: mp+243346@code.launchpad.net

Commit message

Builds on rev 239 by Kapil.
All instance types need to have costs in each region. The G series didn't and the tests needed updating to catch this.

Also, I reformatted with gofmt using standard options. A bit of noise this time, but much cleaner next time.

Description of the change

Builds on rev 239 by Kapil.
All instance types need to have costs in each region. The G series didn't and the tests needed updating to catch this.

Also, I reformatted with gofmt using standard options. A bit of noise this time, but much cleaner next time.

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) :
review: Approve
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

Fwiw There's one more drive by associated with these type maps.. the small
to extra large names aren't used anymore we need some aliases to
a1-a4 there's a separate bug on this filed by a user.

On Monday, December 1, 2014, Andrew Wilkins <email address hidden>
wrote:

> Review: Approve
>
>
> --
>
> https://code.launchpad.net/~wallyworld/gwacl/ensure-all-roles-have-costs/+merge/243346
> Your team GWACL Hackers is subscribed to branch lp:gwacl.
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'Makefile'
--- Makefile 2013-08-29 17:07:28 +0000
+++ Makefile 2014-12-02 00:40:19 +0000
@@ -18,10 +18,13 @@
18clean:18clean:
19 $(RM) $(example_binaries)19 $(RM) $(example_binaries)
2020
21# Reformat the source files to match our layout standards.21# Reformat source files.
22# This includes gofmt's "simplify" option to streamline the source code.
23format:22format:
24 ./utilities/format -s23 gofmt -w -l .
24
25# Reformat and simplify source files.
26simplify:
27 gofmt -w -l -s .
2528
26# Build the examples (we have no tests for them).29# Build the examples (we have no tests for them).
27examples: $(example_binaries)30examples: $(example_binaries)
2831
=== modified file 'dedent/dedent.go'
--- dedent/dedent.go 2013-03-12 17:05:36 +0000
+++ dedent/dedent.go 2014-12-02 00:40:19 +0000
@@ -4,8 +4,8 @@
4package dedent4package dedent
55
6import (6import (
7 "regexp"7 "regexp"
8 "strings"8 "strings"
9)9)
1010
11const emptyString = ""11const emptyString = ""
@@ -14,7 +14,7 @@
1414
15// Split the given text into lines.15// Split the given text into lines.
16func splitLines(text string) []string {16func splitLines(text string) []string {
17 return reLine.FindAllString(text, -1)17 return reLine.FindAllString(text, -1)
18}18}
1919
20// Match leading whitespace or tabs. \p{Zs} is a Unicode character class:20// Match leading whitespace or tabs. \p{Zs} is a Unicode character class:
@@ -23,46 +23,46 @@
2323
24// Find the longest leading margin common between the given lines.24// Find the longest leading margin common between the given lines.
25func calculateMargin(lines []string) string {25func calculateMargin(lines []string) string {
26 var margin string26 var margin string
27 var first bool = true27 var first bool = true
28 for _, line := range lines {28 for _, line := range lines {
29 indent := reLeadingWhitespace.FindString(line)29 indent := reLeadingWhitespace.FindString(line)
30 switch {30 switch {
31 case len(indent) == len(line):31 case len(indent) == len(line):
32 // The line is either empty or whitespace and will be ignored for32 // The line is either empty or whitespace and will be ignored for
33 // the purposes of calculating the margin.33 // the purposes of calculating the margin.
34 case first:34 case first:
35 // This is the first line with an indent, so start from here.35 // This is the first line with an indent, so start from here.
36 margin = indent36 margin = indent
37 first = false37 first = false
38 case strings.HasPrefix(indent, margin):38 case strings.HasPrefix(indent, margin):
39 // This line's indent is longer or equal to the margin. The39 // This line's indent is longer or equal to the margin. The
40 // current margin remains unalterered.40 // current margin remains unalterered.
41 case strings.HasPrefix(margin, indent):41 case strings.HasPrefix(margin, indent):
42 // This line's indent is compatible with the margin but shorter42 // This line's indent is compatible with the margin but shorter
43 // (strictly it could be equal, however that condition is handled43 // (strictly it could be equal, however that condition is handled
44 // earlier in this switch). The current indent becomes the margin.44 // earlier in this switch). The current indent becomes the margin.
45 margin = indent45 margin = indent
46 default:46 default:
47 // There is no common margin so stop scanning.47 // There is no common margin so stop scanning.
48 return emptyString48 return emptyString
49 }49 }
50 }50 }
51 return margin51 return margin
52}52}
5353
54// Remove a prefix from each line, if present.54// Remove a prefix from each line, if present.
55func trimPrefix(lines []string, prefix string) {55func trimPrefix(lines []string, prefix string) {
56 trim := len(prefix)56 trim := len(prefix)
57 for i, line := range lines {57 for i, line := range lines {
58 if strings.HasPrefix(line, prefix) {58 if strings.HasPrefix(line, prefix) {
59 lines[i] = line[trim:]59 lines[i] = line[trim:]
60 }60 }
61 }61 }
62}62}
6363
64func Dedent(text string) string {64func Dedent(text string) string {
65 lines := splitLines(text)65 lines := splitLines(text)
66 trimPrefix(lines, calculateMargin(lines))66 trimPrefix(lines, calculateMargin(lines))
67 return strings.Join(lines, "\n")67 return strings.Join(lines, "\n")
68}68}
6969
=== modified file 'dedent/dedent_test.go'
--- dedent/dedent_test.go 2013-03-26 06:57:55 +0000
+++ dedent/dedent_test.go 2014-12-02 00:40:19 +0000
@@ -4,8 +4,8 @@
4package dedent4package dedent
55
6import (6import (
7 . "launchpad.net/gocheck"7 . "launchpad.net/gocheck"
8 "testing"8 "testing"
9)9)
1010
11type dedentSuite struct{}11type dedentSuite struct{}
@@ -14,81 +14,81 @@
1414
15// Dedent() does nothing with the empty string.15// Dedent() does nothing with the empty string.
16func (suite *dedentSuite) TestEmptyString(c *C) {16func (suite *dedentSuite) TestEmptyString(c *C) {
17 input := ""17 input := ""
18 expected := input18 expected := input
19 observed := Dedent(input)19 observed := Dedent(input)
20 c.Check(observed, Equals, expected)20 c.Check(observed, Equals, expected)
21}21}
2222
23// Dedent() does nothing to a single line without an indent.23// Dedent() does nothing to a single line without an indent.
24func (suite *dedentSuite) TestSingleLine(c *C) {24func (suite *dedentSuite) TestSingleLine(c *C) {
25 input := "This is a single line."25 input := "This is a single line."
26 expected := input26 expected := input
27 observed := Dedent(input)27 observed := Dedent(input)
28 c.Check(observed, Equals, expected)28 c.Check(observed, Equals, expected)
29}29}
3030
31// Dedent() removes all leading whitespace from single lines.31// Dedent() removes all leading whitespace from single lines.
32func (suite *dedentSuite) TestSingleLineWithIndent(c *C) {32func (suite *dedentSuite) TestSingleLineWithIndent(c *C) {
33 input := " This is a single line."33 input := " This is a single line."
34 expected := "This is a single line."34 expected := "This is a single line."
35 observed := Dedent(input)35 observed := Dedent(input)
36 c.Check(observed, Equals, expected)36 c.Check(observed, Equals, expected)
37}37}
3838
39// Dedent() does nothing when none of the lines are indented.39// Dedent() does nothing when none of the lines are indented.
40func (suite *dedentSuite) TestLines(c *C) {40func (suite *dedentSuite) TestLines(c *C) {
41 input := "One\nTwo\n"41 input := "One\nTwo\n"
42 expected := input42 expected := input
43 observed := Dedent(input)43 observed := Dedent(input)
44 c.Check(observed, Equals, expected)44 c.Check(observed, Equals, expected)
45}45}
4646
47// Dedent() does nothing when *any* line is not indented.47// Dedent() does nothing when *any* line is not indented.
48func (suite *dedentSuite) TestLinesWithSomeIndents(c *C) {48func (suite *dedentSuite) TestLinesWithSomeIndents(c *C) {
49 input := "One\n Two\n"49 input := "One\n Two\n"
50 expected := input50 expected := input
51 observed := Dedent(input)51 observed := Dedent(input)
52 c.Check(observed, Equals, expected)52 c.Check(observed, Equals, expected)
53}53}
5454
55// Dedent() removes the common leading indent from each line.55// Dedent() removes the common leading indent from each line.
56func (suite *dedentSuite) TestLinesWithIndents(c *C) {56func (suite *dedentSuite) TestLinesWithIndents(c *C) {
57 input := " One\n Two\n"57 input := " One\n Two\n"
58 expected := "One\n Two\n"58 expected := "One\n Two\n"
59 observed := Dedent(input)59 observed := Dedent(input)
60 c.Check(observed, Equals, expected)60 c.Check(observed, Equals, expected)
61}61}
6262
63// Dedent() ignores all-whitespace lines for the purposes of margin63// Dedent() ignores all-whitespace lines for the purposes of margin
64// calculation. However, the margin *is* trimmed from these lines, if they64// calculation. However, the margin *is* trimmed from these lines, if they
65// begin with it.65// begin with it.
66func (suite *dedentSuite) TestLinesWithEmptyLine(c *C) {66func (suite *dedentSuite) TestLinesWithEmptyLine(c *C) {
67 input := " One\n \n Three\n"67 input := " One\n \n Three\n"
68 expected := "One\n \nThree\n"68 expected := "One\n \nThree\n"
69 observed := Dedent(input)69 observed := Dedent(input)
70 c.Check(observed, Equals, expected)70 c.Check(observed, Equals, expected)
71}71}
7272
73// Dedent() ignores blank lines for the purposes of margin calculation,73// Dedent() ignores blank lines for the purposes of margin calculation,
74// including the first line.74// including the first line.
75func (suite *dedentSuite) TestLinesWithEmptyFirstLine(c *C) {75func (suite *dedentSuite) TestLinesWithEmptyFirstLine(c *C) {
76 input := "\n Two\n Three\n"76 input := "\n Two\n Three\n"
77 expected := "\nTwo\nThree\n"77 expected := "\nTwo\nThree\n"
78 observed := Dedent(input)78 observed := Dedent(input)
79 c.Check(observed, Equals, expected)79 c.Check(observed, Equals, expected)
80}80}
8181
82// Dedent() treats spaces and tabs as completely different; no number of82// Dedent() treats spaces and tabs as completely different; no number of
83// spaces is equivalent to a tab.83// spaces is equivalent to a tab.
84func (suite *dedentSuite) TestLinesWithTabsAndSpaces(c *C) {84func (suite *dedentSuite) TestLinesWithTabsAndSpaces(c *C) {
85 input := "\tOne\n Two\n"85 input := "\tOne\n Two\n"
86 expected := input86 expected := input
87 observed := Dedent(input)87 observed := Dedent(input)
88 c.Check(observed, Equals, expected)88 c.Check(observed, Equals, expected)
89}89}
9090
91// Master loader for all tests.91// Master loader for all tests.
92func Test(t *testing.T) {92func Test(t *testing.T) {
93 TestingT(t)93 TestingT(t)
94}94}
9595
=== modified file 'deletedisk.go'
--- deletedisk.go 2013-07-16 05:48:26 +0000
+++ deletedisk.go 2014-12-02 00:40:19 +0000
@@ -18,24 +18,24 @@
18package gwacl18package gwacl
1919
20import (20import (
21 "fmt"21 "fmt"
22 "regexp"22 "regexp"
23 "time"23 "time"
24)24)
2525
26var deleteDiskTimeout = 30 * time.Minute26var deleteDiskTimeout = 30 * time.Minute
27var deleteDiskInterval = 10 * time.Second27var deleteDiskInterval = 10 * time.Second
2828
29type diskDeletePoller struct {29type diskDeletePoller struct {
30 api *ManagementAPI30 api *ManagementAPI
31 diskName string31 diskName string
32 deleteBlob bool32 deleteBlob bool
33}33}
3434
35var _ poller = &diskDeletePoller{}35var _ poller = &diskDeletePoller{}
3636
37func (poller diskDeletePoller) poll() (*x509Response, error) {37func (poller diskDeletePoller) poll() (*x509Response, error) {
38 return nil, poller.api._DeleteDisk(poller.diskName, poller.deleteBlob)38 return nil, poller.api._DeleteDisk(poller.diskName, poller.deleteBlob)
39}39}
4040
41// isInUseError returns whether or not the given string is of the "disk in use"41// isInUseError returns whether or not the given string is of the "disk in use"
@@ -46,20 +46,20 @@
46// gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http46// gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http
47// code 400: Bad Request)"47// code 400: Bad Request)"
48func isInUseError(errString string, diskName string) bool {48func isInUseError(errString string, diskName string) bool {
49 pattern := fmt.Sprintf("BadRequest - A disk with name %s is currently in use by virtual machine.*", regexp.QuoteMeta(diskName))49 pattern := fmt.Sprintf("BadRequest - A disk with name %s is currently in use by virtual machine.*", regexp.QuoteMeta(diskName))
50 reg := regexp.MustCompile(pattern)50 reg := regexp.MustCompile(pattern)
51 return reg.MatchString(errString)51 return reg.MatchString(errString)
52}52}
5353
54func (poller diskDeletePoller) isDone(response *x509Response, pollerErr error) (bool, error) {54func (poller diskDeletePoller) isDone(response *x509Response, pollerErr error) (bool, error) {
55 if pollerErr == nil {55 if pollerErr == nil {
56 return true, nil56 return true, nil
57 }57 }
58 if isInUseError(pollerErr.Error(), poller.diskName) {58 if isInUseError(pollerErr.Error(), poller.diskName) {
59 // The error is of the "disk in use" type: continue polling.59 // The error is of the "disk in use" type: continue polling.
60 return false, nil60 return false, nil
61 }61 }
62 // The error is *not* of the "disk in use" type: stop polling and return62 // The error is *not* of the "disk in use" type: stop polling and return
63 // the error.63 // the error.
64 return true, pollerErr64 return true, pollerErr
65}65}
6666
=== modified file 'deletedisk_test.go'
--- deletedisk_test.go 2013-07-16 05:48:59 +0000
+++ deletedisk_test.go 2014-12-02 00:40:19 +0000
@@ -4,10 +4,10 @@
4package gwacl4package gwacl
55
6import (6import (
7 "fmt"7 "fmt"
8 . "launchpad.net/gocheck"8 . "launchpad.net/gocheck"
9 "net/http"9 "net/http"
10 "time"10 "time"
11)11)
1212
13type deleteDiskSuite struct{}13type deleteDiskSuite struct{}
@@ -16,88 +16,88 @@
1616
17// Real-world error messages and names.17// Real-world error messages and names.
18const (18const (
19 diskInUseErrorTemplate = "BadRequest - A disk with name %s is currently in use by virtual machine gwaclrolemvo1yab running within hosted service gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http code 400: Bad Request)"19 diskInUseErrorTemplate = "BadRequest - A disk with name %s is currently in use by virtual machine gwaclrolemvo1yab running within hosted service gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http code 400: Bad Request)"
20 diskName = "gwacldiske5w7lkj"20 diskName = "gwacldiske5w7lkj"
21 diskDoesNotExistError = "DELETE request failed: ResourceNotFound - The disk with the specified name does not exist. (http code 404: Not Found)"21 diskDoesNotExistError = "DELETE request failed: ResourceNotFound - The disk with the specified name does not exist. (http code 404: Not Found)"
22)22)
2323
24func (suite *deleteDiskSuite) TestIsInUseError(c *C) {24func (suite *deleteDiskSuite) TestIsInUseError(c *C) {
25 var testValues = []struct {25 var testValues = []struct {
26 errorString string26 errorString string
27 diskName string27 diskName string
28 expectedResult bool28 expectedResult bool
29 }{29 }{
30 {fmt.Sprintf(diskInUseErrorTemplate, diskName), diskName, true},30 {fmt.Sprintf(diskInUseErrorTemplate, diskName), diskName, true},
31 {fmt.Sprintf(diskInUseErrorTemplate, diskName), "another-disk", false},31 {fmt.Sprintf(diskInUseErrorTemplate, diskName), "another-disk", false},
32 {"unknown error", diskName, false},32 {"unknown error", diskName, false},
33 {diskDoesNotExistError, diskName, false},33 {diskDoesNotExistError, diskName, false},
34 }34 }
35 for _, test := range testValues {35 for _, test := range testValues {
36 c.Check(isInUseError(test.errorString, test.diskName), Equals, test.expectedResult)36 c.Check(isInUseError(test.errorString, test.diskName), Equals, test.expectedResult)
37 }37 }
38}38}
3939
40func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfNilError(c *C) {40func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfNilError(c *C) {
41 poller := diskDeletePoller{nil, "", false}41 poller := diskDeletePoller{nil, "", false}
42 randomResponse := x509Response{StatusCode: http.StatusAccepted}42 randomResponse := x509Response{StatusCode: http.StatusAccepted}
43 done, err := poller.isDone(&randomResponse, nil)43 done, err := poller.isDone(&randomResponse, nil)
44 c.Check(done, Equals, true)44 c.Check(done, Equals, true)
45 c.Check(err, IsNil)45 c.Check(err, IsNil)
46}46}
4747
48func (suite *deleteDiskSuite) TestIsDoneReturnsFalseIfDiskInUseError(c *C) {48func (suite *deleteDiskSuite) TestIsDoneReturnsFalseIfDiskInUseError(c *C) {
49 diskName := "gwacldiske5w7lkj"49 diskName := "gwacldiske5w7lkj"
50 diskInUseError := fmt.Errorf(diskInUseErrorTemplate, diskName)50 diskInUseError := fmt.Errorf(diskInUseErrorTemplate, diskName)
51 poller := diskDeletePoller{nil, diskName, false}51 poller := diskDeletePoller{nil, diskName, false}
52 done, err := poller.isDone(nil, diskInUseError)52 done, err := poller.isDone(nil, diskInUseError)
53 c.Check(done, Equals, false)53 c.Check(done, Equals, false)
54 c.Check(err, IsNil)54 c.Check(err, IsNil)
55}55}
5656
57func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfAnotherError(c *C) {57func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfAnotherError(c *C) {
58 anotherError := fmt.Errorf("Unknown error")58 anotherError := fmt.Errorf("Unknown error")
59 poller := diskDeletePoller{nil, "disk-name", false}59 poller := diskDeletePoller{nil, "disk-name", false}
60 done, err := poller.isDone(nil, anotherError)60 done, err := poller.isDone(nil, anotherError)
61 c.Check(done, Equals, true)61 c.Check(done, Equals, true)
62 c.Check(err, Equals, anotherError)62 c.Check(err, Equals, anotherError)
63}63}
6464
65func (suite *deleteDiskSuite) TestPollCallsDeleteDisk(c *C) {65func (suite *deleteDiskSuite) TestPollCallsDeleteDisk(c *C) {
66 api := makeAPI(c)66 api := makeAPI(c)
67 recordedRequests := setUpDispatcher("operationID")67 recordedRequests := setUpDispatcher("operationID")
68 diskName := "gwacldiske5w7lkj"68 diskName := "gwacldiske5w7lkj"
69 poller := diskDeletePoller{api, diskName, false}69 poller := diskDeletePoller{api, diskName, false}
7070
71 response, err := poller.poll()71 response, err := poller.poll()
7272
73 c.Assert(response, IsNil)73 c.Assert(response, IsNil)
74 c.Assert(err, IsNil)74 c.Assert(err, IsNil)
75 expectedURL := api.session.composeURL("services/disks/" + diskName)75 expectedURL := api.session.composeURL("services/disks/" + diskName)
76 checkOneRequest(c, recordedRequests, expectedURL, "2012-08-01", nil, "DELETE")76 checkOneRequest(c, recordedRequests, expectedURL, "2012-08-01", nil, "DELETE")
77}77}
7878
79func (suite *deleteDiskSuite) TestManagementAPIDeleteDiskPolls(c *C) {79func (suite *deleteDiskSuite) TestManagementAPIDeleteDiskPolls(c *C) {
80 firstResponse := DispatcherResponse{80 firstResponse := DispatcherResponse{
81 response: &x509Response{},81 response: &x509Response{},
82 errorObject: fmt.Errorf(diskInUseErrorTemplate, diskName)}82 errorObject: fmt.Errorf(diskInUseErrorTemplate, diskName)}
83 secondResponse := DispatcherResponse{83 secondResponse := DispatcherResponse{
84 response: &x509Response{StatusCode: http.StatusOK},84 response: &x509Response{StatusCode: http.StatusOK},
85 errorObject: nil}85 errorObject: nil}
86 responses := []DispatcherResponse{firstResponse, secondResponse}86 responses := []DispatcherResponse{firstResponse, secondResponse}
87 rigPreparedResponseDispatcher(responses)87 rigPreparedResponseDispatcher(responses)
88 recordedRequests := make([]*X509Request, 0)88 recordedRequests := make([]*X509Request, 0)
89 rigRecordingDispatcher(&recordedRequests)89 rigRecordingDispatcher(&recordedRequests)
9090
91 api := makeAPI(c)91 api := makeAPI(c)
92 diskName := "gwacldiske5w7lkj"92 diskName := "gwacldiske5w7lkj"
93 poller := diskDeletePoller{api, diskName, false}93 poller := diskDeletePoller{api, diskName, false}
9494
95 response, err := performPolling(poller, time.Nanosecond, time.Minute)95 response, err := performPolling(poller, time.Nanosecond, time.Minute)
9696
97 c.Assert(response, IsNil)97 c.Assert(response, IsNil)
98 c.Assert(err, IsNil)98 c.Assert(err, IsNil)
99 expectedURL := api.session.composeURL("services/disks/" + diskName)99 expectedURL := api.session.composeURL("services/disks/" + diskName)
100 c.Check(len(recordedRequests), Equals, 2)100 c.Check(len(recordedRequests), Equals, 2)
101 checkRequest(c, recordedRequests[0], expectedURL, "2012-08-01", nil, "DELETE")101 checkRequest(c, recordedRequests[0], expectedURL, "2012-08-01", nil, "DELETE")
102 checkRequest(c, recordedRequests[1], expectedURL, "2012-08-01", nil, "DELETE")102 checkRequest(c, recordedRequests[1], expectedURL, "2012-08-01", nil, "DELETE")
103}103}
104104
=== modified file 'endpoints.go'
--- endpoints.go 2013-08-06 10:15:11 +0000
+++ endpoints.go 2014-12-02 00:40:19 +0000
@@ -4,9 +4,9 @@
4package gwacl4package gwacl
55
6import (6import (
7 "fmt"7 "fmt"
8 "net/url"8 "net/url"
9 "strings"9 "strings"
10)10)
1111
12// APIEndpoint describes the base URL for accesing Windows Azure's APIs.12// APIEndpoint describes the base URL for accesing Windows Azure's APIs.
@@ -20,13 +20,13 @@
20// GetEndpoint returns the API endpoint for the given location. This is20// GetEndpoint returns the API endpoint for the given location. This is
21// hard-coded, so some guesswork may be involved.21// hard-coded, so some guesswork may be involved.
22func GetEndpoint(location string) APIEndpoint {22func GetEndpoint(location string) APIEndpoint {
23 if strings.Contains(location, "China") {23 if strings.Contains(location, "China") {
24 // Mainland China is a special case. It has its own endpoint.24 // Mainland China is a special case. It has its own endpoint.
25 return "https://core.chinacloudapi.cn/"25 return "https://core.chinacloudapi.cn/"
26 }26 }
2727
28 // The rest of the world shares a single endpoint.28 // The rest of the world shares a single endpoint.
29 return "https://core.windows.net/"29 return "https://core.windows.net/"
30}30}
3131
32// prefixHost prefixes the hostname part of a URL with a subdomain. For32// prefixHost prefixes the hostname part of a URL with a subdomain. For
@@ -35,26 +35,26 @@
35//35//
36// The URL must be well-formed, and contain a hostname.36// The URL must be well-formed, and contain a hostname.
37func prefixHost(host, originalURL string) string {37func prefixHost(host, originalURL string) string {
38 parsedURL, err := url.Parse(originalURL)38 parsedURL, err := url.Parse(originalURL)
39 if err != nil {39 if err != nil {
40 panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err))40 panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err))
41 }41 }
42 if parsedURL.Host == "" {42 if parsedURL.Host == "" {
43 panic(fmt.Errorf("no hostname in URL '%s'", originalURL))43 panic(fmt.Errorf("no hostname in URL '%s'", originalURL))
44 }44 }
45 // Escape manually. Strangely, turning a url.URL into a string does not45 // Escape manually. Strangely, turning a url.URL into a string does not
46 // do this for you.46 // do this for you.
47 parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host47 parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host
48 return parsedURL.String()48 return parsedURL.String()
49}49}
5050
51// ManagementAPI returns the URL for the endpoint's management API.51// ManagementAPI returns the URL for the endpoint's management API.
52func (endpoint APIEndpoint) ManagementAPI() string {52func (endpoint APIEndpoint) ManagementAPI() string {
53 return prefixHost("management", string(endpoint))53 return prefixHost("management", string(endpoint))
54}54}
5555
56// BlobStorageAPI returns a URL for the endpoint's blob storage API, for56// BlobStorageAPI returns a URL for the endpoint's blob storage API, for
57// requests on the given account.57// requests on the given account.
58func (endpoint APIEndpoint) BlobStorageAPI(account string) string {58func (endpoint APIEndpoint) BlobStorageAPI(account string) string {
59 return prefixHost(account, prefixHost("blob", string(endpoint)))59 return prefixHost(account, prefixHost("blob", string(endpoint)))
60}60}
6161
=== modified file 'endpoints_test.go'
--- endpoints_test.go 2013-08-06 10:12:51 +0000
+++ endpoints_test.go 2014-12-02 00:40:19 +0000
@@ -4,9 +4,9 @@
4package gwacl4package gwacl
55
6import (6import (
7 "fmt"7 "fmt"
8 . "launchpad.net/gocheck"8 . "launchpad.net/gocheck"
9 "net/url"9 "net/url"
10)10)
1111
12type endpointsSuite struct{}12type endpointsSuite struct{}
@@ -14,109 +14,109 @@
14var _ = Suite(&endpointsSuite{})14var _ = Suite(&endpointsSuite{})
1515
16func (*endpointsSuite) TestGetEndpointReturnsEndpointsForKnownRegions(c *C) {16func (*endpointsSuite) TestGetEndpointReturnsEndpointsForKnownRegions(c *C) {
17 internationalLocations := []string{17 internationalLocations := []string{
18 "West Europe",18 "West Europe",
19 "East Asia",19 "East Asia",
20 "East US 2",20 "East US 2",
21 "Southeast Asia",21 "Southeast Asia",
22 "East US",22 "East US",
23 "Central US",23 "Central US",
24 "West US",24 "West US",
25 "North Europe",25 "North Europe",
26 }26 }
27 internationalEndpoint := APIEndpoint("https://core.windows.net/")27 internationalEndpoint := APIEndpoint("https://core.windows.net/")
2828
29 for _, location := range internationalLocations {29 for _, location := range internationalLocations {
30 c.Check(GetEndpoint(location), Equals, internationalEndpoint)30 c.Check(GetEndpoint(location), Equals, internationalEndpoint)
31 }31 }
3232
33 // The mainland-China locations have a different endpoint.33 // The mainland-China locations have a different endpoint.
34 // (Actually the East Asia data centre is said to be in Hong Kong, but it34 // (Actually the East Asia data centre is said to be in Hong Kong, but it
35 // acts as international).35 // acts as international).
36 mainlandChinaLocations := []string{36 mainlandChinaLocations := []string{
37 "China East",37 "China East",
38 "China North",38 "China North",
39 }39 }
40 mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/")40 mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/")
41 for _, location := range mainlandChinaLocations {41 for _, location := range mainlandChinaLocations {
42 c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint)42 c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint)
43 }43 }
44}44}
4545
46func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) {46func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) {
47 c.Check(47 c.Check(
48 GetEndpoint("South San Marino Highlands"),48 GetEndpoint("South San Marino Highlands"),
49 Equals,49 Equals,
50 GetEndpoint("West US"))50 GetEndpoint("West US"))
51 c.Check(51 c.Check(
52 GetEndpoint("Central China West"),52 GetEndpoint("Central China West"),
53 Equals,53 Equals,
54 GetEndpoint("China East"))54 GetEndpoint("China East"))
55}55}
5656
57func (*endpointsSuite) TestPrefixHostPrefixesSubdomain(c *C) {57func (*endpointsSuite) TestPrefixHostPrefixesSubdomain(c *C) {
58 c.Check(58 c.Check(
59 prefixHost("foo", "http://example.com"),59 prefixHost("foo", "http://example.com"),
60 Equals,60 Equals,
61 "http://foo.example.com")61 "http://foo.example.com")
62}62}
6363
64func (*endpointsSuite) TestPrefixHostPreservesOtherURLComponents(c *C) {64func (*endpointsSuite) TestPrefixHostPreservesOtherURLComponents(c *C) {
65 c.Check(65 c.Check(
66 prefixHost("foo", "http://example.com/"),66 prefixHost("foo", "http://example.com/"),
67 Equals,67 Equals,
68 "http://foo.example.com/")68 "http://foo.example.com/")
69 c.Check(69 c.Check(
70 prefixHost("foo", "nntp://example.com"),70 prefixHost("foo", "nntp://example.com"),
71 Equals,71 Equals,
72 "nntp://foo.example.com")72 "nntp://foo.example.com")
73 c.Check(73 c.Check(
74 prefixHost("foo", "http://user@example.com"),74 prefixHost("foo", "http://user@example.com"),
75 Equals,75 Equals,
76 "http://user@foo.example.com")76 "http://user@foo.example.com")
77 c.Check(77 c.Check(
78 prefixHost("foo", "http://example.com:999"),78 prefixHost("foo", "http://example.com:999"),
79 Equals,79 Equals,
80 "http://foo.example.com:999")80 "http://foo.example.com:999")
81 c.Check(81 c.Check(
82 prefixHost("foo", "http://example.com/path"),82 prefixHost("foo", "http://example.com/path"),
83 Equals,83 Equals,
84 "http://foo.example.com/path")84 "http://foo.example.com/path")
85}85}
8686
87func (*endpointsSuite) TestPrefixHostEscapes(c *C) {87func (*endpointsSuite) TestPrefixHostEscapes(c *C) {
88 host := "5%=1/20?"88 host := "5%=1/20?"
89 c.Check(89 c.Check(
90 prefixHost(host, "http://example.com"),90 prefixHost(host, "http://example.com"),
91 Equals,91 Equals,
92 fmt.Sprintf("http://%s.example.com", url.QueryEscape(host)))92 fmt.Sprintf("http://%s.example.com", url.QueryEscape(host)))
93}93}
9494
95func (*endpointsSuite) TestManagementAPICombinesWithGetEndpoint(c *C) {95func (*endpointsSuite) TestManagementAPICombinesWithGetEndpoint(c *C) {
96 c.Check(96 c.Check(
97 GetEndpoint("West US").ManagementAPI(),97 GetEndpoint("West US").ManagementAPI(),
98 Equals,98 Equals,
99 "https://management.core.windows.net/")99 "https://management.core.windows.net/")
100 c.Check(100 c.Check(
101 GetEndpoint("China East").ManagementAPI(),101 GetEndpoint("China East").ManagementAPI(),
102 Equals,102 Equals,
103 "https://management.core.chinacloudapi.cn/")103 "https://management.core.chinacloudapi.cn/")
104}104}
105105
106func (*endpointsSuite) TestBlobStorageAPIIncludesAccountName(c *C) {106func (*endpointsSuite) TestBlobStorageAPIIncludesAccountName(c *C) {
107 c.Check(107 c.Check(
108 APIEndpoint("http://example.com").BlobStorageAPI("myaccount"),108 APIEndpoint("http://example.com").BlobStorageAPI("myaccount"),
109 Equals,109 Equals,
110 "http://myaccount.blob.example.com")110 "http://myaccount.blob.example.com")
111}111}
112112
113func (*endpointsSuite) TestBlobStorageAPICombinesWithGetEndpoint(c *C) {113func (*endpointsSuite) TestBlobStorageAPICombinesWithGetEndpoint(c *C) {
114 c.Check(114 c.Check(
115 GetEndpoint("West US").BlobStorageAPI("account"),115 GetEndpoint("West US").BlobStorageAPI("account"),
116 Equals,116 Equals,
117 "https://account.blob.core.windows.net/")117 "https://account.blob.core.windows.net/")
118 c.Check(118 c.Check(
119 GetEndpoint("China East").BlobStorageAPI("account"),119 GetEndpoint("China East").BlobStorageAPI("account"),
120 Equals,120 Equals,
121 "https://account.blob.core.chinacloudapi.cn/")121 "https://account.blob.core.chinacloudapi.cn/")
122}122}
123123
=== modified file 'example/management/run.go'
--- example/management/run.go 2014-03-10 03:56:29 +0000
+++ example/management/run.go 2014-12-02 00:40:19 +0000
@@ -10,14 +10,14 @@
10package main10package main
1111
12import (12import (
13 "encoding/base64"13 "encoding/base64"
14 "flag"14 "flag"
15 "fmt"15 "fmt"
16 "launchpad.net/gwacl"16 "launchpad.net/gwacl"
17 . "launchpad.net/gwacl/logging"17 . "launchpad.net/gwacl/logging"
18 "math/rand"18 "math/rand"
19 "os"19 "os"
20 "time"20 "time"
21)21)
2222
23var certFile string23var certFile string
@@ -26,26 +26,26 @@
26var location string26var location string
2727
28func getParams() error {28func getParams() error {
29 flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).")29 flag.StringVar(&certFile, "cert", "", "Name of your management certificate file (in PEM format).")
30 flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.")30 flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.")
31 flag.BoolVar(&pause, "pause", false, "Wait for user input after the VM is brought up (useful for further testing)")31 flag.BoolVar(&pause, "pause", false, "Wait for user input after the VM is brought up (useful for further testing)")
32 flag.StringVar(&location, "location", "North Europe", "Azure cloud location, e.g. 'West US' or 'China East'")32 flag.StringVar(&location, "location", "North Europe", "Azure cloud location, e.g. 'West US' or 'China East'")
3333
34 flag.Parse()34 flag.Parse()
3535
36 if certFile == "" {36 if certFile == "" {
37 return fmt.Errorf("No .pem certificate specified. Use the -cert option.")37 return fmt.Errorf("No .pem certificate specified. Use the -cert option.")
38 }38 }
39 if subscriptionID == "" {39 if subscriptionID == "" {
40 return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.")40 return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.")
41 }41 }
42 return nil42 return nil
43}43}
4444
45func checkError(err error) {45func checkError(err error) {
46 if err != nil {46 if err != nil {
47 panic(err)47 panic(err)
48 }48 }
49}49}
5050
51// makeRandomIdentifier creates an arbitrary identifier of the given length,51// makeRandomIdentifier creates an arbitrary identifier of the given length,
@@ -53,231 +53,231 @@
53// The identifier will start with the given prefix. The prefix must be no53// The identifier will start with the given prefix. The prefix must be no
54// longer than the specified length, or there'll be trouble.54// longer than the specified length, or there'll be trouble.
55func makeRandomIdentifier(prefix string, length int) string {55func makeRandomIdentifier(prefix string, length int) string {
56 // Only digits and lower-case ASCII letters are accepted.56 // Only digits and lower-case ASCII letters are accepted.
57 const chars = "abcdefghijklmnopqrstuvwxyz0123456789"57 const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
5858
59 if len(prefix) > length {59 if len(prefix) > length {
60 panic(fmt.Errorf("prefix '%s' is more than the requested %d characters long", prefix, length))60 panic(fmt.Errorf("prefix '%s' is more than the requested %d characters long", prefix, length))
61 }61 }
6262
63 id := prefix63 id := prefix
64 for len(id) < length {64 for len(id) < length {
65 id += string(chars[rand.Intn(len(chars))])65 id += string(chars[rand.Intn(len(chars))])
66 }66 }
67 return id67 return id
68}68}
6969
70func main() {70func main() {
71 rand.Seed(int64(time.Now().Nanosecond()))71 rand.Seed(int64(time.Now().Nanosecond()))
7272
73 err := getParams()73 err := getParams()
74 if err != nil {74 if err != nil {
75 Info(err)75 Info(err)
76 os.Exit(1)76 os.Exit(1)
77 }77 }
7878
79 api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location)79 api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location)
80 checkError(err)80 checkError(err)
8181
82 ExerciseHostedServicesAPI(api)82 ExerciseHostedServicesAPI(api)
8383
84 Info("All done.")84 Info("All done.")
85}85}
8686
87func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) {87func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) {
88 var err error88 var err error
89 location := "West US"89 location := "West US"
90 release := "13.04"90 release := "13.04"
9191
92 affinityGroupName := gwacl.MakeRandomHostname("affinitygroup")92 affinityGroupName := gwacl.MakeRandomHostname("affinitygroup")
93 Info("Creating an affinity group...")93 Info("Creating an affinity group...")
94 cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location)94 cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location)
95 err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{95 err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{
96 CreateAffinityGroup: cag})96 CreateAffinityGroup: cag})
97 checkError(err)97 checkError(err)
98 Infof("Created affinity group %s\n", affinityGroupName)98 Infof("Created affinity group %s\n", affinityGroupName)
9999
100 defer func() {100 defer func() {
101 Infof("Deleting affinity group %s\n", affinityGroupName)101 Infof("Deleting affinity group %s\n", affinityGroupName)
102 err := api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{102 err := api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{
103 Name: affinityGroupName})103 Name: affinityGroupName})
104 checkError(err)104 checkError(err)
105 Infof("Done deleting affinity group %s\n", affinityGroupName)105 Infof("Done deleting affinity group %s\n", affinityGroupName)
106 }()106 }()
107107
108 virtualNetworkName := gwacl.MakeRandomVirtualNetworkName("virtual-net-")108 virtualNetworkName := gwacl.MakeRandomVirtualNetworkName("virtual-net-")
109 Infof("Creating virtual network %s...\n", virtualNetworkName)109 Infof("Creating virtual network %s...\n", virtualNetworkName)
110 virtualNetwork := gwacl.VirtualNetworkSite{110 virtualNetwork := gwacl.VirtualNetworkSite{
111 Name: virtualNetworkName,111 Name: virtualNetworkName,
112 AffinityGroup: affinityGroupName,112 AffinityGroup: affinityGroupName,
113 AddressSpacePrefixes: []string{113 AddressSpacePrefixes: []string{
114 "10.0.0.0/8",114 "10.0.0.0/8",
115 },115 },
116 }116 }
117 err = api.AddVirtualNetworkSite(&virtualNetwork)117 err = api.AddVirtualNetworkSite(&virtualNetwork)
118 checkError(err)118 checkError(err)
119 Info("Done creating virtual network")119 Info("Done creating virtual network")
120120
121 defer func() {121 defer func() {
122 Infof("Deleting virtual network %s...\n", virtualNetworkName)122 Infof("Deleting virtual network %s...\n", virtualNetworkName)
123 err := api.RemoveVirtualNetworkSite(virtualNetworkName)123 err := api.RemoveVirtualNetworkSite(virtualNetworkName)
124 checkError(err)124 checkError(err)
125 Infof("Done deleting virtual network %s\n", virtualNetworkName)125 Infof("Done deleting virtual network %s\n", virtualNetworkName)
126 }()126 }()
127127
128 networkConfig, err := api.GetNetworkConfiguration()128 networkConfig, err := api.GetNetworkConfiguration()
129 checkError(err)129 checkError(err)
130 if networkConfig == nil {130 if networkConfig == nil {
131 Info("No network configuration is set")131 Info("No network configuration is set")
132 } else {132 } else {
133 xml, err := networkConfig.Serialize()133 xml, err := networkConfig.Serialize()
134 checkError(err)134 checkError(err)
135 Info(xml)135 Info(xml)
136 }136 }
137137
138 Infof("Getting OS Image for release '%s' and location '%s'...\n", release, location)138 Infof("Getting OS Image for release '%s' and location '%s'...\n", release, location)
139 images, err := api.ListOSImages()139 images, err := api.ListOSImages()
140 checkError(err)140 checkError(err)
141 image, err := images.GetLatestUbuntuImage(release, location)141 image, err := images.GetLatestUbuntuImage(release, location)
142 checkError(err)142 checkError(err)
143 sourceImageName := image.Name143 sourceImageName := image.Name
144 Infof("Got image named '%s'\n", sourceImageName)144 Infof("Got image named '%s'\n", sourceImageName)
145 Info("Done getting OS Image\n")145 Info("Done getting OS Image\n")
146146
147 hostServiceName := gwacl.MakeRandomHostedServiceName("gwacl")147 hostServiceName := gwacl.MakeRandomHostedServiceName("gwacl")
148 Infof("Creating a hosted service: '%s'...\n", hostServiceName)148 Infof("Creating a hosted service: '%s'...\n", hostServiceName)
149 createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location)149 createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location)
150 createHostedService.AffinityGroup = affinityGroupName150 createHostedService.AffinityGroup = affinityGroupName
151 err = api.AddHostedService(createHostedService)151 err = api.AddHostedService(createHostedService)
152 checkError(err)152 checkError(err)
153 Info("Done creating a hosted service\n")153 Info("Done creating a hosted service\n")
154154
155 defer func() {155 defer func() {
156 Info("Destroying hosted service...")156 Info("Destroying hosted service...")
157 // FIXME: Check error157 // FIXME: Check error
158 api.DestroyHostedService(&gwacl.DestroyHostedServiceRequest{158 api.DestroyHostedService(&gwacl.DestroyHostedServiceRequest{
159 ServiceName: hostServiceName})159 ServiceName: hostServiceName})
160 Info("Done destroying hosted service\n")160 Info("Done destroying hosted service\n")
161 }()161 }()
162162
163 Info("Listing hosted services...")163 Info("Listing hosted services...")
164 hostedServices, err := api.ListHostedServices()164 hostedServices, err := api.ListHostedServices()
165 checkError(err)165 checkError(err)
166 Infof("Got %d hosted service(s)\n", len(hostedServices))166 Infof("Got %d hosted service(s)\n", len(hostedServices))
167 if len(hostedServices) > 0 {167 if len(hostedServices) > 0 {
168 hostedService := hostedServices[0]168 hostedService := hostedServices[0]
169 detailedHostedService, err := api.GetHostedServiceProperties(hostedService.ServiceName, true)169 detailedHostedService, err := api.GetHostedServiceProperties(hostedService.ServiceName, true)
170 checkError(err)170 checkError(err)
171 Infof("Hosted service '%s' contains %d deployments\n", hostedService.ServiceName, len(detailedHostedService.Deployments))171 Infof("Hosted service '%s' contains %d deployments\n", hostedService.ServiceName, len(detailedHostedService.Deployments))
172 // Do the same again with ListAllDeployments.172 // Do the same again with ListAllDeployments.
173 deployments, err := api.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ServiceName: hostedService.ServiceName})173 deployments, err := api.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ServiceName: hostedService.ServiceName})
174 checkError(err)174 checkError(err)
175 if len(deployments) != len(detailedHostedService.Deployments) {175 if len(deployments) != len(detailedHostedService.Deployments) {
176 Errorf(176 Errorf(
177 "Mismatch in reported deployments: %d != %d",177 "Mismatch in reported deployments: %d != %d",
178 len(deployments), len(detailedHostedService.Deployments))178 len(deployments), len(detailedHostedService.Deployments))
179 }179 }
180 }180 }
181 Info("Done listing hosted services\n")181 Info("Done listing hosted services\n")
182182
183 Info("Adding VM deployment...")183 Info("Adding VM deployment...")
184 hostname := gwacl.MakeRandomHostname("gwaclhost")184 hostname := gwacl.MakeRandomHostname("gwaclhost")
185 // Random passwords are no use to man nor beast here if you want to185 // Random passwords are no use to man nor beast here if you want to
186 // test with your instance, so we'll use a fixed one. It's not really a186 // test with your instance, so we'll use a fixed one. It's not really a
187 // security hazard in such a short-lived private instance.187 // security hazard in such a short-lived private instance.
188 password := "Ubuntu123"188 password := "Ubuntu123"
189 username := "ubuntu"189 username := "ubuntu"
190 vhdName := gwacl.MakeRandomDiskName("gwacldisk")190 vhdName := gwacl.MakeRandomDiskName("gwacldisk")
191 userdata := base64.StdEncoding.EncodeToString([]byte("TEST_USER_DATA"))191 userdata := base64.StdEncoding.EncodeToString([]byte("TEST_USER_DATA"))
192 linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet(192 linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet(
193 hostname, username, password, userdata, "false")193 hostname, username, password, userdata, "false")
194 inputendpoint := gwacl.InputEndpoint{LocalPort: 22, Name: "sshport", Port: 22, Protocol: "TCP"}194 inputendpoint := gwacl.InputEndpoint{LocalPort: 22, Name: "sshport", Port: 22, Protocol: "TCP"}
195 networkConfigurationSet := gwacl.NewNetworkConfigurationSet([]gwacl.InputEndpoint{inputendpoint}, nil)195 networkConfigurationSet := gwacl.NewNetworkConfigurationSet([]gwacl.InputEndpoint{inputendpoint}, nil)
196196
197 storageAccount := makeRandomIdentifier("gwacl", 24)197 storageAccount := makeRandomIdentifier("gwacl", 24)
198 storageLabel := makeRandomIdentifier("gwacl", 64)198 storageLabel := makeRandomIdentifier("gwacl", 64)
199 Infof("Requesting storage account with name '%s' and label '%s'...\n", storageAccount, storageLabel)199 Infof("Requesting storage account with name '%s' and label '%s'...\n", storageAccount, storageLabel)
200 cssi := gwacl.NewCreateStorageServiceInputWithLocation(storageAccount, storageLabel, location, "false")200 cssi := gwacl.NewCreateStorageServiceInputWithLocation(storageAccount, storageLabel, location, "false")
201 err = api.AddStorageAccount(cssi)201 err = api.AddStorageAccount(cssi)
202 checkError(err)202 checkError(err)
203 Info("Done requesting storage account\n")203 Info("Done requesting storage account\n")
204204
205 defer func() {205 defer func() {
206 Infof("Deleting storage account %s...\n", storageAccount)206 Infof("Deleting storage account %s...\n", storageAccount)
207 // FIXME: Check error207 // FIXME: Check error
208 api.DeleteStorageAccount(storageAccount)208 api.DeleteStorageAccount(storageAccount)
209 Info("Done deleting storage account\n")209 Info("Done deleting storage account\n")
210 }()210 }()
211211
212 mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, fmt.Sprintf("vhds/%s.vhd", vhdName))212 mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, fmt.Sprintf("vhds/%s.vhd", vhdName))
213 diskName := makeRandomIdentifier("gwacldisk", 16)213 diskName := makeRandomIdentifier("gwacldisk", 16)
214 diskLabel := makeRandomIdentifier("gwacl", 64)214 diskLabel := makeRandomIdentifier("gwacl", 64)
215 vhd := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName, "Linux")215 vhd := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName, "Linux")
216 roleName := gwacl.MakeRandomRoleName("gwaclrole")216 roleName := gwacl.MakeRandomRoleName("gwaclrole")
217 role := gwacl.NewRole("ExtraSmall", roleName, vhd,217 role := gwacl.NewRole("ExtraSmall", roleName, vhd,
218 []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet})218 []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet})
219 machineName := makeRandomIdentifier("gwaclmachine", 20)219 machineName := makeRandomIdentifier("gwaclmachine", 20)
220 deployment := gwacl.NewDeploymentForCreateVMDeployment(220 deployment := gwacl.NewDeploymentForCreateVMDeployment(
221 machineName, "Production", machineName, []gwacl.Role{*role}, virtualNetworkName)221 machineName, "Production", machineName, []gwacl.Role{*role}, virtualNetworkName)
222 err = api.AddDeployment(deployment, hostServiceName)222 err = api.AddDeployment(deployment, hostServiceName)
223 checkError(err)223 checkError(err)
224 Info("Done adding VM deployment\n")224 Info("Done adding VM deployment\n")
225225
226 Info("Starting VM...")226 Info("Starting VM...")
227 err = api.StartRole(&gwacl.StartRoleRequest{hostServiceName, deployment.Name, role.RoleName})227 err = api.StartRole(&gwacl.StartRoleRequest{hostServiceName, deployment.Name, role.RoleName})
228 checkError(err)228 checkError(err)
229 Info("Done starting VM\n")229 Info("Done starting VM\n")
230230
231 Info("Listing VM...")231 Info("Listing VM...")
232 instances, err := api.ListInstances(&gwacl.ListInstancesRequest{hostServiceName})232 instances, err := api.ListInstances(&gwacl.ListInstancesRequest{hostServiceName})
233 checkError(err)233 checkError(err)
234 Infof("Got %d instance(s)\n", len(instances))234 Infof("Got %d instance(s)\n", len(instances))
235 Info("Done listing VM\n")235 Info("Done listing VM\n")
236236
237 Info("Getting deployment info...")237 Info("Getting deployment info...")
238 request := &gwacl.GetDeploymentRequest{ServiceName: hostServiceName, DeploymentName: machineName}238 request := &gwacl.GetDeploymentRequest{ServiceName: hostServiceName, DeploymentName: machineName}
239 deploy, err := api.GetDeployment(request)239 deploy, err := api.GetDeployment(request)
240 checkError(err)240 checkError(err)
241 fqdn, err := deploy.GetFQDN()241 fqdn, err := deploy.GetFQDN()
242 checkError(err)242 checkError(err)
243 Info("Got deployment info\n")243 Info("Got deployment info\n")
244244
245 Info("Adding role input endpoint...")245 Info("Adding role input endpoint...")
246 endpoint := gwacl.InputEndpoint{246 endpoint := gwacl.InputEndpoint{
247 Name: gwacl.MakeRandomHostname("endpoint-"),247 Name: gwacl.MakeRandomHostname("endpoint-"),
248 Port: 1080,248 Port: 1080,
249 LocalPort: 80,249 LocalPort: 80,
250 Protocol: "TCP",250 Protocol: "TCP",
251 }251 }
252 err = api.AddRoleEndpoints(&gwacl.AddRoleEndpointsRequest{252 err = api.AddRoleEndpoints(&gwacl.AddRoleEndpointsRequest{
253 ServiceName: hostServiceName,253 ServiceName: hostServiceName,
254 DeploymentName: deployment.Name,254 DeploymentName: deployment.Name,
255 RoleName: role.RoleName,255 RoleName: role.RoleName,
256 InputEndpoints: []gwacl.InputEndpoint{endpoint},256 InputEndpoints: []gwacl.InputEndpoint{endpoint},
257 })257 })
258 checkError(err)258 checkError(err)
259 Info("Added role input endpoint\n")259 Info("Added role input endpoint\n")
260260
261 defer func() {261 defer func() {
262 Info("Removing role input endpoint...")262 Info("Removing role input endpoint...")
263 err := api.RemoveRoleEndpoints(&gwacl.RemoveRoleEndpointsRequest{263 err := api.RemoveRoleEndpoints(&gwacl.RemoveRoleEndpointsRequest{
264 ServiceName: hostServiceName,264 ServiceName: hostServiceName,
265 DeploymentName: deployment.Name,265 DeploymentName: deployment.Name,
266 RoleName: role.RoleName,266 RoleName: role.RoleName,
267 InputEndpoints: []gwacl.InputEndpoint{endpoint},267 InputEndpoints: []gwacl.InputEndpoint{endpoint},
268 })268 })
269 checkError(err)269 checkError(err)
270 Info("Removed role input endpoint\n")270 Info("Removed role input endpoint\n")
271 }()271 }()
272272
273 if pause {273 if pause {
274 var wait string274 var wait string
275 fmt.Println("host:", fqdn)275 fmt.Println("host:", fqdn)
276 fmt.Println("username:", username)276 fmt.Println("username:", username)
277 fmt.Println("password:", password)277 fmt.Println("password:", password)
278 fmt.Println("")278 fmt.Println("")
279 fmt.Println("Pausing so you can play with the newly-created VM")279 fmt.Println("Pausing so you can play with the newly-created VM")
280 fmt.Println("To clear up, type something and press enter to continue")280 fmt.Println("To clear up, type something and press enter to continue")
281 fmt.Scan(&wait)281 fmt.Scan(&wait)
282 }282 }
283}283}
284284
=== modified file 'example/storage/run.go'
--- example/storage/run.go 2013-08-19 06:05:38 +0000
+++ example/storage/run.go 2014-12-02 00:40:19 +0000
@@ -10,106 +10,106 @@
10package main10package main
1111
12import (12import (
13 "flag"13 "flag"
14 "fmt"14 "fmt"
15 "io/ioutil"15 "io/ioutil"
16 "launchpad.net/gwacl"16 "launchpad.net/gwacl"
17 "os"17 "os"
18 "strings"18 "strings"
19)19)
2020
21func badOperationError() error {21func badOperationError() error {
22 return fmt.Errorf("Must specify one of %v", operationNames)22 return fmt.Errorf("Must specify one of %v", operationNames)
23}23}
2424
25// operation is something you can instruct this program to do, by specifying25// operation is something you can instruct this program to do, by specifying
26// its name on the command line.26// its name on the command line.
27type operation struct {27type operation struct {
28 // name is the operation name as used on the command line.28 // name is the operation name as used on the command line.
29 name string29 name string
30 // description holds a description of what the operation does.30 // description holds a description of what the operation does.
31 description string31 description string
32 // example illustrates how the operation is used.32 // example illustrates how the operation is used.
33 example string33 example string
34 // requiredArgs lists the command-line options that are required for this34 // requiredArgs lists the command-line options that are required for this
35 // operation.35 // operation.
36 requiredArgs []string36 requiredArgs []string
37 // validate is an optional callback to perform more detailed checking on37 // validate is an optional callback to perform more detailed checking on
38 // the operation's arguments.38 // the operation's arguments.
39 validate func() error39 validate func() error
40 // implementation is a function that performs the operation. If it fails,40 // implementation is a function that performs the operation. If it fails,
41 // it just panics.41 // it just panics.
42 implementation func(gwacl.StorageContext)42 implementation func(gwacl.StorageContext)
43}43}
4444
45// operations defines what operations are available to be invoked from the45// operations defines what operations are available to be invoked from the
46// command line.46// command line.
47var operations = []operation{47var operations = []operation{
48 {48 {
49 name: "listcontainers",49 name: "listcontainers",
50 description: "Show existing storage containers",50 description: "Show existing storage containers",
51 example: "listcontainers",51 example: "listcontainers",
52 implementation: listcontainers,52 implementation: listcontainers,
53 },53 },
54 {54 {
55 name: "list",55 name: "list",
56 description: "List files in a container",56 description: "List files in a container",
57 example: "-container=<container> list",57 example: "-container=<container> list",
58 requiredArgs: []string{"container"},58 requiredArgs: []string{"container"},
59 implementation: list,59 implementation: list,
60 },60 },
61 {61 {
62 name: "containeracl",62 name: "containeracl",
63 description: "Set access on a container",63 description: "Set access on a container",
64 example: "-container=<container> -acl <container|blob|private> containeracl",64 example: "-container=<container> -acl <container|blob|private> containeracl",
65 requiredArgs: []string{"container", "key", "acl"},65 requiredArgs: []string{"container", "key", "acl"},
66 validate: func() error {66 validate: func() error {
67 if acl != "container" && acl != "blob" && acl != "private" {67 if acl != "container" && acl != "blob" && acl != "private" {
68 return fmt.Errorf(68 return fmt.Errorf(
69 "Usage: containeracl -container=<container> <container|blob|private>")69 "Usage: containeracl -container=<container> <container|blob|private>")
70 }70 }
71 return nil71 return nil
72 },72 },
73 implementation: containeracl,73 implementation: containeracl,
74 },74 },
75 {75 {
76 name: "getblob",76 name: "getblob",
77 description: "Get a file from a container (it's returned on stdout)",77 description: "Get a file from a container (it's returned on stdout)",
78 example: "-container=<container> -filename=<filename> getblob",78 example: "-container=<container> -filename=<filename> getblob",
79 requiredArgs: []string{"container", "filename"},79 requiredArgs: []string{"container", "filename"},
80 implementation: getblob,80 implementation: getblob,
81 },81 },
82 {82 {
83 name: "addblock",83 name: "addblock",
84 description: "Upload a file to a block blob",84 description: "Upload a file to a block blob",
85 example: "-container=<container> -filename=<filename> addblock",85 example: "-container=<container> -filename=<filename> addblock",
86 requiredArgs: []string{"key", "container", "filename"},86 requiredArgs: []string{"key", "container", "filename"},
87 implementation: addblock,87 implementation: addblock,
88 },88 },
89 {89 {
90 name: "deleteblob",90 name: "deleteblob",
91 description: "Delete a blob",91 description: "Delete a blob",
92 example: "-container=<container> -filename=<filename> deleteblob",92 example: "-container=<container> -filename=<filename> deleteblob",
93 requiredArgs: []string{"key", "container", "filename"},93 requiredArgs: []string{"key", "container", "filename"},
94 implementation: deleteblob,94 implementation: deleteblob,
95 },95 },
96 {96 {
97 name: "putblob",97 name: "putblob",
98 description: "Create an empty page blob",98 description: "Create an empty page blob",
99 example: "-container=<container> -blobname=<blobname> -size=<bytes> " +99 example: "-container=<container> -blobname=<blobname> -size=<bytes> " +
100 "-blobtype=\"page\" putblob",100 "-blobtype=\"page\" putblob",
101 requiredArgs: []string{"key", "blobname", "blobtype", "container", "size"},101 requiredArgs: []string{"key", "blobname", "blobtype", "container", "size"},
102 implementation: putblob,102 implementation: putblob,
103 },103 },
104 {104 {
105 name: "putpage",105 name: "putpage",
106 description: "Upload a file to a page blob's page. The range parameters must " +106 description: "Upload a file to a page blob's page. The range parameters must " +
107 "be (modulo 512)-(modulo 512 -1), eg: -pagerange=0-511",107 "be (modulo 512)-(modulo 512 -1), eg: -pagerange=0-511",
108 example: "-container=<container> -blobname=<blobname> -pagerange=<N-N> " +108 example: "-container=<container> -blobname=<blobname> -pagerange=<N-N> " +
109 "-filename=<local file> putpage",109 "-filename=<local file> putpage",
110 requiredArgs: []string{"key", "blobname", "container", "pagerange", "filename"},110 requiredArgs: []string{"key", "blobname", "container", "pagerange", "filename"},
111 implementation: putpage,111 implementation: putpage,
112 },112 },
113}113}
114114
115// operationsByName maps each opeation name to an operation struct that115// operationsByName maps each opeation name to an operation struct that
@@ -121,130 +121,130 @@
121var operationNames []string121var operationNames []string
122122
123func init() {123func init() {
124 operationsByName = make(map[string]operation, len(operations))124 operationsByName = make(map[string]operation, len(operations))
125 for _, op := range operations {125 for _, op := range operations {
126 operationsByName[op.name] = op126 operationsByName[op.name] = op
127 }127 }
128128
129 operationNames = make([]string, len(operations))129 operationNames = make([]string, len(operations))
130 for index, op := range operations {130 for index, op := range operations {
131 operationNames[index] = op.name131 operationNames[index] = op.name
132 }132 }
133}133}
134134
135// Variables set by command-line options.135// Variables set by command-line options.
136var (136var (
137 help bool137 help bool
138 account string138 account string
139 location string139 location string
140 key string140 key string
141 filename string141 filename string
142 container string142 container string
143 prefix string143 prefix string
144 blobname string144 blobname string
145 blobtype string145 blobtype string
146 size int146 size int
147 pagerange string147 pagerange string
148 acl string148 acl string
149)149)
150150
151// argumentGiven returns whether the named argument was specified on the151// argumentGiven returns whether the named argument was specified on the
152// command line.152// command line.
153func argumentGiven(name string) bool {153func argumentGiven(name string) bool {
154 // This is stupid. There must be a way to ask the flag module directly!154 // This is stupid. There must be a way to ask the flag module directly!
155 switch name {155 switch name {
156 case "account":156 case "account":
157 return account != ""157 return account != ""
158 case "location":158 case "location":
159 return location != ""159 return location != ""
160 case "key":160 case "key":
161 return key != ""161 return key != ""
162 case "container":162 case "container":
163 return container != ""163 return container != ""
164 case "filename":164 case "filename":
165 return filename != ""165 return filename != ""
166 case "prefix":166 case "prefix":
167 return prefix != ""167 return prefix != ""
168 case "blobname":168 case "blobname":
169 return blobname != ""169 return blobname != ""
170 case "blobtype":170 case "blobtype":
171 return blobtype != ""171 return blobtype != ""
172 case "size":172 case "size":
173 return size != 0173 return size != 0
174 case "pagerange":174 case "pagerange":
175 return pagerange != ""175 return pagerange != ""
176 case "acl":176 case "acl":
177 return acl != ""177 return acl != ""
178 }178 }
179 panic(fmt.Errorf("internal error: unknown command-line option: %s", name))179 panic(fmt.Errorf("internal error: unknown command-line option: %s", name))
180}180}
181181
182func getParams() (string, error) {182func getParams() (string, error) {
183 flag.BoolVar(&help, "h", false, "Show usage and exit")183 flag.BoolVar(&help, "h", false, "Show usage and exit")
184184
185 flag.StringVar(&account, "account", "", "Storage account name")185 flag.StringVar(&account, "account", "", "Storage account name")
186 flag.StringVar(&location, "location", "", "Azure location, e.g. \"West US\", \"China East\", or \"North Europe\"")186 flag.StringVar(&location, "location", "", "Azure location, e.g. \"West US\", \"China East\", or \"North Europe\"")
187 flag.StringVar(&key, "key", "", "A valid storage account key (base64 encoded), defaults to the empty string (i.e. anonymous access)")187 flag.StringVar(&key, "key", "", "A valid storage account key (base64 encoded), defaults to the empty string (i.e. anonymous access)")
188 flag.StringVar(&container, "container", "", "Name of the container to use")188 flag.StringVar(&container, "container", "", "Name of the container to use")
189 flag.StringVar(&filename, "filename", "", "File containing blob/page to upload/download")189 flag.StringVar(&filename, "filename", "", "File containing blob/page to upload/download")
190 flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs")190 flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs")
191 flag.StringVar(&blobname, "blobname", "", "Name of blob in container")191 flag.StringVar(&blobname, "blobname", "", "Name of blob in container")
192 flag.StringVar(&blobtype, "blobtype", "", "Type of blob, 'page' or 'block'")192 flag.StringVar(&blobtype, "blobtype", "", "Type of blob, 'page' or 'block'")
193 flag.IntVar(&size, "size", 0, "Size of blob to create for a page 'putblob'")193 flag.IntVar(&size, "size", 0, "Size of blob to create for a page 'putblob'")
194 flag.StringVar(&pagerange, "pagerange", "", "When uploading to a page blob, this specifies what range in the blob. Use the format 'start-end', e.g. -pagerange 1024-2048")194 flag.StringVar(&pagerange, "pagerange", "", "When uploading to a page blob, this specifies what range in the blob. Use the format 'start-end', e.g. -pagerange 1024-2048")
195 flag.StringVar(&acl, "acl", "", "When using 'containeracl', specify an ACL type")195 flag.StringVar(&acl, "acl", "", "When using 'containeracl', specify an ACL type")
196 flag.Parse()196 flag.Parse()
197197
198 if help {198 if help {
199 return "", nil199 return "", nil
200 }200 }
201201
202 opName := flag.Arg(0)202 opName := flag.Arg(0)
203 if opName == "" {203 if opName == "" {
204 return "", fmt.Errorf("No operation specified")204 return "", fmt.Errorf("No operation specified")
205 }205 }
206206
207 requiredArgs := []string{"account", "location"}207 requiredArgs := []string{"account", "location"}
208 for _, arg := range requiredArgs {208 for _, arg := range requiredArgs {
209 if !argumentGiven(arg) {209 if !argumentGiven(arg) {
210 return "", fmt.Errorf("Must supply %q parameter.", arg)210 return "", fmt.Errorf("Must supply %q parameter.", arg)
211 }211 }
212 }212 }
213213
214 if len(flag.Args()) != 1 {214 if len(flag.Args()) != 1 {
215 return "", badOperationError()215 return "", badOperationError()
216 }216 }
217217
218 op, isDefined := operationsByName[opName]218 op, isDefined := operationsByName[opName]
219 if !isDefined {219 if !isDefined {
220 return "", badOperationError()220 return "", badOperationError()
221 }221 }
222222
223 for _, arg := range op.requiredArgs {223 for _, arg := range op.requiredArgs {
224 if !argumentGiven(arg) {224 if !argumentGiven(arg) {
225 return "", fmt.Errorf("%q requires these options: %v", op.name, op.requiredArgs)225 return "", fmt.Errorf("%q requires these options: %v", op.name, op.requiredArgs)
226 }226 }
227 }227 }
228228
229 if op.validate != nil {229 if op.validate != nil {
230 err := op.validate()230 err := op.validate()
231 if err != nil {231 if err != nil {
232 return "", err232 return "", err
233 }233 }
234 }234 }
235235
236 return op.name, nil236 return op.name, nil
237}237}
238238
239func Usage() {239func Usage() {
240 fmt.Fprintf(240 fmt.Fprintf(
241 os.Stderr,241 os.Stderr,
242 "Usage:\n %s [args] <%s>\n",242 "Usage:\n %s [args] <%s>\n",
243 os.Args[0],243 os.Args[0],
244 strings.Join(operationNames, "|"))244 strings.Join(operationNames, "|"))
245 flag.PrintDefaults()245 flag.PrintDefaults()
246246
247 fmt.Fprintf(os.Stderr, `247 fmt.Fprintf(os.Stderr, `
248 This is an example of how to interact with the Azure storage service.248 This is an example of how to interact with the Azure storage service.
249 It is not a complete example but it does give a useful way to do do some249 It is not a complete example but it does give a useful way to do do some
250 basic operations.250 basic operations.
@@ -255,142 +255,142 @@
255 invocation parameters:255 invocation parameters:
256 `)256 `)
257257
258 for _, op := range operations {258 for _, op := range operations {
259 fmt.Fprintf(os.Stderr, "\n %s:\n %s\n", op.description, op.example)259 fmt.Fprintf(os.Stderr, "\n %s:\n %s\n", op.description, op.example)
260 }260 }
261}261}
262262
263func dumpError(err error) {263func dumpError(err error) {
264 if err != nil {264 if err != nil {
265 fmt.Fprintf(os.Stderr, "ERROR:")265 fmt.Fprintf(os.Stderr, "ERROR:")
266 fmt.Fprintf(os.Stderr, "%s\n", err)266 fmt.Fprintf(os.Stderr, "%s\n", err)
267 }267 }
268}268}
269269
270func listcontainers(storage gwacl.StorageContext) {270func listcontainers(storage gwacl.StorageContext) {
271 res, e := storage.ListAllContainers()271 res, e := storage.ListAllContainers()
272 if e != nil {272 if e != nil {
273 dumpError(e)273 dumpError(e)
274 return274 return
275 }275 }
276 for _, c := range res.Containers {276 for _, c := range res.Containers {
277 // TODO: embellish with the other container data277 // TODO: embellish with the other container data
278 fmt.Println(c.Name)278 fmt.Println(c.Name)
279 }279 }
280}280}
281281
282func containeracl(storage gwacl.StorageContext) {282func containeracl(storage gwacl.StorageContext) {
283 err := storage.SetContainerACL(&gwacl.SetContainerACLRequest{283 err := storage.SetContainerACL(&gwacl.SetContainerACLRequest{
284 Container: container,284 Container: container,
285 Access: acl,285 Access: acl,
286 })286 })
287 dumpError(err)287 dumpError(err)
288}288}
289289
290func list(storage gwacl.StorageContext) {290func list(storage gwacl.StorageContext) {
291 request := &gwacl.ListBlobsRequest{291 request := &gwacl.ListBlobsRequest{
292 Container: container, Prefix: prefix}292 Container: container, Prefix: prefix}
293 res, err := storage.ListAllBlobs(request)293 res, err := storage.ListAllBlobs(request)
294 if err != nil {294 if err != nil {
295 dumpError(err)295 dumpError(err)
296 return296 return
297 }297 }
298 for _, b := range res.Blobs {298 for _, b := range res.Blobs {
299 fmt.Printf("%s, %s, %s\n", b.ContentLength, b.LastModified, b.Name)299 fmt.Printf("%s, %s, %s\n", b.ContentLength, b.LastModified, b.Name)
300 }300 }
301}301}
302302
303func addblock(storage gwacl.StorageContext) {303func addblock(storage gwacl.StorageContext) {
304 var err error304 var err error
305 file, err := os.Open(filename)305 file, err := os.Open(filename)
306 if err != nil {306 if err != nil {
307 dumpError(err)307 dumpError(err)
308 return308 return
309 }309 }
310 defer file.Close()310 defer file.Close()
311311
312 err = storage.UploadBlockBlob(container, filename, file)312 err = storage.UploadBlockBlob(container, filename, file)
313 if err != nil {313 if err != nil {
314 dumpError(err)314 dumpError(err)
315 return315 return
316 }316 }
317}317}
318318
319func deleteblob(storage gwacl.StorageContext) {319func deleteblob(storage gwacl.StorageContext) {
320 err := storage.DeleteBlob(container, filename)320 err := storage.DeleteBlob(container, filename)
321 dumpError(err)321 dumpError(err)
322}322}
323323
324func getblob(storage gwacl.StorageContext) {324func getblob(storage gwacl.StorageContext) {
325 var err error325 var err error
326 file, err := storage.GetBlob(container, filename)326 file, err := storage.GetBlob(container, filename)
327 if err != nil {327 if err != nil {
328 dumpError(err)328 dumpError(err)
329 return329 return
330 }330 }
331 data, err := ioutil.ReadAll(file)331 data, err := ioutil.ReadAll(file)
332 if err != nil {332 if err != nil {
333 dumpError(err)333 dumpError(err)
334 return334 return
335 }335 }
336 os.Stdout.Write(data)336 os.Stdout.Write(data)
337}337}
338338
339func putblob(storage gwacl.StorageContext) {339func putblob(storage gwacl.StorageContext) {
340 err := storage.PutBlob(&gwacl.PutBlobRequest{340 err := storage.PutBlob(&gwacl.PutBlobRequest{
341 Container: container,341 Container: container,
342 BlobType: blobtype,342 BlobType: blobtype,
343 Filename: blobname,343 Filename: blobname,
344 Size: size,344 Size: size,
345 })345 })
346 dumpError(err)346 dumpError(err)
347}347}
348348
349func putpage(storage gwacl.StorageContext) {349func putpage(storage gwacl.StorageContext) {
350 var err error350 var err error
351 file, err := os.Open(filename)351 file, err := os.Open(filename)
352 if err != nil {352 if err != nil {
353 dumpError(err)353 dumpError(err)
354 return354 return
355 }355 }
356 defer file.Close()356 defer file.Close()
357357
358 var start, end int358 var start, end int
359 fmt.Sscanf(pagerange, "%d-%d", &start, &end)359 fmt.Sscanf(pagerange, "%d-%d", &start, &end)
360360
361 err = storage.PutPage(&gwacl.PutPageRequest{361 err = storage.PutPage(&gwacl.PutPageRequest{
362 Container: container,362 Container: container,
363 Filename: blobname,363 Filename: blobname,
364 StartRange: start,364 StartRange: start,
365 EndRange: end,365 EndRange: end,
366 Data: file,366 Data: file,
367 })367 })
368 if err != nil {368 if err != nil {
369 dumpError(err)369 dumpError(err)
370 return370 return
371 }371 }
372}372}
373373
374func main() {374func main() {
375 flag.Usage = Usage375 flag.Usage = Usage
376 var err error376 var err error
377 op, err := getParams()377 op, err := getParams()
378 if err != nil {378 if err != nil {
379 fmt.Fprintf(os.Stderr, "%s\n", err.Error())379 fmt.Fprintf(os.Stderr, "%s\n", err.Error())
380 fmt.Fprintf(os.Stderr, "Use -h for help with using this program.\n")380 fmt.Fprintf(os.Stderr, "Use -h for help with using this program.\n")
381 os.Exit(1)381 os.Exit(1)
382 }382 }
383 if help {383 if help {
384 Usage()384 Usage()
385 os.Exit(0)385 os.Exit(0)
386 }386 }
387387
388 storage := gwacl.StorageContext{388 storage := gwacl.StorageContext{
389 Account: account,389 Account: account,
390 Key: key,390 Key: key,
391 AzureEndpoint: gwacl.GetEndpoint(location),391 AzureEndpoint: gwacl.GetEndpoint(location),
392 }392 }
393393
394 perform := operationsByName[op].implementation394 perform := operationsByName[op].implementation
395 perform(storage)395 perform(storage)
396}396}
397397
=== modified file 'fork/http/chunked.go'
--- fork/http/chunked.go 2013-07-22 12:53:27 +0000
+++ fork/http/chunked.go 2014-12-02 00:40:19 +0000
@@ -10,11 +10,11 @@
10package http10package http
1111
12import (12import (
13 "bufio"13 "bufio"
14 "bytes"14 "bytes"
15 "errors"15 "errors"
16 "io"16 "io"
17 "strconv"17 "strconv"
18)18)
1919
20const maxLineLength = 4096 // assumed <= bufio.defaultBufSize20const maxLineLength = 4096 // assumed <= bufio.defaultBufSize
@@ -28,60 +28,60 @@
28// newChunkedReader is not needed by normal applications. The http package28// newChunkedReader is not needed by normal applications. The http package
29// automatically decodes chunking when reading response bodies.29// automatically decodes chunking when reading response bodies.
30func newChunkedReader(r io.Reader) io.Reader {30func newChunkedReader(r io.Reader) io.Reader {
31 br, ok := r.(*bufio.Reader)31 br, ok := r.(*bufio.Reader)
32 if !ok {32 if !ok {
33 br = bufio.NewReader(r)33 br = bufio.NewReader(r)
34 }34 }
35 return &chunkedReader{r: br}35 return &chunkedReader{r: br}
36}36}
3737
38type chunkedReader struct {38type chunkedReader struct {
39 r *bufio.Reader39 r *bufio.Reader
40 n uint64 // unread bytes in chunk40 n uint64 // unread bytes in chunk
41 err error41 err error
42}42}
4343
44func (cr *chunkedReader) beginChunk() {44func (cr *chunkedReader) beginChunk() {
45 // chunk-size CRLF45 // chunk-size CRLF
46 var line string46 var line string
47 line, cr.err = readLine(cr.r)47 line, cr.err = readLine(cr.r)
48 if cr.err != nil {48 if cr.err != nil {
49 return49 return
50 }50 }
51 cr.n, cr.err = strconv.ParseUint(line, 16, 64)51 cr.n, cr.err = strconv.ParseUint(line, 16, 64)
52 if cr.err != nil {52 if cr.err != nil {
53 return53 return
54 }54 }
55 if cr.n == 0 {55 if cr.n == 0 {
56 cr.err = io.EOF56 cr.err = io.EOF
57 }57 }
58}58}
5959
60func (cr *chunkedReader) Read(b []uint8) (n int, err error) {60func (cr *chunkedReader) Read(b []uint8) (n int, err error) {
61 if cr.err != nil {61 if cr.err != nil {
62 return 0, cr.err62 return 0, cr.err
63 }63 }
64 if cr.n == 0 {64 if cr.n == 0 {
65 cr.beginChunk()65 cr.beginChunk()
66 if cr.err != nil {66 if cr.err != nil {
67 return 0, cr.err67 return 0, cr.err
68 }68 }
69 }69 }
70 if uint64(len(b)) > cr.n {70 if uint64(len(b)) > cr.n {
71 b = b[0:cr.n]71 b = b[0:cr.n]
72 }72 }
73 n, cr.err = cr.r.Read(b)73 n, cr.err = cr.r.Read(b)
74 cr.n -= uint64(n)74 cr.n -= uint64(n)
75 if cr.n == 0 && cr.err == nil {75 if cr.n == 0 && cr.err == nil {
76 // end of chunk (CRLF)76 // end of chunk (CRLF)
77 b := make([]byte, 2)77 b := make([]byte, 2)
78 if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil {78 if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil {
79 if b[0] != '\r' || b[1] != '\n' {79 if b[0] != '\r' || b[1] != '\n' {
80 cr.err = errors.New("malformed chunked encoding")80 cr.err = errors.New("malformed chunked encoding")
81 }81 }
82 }82 }
83 }83 }
84 return n, cr.err84 return n, cr.err
85}85}
8686
87// Read a line of bytes (up to \n) from b.87// Read a line of bytes (up to \n) from b.
@@ -89,33 +89,33 @@
89// The returned bytes are a pointer into storage in89// The returned bytes are a pointer into storage in
90// the bufio, so they are only valid until the next bufio read.90// the bufio, so they are only valid until the next bufio read.
91func readLineBytes(b *bufio.Reader) (p []byte, err error) {91func readLineBytes(b *bufio.Reader) (p []byte, err error) {
92 if p, err = b.ReadSlice('\n'); err != nil {92 if p, err = b.ReadSlice('\n'); err != nil {
93 // We always know when EOF is coming.93 // We always know when EOF is coming.
94 // If the caller asked for a line, there should be a line.94 // If the caller asked for a line, there should be a line.
95 if err == io.EOF {95 if err == io.EOF {
96 err = io.ErrUnexpectedEOF96 err = io.ErrUnexpectedEOF
97 } else if err == bufio.ErrBufferFull {97 } else if err == bufio.ErrBufferFull {
98 err = ErrLineTooLong98 err = ErrLineTooLong
99 }99 }
100 return nil, err100 return nil, err
101 }101 }
102 if len(p) >= maxLineLength {102 if len(p) >= maxLineLength {
103 return nil, ErrLineTooLong103 return nil, ErrLineTooLong
104 }104 }
105105
106 // Chop off trailing white space.106 // Chop off trailing white space.
107 p = bytes.TrimRight(p, " \r\t\n")107 p = bytes.TrimRight(p, " \r\t\n")
108108
109 return p, nil109 return p, nil
110}110}
111111
112// readLineBytes, but convert the bytes into a string.112// readLineBytes, but convert the bytes into a string.
113func readLine(b *bufio.Reader) (s string, err error) {113func readLine(b *bufio.Reader) (s string, err error) {
114 p, e := readLineBytes(b)114 p, e := readLineBytes(b)
115 if e != nil {115 if e != nil {
116 return "", e116 return "", e
117 }117 }
118 return string(p), nil118 return string(p), nil
119}119}
120120
121// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP121// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP
@@ -128,13 +128,13 @@
128// would result in double chunking or chunking with a Content-Length128// would result in double chunking or chunking with a Content-Length
129// length, both of which are wrong.129// length, both of which are wrong.
130func newChunkedWriter(w io.Writer) io.WriteCloser {130func newChunkedWriter(w io.Writer) io.WriteCloser {
131 return &chunkedWriter{w}131 return &chunkedWriter{w}
132}132}
133133
134// Writing to chunkedWriter translates to writing in HTTP chunked Transfer134// Writing to chunkedWriter translates to writing in HTTP chunked Transfer
135// Encoding wire format to the underlying Wire chunkedWriter.135// Encoding wire format to the underlying Wire chunkedWriter.
136type chunkedWriter struct {136type chunkedWriter struct {
137 Wire io.Writer137 Wire io.Writer
138}138}
139139
140// Write the contents of data as one chunk to Wire.140// Write the contents of data as one chunk to Wire.
@@ -142,29 +142,29 @@
142// a bug since it does not check for success of io.WriteString142// a bug since it does not check for success of io.WriteString
143func (cw *chunkedWriter) Write(data []byte) (n int, err error) {143func (cw *chunkedWriter) Write(data []byte) (n int, err error) {
144144
145 // Don't send 0-length data. It looks like EOF for chunked encoding.145 // Don't send 0-length data. It looks like EOF for chunked encoding.
146 if len(data) == 0 {146 if len(data) == 0 {
147 return 0, nil147 return 0, nil
148 }148 }
149149
150 head := strconv.FormatInt(int64(len(data)), 16) + "\r\n"150 head := strconv.FormatInt(int64(len(data)), 16) + "\r\n"
151151
152 if _, err = io.WriteString(cw.Wire, head); err != nil {152 if _, err = io.WriteString(cw.Wire, head); err != nil {
153 return 0, err153 return 0, err
154 }154 }
155 if n, err = cw.Wire.Write(data); err != nil {155 if n, err = cw.Wire.Write(data); err != nil {
156 return156 return
157 }157 }
158 if n != len(data) {158 if n != len(data) {
159 err = io.ErrShortWrite159 err = io.ErrShortWrite
160 return160 return
161 }161 }
162 _, err = io.WriteString(cw.Wire, "\r\n")162 _, err = io.WriteString(cw.Wire, "\r\n")
163163
164 return164 return
165}165}
166166
167func (cw *chunkedWriter) Close() error {167func (cw *chunkedWriter) Close() error {
168 _, err := io.WriteString(cw.Wire, "0\r\n")168 _, err := io.WriteString(cw.Wire, "0\r\n")
169 return err169 return err
170}170}
171171
=== modified file 'fork/http/client.go'
--- fork/http/client.go 2013-07-22 12:53:27 +0000
+++ fork/http/client.go 2014-12-02 00:40:19 +0000
@@ -10,12 +10,12 @@
10package http10package http
1111
12import (12import (
13 "encoding/base64"13 "encoding/base64"
14 "errors"14 "errors"
15 "fmt"15 "fmt"
16 "io"16 "io"
17 "net/url"17 "net/url"
18 "strings"18 "strings"
19)19)
2020
21// A Client is an HTTP client. Its zero value (DefaultClient) is a usable client21// A Client is an HTTP client. Its zero value (DefaultClient) is a usable client
@@ -25,26 +25,26 @@
25// TCP connections), so Clients should be reused instead of created as25// TCP connections), so Clients should be reused instead of created as
26// needed. Clients are safe for concurrent use by multiple goroutines.26// needed. Clients are safe for concurrent use by multiple goroutines.
27type Client struct {27type Client struct {
28 // Transport specifies the mechanism by which individual28 // Transport specifies the mechanism by which individual
29 // HTTP requests are made.29 // HTTP requests are made.
30 // If nil, DefaultTransport is used.30 // If nil, DefaultTransport is used.
31 Transport RoundTripper31 Transport RoundTripper
3232
33 // CheckRedirect specifies the policy for handling redirects.33 // CheckRedirect specifies the policy for handling redirects.
34 // If CheckRedirect is not nil, the client calls it before34 // If CheckRedirect is not nil, the client calls it before
35 // following an HTTP redirect. The arguments req and via35 // following an HTTP redirect. The arguments req and via
36 // are the upcoming request and the requests made already,36 // are the upcoming request and the requests made already,
37 // oldest first. If CheckRedirect returns an error, the client37 // oldest first. If CheckRedirect returns an error, the client
38 // returns that error instead of issue the Request req.38 // returns that error instead of issue the Request req.
39 //39 //
40 // If CheckRedirect is nil, the Client uses its default policy,40 // If CheckRedirect is nil, the Client uses its default policy,
41 // which is to stop after 10 consecutive requests.41 // which is to stop after 10 consecutive requests.
42 CheckRedirect func(req *Request, via []*Request) error42 CheckRedirect func(req *Request, via []*Request) error
4343
44 // Jar specifies the cookie jar.44 // Jar specifies the cookie jar.
45 // If Jar is nil, cookies are not sent in requests and ignored45 // If Jar is nil, cookies are not sent in requests and ignored
46 // in responses.46 // in responses.
47 Jar CookieJar47 Jar CookieJar
48}48}
4949
50// DefaultClient is the default Client and is used by Get, Head, and Post.50// DefaultClient is the default Client and is used by Get, Head, and Post.
@@ -56,20 +56,20 @@
56// A RoundTripper must be safe for concurrent use by multiple56// A RoundTripper must be safe for concurrent use by multiple
57// goroutines.57// goroutines.
58type RoundTripper interface {58type RoundTripper interface {
59 // RoundTrip executes a single HTTP transaction, returning59 // RoundTrip executes a single HTTP transaction, returning
60 // the Response for the request req. RoundTrip should not60 // the Response for the request req. RoundTrip should not
61 // attempt to interpret the response. In particular,61 // attempt to interpret the response. In particular,
62 // RoundTrip must return err == nil if it obtained a response,62 // RoundTrip must return err == nil if it obtained a response,
63 // regardless of the response's HTTP status code. A non-nil63 // regardless of the response's HTTP status code. A non-nil
64 // err should be reserved for failure to obtain a response.64 // err should be reserved for failure to obtain a response.
65 // Similarly, RoundTrip should not attempt to handle65 // Similarly, RoundTrip should not attempt to handle
66 // higher-level protocol details such as redirects,66 // higher-level protocol details such as redirects,
67 // authentication, or cookies.67 // authentication, or cookies.
68 //68 //
69 // RoundTrip should not modify the request, except for69 // RoundTrip should not modify the request, except for
70 // consuming the Body. The request's URL and Header fields70 // consuming the Body. The request's URL and Header fields
71 // are guaranteed to be initialized.71 // are guaranteed to be initialized.
72 RoundTrip(*Request) (*Response, error)72 RoundTrip(*Request) (*Response, error)
73}73}
7474
75// Given a string of the form "host", "host:port", or "[ipv6::address]:port",75// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
@@ -80,8 +80,8 @@
80// bufio.Reader through which we read the response, and the underlying80// bufio.Reader through which we read the response, and the underlying
81// network connection.81// network connection.
82type readClose struct {82type readClose struct {
83 io.Reader83 io.Reader
84 io.Closer84 io.Closer
85}85}
8686
87// Do sends an HTTP request and returns an HTTP response, following87// Do sends an HTTP request and returns an HTTP response, following
@@ -96,51 +96,51 @@
96//96//
97// Generally Get, Post, or PostForm will be used instead of Do.97// Generally Get, Post, or PostForm will be used instead of Do.
98func (c *Client) Do(req *Request) (resp *Response, err error) {98func (c *Client) Do(req *Request) (resp *Response, err error) {
99 if req.Method == "GET" || req.Method == "HEAD" {99 if req.Method == "GET" || req.Method == "HEAD" {
100 return c.doFollowingRedirects(req)100 return c.doFollowingRedirects(req)
101 }101 }
102 return send(req, c.Transport)102 return send(req, c.Transport)
103}103}
104104
105// send issues an HTTP request. Caller should close resp.Body when done reading from it.105// send issues an HTTP request. Caller should close resp.Body when done reading from it.
106func send(req *Request, t RoundTripper) (resp *Response, err error) {106func send(req *Request, t RoundTripper) (resp *Response, err error) {
107 if t == nil {107 if t == nil {
108 t = DefaultTransport108 t = DefaultTransport
109 if t == nil {109 if t == nil {
110 err = errors.New("http: no Client.Transport or DefaultTransport")110 err = errors.New("http: no Client.Transport or DefaultTransport")
111 return111 return
112 }112 }
113 }113 }
114114
115 if req.URL == nil {115 if req.URL == nil {
116 return nil, errors.New("http: nil Request.URL")116 return nil, errors.New("http: nil Request.URL")
117 }117 }
118118
119 if req.RequestURI != "" {119 if req.RequestURI != "" {
120 return nil, errors.New("http: Request.RequestURI can't be set in client requests.")120 return nil, errors.New("http: Request.RequestURI can't be set in client requests.")
121 }121 }
122122
123 // Most the callers of send (Get, Post, et al) don't need123 // Most the callers of send (Get, Post, et al) don't need
124 // Headers, leaving it uninitialized. We guarantee to the124 // Headers, leaving it uninitialized. We guarantee to the
125 // Transport that this has been initialized, though.125 // Transport that this has been initialized, though.
126 if req.Header == nil {126 if req.Header == nil {
127 req.Header = make(Header)127 req.Header = make(Header)
128 }128 }
129129
130 if u := req.URL.User; u != nil {130 if u := req.URL.User; u != nil {
131 req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))131 req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String())))
132 }132 }
133 return t.RoundTrip(req)133 return t.RoundTrip(req)
134}134}
135135
136// True if the specified HTTP status code is one for which the Get utility should136// True if the specified HTTP status code is one for which the Get utility should
137// automatically redirect.137// automatically redirect.
138func shouldRedirect(statusCode int) bool {138func shouldRedirect(statusCode int) bool {
139 switch statusCode {139 switch statusCode {
140 case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:140 case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect:
141 return true141 return true
142 }142 }
143 return false143 return false
144}144}
145145
146// Get issues a GET to the specified URL. If the response is one of the following146// Get issues a GET to the specified URL. If the response is one of the following
@@ -155,7 +155,7 @@
155//155//
156// Get is a wrapper around DefaultClient.Get.156// Get is a wrapper around DefaultClient.Get.
157func Get(url string) (r *Response, err error) {157func Get(url string) (r *Response, err error) {
158 return DefaultClient.Get(url)158 return DefaultClient.Get(url)
159}159}
160160
161// Get issues a GET to the specified URL. If the response is one of the161// Get issues a GET to the specified URL. If the response is one of the
@@ -169,95 +169,95 @@
169//169//
170// Caller should close r.Body when done reading from it.170// Caller should close r.Body when done reading from it.
171func (c *Client) Get(url string) (r *Response, err error) {171func (c *Client) Get(url string) (r *Response, err error) {
172 req, err := NewRequest("GET", url, nil)172 req, err := NewRequest("GET", url, nil)
173 if err != nil {173 if err != nil {
174 return nil, err174 return nil, err
175 }175 }
176 return c.doFollowingRedirects(req)176 return c.doFollowingRedirects(req)
177}177}
178178
179func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) {179func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) {
180 // TODO: if/when we add cookie support, the redirected request shouldn't180 // TODO: if/when we add cookie support, the redirected request shouldn't
181 // necessarily supply the same cookies as the original.181 // necessarily supply the same cookies as the original.
182 var base *url.URL182 var base *url.URL
183 redirectChecker := c.CheckRedirect183 redirectChecker := c.CheckRedirect
184 if redirectChecker == nil {184 if redirectChecker == nil {
185 redirectChecker = defaultCheckRedirect185 redirectChecker = defaultCheckRedirect
186 }186 }
187 var via []*Request187 var via []*Request
188188
189 if ireq.URL == nil {189 if ireq.URL == nil {
190 return nil, errors.New("http: nil Request.URL")190 return nil, errors.New("http: nil Request.URL")
191 }191 }
192192
193 jar := c.Jar193 jar := c.Jar
194 if jar == nil {194 if jar == nil {
195 jar = blackHoleJar{}195 jar = blackHoleJar{}
196 }196 }
197197
198 req := ireq198 req := ireq
199 urlStr := "" // next relative or absolute URL to fetch (after first request)199 urlStr := "" // next relative or absolute URL to fetch (after first request)
200 for redirect := 0; ; redirect++ {200 for redirect := 0; ; redirect++ {
201 if redirect != 0 {201 if redirect != 0 {
202 req = new(Request)202 req = new(Request)
203 req.Method = ireq.Method203 req.Method = ireq.Method
204 req.Header = make(Header)204 req.Header = make(Header)
205 req.URL, err = base.Parse(urlStr)205 req.URL, err = base.Parse(urlStr)
206 if err != nil {206 if err != nil {
207 break207 break
208 }208 }
209 if len(via) > 0 {209 if len(via) > 0 {
210 // Add the Referer header.210 // Add the Referer header.
211 lastReq := via[len(via)-1]211 lastReq := via[len(via)-1]
212 if lastReq.URL.Scheme != "https" {212 if lastReq.URL.Scheme != "https" {
213 req.Header.Set("Referer", lastReq.URL.String())213 req.Header.Set("Referer", lastReq.URL.String())
214 }214 }
215215
216 err = redirectChecker(req, via)216 err = redirectChecker(req, via)
217 if err != nil {217 if err != nil {
218 break218 break
219 }219 }
220 }220 }
221 }221 }
222222
223 for _, cookie := range jar.Cookies(req.URL) {223 for _, cookie := range jar.Cookies(req.URL) {
224 req.AddCookie(cookie)224 req.AddCookie(cookie)
225 }225 }
226 urlStr = req.URL.String()226 urlStr = req.URL.String()
227 if r, err = send(req, c.Transport); err != nil {227 if r, err = send(req, c.Transport); err != nil {
228 break228 break
229 }229 }
230 if c := r.Cookies(); len(c) > 0 {230 if c := r.Cookies(); len(c) > 0 {
231 jar.SetCookies(req.URL, c)231 jar.SetCookies(req.URL, c)
232 }232 }
233233
234 if shouldRedirect(r.StatusCode) {234 if shouldRedirect(r.StatusCode) {
235 r.Body.Close()235 r.Body.Close()
236 if urlStr = r.Header.Get("Location"); urlStr == "" {236 if urlStr = r.Header.Get("Location"); urlStr == "" {
237 err = errors.New(fmt.Sprintf("%d response missing Location header", r.StatusCode))237 err = errors.New(fmt.Sprintf("%d response missing Location header", r.StatusCode))
238 break238 break
239 }239 }
240 base = req.URL240 base = req.URL
241 via = append(via, req)241 via = append(via, req)
242 continue242 continue
243 }243 }
244 return244 return
245 }245 }
246246
247 method := ireq.Method247 method := ireq.Method
248 err = &url.Error{248 err = &url.Error{
249 Op: method[0:1] + strings.ToLower(method[1:]),249 Op: method[0:1] + strings.ToLower(method[1:]),
250 URL: urlStr,250 URL: urlStr,
251 Err: err,251 Err: err,
252 }252 }
253 return253 return
254}254}
255255
256func defaultCheckRedirect(req *Request, via []*Request) error {256func defaultCheckRedirect(req *Request, via []*Request) error {
257 if len(via) >= 10 {257 if len(via) >= 10 {
258 return errors.New("stopped after 10 redirects")258 return errors.New("stopped after 10 redirects")
259 }259 }
260 return nil260 return nil
261}261}
262262
263// Post issues a POST to the specified URL.263// Post issues a POST to the specified URL.
@@ -266,28 +266,28 @@
266//266//
267// Post is a wrapper around DefaultClient.Post267// Post is a wrapper around DefaultClient.Post
268func Post(url string, bodyType string, body io.Reader) (r *Response, err error) {268func Post(url string, bodyType string, body io.Reader) (r *Response, err error) {
269 return DefaultClient.Post(url, bodyType, body)269 return DefaultClient.Post(url, bodyType, body)
270}270}
271271
272// Post issues a POST to the specified URL.272// Post issues a POST to the specified URL.
273//273//
274// Caller should close r.Body when done reading from it.274// Caller should close r.Body when done reading from it.
275func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) {275func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) {
276 req, err := NewRequest("POST", url, body)276 req, err := NewRequest("POST", url, body)
277 if err != nil {277 if err != nil {
278 return nil, err278 return nil, err
279 }279 }
280 req.Header.Set("Content-Type", bodyType)280 req.Header.Set("Content-Type", bodyType)
281 if c.Jar != nil {281 if c.Jar != nil {
282 for _, cookie := range c.Jar.Cookies(req.URL) {282 for _, cookie := range c.Jar.Cookies(req.URL) {
283 req.AddCookie(cookie)283 req.AddCookie(cookie)
284 }284 }
285 }285 }
286 r, err = send(req, c.Transport)286 r, err = send(req, c.Transport)
287 if err == nil && c.Jar != nil {287 if err == nil && c.Jar != nil {
288 c.Jar.SetCookies(req.URL, r.Cookies())288 c.Jar.SetCookies(req.URL, r.Cookies())
289 }289 }
290 return r, err290 return r, err
291}291}
292292
293// PostForm issues a POST to the specified URL,293// PostForm issues a POST to the specified URL,
@@ -297,7 +297,7 @@
297//297//
298// PostForm is a wrapper around DefaultClient.PostForm298// PostForm is a wrapper around DefaultClient.PostForm
299func PostForm(url string, data url.Values) (r *Response, err error) {299func PostForm(url string, data url.Values) (r *Response, err error) {
300 return DefaultClient.PostForm(url, data)300 return DefaultClient.PostForm(url, data)
301}301}
302302
303// PostForm issues a POST to the specified URL,303// PostForm issues a POST to the specified URL,
@@ -305,7 +305,7 @@
305//305//
306// Caller should close r.Body when done reading from it.306// Caller should close r.Body when done reading from it.
307func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) {307func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) {
308 return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))308 return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
309}309}
310310
311// Head issues a HEAD to the specified URL. If the response is one of the311// Head issues a HEAD to the specified URL. If the response is one of the
@@ -319,7 +319,7 @@
319//319//
320// Head is a wrapper around DefaultClient.Head320// Head is a wrapper around DefaultClient.Head
321func Head(url string) (r *Response, err error) {321func Head(url string) (r *Response, err error) {
322 return DefaultClient.Head(url)322 return DefaultClient.Head(url)
323}323}
324324
325// Head issues a HEAD to the specified URL. If the response is one of the325// Head issues a HEAD to the specified URL. If the response is one of the
@@ -331,9 +331,9 @@
331// 303 (See Other)331// 303 (See Other)
332// 307 (Temporary Redirect)332// 307 (Temporary Redirect)
333func (c *Client) Head(url string) (r *Response, err error) {333func (c *Client) Head(url string) (r *Response, err error) {
334 req, err := NewRequest("HEAD", url, nil)334 req, err := NewRequest("HEAD", url, nil)
335 if err != nil {335 if err != nil {
336 return nil, err336 return nil, err
337 }337 }
338 return c.doFollowingRedirects(req)338 return c.doFollowingRedirects(req)
339}339}
340340
=== modified file 'fork/http/cookie.go'
--- fork/http/cookie.go 2013-07-22 12:53:27 +0000
+++ fork/http/cookie.go 2014-12-02 00:40:19 +0000
@@ -5,11 +5,11 @@
5package http5package http
66
7import (7import (
8 "bytes"8 "bytes"
9 "fmt"9 "fmt"
10 "strconv"10 "strconv"
11 "strings"11 "strings"
12 "time"12 "time"
13)13)
1414
15// This implementation is done according to RFC 6265:15// This implementation is done according to RFC 6265:
@@ -19,148 +19,148 @@
19// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an19// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
20// HTTP response or the Cookie header of an HTTP request.20// HTTP response or the Cookie header of an HTTP request.
21type Cookie struct {21type Cookie struct {
22 Name string22 Name string
23 Value string23 Value string
24 Path string24 Path string
25 Domain string25 Domain string
26 Expires time.Time26 Expires time.Time
27 RawExpires string27 RawExpires string
2828
29 // MaxAge=0 means no 'Max-Age' attribute specified.29 // MaxAge=0 means no 'Max-Age' attribute specified.
30 // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'30 // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
31 // MaxAge>0 means Max-Age attribute present and given in seconds31 // MaxAge>0 means Max-Age attribute present and given in seconds
32 MaxAge int32 MaxAge int
33 Secure bool33 Secure bool
34 HttpOnly bool34 HttpOnly bool
35 Raw string35 Raw string
36 Unparsed []string // Raw text of unparsed attribute-value pairs36 Unparsed []string // Raw text of unparsed attribute-value pairs
37}37}
3838
39// readSetCookies parses all "Set-Cookie" values from39// readSetCookies parses all "Set-Cookie" values from
40// the header h and returns the successfully parsed Cookies.40// the header h and returns the successfully parsed Cookies.
41func readSetCookies(h Header) []*Cookie {41func readSetCookies(h Header) []*Cookie {
42 cookies := []*Cookie{}42 cookies := []*Cookie{}
43 for _, line := range h["Set-Cookie"] {43 for _, line := range h["Set-Cookie"] {
44 parts := strings.Split(strings.TrimSpace(line), ";")44 parts := strings.Split(strings.TrimSpace(line), ";")
45 if len(parts) == 1 && parts[0] == "" {45 if len(parts) == 1 && parts[0] == "" {
46 continue46 continue
47 }47 }
48 parts[0] = strings.TrimSpace(parts[0])48 parts[0] = strings.TrimSpace(parts[0])
49 j := strings.Index(parts[0], "=")49 j := strings.Index(parts[0], "=")
50 if j < 0 {50 if j < 0 {
51 continue51 continue
52 }52 }
53 name, value := parts[0][:j], parts[0][j+1:]53 name, value := parts[0][:j], parts[0][j+1:]
54 if !isCookieNameValid(name) {54 if !isCookieNameValid(name) {
55 continue55 continue
56 }56 }
57 value, success := parseCookieValue(value)57 value, success := parseCookieValue(value)
58 if !success {58 if !success {
59 continue59 continue
60 }60 }
61 c := &Cookie{61 c := &Cookie{
62 Name: name,62 Name: name,
63 Value: value,63 Value: value,
64 Raw: line,64 Raw: line,
65 }65 }
66 for i := 1; i < len(parts); i++ {66 for i := 1; i < len(parts); i++ {
67 parts[i] = strings.TrimSpace(parts[i])67 parts[i] = strings.TrimSpace(parts[i])
68 if len(parts[i]) == 0 {68 if len(parts[i]) == 0 {
69 continue69 continue
70 }70 }
7171
72 attr, val := parts[i], ""72 attr, val := parts[i], ""
73 if j := strings.Index(attr, "="); j >= 0 {73 if j := strings.Index(attr, "="); j >= 0 {
74 attr, val = attr[:j], attr[j+1:]74 attr, val = attr[:j], attr[j+1:]
75 }75 }
76 lowerAttr := strings.ToLower(attr)76 lowerAttr := strings.ToLower(attr)
77 parseCookieValueFn := parseCookieValue77 parseCookieValueFn := parseCookieValue
78 if lowerAttr == "expires" {78 if lowerAttr == "expires" {
79 parseCookieValueFn = parseCookieExpiresValue79 parseCookieValueFn = parseCookieExpiresValue
80 }80 }
81 val, success = parseCookieValueFn(val)81 val, success = parseCookieValueFn(val)
82 if !success {82 if !success {
83 c.Unparsed = append(c.Unparsed, parts[i])83 c.Unparsed = append(c.Unparsed, parts[i])
84 continue84 continue
85 }85 }
86 switch lowerAttr {86 switch lowerAttr {
87 case "secure":87 case "secure":
88 c.Secure = true88 c.Secure = true
89 continue89 continue
90 case "httponly":90 case "httponly":
91 c.HttpOnly = true91 c.HttpOnly = true
92 continue92 continue
93 case "domain":93 case "domain":
94 c.Domain = val94 c.Domain = val
95 // TODO: Add domain parsing95 // TODO: Add domain parsing
96 continue96 continue
97 case "max-age":97 case "max-age":
98 secs, err := strconv.Atoi(val)98 secs, err := strconv.Atoi(val)
99 if err != nil || secs != 0 && val[0] == '0' {99 if err != nil || secs != 0 && val[0] == '0' {
100 break100 break
101 }101 }
102 if secs <= 0 {102 if secs <= 0 {
103 c.MaxAge = -1103 c.MaxAge = -1
104 } else {104 } else {
105 c.MaxAge = secs105 c.MaxAge = secs
106 }106 }
107 continue107 continue
108 case "expires":108 case "expires":
109 c.RawExpires = val109 c.RawExpires = val
110 exptime, err := time.Parse(time.RFC1123, val)110 exptime, err := time.Parse(time.RFC1123, val)
111 if err != nil {111 if err != nil {
112 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)112 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
113 if err != nil {113 if err != nil {
114 c.Expires = time.Time{}114 c.Expires = time.Time{}
115 break115 break
116 }116 }
117 }117 }
118 c.Expires = exptime.UTC()118 c.Expires = exptime.UTC()
119 continue119 continue
120 case "path":120 case "path":
121 c.Path = val121 c.Path = val
122 // TODO: Add path parsing122 // TODO: Add path parsing
123 continue123 continue
124 }124 }
125 c.Unparsed = append(c.Unparsed, parts[i])125 c.Unparsed = append(c.Unparsed, parts[i])
126 }126 }
127 cookies = append(cookies, c)127 cookies = append(cookies, c)
128 }128 }
129 return cookies129 return cookies
130}130}
131131
132// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.132// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
133func SetCookie(w ResponseWriter, cookie *Cookie) {133func SetCookie(w ResponseWriter, cookie *Cookie) {
134 w.Header().Add("Set-Cookie", cookie.String())134 w.Header().Add("Set-Cookie", cookie.String())
135}135}
136136
137// String returns the serialization of the cookie for use in a Cookie137// String returns the serialization of the cookie for use in a Cookie
138// header (if only Name and Value are set) or a Set-Cookie response138// header (if only Name and Value are set) or a Set-Cookie response
139// header (if other fields are set).139// header (if other fields are set).
140func (c *Cookie) String() string {140func (c *Cookie) String() string {
141 var b bytes.Buffer141 var b bytes.Buffer
142 fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))142 fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))
143 if len(c.Path) > 0 {143 if len(c.Path) > 0 {
144 fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path))144 fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path))
145 }145 }
146 if len(c.Domain) > 0 {146 if len(c.Domain) > 0 {
147 fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain))147 fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain))
148 }148 }
149 if c.Expires.Unix() > 0 {149 if c.Expires.Unix() > 0 {
150 fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123))150 fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123))
151 }151 }
152 if c.MaxAge > 0 {152 if c.MaxAge > 0 {
153 fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)153 fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
154 } else if c.MaxAge < 0 {154 } else if c.MaxAge < 0 {
155 fmt.Fprintf(&b, "; Max-Age=0")155 fmt.Fprintf(&b, "; Max-Age=0")
156 }156 }
157 if c.HttpOnly {157 if c.HttpOnly {
158 fmt.Fprintf(&b, "; HttpOnly")158 fmt.Fprintf(&b, "; HttpOnly")
159 }159 }
160 if c.Secure {160 if c.Secure {
161 fmt.Fprintf(&b, "; Secure")161 fmt.Fprintf(&b, "; Secure")
162 }162 }
163 return b.String()163 return b.String()
164}164}
165165
166// readCookies parses all "Cookie" values from the header h and166// readCookies parses all "Cookie" values from the header h and
@@ -168,100 +168,100 @@
168//168//
169// if filter isn't empty, only cookies of that name are returned169// if filter isn't empty, only cookies of that name are returned
170func readCookies(h Header, filter string) []*Cookie {170func readCookies(h Header, filter string) []*Cookie {
171 cookies := []*Cookie{}171 cookies := []*Cookie{}
172 lines, ok := h["Cookie"]172 lines, ok := h["Cookie"]
173 if !ok {173 if !ok {
174 return cookies174 return cookies
175 }175 }
176176
177 for _, line := range lines {177 for _, line := range lines {
178 parts := strings.Split(strings.TrimSpace(line), ";")178 parts := strings.Split(strings.TrimSpace(line), ";")
179 if len(parts) == 1 && parts[0] == "" {179 if len(parts) == 1 && parts[0] == "" {
180 continue180 continue
181 }181 }
182 // Per-line attributes182 // Per-line attributes
183 parsedPairs := 0183 parsedPairs := 0
184 for i := 0; i < len(parts); i++ {184 for i := 0; i < len(parts); i++ {
185 parts[i] = strings.TrimSpace(parts[i])185 parts[i] = strings.TrimSpace(parts[i])
186 if len(parts[i]) == 0 {186 if len(parts[i]) == 0 {
187 continue187 continue
188 }188 }
189 name, val := parts[i], ""189 name, val := parts[i], ""
190 if j := strings.Index(name, "="); j >= 0 {190 if j := strings.Index(name, "="); j >= 0 {
191 name, val = name[:j], name[j+1:]191 name, val = name[:j], name[j+1:]
192 }192 }
193 if !isCookieNameValid(name) {193 if !isCookieNameValid(name) {
194 continue194 continue
195 }195 }
196 if filter != "" && filter != name {196 if filter != "" && filter != name {
197 continue197 continue
198 }198 }
199 val, success := parseCookieValue(val)199 val, success := parseCookieValue(val)
200 if !success {200 if !success {
201 continue201 continue
202 }202 }
203 cookies = append(cookies, &Cookie{Name: name, Value: val})203 cookies = append(cookies, &Cookie{Name: name, Value: val})
204 parsedPairs++204 parsedPairs++
205 }205 }
206 }206 }
207 return cookies207 return cookies
208}208}
209209
210var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")210var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-")
211211
212func sanitizeName(n string) string {212func sanitizeName(n string) string {
213 return cookieNameSanitizer.Replace(n)213 return cookieNameSanitizer.Replace(n)
214}214}
215215
216var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")216var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ")
217217
218func sanitizeValue(v string) string {218func sanitizeValue(v string) string {
219 return cookieValueSanitizer.Replace(v)219 return cookieValueSanitizer.Replace(v)
220}220}
221221
222func unquoteCookieValue(v string) string {222func unquoteCookieValue(v string) string {
223 if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {223 if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
224 return v[1 : len(v)-1]224 return v[1 : len(v)-1]
225 }225 }
226 return v226 return v
227}227}
228228
229func isCookieByte(c byte) bool {229func isCookieByte(c byte) bool {
230 switch {230 switch {
231 case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,231 case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
232 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:232 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
233 return true233 return true
234 }234 }
235 return false235 return false
236}236}
237237
238func isCookieExpiresByte(c byte) (ok bool) {238func isCookieExpiresByte(c byte) (ok bool) {
239 return isCookieByte(c) || c == ',' || c == ' '239 return isCookieByte(c) || c == ',' || c == ' '
240}240}
241241
242func parseCookieValue(raw string) (string, bool) {242func parseCookieValue(raw string) (string, bool) {
243 return parseCookieValueUsing(raw, isCookieByte)243 return parseCookieValueUsing(raw, isCookieByte)
244}244}
245245
246func parseCookieExpiresValue(raw string) (string, bool) {246func parseCookieExpiresValue(raw string) (string, bool) {
247 return parseCookieValueUsing(raw, isCookieExpiresByte)247 return parseCookieValueUsing(raw, isCookieExpiresByte)
248}248}
249249
250func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) {250func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) {
251 raw = unquoteCookieValue(raw)251 raw = unquoteCookieValue(raw)
252 for i := 0; i < len(raw); i++ {252 for i := 0; i < len(raw); i++ {
253 if !validByte(raw[i]) {253 if !validByte(raw[i]) {
254 return "", false254 return "", false
255 }255 }
256 }256 }
257 return raw, true257 return raw, true
258}258}
259259
260func isCookieNameValid(raw string) bool {260func isCookieNameValid(raw string) bool {
261 for _, c := range raw {261 for _, c := range raw {
262 if !isToken(byte(c)) {262 if !isToken(byte(c)) {
263 return false263 return false
264 }264 }
265 }265 }
266 return true266 return true
267}267}
268268
=== modified file 'fork/http/filetransport.go'
--- fork/http/filetransport.go 2013-07-22 12:53:27 +0000
+++ fork/http/filetransport.go 2014-12-02 00:40:19 +0000
@@ -5,13 +5,13 @@
5package http5package http
66
7import (7import (
8 "fmt"8 "fmt"
9 "io"9 "io"
10)10)
1111
12// fileTransport implements RoundTripper for the 'file' protocol.12// fileTransport implements RoundTripper for the 'file' protocol.
13type fileTransport struct {13type fileTransport struct {
14 fh fileHandler14 fh fileHandler
15}15}
1616
17// NewFileTransport returns a new RoundTripper, serving the provided17// NewFileTransport returns a new RoundTripper, serving the provided
@@ -28,38 +28,38 @@
28// res, err := c.Get("file:///etc/passwd")28// res, err := c.Get("file:///etc/passwd")
29// ...29// ...
30func NewFileTransport(fs FileSystem) RoundTripper {30func NewFileTransport(fs FileSystem) RoundTripper {
31 return fileTransport{fileHandler{fs}}31 return fileTransport{fileHandler{fs}}
32}32}
3333
34func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {34func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) {
35 // We start ServeHTTP in a goroutine, which may take a long35 // We start ServeHTTP in a goroutine, which may take a long
36 // time if the file is large. The newPopulateResponseWriter36 // time if the file is large. The newPopulateResponseWriter
37 // call returns a channel which either ServeHTTP or finish()37 // call returns a channel which either ServeHTTP or finish()
38 // sends our *Response on, once the *Response itself has been38 // sends our *Response on, once the *Response itself has been
39 // populated (even if the body itself is still being39 // populated (even if the body itself is still being
40 // written to the res.Body, a pipe)40 // written to the res.Body, a pipe)
41 rw, resc := newPopulateResponseWriter()41 rw, resc := newPopulateResponseWriter()
42 go func() {42 go func() {
43 t.fh.ServeHTTP(rw, req)43 t.fh.ServeHTTP(rw, req)
44 rw.finish()44 rw.finish()
45 }()45 }()
46 return <-resc, nil46 return <-resc, nil
47}47}
4848
49func newPopulateResponseWriter() (*populateResponse, <-chan *Response) {49func newPopulateResponseWriter() (*populateResponse, <-chan *Response) {
50 pr, pw := io.Pipe()50 pr, pw := io.Pipe()
51 rw := &populateResponse{51 rw := &populateResponse{
52 ch: make(chan *Response),52 ch: make(chan *Response),
53 pw: pw,53 pw: pw,
54 res: &Response{54 res: &Response{
55 Proto: "HTTP/1.0",55 Proto: "HTTP/1.0",
56 ProtoMajor: 1,56 ProtoMajor: 1,
57 Header: make(Header),57 Header: make(Header),
58 Close: true,58 Close: true,
59 Body: pr,59 Body: pr,
60 },60 },
61 }61 }
62 return rw, rw.ch62 return rw, rw.ch
63}63}
6464
65// populateResponse is a ResponseWriter that populates the *Response65// populateResponse is a ResponseWriter that populates the *Response
@@ -67,57 +67,57 @@
67// body. Once writes begin or finish() is called, the response is sent67// body. Once writes begin or finish() is called, the response is sent
68// on ch.68// on ch.
69type populateResponse struct {69type populateResponse struct {
70 res *Response70 res *Response
71 ch chan *Response71 ch chan *Response
72 wroteHeader bool72 wroteHeader bool
73 hasContent bool73 hasContent bool
74 sentResponse bool74 sentResponse bool
75 pw *io.PipeWriter75 pw *io.PipeWriter
76}76}
7777
78func (pr *populateResponse) finish() {78func (pr *populateResponse) finish() {
79 if !pr.wroteHeader {79 if !pr.wroteHeader {
80 pr.WriteHeader(500)80 pr.WriteHeader(500)
81 }81 }
82 if !pr.sentResponse {82 if !pr.sentResponse {
83 pr.sendResponse()83 pr.sendResponse()
84 }84 }
85 pr.pw.Close()85 pr.pw.Close()
86}86}
8787
88func (pr *populateResponse) sendResponse() {88func (pr *populateResponse) sendResponse() {
89 if pr.sentResponse {89 if pr.sentResponse {
90 return90 return
91 }91 }
92 pr.sentResponse = true92 pr.sentResponse = true
9393
94 if pr.hasContent {94 if pr.hasContent {
95 pr.res.ContentLength = -195 pr.res.ContentLength = -1
96 }96 }
97 pr.ch <- pr.res97 pr.ch <- pr.res
98}98}
9999
100func (pr *populateResponse) Header() Header {100func (pr *populateResponse) Header() Header {
101 return pr.res.Header101 return pr.res.Header
102}102}
103103
104func (pr *populateResponse) WriteHeader(code int) {104func (pr *populateResponse) WriteHeader(code int) {
105 if pr.wroteHeader {105 if pr.wroteHeader {
106 return106 return
107 }107 }
108 pr.wroteHeader = true108 pr.wroteHeader = true
109109
110 pr.res.StatusCode = code110 pr.res.StatusCode = code
111 pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code))111 pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code))
112}112}
113113
114func (pr *populateResponse) Write(p []byte) (n int, err error) {114func (pr *populateResponse) Write(p []byte) (n int, err error) {
115 if !pr.wroteHeader {115 if !pr.wroteHeader {
116 pr.WriteHeader(StatusOK)116 pr.WriteHeader(StatusOK)
117 }117 }
118 pr.hasContent = true118 pr.hasContent = true
119 if !pr.sentResponse {119 if !pr.sentResponse {
120 pr.sendResponse()120 pr.sendResponse()
121 }121 }
122 return pr.pw.Write(p)122 return pr.pw.Write(p)
123}123}
124124
=== modified file 'fork/http/fs.go'
--- fork/http/fs.go 2013-07-22 12:53:27 +0000
+++ fork/http/fs.go 2014-12-02 00:40:19 +0000
@@ -7,16 +7,16 @@
7package http7package http
88
9import (9import (
10 "errors"10 "errors"
11 "fmt"11 "fmt"
12 "io"12 "io"
13 "mime"13 "mime"
14 "os"14 "os"
15 "path"15 "path"
16 "path/filepath"16 "path/filepath"
17 "strconv"17 "strconv"
18 "strings"18 "strings"
19 "time"19 "time"
20)20)
2121
22// A Dir implements http.FileSystem using the native file22// A Dir implements http.FileSystem using the native file
@@ -26,55 +26,55 @@
26type Dir string26type Dir string
2727
28func (d Dir) Open(name string) (File, error) {28func (d Dir) Open(name string) (File, error) {
29 if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {29 if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 {
30 return nil, errors.New("http: invalid character in file path")30 return nil, errors.New("http: invalid character in file path")
31 }31 }
32 dir := string(d)32 dir := string(d)
33 if dir == "" {33 if dir == "" {
34 dir = "."34 dir = "."
35 }35 }
36 f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))36 f, err := os.Open(filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))))
37 if err != nil {37 if err != nil {
38 return nil, err38 return nil, err
39 }39 }
40 return f, nil40 return f, nil
41}41}
4242
43// A FileSystem implements access to a collection of named files.43// A FileSystem implements access to a collection of named files.
44// The elements in a file path are separated by slash ('/', U+002F)44// The elements in a file path are separated by slash ('/', U+002F)
45// characters, regardless of host operating system convention.45// characters, regardless of host operating system convention.
46type FileSystem interface {46type FileSystem interface {
47 Open(name string) (File, error)47 Open(name string) (File, error)
48}48}
4949
50// A File is returned by a FileSystem's Open method and can be50// A File is returned by a FileSystem's Open method and can be
51// served by the FileServer implementation.51// served by the FileServer implementation.
52type File interface {52type File interface {
53 Close() error53 Close() error
54 Stat() (os.FileInfo, error)54 Stat() (os.FileInfo, error)
55 Readdir(count int) ([]os.FileInfo, error)55 Readdir(count int) ([]os.FileInfo, error)
56 Read([]byte) (int, error)56 Read([]byte) (int, error)
57 Seek(offset int64, whence int) (int64, error)57 Seek(offset int64, whence int) (int64, error)
58}58}
5959
60func dirList(w ResponseWriter, f File) {60func dirList(w ResponseWriter, f File) {
61 w.Header().Set("Content-Type", "text/html; charset=utf-8")61 w.Header().Set("Content-Type", "text/html; charset=utf-8")
62 fmt.Fprintf(w, "<pre>\n")62 fmt.Fprintf(w, "<pre>\n")
63 for {63 for {
64 dirs, err := f.Readdir(100)64 dirs, err := f.Readdir(100)
65 if err != nil || len(dirs) == 0 {65 if err != nil || len(dirs) == 0 {
66 break66 break
67 }67 }
68 for _, d := range dirs {68 for _, d := range dirs {
69 name := d.Name()69 name := d.Name()
70 if d.IsDir() {70 if d.IsDir() {
71 name += "/"71 name += "/"
72 }72 }
73 // TODO htmlescape73 // TODO htmlescape
74 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)74 fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name)
75 }75 }
76 }76 }
77 fmt.Fprintf(w, "</pre>\n")77 fmt.Fprintf(w, "</pre>\n")
78}78}
7979
80// ServeContent replies to the request using the content in the80// ServeContent replies to the request using the content in the
@@ -99,192 +99,192 @@
99//99//
100// Note that *os.File implements the io.ReadSeeker interface.100// Note that *os.File implements the io.ReadSeeker interface.
101func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {101func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) {
102 size, err := content.Seek(0, os.SEEK_END)102 size, err := content.Seek(0, os.SEEK_END)
103 if err != nil {103 if err != nil {
104 Error(w, "seeker can't seek", StatusInternalServerError)104 Error(w, "seeker can't seek", StatusInternalServerError)
105 return105 return
106 }106 }
107 _, err = content.Seek(0, os.SEEK_SET)107 _, err = content.Seek(0, os.SEEK_SET)
108 if err != nil {108 if err != nil {
109 Error(w, "seeker can't seek", StatusInternalServerError)109 Error(w, "seeker can't seek", StatusInternalServerError)
110 return110 return
111 }111 }
112 serveContent(w, req, name, modtime, size, content)112 serveContent(w, req, name, modtime, size, content)
113}113}
114114
115// if name is empty, filename is unknown. (used for mime type, before sniffing)115// if name is empty, filename is unknown. (used for mime type, before sniffing)
116// if modtime.IsZero(), modtime is unknown.116// if modtime.IsZero(), modtime is unknown.
117// content must be seeked to the beginning of the file.117// content must be seeked to the beginning of the file.
118func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) {118func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) {
119 if checkLastModified(w, r, modtime) {119 if checkLastModified(w, r, modtime) {
120 return120 return
121 }121 }
122122
123 code := StatusOK123 code := StatusOK
124124
125 // If Content-Type isn't set, use the file's extension to find it.125 // If Content-Type isn't set, use the file's extension to find it.
126 if w.Header().Get("Content-Type") == "" {126 if w.Header().Get("Content-Type") == "" {
127 ctype := mime.TypeByExtension(filepath.Ext(name))127 ctype := mime.TypeByExtension(filepath.Ext(name))
128 if ctype == "" {128 if ctype == "" {
129 // read a chunk to decide between utf-8 text and binary129 // read a chunk to decide between utf-8 text and binary
130 var buf [1024]byte130 var buf [1024]byte
131 n, _ := io.ReadFull(content, buf[:])131 n, _ := io.ReadFull(content, buf[:])
132 b := buf[:n]132 b := buf[:n]
133 ctype = DetectContentType(b)133 ctype = DetectContentType(b)
134 _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file134 _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file
135 if err != nil {135 if err != nil {
136 Error(w, "seeker can't seek", StatusInternalServerError)136 Error(w, "seeker can't seek", StatusInternalServerError)
137 return137 return
138 }138 }
139 }139 }
140 w.Header().Set("Content-Type", ctype)140 w.Header().Set("Content-Type", ctype)
141 }141 }
142142
143 // handle Content-Range header.143 // handle Content-Range header.
144 // TODO(adg): handle multiple ranges144 // TODO(adg): handle multiple ranges
145 sendSize := size145 sendSize := size
146 if size >= 0 {146 if size >= 0 {
147 ranges, err := parseRange(r.Header.Get("Range"), size)147 ranges, err := parseRange(r.Header.Get("Range"), size)
148 if err == nil && len(ranges) > 1 {148 if err == nil && len(ranges) > 1 {
149 err = errors.New("multiple ranges not supported")149 err = errors.New("multiple ranges not supported")
150 }150 }
151 if err != nil {151 if err != nil {
152 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)152 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
153 return153 return
154 }154 }
155 if len(ranges) == 1 {155 if len(ranges) == 1 {
156 ra := ranges[0]156 ra := ranges[0]
157 if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil {157 if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil {
158 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)158 Error(w, err.Error(), StatusRequestedRangeNotSatisfiable)
159 return159 return
160 }160 }
161 sendSize = ra.length161 sendSize = ra.length
162 code = StatusPartialContent162 code = StatusPartialContent
163 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, size))163 w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, size))
164 }164 }
165165
166 w.Header().Set("Accept-Ranges", "bytes")166 w.Header().Set("Accept-Ranges", "bytes")
167 if w.Header().Get("Content-Encoding") == "" {167 if w.Header().Get("Content-Encoding") == "" {
168 w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))168 w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
169 }169 }
170 }170 }
171171
172 w.WriteHeader(code)172 w.WriteHeader(code)
173173
174 if r.Method != "HEAD" {174 if r.Method != "HEAD" {
175 if sendSize == -1 {175 if sendSize == -1 {
176 io.Copy(w, content)176 io.Copy(w, content)
177 } else {177 } else {
178 io.CopyN(w, content, sendSize)178 io.CopyN(w, content, sendSize)
179 }179 }
180 }180 }
181}181}
182182
183// modtime is the modification time of the resource to be served, or IsZero().183// modtime is the modification time of the resource to be served, or IsZero().
184// return value is whether this request is now complete.184// return value is whether this request is now complete.
185func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool {185func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool {
186 if modtime.IsZero() {186 if modtime.IsZero() {
187 return false187 return false
188 }188 }
189189
190 // The Date-Modified header truncates sub-second precision, so190 // The Date-Modified header truncates sub-second precision, so
191 // use mtime < t+1s instead of mtime <= t to check for unmodified.191 // use mtime < t+1s instead of mtime <= t to check for unmodified.
192 if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {192 if t, err := time.Parse(TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) {
193 w.WriteHeader(StatusNotModified)193 w.WriteHeader(StatusNotModified)
194 return true194 return true
195 }195 }
196 w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))196 w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat))
197 return false197 return false
198}198}
199199
200// name is '/'-separated, not filepath.Separator.200// name is '/'-separated, not filepath.Separator.
201func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {201func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) {
202 const indexPage = "/index.html"202 const indexPage = "/index.html"
203203
204 // redirect .../index.html to .../204 // redirect .../index.html to .../
205 // can't use Redirect() because that would make the path absolute,205 // can't use Redirect() because that would make the path absolute,
206 // which would be a problem running under StripPrefix206 // which would be a problem running under StripPrefix
207 if strings.HasSuffix(r.URL.Path, indexPage) {207 if strings.HasSuffix(r.URL.Path, indexPage) {
208 localRedirect(w, r, "./")208 localRedirect(w, r, "./")
209 return209 return
210 }210 }
211211
212 f, err := fs.Open(name)212 f, err := fs.Open(name)
213 if err != nil {213 if err != nil {
214 // TODO expose actual error?214 // TODO expose actual error?
215 NotFound(w, r)215 NotFound(w, r)
216 return216 return
217 }217 }
218 defer f.Close()218 defer f.Close()
219219
220 d, err1 := f.Stat()220 d, err1 := f.Stat()
221 if err1 != nil {221 if err1 != nil {
222 // TODO expose actual error?222 // TODO expose actual error?
223 NotFound(w, r)223 NotFound(w, r)
224 return224 return
225 }225 }
226226
227 if redirect {227 if redirect {
228 // redirect to canonical path: / at end of directory url228 // redirect to canonical path: / at end of directory url
229 // r.URL.Path always begins with /229 // r.URL.Path always begins with /
230 url := r.URL.Path230 url := r.URL.Path
231 if d.IsDir() {231 if d.IsDir() {
232 if url[len(url)-1] != '/' {232 if url[len(url)-1] != '/' {
233 localRedirect(w, r, path.Base(url)+"/")233 localRedirect(w, r, path.Base(url)+"/")
234 return234 return
235 }235 }
236 } else {236 } else {
237 if url[len(url)-1] == '/' {237 if url[len(url)-1] == '/' {
238 localRedirect(w, r, "../"+path.Base(url))238 localRedirect(w, r, "../"+path.Base(url))
239 return239 return
240 }240 }
241 }241 }
242 }242 }
243243
244 // use contents of index.html for directory, if present244 // use contents of index.html for directory, if present
245 if d.IsDir() {245 if d.IsDir() {
246 if checkLastModified(w, r, d.ModTime()) {246 if checkLastModified(w, r, d.ModTime()) {
247 return247 return
248 }248 }
249 index := name + indexPage249 index := name + indexPage
250 ff, err := fs.Open(index)250 ff, err := fs.Open(index)
251 if err == nil {251 if err == nil {
252 defer ff.Close()252 defer ff.Close()
253 dd, err := ff.Stat()253 dd, err := ff.Stat()
254 if err == nil {254 if err == nil {
255 name = index255 name = index
256 d = dd256 d = dd
257 f = ff257 f = ff
258 }258 }
259 }259 }
260 }260 }
261261
262 if d.IsDir() {262 if d.IsDir() {
263 dirList(w, f)263 dirList(w, f)
264 return264 return
265 }265 }
266266
267 serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f)267 serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f)
268}268}
269269
270// localRedirect gives a Moved Permanently response.270// localRedirect gives a Moved Permanently response.
271// It does not convert relative paths to absolute paths like Redirect does.271// It does not convert relative paths to absolute paths like Redirect does.
272func localRedirect(w ResponseWriter, r *Request, newPath string) {272func localRedirect(w ResponseWriter, r *Request, newPath string) {
273 if q := r.URL.RawQuery; q != "" {273 if q := r.URL.RawQuery; q != "" {
274 newPath += "?" + q274 newPath += "?" + q
275 }275 }
276 w.Header().Set("Location", newPath)276 w.Header().Set("Location", newPath)
277 w.WriteHeader(StatusMovedPermanently)277 w.WriteHeader(StatusMovedPermanently)
278}278}
279279
280// ServeFile replies to the request with the contents of the named file or directory.280// ServeFile replies to the request with the contents of the named file or directory.
281func ServeFile(w ResponseWriter, r *Request, name string) {281func ServeFile(w ResponseWriter, r *Request, name string) {
282 dir, file := filepath.Split(name)282 dir, file := filepath.Split(name)
283 serveFile(w, r, Dir(dir), file, false)283 serveFile(w, r, Dir(dir), file, false)
284}284}
285285
286type fileHandler struct {286type fileHandler struct {
287 root FileSystem287 root FileSystem
288}288}
289289
290// FileServer returns a handler that serves HTTP requests290// FileServer returns a handler that serves HTTP requests
@@ -295,73 +295,73 @@
295//295//
296// http.Handle("/", http.FileServer(http.Dir("/tmp")))296// http.Handle("/", http.FileServer(http.Dir("/tmp")))
297func FileServer(root FileSystem) Handler {297func FileServer(root FileSystem) Handler {
298 return &fileHandler{root}298 return &fileHandler{root}
299}299}
300300
301func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {301func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
302 upath := r.URL.Path302 upath := r.URL.Path
303 if !strings.HasPrefix(upath, "/") {303 if !strings.HasPrefix(upath, "/") {
304 upath = "/" + upath304 upath = "/" + upath
305 r.URL.Path = upath305 r.URL.Path = upath
306 }306 }
307 serveFile(w, r, f.root, path.Clean(upath), true)307 serveFile(w, r, f.root, path.Clean(upath), true)
308}308}
309309
310// httpRange specifies the byte range to be sent to the client.310// httpRange specifies the byte range to be sent to the client.
311type httpRange struct {311type httpRange struct {
312 start, length int64312 start, length int64
313}313}
314314
315// parseRange parses a Range header string as per RFC 2616.315// parseRange parses a Range header string as per RFC 2616.
316func parseRange(s string, size int64) ([]httpRange, error) {316func parseRange(s string, size int64) ([]httpRange, error) {
317 if s == "" {317 if s == "" {
318 return nil, nil // header not present318 return nil, nil // header not present
319 }319 }
320 const b = "bytes="320 const b = "bytes="
321 if !strings.HasPrefix(s, b) {321 if !strings.HasPrefix(s, b) {
322 return nil, errors.New("invalid range")322 return nil, errors.New("invalid range")
323 }323 }
324 var ranges []httpRange324 var ranges []httpRange
325 for _, ra := range strings.Split(s[len(b):], ",") {325 for _, ra := range strings.Split(s[len(b):], ",") {
326 i := strings.Index(ra, "-")326 i := strings.Index(ra, "-")
327 if i < 0 {327 if i < 0 {
328 return nil, errors.New("invalid range")328 return nil, errors.New("invalid range")
329 }329 }
330 start, end := ra[:i], ra[i+1:]330 start, end := ra[:i], ra[i+1:]
331 var r httpRange331 var r httpRange
332 if start == "" {332 if start == "" {
333 // If no start is specified, end specifies the333 // If no start is specified, end specifies the
334 // range start relative to the end of the file.334 // range start relative to the end of the file.
335 i, err := strconv.ParseInt(end, 10, 64)335 i, err := strconv.ParseInt(end, 10, 64)
336 if err != nil {336 if err != nil {
337 return nil, errors.New("invalid range")337 return nil, errors.New("invalid range")
338 }338 }
339 if i > size {339 if i > size {
340 i = size340 i = size
341 }341 }
342 r.start = size - i342 r.start = size - i
343 r.length = size - r.start343 r.length = size - r.start
344 } else {344 } else {
345 i, err := strconv.ParseInt(start, 10, 64)345 i, err := strconv.ParseInt(start, 10, 64)
346 if err != nil || i > size || i < 0 {346 if err != nil || i > size || i < 0 {
347 return nil, errors.New("invalid range")347 return nil, errors.New("invalid range")
348 }348 }
349 r.start = i349 r.start = i
350 if end == "" {350 if end == "" {
351 // If no end is specified, range extends to end of the file.351 // If no end is specified, range extends to end of the file.
352 r.length = size - r.start352 r.length = size - r.start
353 } else {353 } else {
354 i, err := strconv.ParseInt(end, 10, 64)354 i, err := strconv.ParseInt(end, 10, 64)
355 if err != nil || r.start > i {355 if err != nil || r.start > i {
356 return nil, errors.New("invalid range")356 return nil, errors.New("invalid range")
357 }357 }
358 if i >= size {358 if i >= size {
359 i = size - 1359 i = size - 1
360 }360 }
361 r.length = i - r.start + 1361 r.length = i - r.start + 1
362 }362 }
363 }363 }
364 ranges = append(ranges, r)364 ranges = append(ranges, r)
365 }365 }
366 return ranges, nil366 return ranges, nil
367}367}
368368
=== modified file 'fork/http/header.go'
--- fork/http/header.go 2013-07-22 12:53:27 +0000
+++ fork/http/header.go 2014-12-02 00:40:19 +0000
@@ -5,11 +5,11 @@
5package http5package http
66
7import (7import (
8 "fmt"8 "fmt"
9 "io"9 "io"
10 "net/textproto"10 "net/textproto"
11 "sort"11 "sort"
12 "strings"12 "strings"
13)13)
1414
15// A Header represents the key-value pairs in an HTTP header.15// A Header represents the key-value pairs in an HTTP header.
@@ -18,14 +18,14 @@
18// Add adds the key, value pair to the header.18// Add adds the key, value pair to the header.
19// It appends to any existing values associated with key.19// It appends to any existing values associated with key.
20func (h Header) Add(key, value string) {20func (h Header) Add(key, value string) {
21 textproto.MIMEHeader(h).Add(key, value)21 textproto.MIMEHeader(h).Add(key, value)
22}22}
2323
24// Set sets the header entries associated with key to24// Set sets the header entries associated with key to
25// the single element value. It replaces any existing25// the single element value. It replaces any existing
26// values associated with key.26// values associated with key.
27func (h Header) Set(key, value string) {27func (h Header) Set(key, value string) {
28 textproto.MIMEHeader(h).Set(key, value)28 textproto.MIMEHeader(h).Set(key, value)
29}29}
3030
31// Get gets the first value associated with the given key.31// Get gets the first value associated with the given key.
@@ -33,17 +33,17 @@
33// To access multiple values of a key, access the map directly33// To access multiple values of a key, access the map directly
34// with CanonicalHeaderKey.34// with CanonicalHeaderKey.
35func (h Header) Get(key string) string {35func (h Header) Get(key string) string {
36 return textproto.MIMEHeader(h).Get(key)36 return textproto.MIMEHeader(h).Get(key)
37}37}
3838
39// Del deletes the values associated with key.39// Del deletes the values associated with key.
40func (h Header) Del(key string) {40func (h Header) Del(key string) {
41 textproto.MIMEHeader(h).Del(key)41 textproto.MIMEHeader(h).Del(key)
42}42}
4343
44// Write writes a header in wire format.44// Write writes a header in wire format.
45func (h Header) Write(w io.Writer) error {45func (h Header) Write(w io.Writer) error {
46 return h.WriteSubset(w, nil)46 return h.WriteSubset(w, nil)
47}47}
4848
49var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ")49var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ")
@@ -51,23 +51,23 @@
51// WriteSubset writes a header in wire format.51// WriteSubset writes a header in wire format.
52// If exclude is not nil, keys where exclude[key] == true are not written.52// If exclude is not nil, keys where exclude[key] == true are not written.
53func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {53func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
54 keys := make([]string, 0, len(h))54 keys := make([]string, 0, len(h))
55 for k := range h {55 for k := range h {
56 if exclude == nil || !exclude[k] {56 if exclude == nil || !exclude[k] {
57 keys = append(keys, k)57 keys = append(keys, k)
58 }58 }
59 }59 }
60 sort.Strings(keys)60 sort.Strings(keys)
61 for _, k := range keys {61 for _, k := range keys {
62 for _, v := range h[k] {62 for _, v := range h[k] {
63 v = headerNewlineToSpace.Replace(v)63 v = headerNewlineToSpace.Replace(v)
64 v = strings.TrimSpace(v)64 v = strings.TrimSpace(v)
65 if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {65 if _, err := fmt.Fprintf(w, "%s: %s\r\n", k, v); err != nil {
66 return err66 return err
67 }67 }
68 }68 }
69 }69 }
70 return nil70 return nil
71}71}
7272
73// CanonicalHeaderKey returns the canonical format of the73// CanonicalHeaderKey returns the canonical format of the
7474
=== modified file 'fork/http/jar.go'
--- fork/http/jar.go 2013-07-22 12:53:27 +0000
+++ fork/http/jar.go 2014-12-02 00:40:19 +0000
@@ -5,7 +5,7 @@
5package http5package http
66
7import (7import (
8 "net/url"8 "net/url"
9)9)
1010
11// A CookieJar manages storage and use of cookies in HTTP requests.11// A CookieJar manages storage and use of cookies in HTTP requests.
@@ -13,15 +13,15 @@
13// Implementations of CookieJar must be safe for concurrent use by multiple13// Implementations of CookieJar must be safe for concurrent use by multiple
14// goroutines.14// goroutines.
15type CookieJar interface {15type CookieJar interface {
16 // SetCookies handles the receipt of the cookies in a reply for the16 // SetCookies handles the receipt of the cookies in a reply for the
17 // given URL. It may or may not choose to save the cookies, depending17 // given URL. It may or may not choose to save the cookies, depending
18 // on the jar's policy and implementation.18 // on the jar's policy and implementation.
19 SetCookies(u *url.URL, cookies []*Cookie)19 SetCookies(u *url.URL, cookies []*Cookie)
2020
21 // Cookies returns the cookies to send in a request for the given URL.21 // Cookies returns the cookies to send in a request for the given URL.
22 // It is up to the implementation to honor the standard cookie use22 // It is up to the implementation to honor the standard cookie use
23 // restrictions such as in RFC 6265.23 // restrictions such as in RFC 6265.
24 Cookies(u *url.URL) []*Cookie24 Cookies(u *url.URL) []*Cookie
25}25}
2626
27type blackHoleJar struct{}27type blackHoleJar struct{}
2828
=== modified file 'fork/http/lex.go'
--- fork/http/lex.go 2013-07-22 12:53:27 +0000
+++ fork/http/lex.go 2014-12-02 00:40:19 +0000
@@ -7,11 +7,11 @@
7// This file deals with lexical matters of HTTP7// This file deals with lexical matters of HTTP
88
9func isSeparator(c byte) bool {9func isSeparator(c byte) bool {
10 switch c {10 switch c {
11 case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':11 case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t':
12 return true12 return true
13 }13 }
14 return false14 return false
15}15}
1616
17func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }17func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 }
@@ -29,108 +29,108 @@
29// characters should probably not be treated as errors by a robust (forgiving)29// characters should probably not be treated as errors by a robust (forgiving)
30// parser, so we replace them with the '?' character.30// parser, so we replace them with the '?' character.
31func httpUnquotePair(b byte) byte {31func httpUnquotePair(b byte) byte {
32 // skip the first byte, which should always be '\'32 // skip the first byte, which should always be '\'
33 switch b {33 switch b {
34 case 'a':34 case 'a':
35 return '\a'35 return '\a'
36 case 'b':36 case 'b':
37 return '\b'37 return '\b'
38 case 'f':38 case 'f':
39 return '\f'39 return '\f'
40 case 'n':40 case 'n':
41 return '\n'41 return '\n'
42 case 'r':42 case 'r':
43 return '\r'43 return '\r'
44 case 't':44 case 't':
45 return '\t'45 return '\t'
46 case 'v':46 case 'v':
47 return '\v'47 return '\v'
48 case '\\':48 case '\\':
49 return '\\'49 return '\\'
50 case '\'':50 case '\'':
51 return '\''51 return '\''
52 case '"':52 case '"':
53 return '"'53 return '"'
54 }54 }
55 return '?'55 return '?'
56}56}
5757
58// raw must begin with a valid quoted string. Only the first quoted string is58// raw must begin with a valid quoted string. Only the first quoted string is
59// parsed and is unquoted in result. eaten is the number of bytes parsed, or -159// parsed and is unquoted in result. eaten is the number of bytes parsed, or -1
60// upon failure.60// upon failure.
61func httpUnquote(raw []byte) (eaten int, result string) {61func httpUnquote(raw []byte) (eaten int, result string) {
62 buf := make([]byte, len(raw))62 buf := make([]byte, len(raw))
63 if raw[0] != '"' {63 if raw[0] != '"' {
64 return -1, ""64 return -1, ""
65 }65 }
66 eaten = 166 eaten = 1
67 j := 0 // # of bytes written in buf67 j := 0 // # of bytes written in buf
68 for i := 1; i < len(raw); i++ {68 for i := 1; i < len(raw); i++ {
69 switch b := raw[i]; b {69 switch b := raw[i]; b {
70 case '"':70 case '"':
71 eaten++71 eaten++
72 buf = buf[0:j]72 buf = buf[0:j]
73 return i + 1, string(buf)73 return i + 1, string(buf)
74 case '\\':74 case '\\':
75 if len(raw) < i+2 {75 if len(raw) < i+2 {
76 return -1, ""76 return -1, ""
77 }77 }
78 buf[j] = httpUnquotePair(raw[i+1])78 buf[j] = httpUnquotePair(raw[i+1])
79 eaten += 279 eaten += 2
80 j++80 j++
81 i++81 i++
82 default:82 default:
83 if isQdText(b) {83 if isQdText(b) {
84 buf[j] = b84 buf[j] = b
85 } else {85 } else {
86 buf[j] = '?'86 buf[j] = '?'
87 }87 }
88 eaten++88 eaten++
89 j++89 j++
90 }90 }
91 }91 }
92 return -1, ""92 return -1, ""
93}93}
9494
95// This is a best effort parse, so errors are not returned, instead not all of95// This is a best effort parse, so errors are not returned, instead not all of
96// the input string might be parsed. result is always non-nil.96// the input string might be parsed. result is always non-nil.
97func httpSplitFieldValue(fv string) (eaten int, result []string) {97func httpSplitFieldValue(fv string) (eaten int, result []string) {
98 result = make([]string, 0, len(fv))98 result = make([]string, 0, len(fv))
99 raw := []byte(fv)99 raw := []byte(fv)
100 i := 0100 i := 0
101 chunk := ""101 chunk := ""
102 for i < len(raw) {102 for i < len(raw) {
103 b := raw[i]103 b := raw[i]
104 switch {104 switch {
105 case b == '"':105 case b == '"':
106 eaten, unq := httpUnquote(raw[i:])106 eaten, unq := httpUnquote(raw[i:])
107 if eaten < 0 {107 if eaten < 0 {
108 return i, result108 return i, result
109 } else {109 } else {
110 i += eaten110 i += eaten
111 chunk += unq111 chunk += unq
112 }112 }
113 case isSeparator(b):113 case isSeparator(b):
114 if chunk != "" {114 if chunk != "" {
115 result = result[0 : len(result)+1]115 result = result[0 : len(result)+1]
116 result[len(result)-1] = chunk116 result[len(result)-1] = chunk
117 chunk = ""117 chunk = ""
118 }118 }
119 i++119 i++
120 case isToken(b):120 case isToken(b):
121 chunk += string(b)121 chunk += string(b)
122 i++122 i++
123 case b == '\n' || b == '\r':123 case b == '\n' || b == '\r':
124 i++124 i++
125 default:125 default:
126 chunk += "?"126 chunk += "?"
127 i++127 i++
128 }128 }
129 }129 }
130 if chunk != "" {130 if chunk != "" {
131 result = result[0 : len(result)+1]131 result = result[0 : len(result)+1]
132 result[len(result)-1] = chunk132 result[len(result)-1] = chunk
133 chunk = ""133 chunk = ""
134 }134 }
135 return i, result135 return i, result
136}136}
137137
=== modified file 'fork/http/request.go'
--- fork/http/request.go 2013-07-22 12:53:27 +0000
+++ fork/http/request.go 2014-12-02 00:40:19 +0000
@@ -7,26 +7,26 @@
7package http7package http
88
9import (9import (
10 "bufio"10 "bufio"
11 "bytes"11 "bytes"
12 "crypto/tls"12 "crypto/tls"
13 "encoding/base64"13 "encoding/base64"
14 "errors"14 "errors"
15 "fmt"15 "fmt"
16 "io"16 "io"
17 "io/ioutil"17 "io/ioutil"
18 "mime"18 "mime"
19 "mime/multipart"19 "mime/multipart"
20 "net/textproto"20 "net/textproto"
21 "net/url"21 "net/url"
22 "strings"22 "strings"
23)23)
2424
25const (25const (
26 maxValueLength = 409626 maxValueLength = 4096
27 maxHeaderLines = 102427 maxHeaderLines = 1024
28 chunkSize = 4 << 10 // 4 KB chunks28 chunkSize = 4 << 10 // 4 KB chunks
29 defaultMaxMemory = 32 << 20 // 32 MB29 defaultMaxMemory = 32 << 20 // 32 MB
30)30)
3131
32// ErrMissingFile is returned by FormFile when the provided file field name32// ErrMissingFile is returned by FormFile when the provided file field name
@@ -35,155 +35,155 @@
3535
36// HTTP request parsing errors.36// HTTP request parsing errors.
37type ProtocolError struct {37type ProtocolError struct {
38 ErrorString string38 ErrorString string
39}39}
4040
41func (err *ProtocolError) Error() string { return err.ErrorString }41func (err *ProtocolError) Error() string { return err.ErrorString }
4242
43var (43var (
44 ErrHeaderTooLong = &ProtocolError{"header too long"}44 ErrHeaderTooLong = &ProtocolError{"header too long"}
45 ErrShortBody = &ProtocolError{"entity body too short"}45 ErrShortBody = &ProtocolError{"entity body too short"}
46 ErrNotSupported = &ProtocolError{"feature not supported"}46 ErrNotSupported = &ProtocolError{"feature not supported"}
47 ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"}47 ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"}
48 ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"}48 ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"}
49 ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"}49 ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"}
50 ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"}50 ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"}
51)51)
5252
53type badStringError struct {53type badStringError struct {
54 what string54 what string
55 str string55 str string
56}56}
5757
58func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }58func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
5959
60// Headers that Request.Write handles itself and should be skipped.60// Headers that Request.Write handles itself and should be skipped.
61var reqWriteExcludeHeader = map[string]bool{61var reqWriteExcludeHeader = map[string]bool{
62 "Host": true, // not in Header map anyway62 "Host": true, // not in Header map anyway
63 "User-Agent": true,63 "User-Agent": true,
64 "Content-Length": true,64 "Content-Length": true,
65 "Transfer-Encoding": true,65 "Transfer-Encoding": true,
66 "Trailer": true,66 "Trailer": true,
67}67}
6868
69// A Request represents an HTTP request received by a server69// A Request represents an HTTP request received by a server
70// or to be sent by a client.70// or to be sent by a client.
71type Request struct {71type Request struct {
72 Method string // GET, POST, PUT, etc.72 Method string // GET, POST, PUT, etc.
73 URL *url.URL73 URL *url.URL
7474
75 // The protocol version for incoming requests.75 // The protocol version for incoming requests.
76 // Outgoing requests always use HTTP/1.1.76 // Outgoing requests always use HTTP/1.1.
77 Proto string // "HTTP/1.0"77 Proto string // "HTTP/1.0"
78 ProtoMajor int // 178 ProtoMajor int // 1
79 ProtoMinor int // 079 ProtoMinor int // 0
8080
81 // A header maps request lines to their values.81 // A header maps request lines to their values.
82 // If the header says82 // If the header says
83 //83 //
84 // accept-encoding: gzip, deflate84 // accept-encoding: gzip, deflate
85 // Accept-Language: en-us85 // Accept-Language: en-us
86 // Connection: keep-alive86 // Connection: keep-alive
87 //87 //
88 // then88 // then
89 //89 //
90 // Header = map[string][]string{90 // Header = map[string][]string{
91 // "Accept-Encoding": {"gzip, deflate"},91 // "Accept-Encoding": {"gzip, deflate"},
92 // "Accept-Language": {"en-us"},92 // "Accept-Language": {"en-us"},
93 // "Connection": {"keep-alive"},93 // "Connection": {"keep-alive"},
94 // }94 // }
95 //95 //
96 // HTTP defines that header names are case-insensitive.96 // HTTP defines that header names are case-insensitive.
97 // The request parser implements this by canonicalizing the97 // The request parser implements this by canonicalizing the
98 // name, making the first character and any characters98 // name, making the first character and any characters
99 // following a hyphen uppercase and the rest lowercase.99 // following a hyphen uppercase and the rest lowercase.
100 Header Header100 Header Header
101101
102 // The message body.102 // The message body.
103 Body io.ReadCloser103 Body io.ReadCloser
104104
105 // ContentLength records the length of the associated content.105 // ContentLength records the length of the associated content.
106 // The value -1 indicates that the length is unknown.106 // The value -1 indicates that the length is unknown.
107 // Values >= 0 indicate that the given number of bytes may107 // Values >= 0 indicate that the given number of bytes may
108 // be read from Body.108 // be read from Body.
109 // For outgoing requests, a value of 0 means unknown if Body is not nil.109 // For outgoing requests, a value of 0 means unknown if Body is not nil.
110 ContentLength int64110 ContentLength int64
111111
112 // TransferEncoding lists the transfer encodings from outermost to112 // TransferEncoding lists the transfer encodings from outermost to
113 // innermost. An empty list denotes the "identity" encoding.113 // innermost. An empty list denotes the "identity" encoding.
114 // TransferEncoding can usually be ignored; chunked encoding is114 // TransferEncoding can usually be ignored; chunked encoding is
115 // automatically added and removed as necessary when sending and115 // automatically added and removed as necessary when sending and
116 // receiving requests.116 // receiving requests.
117 TransferEncoding []string117 TransferEncoding []string
118118
119 // Close indicates whether to close the connection after119 // Close indicates whether to close the connection after
120 // replying to this request.120 // replying to this request.
121 Close bool121 Close bool
122122
123 // The host on which the URL is sought.123 // The host on which the URL is sought.
124 // Per RFC 2616, this is either the value of the Host: header124 // Per RFC 2616, this is either the value of the Host: header
125 // or the host name given in the URL itself.125 // or the host name given in the URL itself.
126 Host string126 Host string
127127
128 // Form contains the parsed form data, including both the URL128 // Form contains the parsed form data, including both the URL
129 // field's query parameters and the POST or PUT form data.129 // field's query parameters and the POST or PUT form data.
130 // This field is only available after ParseForm is called.130 // This field is only available after ParseForm is called.
131 // The HTTP client ignores Form and uses Body instead.131 // The HTTP client ignores Form and uses Body instead.
132 Form url.Values132 Form url.Values
133133
134 // MultipartForm is the parsed multipart form, including file uploads.134 // MultipartForm is the parsed multipart form, including file uploads.
135 // This field is only available after ParseMultipartForm is called.135 // This field is only available after ParseMultipartForm is called.
136 // The HTTP client ignores MultipartForm and uses Body instead.136 // The HTTP client ignores MultipartForm and uses Body instead.
137 MultipartForm *multipart.Form137 MultipartForm *multipart.Form
138138
139 // Trailer maps trailer keys to values. Like for Header, if the139 // Trailer maps trailer keys to values. Like for Header, if the
140 // response has multiple trailer lines with the same key, they will be140 // response has multiple trailer lines with the same key, they will be
141 // concatenated, delimited by commas.141 // concatenated, delimited by commas.
142 // For server requests, Trailer is only populated after Body has been142 // For server requests, Trailer is only populated after Body has been
143 // closed or fully consumed.143 // closed or fully consumed.
144 // Trailer support is only partially complete.144 // Trailer support is only partially complete.
145 Trailer Header145 Trailer Header
146146
147 // RemoteAddr allows HTTP servers and other software to record147 // RemoteAddr allows HTTP servers and other software to record
148 // the network address that sent the request, usually for148 // the network address that sent the request, usually for
149 // logging. This field is not filled in by ReadRequest and149 // logging. This field is not filled in by ReadRequest and
150 // has no defined format. The HTTP server in this package150 // has no defined format. The HTTP server in this package
151 // sets RemoteAddr to an "IP:port" address before invoking a151 // sets RemoteAddr to an "IP:port" address before invoking a
152 // handler.152 // handler.
153 // This field is ignored by the HTTP client.153 // This field is ignored by the HTTP client.
154 RemoteAddr string154 RemoteAddr string
155155
156 // RequestURI is the unmodified Request-URI of the156 // RequestURI is the unmodified Request-URI of the
157 // Request-Line (RFC 2616, Section 5.1) as sent by the client157 // Request-Line (RFC 2616, Section 5.1) as sent by the client
158 // to a server. Usually the URL field should be used instead.158 // to a server. Usually the URL field should be used instead.
159 // It is an error to set this field in an HTTP client request.159 // It is an error to set this field in an HTTP client request.
160 RequestURI string160 RequestURI string
161161
162 // TLS allows HTTP servers and other software to record162 // TLS allows HTTP servers and other software to record
163 // information about the TLS connection on which the request163 // information about the TLS connection on which the request
164 // was received. This field is not filled in by ReadRequest.164 // was received. This field is not filled in by ReadRequest.
165 // The HTTP server in this package sets the field for165 // The HTTP server in this package sets the field for
166 // TLS-enabled connections before invoking a handler;166 // TLS-enabled connections before invoking a handler;
167 // otherwise it leaves the field nil.167 // otherwise it leaves the field nil.
168 // This field is ignored by the HTTP client.168 // This field is ignored by the HTTP client.
169 TLS *tls.ConnectionState169 TLS *tls.ConnectionState
170}170}
171171
172// ProtoAtLeast returns whether the HTTP protocol used172// ProtoAtLeast returns whether the HTTP protocol used
173// in the request is at least major.minor.173// in the request is at least major.minor.
174func (r *Request) ProtoAtLeast(major, minor int) bool {174func (r *Request) ProtoAtLeast(major, minor int) bool {
175 return r.ProtoMajor > major ||175 return r.ProtoMajor > major ||
176 r.ProtoMajor == major && r.ProtoMinor >= minor176 r.ProtoMajor == major && r.ProtoMinor >= minor
177}177}
178178
179// UserAgent returns the client's User-Agent, if sent in the request.179// UserAgent returns the client's User-Agent, if sent in the request.
180func (r *Request) UserAgent() string {180func (r *Request) UserAgent() string {
181 return r.Header.Get("User-Agent")181 return r.Header.Get("User-Agent")
182}182}
183183
184// Cookies parses and returns the HTTP cookies sent with the request.184// Cookies parses and returns the HTTP cookies sent with the request.
185func (r *Request) Cookies() []*Cookie {185func (r *Request) Cookies() []*Cookie {
186 return readCookies(r.Header, "")186 return readCookies(r.Header, "")
187}187}
188188
189var ErrNoCookie = errors.New("http: named cookie not present")189var ErrNoCookie = errors.New("http: named cookie not present")
@@ -191,10 +191,10 @@
191// Cookie returns the named cookie provided in the request or191// Cookie returns the named cookie provided in the request or
192// ErrNoCookie if not found.192// ErrNoCookie if not found.
193func (r *Request) Cookie(name string) (*Cookie, error) {193func (r *Request) Cookie(name string) (*Cookie, error) {
194 for _, c := range readCookies(r.Header, name) {194 for _, c := range readCookies(r.Header, name) {
195 return c, nil195 return c, nil
196 }196 }
197 return nil, ErrNoCookie197 return nil, ErrNoCookie
198}198}
199199
200// AddCookie adds a cookie to the request. Per RFC 6265 section 5.4,200// AddCookie adds a cookie to the request. Per RFC 6265 section 5.4,
@@ -202,12 +202,12 @@
202// means all cookies, if any, are written into the same line,202// means all cookies, if any, are written into the same line,
203// separated by semicolon.203// separated by semicolon.
204func (r *Request) AddCookie(c *Cookie) {204func (r *Request) AddCookie(c *Cookie) {
205 s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))205 s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value))
206 if c := r.Header.Get("Cookie"); c != "" {206 if c := r.Header.Get("Cookie"); c != "" {
207 r.Header.Set("Cookie", c+"; "+s)207 r.Header.Set("Cookie", c+"; "+s)
208 } else {208 } else {
209 r.Header.Set("Cookie", s)209 r.Header.Set("Cookie", s)
210 }210 }
211}211}
212212
213// Referer returns the referring URL, if sent in the request.213// Referer returns the referring URL, if sent in the request.
@@ -219,15 +219,15 @@
219// alternate (correct English) spelling req.Referrer() but cannot219// alternate (correct English) spelling req.Referrer() but cannot
220// diagnose programs that use Header["Referrer"].220// diagnose programs that use Header["Referrer"].
221func (r *Request) Referer() string {221func (r *Request) Referer() string {
222 return r.Header.Get("Referer")222 return r.Header.Get("Referer")
223}223}
224224
225// multipartByReader is a sentinel value.225// multipartByReader is a sentinel value.
226// Its presence in Request.MultipartForm indicates that parsing of the request226// Its presence in Request.MultipartForm indicates that parsing of the request
227// body has been handed off to a MultipartReader instead of ParseMultipartFrom.227// body has been handed off to a MultipartReader instead of ParseMultipartFrom.
228var multipartByReader = &multipart.Form{228var multipartByReader = &multipart.Form{
229 Value: make(map[string][]string),229 Value: make(map[string][]string),
230 File: make(map[string][]*multipart.FileHeader),230 File: make(map[string][]*multipart.FileHeader),
231}231}
232232
233// MultipartReader returns a MIME multipart reader if this is a233// MultipartReader returns a MIME multipart reader if this is a
@@ -235,38 +235,38 @@
235// Use this function instead of ParseMultipartForm to235// Use this function instead of ParseMultipartForm to
236// process the request body as a stream.236// process the request body as a stream.
237func (r *Request) MultipartReader() (*multipart.Reader, error) {237func (r *Request) MultipartReader() (*multipart.Reader, error) {
238 if r.MultipartForm == multipartByReader {238 if r.MultipartForm == multipartByReader {
239 return nil, errors.New("http: MultipartReader called twice")239 return nil, errors.New("http: MultipartReader called twice")
240 }240 }
241 if r.MultipartForm != nil {241 if r.MultipartForm != nil {
242 return nil, errors.New("http: multipart handled by ParseMultipartForm")242 return nil, errors.New("http: multipart handled by ParseMultipartForm")
243 }243 }
244 r.MultipartForm = multipartByReader244 r.MultipartForm = multipartByReader
245 return r.multipartReader()245 return r.multipartReader()
246}246}
247247
248func (r *Request) multipartReader() (*multipart.Reader, error) {248func (r *Request) multipartReader() (*multipart.Reader, error) {
249 v := r.Header.Get("Content-Type")249 v := r.Header.Get("Content-Type")
250 if v == "" {250 if v == "" {
251 return nil, ErrNotMultipart251 return nil, ErrNotMultipart
252 }252 }
253 d, params, err := mime.ParseMediaType(v)253 d, params, err := mime.ParseMediaType(v)
254 if err != nil || d != "multipart/form-data" {254 if err != nil || d != "multipart/form-data" {
255 return nil, ErrNotMultipart255 return nil, ErrNotMultipart
256 }256 }
257 boundary, ok := params["boundary"]257 boundary, ok := params["boundary"]
258 if !ok {258 if !ok {
259 return nil, ErrMissingBoundary259 return nil, ErrMissingBoundary
260 }260 }
261 return multipart.NewReader(r.Body, boundary), nil261 return multipart.NewReader(r.Body, boundary), nil
262}262}
263263
264// Return value if nonempty, def otherwise.264// Return value if nonempty, def otherwise.
265func valueOrDefault(value, def string) string {265func valueOrDefault(value, def string) string {
266 if value != "" {266 if value != "" {
267 return value267 return value
268 }268 }
269 return def269 return def
270}270}
271271
272const defaultUserAgent = "Go http package"272const defaultUserAgent = "Go http package"
@@ -285,7 +285,7 @@
285// hasn't been set to "identity", Write adds "Transfer-Encoding:285// hasn't been set to "identity", Write adds "Transfer-Encoding:
286// chunked" to the header. Body is closed after it is sent.286// chunked" to the header. Body is closed after it is sent.
287func (r *Request) Write(w io.Writer) error {287func (r *Request) Write(w io.Writer) error {
288 return r.write(w, false, nil)288 return r.write(w, false, nil)
289}289}
290290
291// WriteProxy is like Write but writes the request in the form291// WriteProxy is like Write but writes the request in the form
@@ -295,145 +295,145 @@
295// In either case, WriteProxy also writes a Host header, using295// In either case, WriteProxy also writes a Host header, using
296// either r.Host or r.URL.Host.296// either r.Host or r.URL.Host.
297func (r *Request) WriteProxy(w io.Writer) error {297func (r *Request) WriteProxy(w io.Writer) error {
298 return r.write(w, true, nil)298 return r.write(w, true, nil)
299}299}
300300
301// extraHeaders may be nil301// extraHeaders may be nil
302func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error {302func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error {
303 host := req.Host303 host := req.Host
304 if host == "" {304 if host == "" {
305 if req.URL == nil {305 if req.URL == nil {
306 return errors.New("http: Request.Write on Request with no Host or URL set")306 return errors.New("http: Request.Write on Request with no Host or URL set")
307 }307 }
308 host = req.URL.Host308 host = req.URL.Host
309 }309 }
310310
311 ruri := req.URL.RequestURI()311 ruri := req.URL.RequestURI()
312 if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" {312 if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" {
313 ruri = req.URL.Scheme + "://" + host + ruri313 ruri = req.URL.Scheme + "://" + host + ruri
314 } else if req.Method == "CONNECT" && req.URL.Path == "" {314 } else if req.Method == "CONNECT" && req.URL.Path == "" {
315 // CONNECT requests normally give just the host and port, not a full URL.315 // CONNECT requests normally give just the host and port, not a full URL.
316 ruri = host316 ruri = host
317 }317 }
318 // TODO(bradfitz): escape at least newlines in ruri?318 // TODO(bradfitz): escape at least newlines in ruri?
319319
320 bw := bufio.NewWriter(w)320 bw := bufio.NewWriter(w)
321 fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri)321 fmt.Fprintf(bw, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri)
322322
323 // Header lines323 // Header lines
324 fmt.Fprintf(bw, "Host: %s\r\n", host)324 fmt.Fprintf(bw, "Host: %s\r\n", host)
325325
326 // Use the defaultUserAgent unless the Header contains one, which326 // Use the defaultUserAgent unless the Header contains one, which
327 // may be blank to not send the header.327 // may be blank to not send the header.
328 userAgent := defaultUserAgent328 userAgent := defaultUserAgent
329 if req.Header != nil {329 if req.Header != nil {
330 if ua := req.Header["User-Agent"]; len(ua) > 0 {330 if ua := req.Header["User-Agent"]; len(ua) > 0 {
331 userAgent = ua[0]331 userAgent = ua[0]
332 }332 }
333 }333 }
334 if userAgent != "" {334 if userAgent != "" {
335 fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent)335 fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent)
336 }336 }
337337
338 // Process Body,ContentLength,Close,Trailer338 // Process Body,ContentLength,Close,Trailer
339 tw, err := newTransferWriter(req)339 tw, err := newTransferWriter(req)
340 if err != nil {340 if err != nil {
341 return err341 return err
342 }342 }
343 err = tw.WriteHeader(bw)343 err = tw.WriteHeader(bw)
344 if err != nil {344 if err != nil {
345 return err345 return err
346 }346 }
347347
348 // TODO: split long values? (If so, should share code with Conn.Write)348 // TODO: split long values? (If so, should share code with Conn.Write)
349 err = req.Header.WriteSubset(bw, reqWriteExcludeHeader)349 err = req.Header.WriteSubset(bw, reqWriteExcludeHeader)
350 if err != nil {350 if err != nil {
351 return err351 return err
352 }352 }
353353
354 if extraHeaders != nil {354 if extraHeaders != nil {
355 err = extraHeaders.Write(bw)355 err = extraHeaders.Write(bw)
356 if err != nil {356 if err != nil {
357 return err357 return err
358 }358 }
359 }359 }
360360
361 io.WriteString(bw, "\r\n")361 io.WriteString(bw, "\r\n")
362362
363 // Write body and trailer363 // Write body and trailer
364 err = tw.WriteBody(bw)364 err = tw.WriteBody(bw)
365 if err != nil {365 if err != nil {
366 return err366 return err
367 }367 }
368368
369 return bw.Flush()369 return bw.Flush()
370}370}
371371
372// Convert decimal at s[i:len(s)] to integer,372// Convert decimal at s[i:len(s)] to integer,
373// returning value, string position where the digits stopped,373// returning value, string position where the digits stopped,
374// and whether there was a valid number (digits, not too big).374// and whether there was a valid number (digits, not too big).
375func atoi(s string, i int) (n, i1 int, ok bool) {375func atoi(s string, i int) (n, i1 int, ok bool) {
376 const Big = 1000000376 const Big = 1000000
377 if i >= len(s) || s[i] < '0' || s[i] > '9' {377 if i >= len(s) || s[i] < '0' || s[i] > '9' {
378 return 0, 0, false378 return 0, 0, false
379 }379 }
380 n = 0380 n = 0
381 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {381 for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
382 n = n*10 + int(s[i]-'0')382 n = n*10 + int(s[i]-'0')
383 if n > Big {383 if n > Big {
384 return 0, 0, false384 return 0, 0, false
385 }385 }
386 }386 }
387 return n, i, true387 return n, i, true
388}388}
389389
390// ParseHTTPVersion parses a HTTP version string.390// ParseHTTPVersion parses a HTTP version string.
391// "HTTP/1.0" returns (1, 0, true).391// "HTTP/1.0" returns (1, 0, true).
392func ParseHTTPVersion(vers string) (major, minor int, ok bool) {392func ParseHTTPVersion(vers string) (major, minor int, ok bool) {
393 if len(vers) < 5 || vers[0:5] != "HTTP/" {393 if len(vers) < 5 || vers[0:5] != "HTTP/" {
394 return 0, 0, false394 return 0, 0, false
395 }395 }
396 major, i, ok := atoi(vers, 5)396 major, i, ok := atoi(vers, 5)
397 if !ok || i >= len(vers) || vers[i] != '.' {397 if !ok || i >= len(vers) || vers[i] != '.' {
398 return 0, 0, false398 return 0, 0, false
399 }399 }
400 minor, i, ok = atoi(vers, i+1)400 minor, i, ok = atoi(vers, i+1)
401 if !ok || i != len(vers) {401 if !ok || i != len(vers) {
402 return 0, 0, false402 return 0, 0, false
403 }403 }
404 return major, minor, true404 return major, minor, true
405}405}
406406
407// NewRequest returns a new Request given a method, URL, and optional body.407// NewRequest returns a new Request given a method, URL, and optional body.
408func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {408func NewRequest(method, urlStr string, body io.Reader) (*Request, error) {
409 u, err := url.Parse(urlStr)
410 if err != nil {
411 return nil, err
412 }
413 rc, ok := body.(io.ReadCloser)
414 if !ok && body != nil {
415 rc = ioutil.NopCloser(body)
416 }
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: