Merge lp:~wallyworld/gwacl/ensure-all-roles-have-costs into lp:gwacl
- ensure-all-roles-have-costs
- Merge into trunk
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 |
Related bugs: |
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 : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2013-08-29 17:07:28 +0000 | |||
3 | +++ Makefile 2014-12-02 00:40:19 +0000 | |||
4 | @@ -18,10 +18,13 @@ | |||
5 | 18 | clean: | 18 | clean: |
6 | 19 | $(RM) $(example_binaries) | 19 | $(RM) $(example_binaries) |
7 | 20 | 20 | ||
10 | 21 | # Reformat the source files to match our layout standards. | 21 | # Reformat source files. |
9 | 22 | # This includes gofmt's "simplify" option to streamline the source code. | ||
11 | 23 | format: | 22 | format: |
13 | 24 | ./utilities/format -s | 23 | gofmt -w -l . |
14 | 24 | |||
15 | 25 | # Reformat and simplify source files. | ||
16 | 26 | simplify: | ||
17 | 27 | gofmt -w -l -s . | ||
18 | 25 | 28 | ||
19 | 26 | # Build the examples (we have no tests for them). | 29 | # Build the examples (we have no tests for them). |
20 | 27 | examples: $(example_binaries) | 30 | examples: $(example_binaries) |
21 | 28 | 31 | ||
22 | === modified file 'dedent/dedent.go' | |||
23 | --- dedent/dedent.go 2013-03-12 17:05:36 +0000 | |||
24 | +++ dedent/dedent.go 2014-12-02 00:40:19 +0000 | |||
25 | @@ -4,8 +4,8 @@ | |||
26 | 4 | package dedent | 4 | package dedent |
27 | 5 | 5 | ||
28 | 6 | import ( | 6 | import ( |
31 | 7 | "regexp" | 7 | "regexp" |
32 | 8 | "strings" | 8 | "strings" |
33 | 9 | ) | 9 | ) |
34 | 10 | 10 | ||
35 | 11 | const emptyString = "" | 11 | const emptyString = "" |
36 | @@ -14,7 +14,7 @@ | |||
37 | 14 | 14 | ||
38 | 15 | // Split the given text into lines. | 15 | // Split the given text into lines. |
39 | 16 | func splitLines(text string) []string { | 16 | func splitLines(text string) []string { |
41 | 17 | return reLine.FindAllString(text, -1) | 17 | return reLine.FindAllString(text, -1) |
42 | 18 | } | 18 | } |
43 | 19 | 19 | ||
44 | 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: |
45 | @@ -23,46 +23,46 @@ | |||
46 | 23 | 23 | ||
47 | 24 | // Find the longest leading margin common between the given lines. | 24 | // Find the longest leading margin common between the given lines. |
48 | 25 | func calculateMargin(lines []string) string { | 25 | func calculateMargin(lines []string) string { |
75 | 26 | var margin string | 26 | var margin string |
76 | 27 | var first bool = true | 27 | var first bool = true |
77 | 28 | for _, line := range lines { | 28 | for _, line := range lines { |
78 | 29 | indent := reLeadingWhitespace.FindString(line) | 29 | indent := reLeadingWhitespace.FindString(line) |
79 | 30 | switch { | 30 | switch { |
80 | 31 | case len(indent) == len(line): | 31 | case len(indent) == len(line): |
81 | 32 | // The line is either empty or whitespace and will be ignored for | 32 | // The line is either empty or whitespace and will be ignored for |
82 | 33 | // the purposes of calculating the margin. | 33 | // the purposes of calculating the margin. |
83 | 34 | case first: | 34 | case first: |
84 | 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. |
85 | 36 | margin = indent | 36 | margin = indent |
86 | 37 | first = false | 37 | first = false |
87 | 38 | case strings.HasPrefix(indent, margin): | 38 | case strings.HasPrefix(indent, margin): |
88 | 39 | // This line's indent is longer or equal to the margin. The | 39 | // This line's indent is longer or equal to the margin. The |
89 | 40 | // current margin remains unalterered. | 40 | // current margin remains unalterered. |
90 | 41 | case strings.HasPrefix(margin, indent): | 41 | case strings.HasPrefix(margin, indent): |
91 | 42 | // This line's indent is compatible with the margin but shorter | 42 | // This line's indent is compatible with the margin but shorter |
92 | 43 | // (strictly it could be equal, however that condition is handled | 43 | // (strictly it could be equal, however that condition is handled |
93 | 44 | // earlier in this switch). The current indent becomes the margin. | 44 | // earlier in this switch). The current indent becomes the margin. |
94 | 45 | margin = indent | 45 | margin = indent |
95 | 46 | default: | 46 | default: |
96 | 47 | // There is no common margin so stop scanning. | 47 | // There is no common margin so stop scanning. |
97 | 48 | return emptyString | 48 | return emptyString |
98 | 49 | } | 49 | } |
99 | 50 | } | 50 | } |
100 | 51 | return margin | 51 | return margin |
101 | 52 | } | 52 | } |
102 | 53 | 53 | ||
103 | 54 | // Remove a prefix from each line, if present. | 54 | // Remove a prefix from each line, if present. |
104 | 55 | func trimPrefix(lines []string, prefix string) { | 55 | func trimPrefix(lines []string, prefix string) { |
111 | 56 | trim := len(prefix) | 56 | trim := len(prefix) |
112 | 57 | for i, line := range lines { | 57 | for i, line := range lines { |
113 | 58 | if strings.HasPrefix(line, prefix) { | 58 | if strings.HasPrefix(line, prefix) { |
114 | 59 | lines[i] = line[trim:] | 59 | lines[i] = line[trim:] |
115 | 60 | } | 60 | } |
116 | 61 | } | 61 | } |
117 | 62 | } | 62 | } |
118 | 63 | 63 | ||
119 | 64 | func Dedent(text string) string { | 64 | func Dedent(text string) string { |
123 | 65 | lines := splitLines(text) | 65 | lines := splitLines(text) |
124 | 66 | trimPrefix(lines, calculateMargin(lines)) | 66 | trimPrefix(lines, calculateMargin(lines)) |
125 | 67 | return strings.Join(lines, "\n") | 67 | return strings.Join(lines, "\n") |
126 | 68 | } | 68 | } |
127 | 69 | 69 | ||
128 | === modified file 'dedent/dedent_test.go' | |||
129 | --- dedent/dedent_test.go 2013-03-26 06:57:55 +0000 | |||
130 | +++ dedent/dedent_test.go 2014-12-02 00:40:19 +0000 | |||
131 | @@ -4,8 +4,8 @@ | |||
132 | 4 | package dedent | 4 | package dedent |
133 | 5 | 5 | ||
134 | 6 | import ( | 6 | import ( |
137 | 7 | . "launchpad.net/gocheck" | 7 | . "launchpad.net/gocheck" |
138 | 8 | "testing" | 8 | "testing" |
139 | 9 | ) | 9 | ) |
140 | 10 | 10 | ||
141 | 11 | type dedentSuite struct{} | 11 | type dedentSuite struct{} |
142 | @@ -14,81 +14,81 @@ | |||
143 | 14 | 14 | ||
144 | 15 | // Dedent() does nothing with the empty string. | 15 | // Dedent() does nothing with the empty string. |
145 | 16 | func (suite *dedentSuite) TestEmptyString(c *C) { | 16 | func (suite *dedentSuite) TestEmptyString(c *C) { |
150 | 17 | input := "" | 17 | input := "" |
151 | 18 | expected := input | 18 | expected := input |
152 | 19 | observed := Dedent(input) | 19 | observed := Dedent(input) |
153 | 20 | c.Check(observed, Equals, expected) | 20 | c.Check(observed, Equals, expected) |
154 | 21 | } | 21 | } |
155 | 22 | 22 | ||
156 | 23 | // Dedent() does nothing to a single line without an indent. | 23 | // Dedent() does nothing to a single line without an indent. |
157 | 24 | func (suite *dedentSuite) TestSingleLine(c *C) { | 24 | func (suite *dedentSuite) TestSingleLine(c *C) { |
162 | 25 | input := "This is a single line." | 25 | input := "This is a single line." |
163 | 26 | expected := input | 26 | expected := input |
164 | 27 | observed := Dedent(input) | 27 | observed := Dedent(input) |
165 | 28 | c.Check(observed, Equals, expected) | 28 | c.Check(observed, Equals, expected) |
166 | 29 | } | 29 | } |
167 | 30 | 30 | ||
168 | 31 | // Dedent() removes all leading whitespace from single lines. | 31 | // Dedent() removes all leading whitespace from single lines. |
169 | 32 | func (suite *dedentSuite) TestSingleLineWithIndent(c *C) { | 32 | func (suite *dedentSuite) TestSingleLineWithIndent(c *C) { |
174 | 33 | input := " This is a single line." | 33 | input := " This is a single line." |
175 | 34 | expected := "This is a single line." | 34 | expected := "This is a single line." |
176 | 35 | observed := Dedent(input) | 35 | observed := Dedent(input) |
177 | 36 | c.Check(observed, Equals, expected) | 36 | c.Check(observed, Equals, expected) |
178 | 37 | } | 37 | } |
179 | 38 | 38 | ||
180 | 39 | // Dedent() does nothing when none of the lines are indented. | 39 | // Dedent() does nothing when none of the lines are indented. |
181 | 40 | func (suite *dedentSuite) TestLines(c *C) { | 40 | func (suite *dedentSuite) TestLines(c *C) { |
186 | 41 | input := "One\nTwo\n" | 41 | input := "One\nTwo\n" |
187 | 42 | expected := input | 42 | expected := input |
188 | 43 | observed := Dedent(input) | 43 | observed := Dedent(input) |
189 | 44 | c.Check(observed, Equals, expected) | 44 | c.Check(observed, Equals, expected) |
190 | 45 | } | 45 | } |
191 | 46 | 46 | ||
192 | 47 | // Dedent() does nothing when *any* line is not indented. | 47 | // Dedent() does nothing when *any* line is not indented. |
193 | 48 | func (suite *dedentSuite) TestLinesWithSomeIndents(c *C) { | 48 | func (suite *dedentSuite) TestLinesWithSomeIndents(c *C) { |
198 | 49 | input := "One\n Two\n" | 49 | input := "One\n Two\n" |
199 | 50 | expected := input | 50 | expected := input |
200 | 51 | observed := Dedent(input) | 51 | observed := Dedent(input) |
201 | 52 | c.Check(observed, Equals, expected) | 52 | c.Check(observed, Equals, expected) |
202 | 53 | } | 53 | } |
203 | 54 | 54 | ||
204 | 55 | // Dedent() removes the common leading indent from each line. | 55 | // Dedent() removes the common leading indent from each line. |
205 | 56 | func (suite *dedentSuite) TestLinesWithIndents(c *C) { | 56 | func (suite *dedentSuite) TestLinesWithIndents(c *C) { |
210 | 57 | input := " One\n Two\n" | 57 | input := " One\n Two\n" |
211 | 58 | expected := "One\n Two\n" | 58 | expected := "One\n Two\n" |
212 | 59 | observed := Dedent(input) | 59 | observed := Dedent(input) |
213 | 60 | c.Check(observed, Equals, expected) | 60 | c.Check(observed, Equals, expected) |
214 | 61 | } | 61 | } |
215 | 62 | 62 | ||
216 | 63 | // Dedent() ignores all-whitespace lines for the purposes of margin | 63 | // Dedent() ignores all-whitespace lines for the purposes of margin |
217 | 64 | // calculation. However, the margin *is* trimmed from these lines, if they | 64 | // calculation. However, the margin *is* trimmed from these lines, if they |
218 | 65 | // begin with it. | 65 | // begin with it. |
219 | 66 | func (suite *dedentSuite) TestLinesWithEmptyLine(c *C) { | 66 | func (suite *dedentSuite) TestLinesWithEmptyLine(c *C) { |
224 | 67 | input := " One\n \n Three\n" | 67 | input := " One\n \n Three\n" |
225 | 68 | expected := "One\n \nThree\n" | 68 | expected := "One\n \nThree\n" |
226 | 69 | observed := Dedent(input) | 69 | observed := Dedent(input) |
227 | 70 | c.Check(observed, Equals, expected) | 70 | c.Check(observed, Equals, expected) |
228 | 71 | } | 71 | } |
229 | 72 | 72 | ||
230 | 73 | // Dedent() ignores blank lines for the purposes of margin calculation, | 73 | // Dedent() ignores blank lines for the purposes of margin calculation, |
231 | 74 | // including the first line. | 74 | // including the first line. |
232 | 75 | func (suite *dedentSuite) TestLinesWithEmptyFirstLine(c *C) { | 75 | func (suite *dedentSuite) TestLinesWithEmptyFirstLine(c *C) { |
237 | 76 | input := "\n Two\n Three\n" | 76 | input := "\n Two\n Three\n" |
238 | 77 | expected := "\nTwo\nThree\n" | 77 | expected := "\nTwo\nThree\n" |
239 | 78 | observed := Dedent(input) | 78 | observed := Dedent(input) |
240 | 79 | c.Check(observed, Equals, expected) | 79 | c.Check(observed, Equals, expected) |
241 | 80 | } | 80 | } |
242 | 81 | 81 | ||
243 | 82 | // Dedent() treats spaces and tabs as completely different; no number of | 82 | // Dedent() treats spaces and tabs as completely different; no number of |
244 | 83 | // spaces is equivalent to a tab. | 83 | // spaces is equivalent to a tab. |
245 | 84 | func (suite *dedentSuite) TestLinesWithTabsAndSpaces(c *C) { | 84 | func (suite *dedentSuite) TestLinesWithTabsAndSpaces(c *C) { |
250 | 85 | input := "\tOne\n Two\n" | 85 | input := "\tOne\n Two\n" |
251 | 86 | expected := input | 86 | expected := input |
252 | 87 | observed := Dedent(input) | 87 | observed := Dedent(input) |
253 | 88 | c.Check(observed, Equals, expected) | 88 | c.Check(observed, Equals, expected) |
254 | 89 | } | 89 | } |
255 | 90 | 90 | ||
256 | 91 | // Master loader for all tests. | 91 | // Master loader for all tests. |
257 | 92 | func Test(t *testing.T) { | 92 | func Test(t *testing.T) { |
259 | 93 | TestingT(t) | 93 | TestingT(t) |
260 | 94 | } | 94 | } |
261 | 95 | 95 | ||
262 | === modified file 'deletedisk.go' | |||
263 | --- deletedisk.go 2013-07-16 05:48:26 +0000 | |||
264 | +++ deletedisk.go 2014-12-02 00:40:19 +0000 | |||
265 | @@ -18,24 +18,24 @@ | |||
266 | 18 | package gwacl | 18 | package gwacl |
267 | 19 | 19 | ||
268 | 20 | import ( | 20 | import ( |
272 | 21 | "fmt" | 21 | "fmt" |
273 | 22 | "regexp" | 22 | "regexp" |
274 | 23 | "time" | 23 | "time" |
275 | 24 | ) | 24 | ) |
276 | 25 | 25 | ||
277 | 26 | var deleteDiskTimeout = 30 * time.Minute | 26 | var deleteDiskTimeout = 30 * time.Minute |
278 | 27 | var deleteDiskInterval = 10 * time.Second | 27 | var deleteDiskInterval = 10 * time.Second |
279 | 28 | 28 | ||
280 | 29 | type diskDeletePoller struct { | 29 | type diskDeletePoller struct { |
284 | 30 | api *ManagementAPI | 30 | api *ManagementAPI |
285 | 31 | diskName string | 31 | diskName string |
286 | 32 | deleteBlob bool | 32 | deleteBlob bool |
287 | 33 | } | 33 | } |
288 | 34 | 34 | ||
289 | 35 | var _ poller = &diskDeletePoller{} | 35 | var _ poller = &diskDeletePoller{} |
290 | 36 | 36 | ||
291 | 37 | func (poller diskDeletePoller) poll() (*x509Response, error) { | 37 | func (poller diskDeletePoller) poll() (*x509Response, error) { |
293 | 38 | return nil, poller.api._DeleteDisk(poller.diskName, poller.deleteBlob) | 38 | return nil, poller.api._DeleteDisk(poller.diskName, poller.deleteBlob) |
294 | 39 | } | 39 | } |
295 | 40 | 40 | ||
296 | 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" |
297 | @@ -46,20 +46,20 @@ | |||
298 | 46 | // gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http | 46 | // gwacl623yosxtppsa9577xy5, deployment gwaclmachinewes4n64f. (http |
299 | 47 | // code 400: Bad Request)" | 47 | // code 400: Bad Request)" |
300 | 48 | func isInUseError(errString string, diskName string) bool { | 48 | func isInUseError(errString string, diskName string) bool { |
304 | 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)) |
305 | 50 | reg := regexp.MustCompile(pattern) | 50 | reg := regexp.MustCompile(pattern) |
306 | 51 | return reg.MatchString(errString) | 51 | return reg.MatchString(errString) |
307 | 52 | } | 52 | } |
308 | 53 | 53 | ||
309 | 54 | func (poller diskDeletePoller) isDone(response *x509Response, pollerErr error) (bool, error) { | 54 | func (poller diskDeletePoller) isDone(response *x509Response, pollerErr error) (bool, error) { |
320 | 55 | if pollerErr == nil { | 55 | if pollerErr == nil { |
321 | 56 | return true, nil | 56 | return true, nil |
322 | 57 | } | 57 | } |
323 | 58 | if isInUseError(pollerErr.Error(), poller.diskName) { | 58 | if isInUseError(pollerErr.Error(), poller.diskName) { |
324 | 59 | // The error is of the "disk in use" type: continue polling. | 59 | // The error is of the "disk in use" type: continue polling. |
325 | 60 | return false, nil | 60 | return false, nil |
326 | 61 | } | 61 | } |
327 | 62 | // The error is *not* of the "disk in use" type: stop polling and return | 62 | // The error is *not* of the "disk in use" type: stop polling and return |
328 | 63 | // the error. | 63 | // the error. |
329 | 64 | return true, pollerErr | 64 | return true, pollerErr |
330 | 65 | } | 65 | } |
331 | 66 | 66 | ||
332 | === modified file 'deletedisk_test.go' | |||
333 | --- deletedisk_test.go 2013-07-16 05:48:59 +0000 | |||
334 | +++ deletedisk_test.go 2014-12-02 00:40:19 +0000 | |||
335 | @@ -4,10 +4,10 @@ | |||
336 | 4 | package gwacl | 4 | package gwacl |
337 | 5 | 5 | ||
338 | 6 | import ( | 6 | import ( |
343 | 7 | "fmt" | 7 | "fmt" |
344 | 8 | . "launchpad.net/gocheck" | 8 | . "launchpad.net/gocheck" |
345 | 9 | "net/http" | 9 | "net/http" |
346 | 10 | "time" | 10 | "time" |
347 | 11 | ) | 11 | ) |
348 | 12 | 12 | ||
349 | 13 | type deleteDiskSuite struct{} | 13 | type deleteDiskSuite struct{} |
350 | @@ -16,88 +16,88 @@ | |||
351 | 16 | 16 | ||
352 | 17 | // Real-world error messages and names. | 17 | // Real-world error messages and names. |
353 | 18 | const ( | 18 | const ( |
357 | 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)" |
358 | 20 | diskName = "gwacldiske5w7lkj" | 20 | diskName = "gwacldiske5w7lkj" |
359 | 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)" |
360 | 22 | ) | 22 | ) |
361 | 23 | 23 | ||
362 | 24 | func (suite *deleteDiskSuite) TestIsInUseError(c *C) { | 24 | func (suite *deleteDiskSuite) TestIsInUseError(c *C) { |
376 | 25 | var testValues = []struct { | 25 | var testValues = []struct { |
377 | 26 | errorString string | 26 | errorString string |
378 | 27 | diskName string | 27 | diskName string |
379 | 28 | expectedResult bool | 28 | expectedResult bool |
380 | 29 | }{ | 29 | }{ |
381 | 30 | {fmt.Sprintf(diskInUseErrorTemplate, diskName), diskName, true}, | 30 | {fmt.Sprintf(diskInUseErrorTemplate, diskName), diskName, true}, |
382 | 31 | {fmt.Sprintf(diskInUseErrorTemplate, diskName), "another-disk", false}, | 31 | {fmt.Sprintf(diskInUseErrorTemplate, diskName), "another-disk", false}, |
383 | 32 | {"unknown error", diskName, false}, | 32 | {"unknown error", diskName, false}, |
384 | 33 | {diskDoesNotExistError, diskName, false}, | 33 | {diskDoesNotExistError, diskName, false}, |
385 | 34 | } | 34 | } |
386 | 35 | for _, test := range testValues { | 35 | for _, test := range testValues { |
387 | 36 | c.Check(isInUseError(test.errorString, test.diskName), Equals, test.expectedResult) | 36 | c.Check(isInUseError(test.errorString, test.diskName), Equals, test.expectedResult) |
388 | 37 | } | 37 | } |
389 | 38 | } | 38 | } |
390 | 39 | 39 | ||
391 | 40 | func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfNilError(c *C) { | 40 | func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfNilError(c *C) { |
397 | 41 | poller := diskDeletePoller{nil, "", false} | 41 | poller := diskDeletePoller{nil, "", false} |
398 | 42 | randomResponse := x509Response{StatusCode: http.StatusAccepted} | 42 | randomResponse := x509Response{StatusCode: http.StatusAccepted} |
399 | 43 | done, err := poller.isDone(&randomResponse, nil) | 43 | done, err := poller.isDone(&randomResponse, nil) |
400 | 44 | c.Check(done, Equals, true) | 44 | c.Check(done, Equals, true) |
401 | 45 | c.Check(err, IsNil) | 45 | c.Check(err, IsNil) |
402 | 46 | } | 46 | } |
403 | 47 | 47 | ||
404 | 48 | func (suite *deleteDiskSuite) TestIsDoneReturnsFalseIfDiskInUseError(c *C) { | 48 | func (suite *deleteDiskSuite) TestIsDoneReturnsFalseIfDiskInUseError(c *C) { |
411 | 49 | diskName := "gwacldiske5w7lkj" | 49 | diskName := "gwacldiske5w7lkj" |
412 | 50 | diskInUseError := fmt.Errorf(diskInUseErrorTemplate, diskName) | 50 | diskInUseError := fmt.Errorf(diskInUseErrorTemplate, diskName) |
413 | 51 | poller := diskDeletePoller{nil, diskName, false} | 51 | poller := diskDeletePoller{nil, diskName, false} |
414 | 52 | done, err := poller.isDone(nil, diskInUseError) | 52 | done, err := poller.isDone(nil, diskInUseError) |
415 | 53 | c.Check(done, Equals, false) | 53 | c.Check(done, Equals, false) |
416 | 54 | c.Check(err, IsNil) | 54 | c.Check(err, IsNil) |
417 | 55 | } | 55 | } |
418 | 56 | 56 | ||
419 | 57 | func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfAnotherError(c *C) { | 57 | func (suite *deleteDiskSuite) TestIsDoneReturnsTrueIfAnotherError(c *C) { |
425 | 58 | anotherError := fmt.Errorf("Unknown error") | 58 | anotherError := fmt.Errorf("Unknown error") |
426 | 59 | poller := diskDeletePoller{nil, "disk-name", false} | 59 | poller := diskDeletePoller{nil, "disk-name", false} |
427 | 60 | done, err := poller.isDone(nil, anotherError) | 60 | done, err := poller.isDone(nil, anotherError) |
428 | 61 | c.Check(done, Equals, true) | 61 | c.Check(done, Equals, true) |
429 | 62 | c.Check(err, Equals, anotherError) | 62 | c.Check(err, Equals, anotherError) |
430 | 63 | } | 63 | } |
431 | 64 | 64 | ||
432 | 65 | func (suite *deleteDiskSuite) TestPollCallsDeleteDisk(c *C) { | 65 | func (suite *deleteDiskSuite) TestPollCallsDeleteDisk(c *C) { |
444 | 66 | api := makeAPI(c) | 66 | api := makeAPI(c) |
445 | 67 | recordedRequests := setUpDispatcher("operationID") | 67 | recordedRequests := setUpDispatcher("operationID") |
446 | 68 | diskName := "gwacldiske5w7lkj" | 68 | diskName := "gwacldiske5w7lkj" |
447 | 69 | poller := diskDeletePoller{api, diskName, false} | 69 | poller := diskDeletePoller{api, diskName, false} |
448 | 70 | 70 | ||
449 | 71 | response, err := poller.poll() | 71 | response, err := poller.poll() |
450 | 72 | 72 | ||
451 | 73 | c.Assert(response, IsNil) | 73 | c.Assert(response, IsNil) |
452 | 74 | c.Assert(err, IsNil) | 74 | c.Assert(err, IsNil) |
453 | 75 | expectedURL := api.session.composeURL("services/disks/" + diskName) | 75 | expectedURL := api.session.composeURL("services/disks/" + diskName) |
454 | 76 | checkOneRequest(c, recordedRequests, expectedURL, "2012-08-01", nil, "DELETE") | 76 | checkOneRequest(c, recordedRequests, expectedURL, "2012-08-01", nil, "DELETE") |
455 | 77 | } | 77 | } |
456 | 78 | 78 | ||
457 | 79 | func (suite *deleteDiskSuite) TestManagementAPIDeleteDiskPolls(c *C) { | 79 | func (suite *deleteDiskSuite) TestManagementAPIDeleteDiskPolls(c *C) { |
481 | 80 | firstResponse := DispatcherResponse{ | 80 | firstResponse := DispatcherResponse{ |
482 | 81 | response: &x509Response{}, | 81 | response: &x509Response{}, |
483 | 82 | errorObject: fmt.Errorf(diskInUseErrorTemplate, diskName)} | 82 | errorObject: fmt.Errorf(diskInUseErrorTemplate, diskName)} |
484 | 83 | secondResponse := DispatcherResponse{ | 83 | secondResponse := DispatcherResponse{ |
485 | 84 | response: &x509Response{StatusCode: http.StatusOK}, | 84 | response: &x509Response{StatusCode: http.StatusOK}, |
486 | 85 | errorObject: nil} | 85 | errorObject: nil} |
487 | 86 | responses := []DispatcherResponse{firstResponse, secondResponse} | 86 | responses := []DispatcherResponse{firstResponse, secondResponse} |
488 | 87 | rigPreparedResponseDispatcher(responses) | 87 | rigPreparedResponseDispatcher(responses) |
489 | 88 | recordedRequests := make([]*X509Request, 0) | 88 | recordedRequests := make([]*X509Request, 0) |
490 | 89 | rigRecordingDispatcher(&recordedRequests) | 89 | rigRecordingDispatcher(&recordedRequests) |
491 | 90 | 90 | ||
492 | 91 | api := makeAPI(c) | 91 | api := makeAPI(c) |
493 | 92 | diskName := "gwacldiske5w7lkj" | 92 | diskName := "gwacldiske5w7lkj" |
494 | 93 | poller := diskDeletePoller{api, diskName, false} | 93 | poller := diskDeletePoller{api, diskName, false} |
495 | 94 | 94 | ||
496 | 95 | response, err := performPolling(poller, time.Nanosecond, time.Minute) | 95 | response, err := performPolling(poller, time.Nanosecond, time.Minute) |
497 | 96 | 96 | ||
498 | 97 | c.Assert(response, IsNil) | 97 | c.Assert(response, IsNil) |
499 | 98 | c.Assert(err, IsNil) | 98 | c.Assert(err, IsNil) |
500 | 99 | expectedURL := api.session.composeURL("services/disks/" + diskName) | 99 | expectedURL := api.session.composeURL("services/disks/" + diskName) |
501 | 100 | c.Check(len(recordedRequests), Equals, 2) | 100 | c.Check(len(recordedRequests), Equals, 2) |
502 | 101 | checkRequest(c, recordedRequests[0], expectedURL, "2012-08-01", nil, "DELETE") | 101 | checkRequest(c, recordedRequests[0], expectedURL, "2012-08-01", nil, "DELETE") |
503 | 102 | checkRequest(c, recordedRequests[1], expectedURL, "2012-08-01", nil, "DELETE") | 102 | checkRequest(c, recordedRequests[1], expectedURL, "2012-08-01", nil, "DELETE") |
504 | 103 | } | 103 | } |
505 | 104 | 104 | ||
506 | === modified file 'endpoints.go' | |||
507 | --- endpoints.go 2013-08-06 10:15:11 +0000 | |||
508 | +++ endpoints.go 2014-12-02 00:40:19 +0000 | |||
509 | @@ -4,9 +4,9 @@ | |||
510 | 4 | package gwacl | 4 | package gwacl |
511 | 5 | 5 | ||
512 | 6 | import ( | 6 | import ( |
516 | 7 | "fmt" | 7 | "fmt" |
517 | 8 | "net/url" | 8 | "net/url" |
518 | 9 | "strings" | 9 | "strings" |
519 | 10 | ) | 10 | ) |
520 | 11 | 11 | ||
521 | 12 | // APIEndpoint describes the base URL for accesing Windows Azure's APIs. | 12 | // APIEndpoint describes the base URL for accesing Windows Azure's APIs. |
522 | @@ -20,13 +20,13 @@ | |||
523 | 20 | // GetEndpoint returns the API endpoint for the given location. This is | 20 | // GetEndpoint returns the API endpoint for the given location. This is |
524 | 21 | // hard-coded, so some guesswork may be involved. | 21 | // hard-coded, so some guesswork may be involved. |
525 | 22 | func GetEndpoint(location string) APIEndpoint { | 22 | func GetEndpoint(location string) APIEndpoint { |
530 | 23 | if strings.Contains(location, "China") { | 23 | if strings.Contains(location, "China") { |
531 | 24 | // Mainland China is a special case. It has its own endpoint. | 24 | // Mainland China is a special case. It has its own endpoint. |
532 | 25 | return "https://core.chinacloudapi.cn/" | 25 | return "https://core.chinacloudapi.cn/" |
533 | 26 | } | 26 | } |
534 | 27 | 27 | ||
537 | 28 | // The rest of the world shares a single endpoint. | 28 | // The rest of the world shares a single endpoint. |
538 | 29 | return "https://core.windows.net/" | 29 | return "https://core.windows.net/" |
539 | 30 | } | 30 | } |
540 | 31 | 31 | ||
541 | 32 | // prefixHost prefixes the hostname part of a URL with a subdomain. For | 32 | // prefixHost prefixes the hostname part of a URL with a subdomain. For |
542 | @@ -35,26 +35,26 @@ | |||
543 | 35 | // | 35 | // |
544 | 36 | // The URL must be well-formed, and contain a hostname. | 36 | // The URL must be well-formed, and contain a hostname. |
545 | 37 | func prefixHost(host, originalURL string) string { | 37 | func prefixHost(host, originalURL string) string { |
557 | 38 | parsedURL, err := url.Parse(originalURL) | 38 | parsedURL, err := url.Parse(originalURL) |
558 | 39 | if err != nil { | 39 | if err != nil { |
559 | 40 | panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err)) | 40 | panic(fmt.Errorf("failed to parse URL %s - %v", originalURL, err)) |
560 | 41 | } | 41 | } |
561 | 42 | if parsedURL.Host == "" { | 42 | if parsedURL.Host == "" { |
562 | 43 | panic(fmt.Errorf("no hostname in URL '%s'", originalURL)) | 43 | panic(fmt.Errorf("no hostname in URL '%s'", originalURL)) |
563 | 44 | } | 44 | } |
564 | 45 | // Escape manually. Strangely, turning a url.URL into a string does not | 45 | // Escape manually. Strangely, turning a url.URL into a string does not |
565 | 46 | // do this for you. | 46 | // do this for you. |
566 | 47 | parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host | 47 | parsedURL.Host = url.QueryEscape(host) + "." + parsedURL.Host |
567 | 48 | return parsedURL.String() | 48 | return parsedURL.String() |
568 | 49 | } | 49 | } |
569 | 50 | 50 | ||
570 | 51 | // ManagementAPI returns the URL for the endpoint's management API. | 51 | // ManagementAPI returns the URL for the endpoint's management API. |
571 | 52 | func (endpoint APIEndpoint) ManagementAPI() string { | 52 | func (endpoint APIEndpoint) ManagementAPI() string { |
573 | 53 | return prefixHost("management", string(endpoint)) | 53 | return prefixHost("management", string(endpoint)) |
574 | 54 | } | 54 | } |
575 | 55 | 55 | ||
576 | 56 | // BlobStorageAPI returns a URL for the endpoint's blob storage API, for | 56 | // BlobStorageAPI returns a URL for the endpoint's blob storage API, for |
577 | 57 | // requests on the given account. | 57 | // requests on the given account. |
578 | 58 | func (endpoint APIEndpoint) BlobStorageAPI(account string) string { | 58 | func (endpoint APIEndpoint) BlobStorageAPI(account string) string { |
580 | 59 | return prefixHost(account, prefixHost("blob", string(endpoint))) | 59 | return prefixHost(account, prefixHost("blob", string(endpoint))) |
581 | 60 | } | 60 | } |
582 | 61 | 61 | ||
583 | === modified file 'endpoints_test.go' | |||
584 | --- endpoints_test.go 2013-08-06 10:12:51 +0000 | |||
585 | +++ endpoints_test.go 2014-12-02 00:40:19 +0000 | |||
586 | @@ -4,9 +4,9 @@ | |||
587 | 4 | package gwacl | 4 | package gwacl |
588 | 5 | 5 | ||
589 | 6 | import ( | 6 | import ( |
593 | 7 | "fmt" | 7 | "fmt" |
594 | 8 | . "launchpad.net/gocheck" | 8 | . "launchpad.net/gocheck" |
595 | 9 | "net/url" | 9 | "net/url" |
596 | 10 | ) | 10 | ) |
597 | 11 | 11 | ||
598 | 12 | type endpointsSuite struct{} | 12 | type endpointsSuite struct{} |
599 | @@ -14,109 +14,109 @@ | |||
600 | 14 | var _ = Suite(&endpointsSuite{}) | 14 | var _ = Suite(&endpointsSuite{}) |
601 | 15 | 15 | ||
602 | 16 | func (*endpointsSuite) TestGetEndpointReturnsEndpointsForKnownRegions(c *C) { | 16 | func (*endpointsSuite) TestGetEndpointReturnsEndpointsForKnownRegions(c *C) { |
630 | 17 | internationalLocations := []string{ | 17 | internationalLocations := []string{ |
631 | 18 | "West Europe", | 18 | "West Europe", |
632 | 19 | "East Asia", | 19 | "East Asia", |
633 | 20 | "East US 2", | 20 | "East US 2", |
634 | 21 | "Southeast Asia", | 21 | "Southeast Asia", |
635 | 22 | "East US", | 22 | "East US", |
636 | 23 | "Central US", | 23 | "Central US", |
637 | 24 | "West US", | 24 | "West US", |
638 | 25 | "North Europe", | 25 | "North Europe", |
639 | 26 | } | 26 | } |
640 | 27 | internationalEndpoint := APIEndpoint("https://core.windows.net/") | 27 | internationalEndpoint := APIEndpoint("https://core.windows.net/") |
641 | 28 | 28 | ||
642 | 29 | for _, location := range internationalLocations { | 29 | for _, location := range internationalLocations { |
643 | 30 | c.Check(GetEndpoint(location), Equals, internationalEndpoint) | 30 | c.Check(GetEndpoint(location), Equals, internationalEndpoint) |
644 | 31 | } | 31 | } |
645 | 32 | 32 | ||
646 | 33 | // The mainland-China locations have a different endpoint. | 33 | // The mainland-China locations have a different endpoint. |
647 | 34 | // (Actually the East Asia data centre is said to be in Hong Kong, but it | 34 | // (Actually the East Asia data centre is said to be in Hong Kong, but it |
648 | 35 | // acts as international). | 35 | // acts as international). |
649 | 36 | mainlandChinaLocations := []string{ | 36 | mainlandChinaLocations := []string{ |
650 | 37 | "China East", | 37 | "China East", |
651 | 38 | "China North", | 38 | "China North", |
652 | 39 | } | 39 | } |
653 | 40 | mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/") | 40 | mainlandChinaEndpoint := APIEndpoint("https://core.chinacloudapi.cn/") |
654 | 41 | for _, location := range mainlandChinaLocations { | 41 | for _, location := range mainlandChinaLocations { |
655 | 42 | c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint) | 42 | c.Check(GetEndpoint(location), Equals, mainlandChinaEndpoint) |
656 | 43 | } | 43 | } |
657 | 44 | } | 44 | } |
658 | 45 | 45 | ||
659 | 46 | func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) { | 46 | func (*endpointsSuite) TestGetEndpointMakesGoodGuessesForUknownRegions(c *C) { |
668 | 47 | c.Check( | 47 | c.Check( |
669 | 48 | GetEndpoint("South San Marino Highlands"), | 48 | GetEndpoint("South San Marino Highlands"), |
670 | 49 | Equals, | 49 | Equals, |
671 | 50 | GetEndpoint("West US")) | 50 | GetEndpoint("West US")) |
672 | 51 | c.Check( | 51 | c.Check( |
673 | 52 | GetEndpoint("Central China West"), | 52 | GetEndpoint("Central China West"), |
674 | 53 | Equals, | 53 | Equals, |
675 | 54 | GetEndpoint("China East")) | 54 | GetEndpoint("China East")) |
676 | 55 | } | 55 | } |
677 | 56 | 56 | ||
678 | 57 | func (*endpointsSuite) TestPrefixHostPrefixesSubdomain(c *C) { | 57 | func (*endpointsSuite) TestPrefixHostPrefixesSubdomain(c *C) { |
683 | 58 | c.Check( | 58 | c.Check( |
684 | 59 | prefixHost("foo", "http://example.com"), | 59 | prefixHost("foo", "http://example.com"), |
685 | 60 | Equals, | 60 | Equals, |
686 | 61 | "http://foo.example.com") | 61 | "http://foo.example.com") |
687 | 62 | } | 62 | } |
688 | 63 | 63 | ||
689 | 64 | func (*endpointsSuite) TestPrefixHostPreservesOtherURLComponents(c *C) { | 64 | func (*endpointsSuite) TestPrefixHostPreservesOtherURLComponents(c *C) { |
710 | 65 | c.Check( | 65 | c.Check( |
711 | 66 | prefixHost("foo", "http://example.com/"), | 66 | prefixHost("foo", "http://example.com/"), |
712 | 67 | Equals, | 67 | Equals, |
713 | 68 | "http://foo.example.com/") | 68 | "http://foo.example.com/") |
714 | 69 | c.Check( | 69 | c.Check( |
715 | 70 | prefixHost("foo", "nntp://example.com"), | 70 | prefixHost("foo", "nntp://example.com"), |
716 | 71 | Equals, | 71 | Equals, |
717 | 72 | "nntp://foo.example.com") | 72 | "nntp://foo.example.com") |
718 | 73 | c.Check( | 73 | c.Check( |
719 | 74 | prefixHost("foo", "http://user@example.com"), | 74 | prefixHost("foo", "http://user@example.com"), |
720 | 75 | Equals, | 75 | Equals, |
721 | 76 | "http://user@foo.example.com") | 76 | "http://user@foo.example.com") |
722 | 77 | c.Check( | 77 | c.Check( |
723 | 78 | prefixHost("foo", "http://example.com:999"), | 78 | prefixHost("foo", "http://example.com:999"), |
724 | 79 | Equals, | 79 | Equals, |
725 | 80 | "http://foo.example.com:999") | 80 | "http://foo.example.com:999") |
726 | 81 | c.Check( | 81 | c.Check( |
727 | 82 | prefixHost("foo", "http://example.com/path"), | 82 | prefixHost("foo", "http://example.com/path"), |
728 | 83 | Equals, | 83 | Equals, |
729 | 84 | "http://foo.example.com/path") | 84 | "http://foo.example.com/path") |
730 | 85 | } | 85 | } |
731 | 86 | 86 | ||
732 | 87 | func (*endpointsSuite) TestPrefixHostEscapes(c *C) { | 87 | func (*endpointsSuite) TestPrefixHostEscapes(c *C) { |
738 | 88 | host := "5%=1/20?" | 88 | host := "5%=1/20?" |
739 | 89 | c.Check( | 89 | c.Check( |
740 | 90 | prefixHost(host, "http://example.com"), | 90 | prefixHost(host, "http://example.com"), |
741 | 91 | Equals, | 91 | Equals, |
742 | 92 | fmt.Sprintf("http://%s.example.com", url.QueryEscape(host))) | 92 | fmt.Sprintf("http://%s.example.com", url.QueryEscape(host))) |
743 | 93 | } | 93 | } |
744 | 94 | 94 | ||
745 | 95 | func (*endpointsSuite) TestManagementAPICombinesWithGetEndpoint(c *C) { | 95 | func (*endpointsSuite) TestManagementAPICombinesWithGetEndpoint(c *C) { |
754 | 96 | c.Check( | 96 | c.Check( |
755 | 97 | GetEndpoint("West US").ManagementAPI(), | 97 | GetEndpoint("West US").ManagementAPI(), |
756 | 98 | Equals, | 98 | Equals, |
757 | 99 | "https://management.core.windows.net/") | 99 | "https://management.core.windows.net/") |
758 | 100 | c.Check( | 100 | c.Check( |
759 | 101 | GetEndpoint("China East").ManagementAPI(), | 101 | GetEndpoint("China East").ManagementAPI(), |
760 | 102 | Equals, | 102 | Equals, |
761 | 103 | "https://management.core.chinacloudapi.cn/") | 103 | "https://management.core.chinacloudapi.cn/") |
762 | 104 | } | 104 | } |
763 | 105 | 105 | ||
764 | 106 | func (*endpointsSuite) TestBlobStorageAPIIncludesAccountName(c *C) { | 106 | func (*endpointsSuite) TestBlobStorageAPIIncludesAccountName(c *C) { |
769 | 107 | c.Check( | 107 | c.Check( |
770 | 108 | APIEndpoint("http://example.com").BlobStorageAPI("myaccount"), | 108 | APIEndpoint("http://example.com").BlobStorageAPI("myaccount"), |
771 | 109 | Equals, | 109 | Equals, |
772 | 110 | "http://myaccount.blob.example.com") | 110 | "http://myaccount.blob.example.com") |
773 | 111 | } | 111 | } |
774 | 112 | 112 | ||
775 | 113 | func (*endpointsSuite) TestBlobStorageAPICombinesWithGetEndpoint(c *C) { | 113 | func (*endpointsSuite) TestBlobStorageAPICombinesWithGetEndpoint(c *C) { |
784 | 114 | c.Check( | 114 | c.Check( |
785 | 115 | GetEndpoint("West US").BlobStorageAPI("account"), | 115 | GetEndpoint("West US").BlobStorageAPI("account"), |
786 | 116 | Equals, | 116 | Equals, |
787 | 117 | "https://account.blob.core.windows.net/") | 117 | "https://account.blob.core.windows.net/") |
788 | 118 | c.Check( | 118 | c.Check( |
789 | 119 | GetEndpoint("China East").BlobStorageAPI("account"), | 119 | GetEndpoint("China East").BlobStorageAPI("account"), |
790 | 120 | Equals, | 120 | Equals, |
791 | 121 | "https://account.blob.core.chinacloudapi.cn/") | 121 | "https://account.blob.core.chinacloudapi.cn/") |
792 | 122 | } | 122 | } |
793 | 123 | 123 | ||
794 | === modified file 'example/management/run.go' | |||
795 | --- example/management/run.go 2014-03-10 03:56:29 +0000 | |||
796 | +++ example/management/run.go 2014-12-02 00:40:19 +0000 | |||
797 | @@ -10,14 +10,14 @@ | |||
798 | 10 | package main | 10 | package main |
799 | 11 | 11 | ||
800 | 12 | import ( | 12 | import ( |
809 | 13 | "encoding/base64" | 13 | "encoding/base64" |
810 | 14 | "flag" | 14 | "flag" |
811 | 15 | "fmt" | 15 | "fmt" |
812 | 16 | "launchpad.net/gwacl" | 16 | "launchpad.net/gwacl" |
813 | 17 | . "launchpad.net/gwacl/logging" | 17 | . "launchpad.net/gwacl/logging" |
814 | 18 | "math/rand" | 18 | "math/rand" |
815 | 19 | "os" | 19 | "os" |
816 | 20 | "time" | 20 | "time" |
817 | 21 | ) | 21 | ) |
818 | 22 | 22 | ||
819 | 23 | var certFile string | 23 | var certFile string |
820 | @@ -26,26 +26,26 @@ | |||
821 | 26 | var location string | 26 | var location string |
822 | 27 | 27 | ||
823 | 28 | func getParams() error { | 28 | func getParams() error { |
838 | 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).") |
839 | 30 | flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.") | 30 | flag.StringVar(&subscriptionID, "subscriptionid", "", "Your Azure subscription ID.") |
840 | 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)") |
841 | 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'") |
842 | 33 | 33 | ||
843 | 34 | flag.Parse() | 34 | flag.Parse() |
844 | 35 | 35 | ||
845 | 36 | if certFile == "" { | 36 | if certFile == "" { |
846 | 37 | return fmt.Errorf("No .pem certificate specified. Use the -cert option.") | 37 | return fmt.Errorf("No .pem certificate specified. Use the -cert option.") |
847 | 38 | } | 38 | } |
848 | 39 | if subscriptionID == "" { | 39 | if subscriptionID == "" { |
849 | 40 | return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.") | 40 | return fmt.Errorf("No subscription ID specified. Use the -subscriptionid option.") |
850 | 41 | } | 41 | } |
851 | 42 | return nil | 42 | return nil |
852 | 43 | } | 43 | } |
853 | 44 | 44 | ||
854 | 45 | func checkError(err error) { | 45 | func checkError(err error) { |
858 | 46 | if err != nil { | 46 | if err != nil { |
859 | 47 | panic(err) | 47 | panic(err) |
860 | 48 | } | 48 | } |
861 | 49 | } | 49 | } |
862 | 50 | 50 | ||
863 | 51 | // makeRandomIdentifier creates an arbitrary identifier of the given length, | 51 | // makeRandomIdentifier creates an arbitrary identifier of the given length, |
864 | @@ -53,231 +53,231 @@ | |||
865 | 53 | // The identifier will start with the given prefix. The prefix must be no | 53 | // The identifier will start with the given prefix. The prefix must be no |
866 | 54 | // longer than the specified length, or there'll be trouble. | 54 | // longer than the specified length, or there'll be trouble. |
867 | 55 | func makeRandomIdentifier(prefix string, length int) string { | 55 | func makeRandomIdentifier(prefix string, length int) string { |
880 | 56 | // Only digits and lower-case ASCII letters are accepted. | 56 | // Only digits and lower-case ASCII letters are accepted. |
881 | 57 | const chars = "abcdefghijklmnopqrstuvwxyz0123456789" | 57 | const chars = "abcdefghijklmnopqrstuvwxyz0123456789" |
882 | 58 | 58 | ||
883 | 59 | if len(prefix) > length { | 59 | if len(prefix) > length { |
884 | 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)) |
885 | 61 | } | 61 | } |
886 | 62 | 62 | ||
887 | 63 | id := prefix | 63 | id := prefix |
888 | 64 | for len(id) < length { | 64 | for len(id) < length { |
889 | 65 | id += string(chars[rand.Intn(len(chars))]) | 65 | id += string(chars[rand.Intn(len(chars))]) |
890 | 66 | } | 66 | } |
891 | 67 | return id | 67 | return id |
892 | 68 | } | 68 | } |
893 | 69 | 69 | ||
894 | 70 | func main() { | 70 | func main() { |
909 | 71 | rand.Seed(int64(time.Now().Nanosecond())) | 71 | rand.Seed(int64(time.Now().Nanosecond())) |
910 | 72 | 72 | ||
911 | 73 | err := getParams() | 73 | err := getParams() |
912 | 74 | if err != nil { | 74 | if err != nil { |
913 | 75 | Info(err) | 75 | Info(err) |
914 | 76 | os.Exit(1) | 76 | os.Exit(1) |
915 | 77 | } | 77 | } |
916 | 78 | 78 | ||
917 | 79 | api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location) | 79 | api, err := gwacl.NewManagementAPI(subscriptionID, certFile, location) |
918 | 80 | checkError(err) | 80 | checkError(err) |
919 | 81 | 81 | ||
920 | 82 | ExerciseHostedServicesAPI(api) | 82 | ExerciseHostedServicesAPI(api) |
921 | 83 | 83 | ||
922 | 84 | Info("All done.") | 84 | Info("All done.") |
923 | 85 | } | 85 | } |
924 | 86 | 86 | ||
925 | 87 | func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) { | 87 | func ExerciseHostedServicesAPI(api *gwacl.ManagementAPI) { |
1121 | 88 | var err error | 88 | var err error |
1122 | 89 | location := "West US" | 89 | location := "West US" |
1123 | 90 | release := "13.04" | 90 | release := "13.04" |
1124 | 91 | 91 | ||
1125 | 92 | affinityGroupName := gwacl.MakeRandomHostname("affinitygroup") | 92 | affinityGroupName := gwacl.MakeRandomHostname("affinitygroup") |
1126 | 93 | Info("Creating an affinity group...") | 93 | Info("Creating an affinity group...") |
1127 | 94 | cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location) | 94 | cag := gwacl.NewCreateAffinityGroup(affinityGroupName, "affinity-label", "affinity-description", location) |
1128 | 95 | err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{ | 95 | err = api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{ |
1129 | 96 | CreateAffinityGroup: cag}) | 96 | CreateAffinityGroup: cag}) |
1130 | 97 | checkError(err) | 97 | checkError(err) |
1131 | 98 | Infof("Created affinity group %s\n", affinityGroupName) | 98 | Infof("Created affinity group %s\n", affinityGroupName) |
1132 | 99 | 99 | ||
1133 | 100 | defer func() { | 100 | defer func() { |
1134 | 101 | Infof("Deleting affinity group %s\n", affinityGroupName) | 101 | Infof("Deleting affinity group %s\n", affinityGroupName) |
1135 | 102 | err := api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{ | 102 | err := api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{ |
1136 | 103 | Name: affinityGroupName}) | 103 | Name: affinityGroupName}) |
1137 | 104 | checkError(err) | 104 | checkError(err) |
1138 | 105 | Infof("Done deleting affinity group %s\n", affinityGroupName) | 105 | Infof("Done deleting affinity group %s\n", affinityGroupName) |
1139 | 106 | }() | 106 | }() |
1140 | 107 | 107 | ||
1141 | 108 | virtualNetworkName := gwacl.MakeRandomVirtualNetworkName("virtual-net-") | 108 | virtualNetworkName := gwacl.MakeRandomVirtualNetworkName("virtual-net-") |
1142 | 109 | Infof("Creating virtual network %s...\n", virtualNetworkName) | 109 | Infof("Creating virtual network %s...\n", virtualNetworkName) |
1143 | 110 | virtualNetwork := gwacl.VirtualNetworkSite{ | 110 | virtualNetwork := gwacl.VirtualNetworkSite{ |
1144 | 111 | Name: virtualNetworkName, | 111 | Name: virtualNetworkName, |
1145 | 112 | AffinityGroup: affinityGroupName, | 112 | AffinityGroup: affinityGroupName, |
1146 | 113 | AddressSpacePrefixes: []string{ | 113 | AddressSpacePrefixes: []string{ |
1147 | 114 | "10.0.0.0/8", | 114 | "10.0.0.0/8", |
1148 | 115 | }, | 115 | }, |
1149 | 116 | } | 116 | } |
1150 | 117 | err = api.AddVirtualNetworkSite(&virtualNetwork) | 117 | err = api.AddVirtualNetworkSite(&virtualNetwork) |
1151 | 118 | checkError(err) | 118 | checkError(err) |
1152 | 119 | Info("Done creating virtual network") | 119 | Info("Done creating virtual network") |
1153 | 120 | 120 | ||
1154 | 121 | defer func() { | 121 | defer func() { |
1155 | 122 | Infof("Deleting virtual network %s...\n", virtualNetworkName) | 122 | Infof("Deleting virtual network %s...\n", virtualNetworkName) |
1156 | 123 | err := api.RemoveVirtualNetworkSite(virtualNetworkName) | 123 | err := api.RemoveVirtualNetworkSite(virtualNetworkName) |
1157 | 124 | checkError(err) | 124 | checkError(err) |
1158 | 125 | Infof("Done deleting virtual network %s\n", virtualNetworkName) | 125 | Infof("Done deleting virtual network %s\n", virtualNetworkName) |
1159 | 126 | }() | 126 | }() |
1160 | 127 | 127 | ||
1161 | 128 | networkConfig, err := api.GetNetworkConfiguration() | 128 | networkConfig, err := api.GetNetworkConfiguration() |
1162 | 129 | checkError(err) | 129 | checkError(err) |
1163 | 130 | if networkConfig == nil { | 130 | if networkConfig == nil { |
1164 | 131 | Info("No network configuration is set") | 131 | Info("No network configuration is set") |
1165 | 132 | } else { | 132 | } else { |
1166 | 133 | xml, err := networkConfig.Serialize() | 133 | xml, err := networkConfig.Serialize() |
1167 | 134 | checkError(err) | 134 | checkError(err) |
1168 | 135 | Info(xml) | 135 | Info(xml) |
1169 | 136 | } | 136 | } |
1170 | 137 | 137 | ||
1171 | 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) |
1172 | 139 | images, err := api.ListOSImages() | 139 | images, err := api.ListOSImages() |
1173 | 140 | checkError(err) | 140 | checkError(err) |
1174 | 141 | image, err := images.GetLatestUbuntuImage(release, location) | 141 | image, err := images.GetLatestUbuntuImage(release, location) |
1175 | 142 | checkError(err) | 142 | checkError(err) |
1176 | 143 | sourceImageName := image.Name | 143 | sourceImageName := image.Name |
1177 | 144 | Infof("Got image named '%s'\n", sourceImageName) | 144 | Infof("Got image named '%s'\n", sourceImageName) |
1178 | 145 | Info("Done getting OS Image\n") | 145 | Info("Done getting OS Image\n") |
1179 | 146 | 146 | ||
1180 | 147 | hostServiceName := gwacl.MakeRandomHostedServiceName("gwacl") | 147 | hostServiceName := gwacl.MakeRandomHostedServiceName("gwacl") |
1181 | 148 | Infof("Creating a hosted service: '%s'...\n", hostServiceName) | 148 | Infof("Creating a hosted service: '%s'...\n", hostServiceName) |
1182 | 149 | createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location) | 149 | createHostedService := gwacl.NewCreateHostedServiceWithLocation(hostServiceName, "testLabel", location) |
1183 | 150 | createHostedService.AffinityGroup = affinityGroupName | 150 | createHostedService.AffinityGroup = affinityGroupName |
1184 | 151 | err = api.AddHostedService(createHostedService) | 151 | err = api.AddHostedService(createHostedService) |
1185 | 152 | checkError(err) | 152 | checkError(err) |
1186 | 153 | Info("Done creating a hosted service\n") | 153 | Info("Done creating a hosted service\n") |
1187 | 154 | 154 | ||
1188 | 155 | defer func() { | 155 | defer func() { |
1189 | 156 | Info("Destroying hosted service...") | 156 | Info("Destroying hosted service...") |
1190 | 157 | // FIXME: Check error | 157 | // FIXME: Check error |
1191 | 158 | api.DestroyHostedService(&gwacl.DestroyHostedServiceRequest{ | 158 | api.DestroyHostedService(&gwacl.DestroyHostedServiceRequest{ |
1192 | 159 | ServiceName: hostServiceName}) | 159 | ServiceName: hostServiceName}) |
1193 | 160 | Info("Done destroying hosted service\n") | 160 | Info("Done destroying hosted service\n") |
1194 | 161 | }() | 161 | }() |
1195 | 162 | 162 | ||
1196 | 163 | Info("Listing hosted services...") | 163 | Info("Listing hosted services...") |
1197 | 164 | hostedServices, err := api.ListHostedServices() | 164 | hostedServices, err := api.ListHostedServices() |
1198 | 165 | checkError(err) | 165 | checkError(err) |
1199 | 166 | Infof("Got %d hosted service(s)\n", len(hostedServices)) | 166 | Infof("Got %d hosted service(s)\n", len(hostedServices)) |
1200 | 167 | if len(hostedServices) > 0 { | 167 | if len(hostedServices) > 0 { |
1201 | 168 | hostedService := hostedServices[0] | 168 | hostedService := hostedServices[0] |
1202 | 169 | detailedHostedService, err := api.GetHostedServiceProperties(hostedService.ServiceName, true) | 169 | detailedHostedService, err := api.GetHostedServiceProperties(hostedService.ServiceName, true) |
1203 | 170 | checkError(err) | 170 | checkError(err) |
1204 | 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)) |
1205 | 172 | // Do the same again with ListAllDeployments. | 172 | // Do the same again with ListAllDeployments. |
1206 | 173 | deployments, err := api.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ServiceName: hostedService.ServiceName}) | 173 | deployments, err := api.ListAllDeployments(&gwacl.ListAllDeploymentsRequest{ServiceName: hostedService.ServiceName}) |
1207 | 174 | checkError(err) | 174 | checkError(err) |
1208 | 175 | if len(deployments) != len(detailedHostedService.Deployments) { | 175 | if len(deployments) != len(detailedHostedService.Deployments) { |
1209 | 176 | Errorf( | 176 | Errorf( |
1210 | 177 | "Mismatch in reported deployments: %d != %d", | 177 | "Mismatch in reported deployments: %d != %d", |
1211 | 178 | len(deployments), len(detailedHostedService.Deployments)) | 178 | len(deployments), len(detailedHostedService.Deployments)) |
1212 | 179 | } | 179 | } |
1213 | 180 | } | 180 | } |
1214 | 181 | Info("Done listing hosted services\n") | 181 | Info("Done listing hosted services\n") |
1215 | 182 | 182 | ||
1216 | 183 | Info("Adding VM deployment...") | 183 | Info("Adding VM deployment...") |
1217 | 184 | hostname := gwacl.MakeRandomHostname("gwaclhost") | 184 | hostname := gwacl.MakeRandomHostname("gwaclhost") |
1218 | 185 | // Random passwords are no use to man nor beast here if you want to | 185 | // Random passwords are no use to man nor beast here if you want to |
1219 | 186 | // test with your instance, so we'll use a fixed one. It's not really a | 186 | // test with your instance, so we'll use a fixed one. It's not really a |
1220 | 187 | // security hazard in such a short-lived private instance. | 187 | // security hazard in such a short-lived private instance. |
1221 | 188 | password := "Ubuntu123" | 188 | password := "Ubuntu123" |
1222 | 189 | username := "ubuntu" | 189 | username := "ubuntu" |
1223 | 190 | vhdName := gwacl.MakeRandomDiskName("gwacldisk") | 190 | vhdName := gwacl.MakeRandomDiskName("gwacldisk") |
1224 | 191 | userdata := base64.StdEncoding.EncodeToString([]byte("TEST_USER_DATA")) | 191 | userdata := base64.StdEncoding.EncodeToString([]byte("TEST_USER_DATA")) |
1225 | 192 | linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet( | 192 | linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet( |
1226 | 193 | hostname, username, password, userdata, "false") | 193 | hostname, username, password, userdata, "false") |
1227 | 194 | inputendpoint := gwacl.InputEndpoint{LocalPort: 22, Name: "sshport", Port: 22, Protocol: "TCP"} | 194 | inputendpoint := gwacl.InputEndpoint{LocalPort: 22, Name: "sshport", Port: 22, Protocol: "TCP"} |
1228 | 195 | networkConfigurationSet := gwacl.NewNetworkConfigurationSet([]gwacl.InputEndpoint{inputendpoint}, nil) | 195 | networkConfigurationSet := gwacl.NewNetworkConfigurationSet([]gwacl.InputEndpoint{inputendpoint}, nil) |
1229 | 196 | 196 | ||
1230 | 197 | storageAccount := makeRandomIdentifier("gwacl", 24) | 197 | storageAccount := makeRandomIdentifier("gwacl", 24) |
1231 | 198 | storageLabel := makeRandomIdentifier("gwacl", 64) | 198 | storageLabel := makeRandomIdentifier("gwacl", 64) |
1232 | 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) |
1233 | 200 | cssi := gwacl.NewCreateStorageServiceInputWithLocation(storageAccount, storageLabel, location, "false") | 200 | cssi := gwacl.NewCreateStorageServiceInputWithLocation(storageAccount, storageLabel, location, "false") |
1234 | 201 | err = api.AddStorageAccount(cssi) | 201 | err = api.AddStorageAccount(cssi) |
1235 | 202 | checkError(err) | 202 | checkError(err) |
1236 | 203 | Info("Done requesting storage account\n") | 203 | Info("Done requesting storage account\n") |
1237 | 204 | 204 | ||
1238 | 205 | defer func() { | 205 | defer func() { |
1239 | 206 | Infof("Deleting storage account %s...\n", storageAccount) | 206 | Infof("Deleting storage account %s...\n", storageAccount) |
1240 | 207 | // FIXME: Check error | 207 | // FIXME: Check error |
1241 | 208 | api.DeleteStorageAccount(storageAccount) | 208 | api.DeleteStorageAccount(storageAccount) |
1242 | 209 | Info("Done deleting storage account\n") | 209 | Info("Done deleting storage account\n") |
1243 | 210 | }() | 210 | }() |
1244 | 211 | 211 | ||
1245 | 212 | mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, fmt.Sprintf("vhds/%s.vhd", vhdName)) | 212 | mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, fmt.Sprintf("vhds/%s.vhd", vhdName)) |
1246 | 213 | diskName := makeRandomIdentifier("gwacldisk", 16) | 213 | diskName := makeRandomIdentifier("gwacldisk", 16) |
1247 | 214 | diskLabel := makeRandomIdentifier("gwacl", 64) | 214 | diskLabel := makeRandomIdentifier("gwacl", 64) |
1248 | 215 | vhd := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName, "Linux") | 215 | vhd := gwacl.NewOSVirtualHardDisk("", diskLabel, diskName, mediaLink, sourceImageName, "Linux") |
1249 | 216 | roleName := gwacl.MakeRandomRoleName("gwaclrole") | 216 | roleName := gwacl.MakeRandomRoleName("gwaclrole") |
1250 | 217 | role := gwacl.NewRole("ExtraSmall", roleName, vhd, | 217 | role := gwacl.NewRole("ExtraSmall", roleName, vhd, |
1251 | 218 | []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet}) | 218 | []gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet}) |
1252 | 219 | machineName := makeRandomIdentifier("gwaclmachine", 20) | 219 | machineName := makeRandomIdentifier("gwaclmachine", 20) |
1253 | 220 | deployment := gwacl.NewDeploymentForCreateVMDeployment( | 220 | deployment := gwacl.NewDeploymentForCreateVMDeployment( |
1254 | 221 | machineName, "Production", machineName, []gwacl.Role{*role}, virtualNetworkName) | 221 | machineName, "Production", machineName, []gwacl.Role{*role}, virtualNetworkName) |
1255 | 222 | err = api.AddDeployment(deployment, hostServiceName) | 222 | err = api.AddDeployment(deployment, hostServiceName) |
1256 | 223 | checkError(err) | 223 | checkError(err) |
1257 | 224 | Info("Done adding VM deployment\n") | 224 | Info("Done adding VM deployment\n") |
1258 | 225 | 225 | ||
1259 | 226 | Info("Starting VM...") | 226 | Info("Starting VM...") |
1260 | 227 | err = api.StartRole(&gwacl.StartRoleRequest{hostServiceName, deployment.Name, role.RoleName}) | 227 | err = api.StartRole(&gwacl.StartRoleRequest{hostServiceName, deployment.Name, role.RoleName}) |
1261 | 228 | checkError(err) | 228 | checkError(err) |
1262 | 229 | Info("Done starting VM\n") | 229 | Info("Done starting VM\n") |
1263 | 230 | 230 | ||
1264 | 231 | Info("Listing VM...") | 231 | Info("Listing VM...") |
1265 | 232 | instances, err := api.ListInstances(&gwacl.ListInstancesRequest{hostServiceName}) | 232 | instances, err := api.ListInstances(&gwacl.ListInstancesRequest{hostServiceName}) |
1266 | 233 | checkError(err) | 233 | checkError(err) |
1267 | 234 | Infof("Got %d instance(s)\n", len(instances)) | 234 | Infof("Got %d instance(s)\n", len(instances)) |
1268 | 235 | Info("Done listing VM\n") | 235 | Info("Done listing VM\n") |
1269 | 236 | 236 | ||
1270 | 237 | Info("Getting deployment info...") | 237 | Info("Getting deployment info...") |
1271 | 238 | request := &gwacl.GetDeploymentRequest{ServiceName: hostServiceName, DeploymentName: machineName} | 238 | request := &gwacl.GetDeploymentRequest{ServiceName: hostServiceName, DeploymentName: machineName} |
1272 | 239 | deploy, err := api.GetDeployment(request) | 239 | deploy, err := api.GetDeployment(request) |
1273 | 240 | checkError(err) | 240 | checkError(err) |
1274 | 241 | fqdn, err := deploy.GetFQDN() | 241 | fqdn, err := deploy.GetFQDN() |
1275 | 242 | checkError(err) | 242 | checkError(err) |
1276 | 243 | Info("Got deployment info\n") | 243 | Info("Got deployment info\n") |
1277 | 244 | 244 | ||
1278 | 245 | Info("Adding role input endpoint...") | 245 | Info("Adding role input endpoint...") |
1279 | 246 | endpoint := gwacl.InputEndpoint{ | 246 | endpoint := gwacl.InputEndpoint{ |
1280 | 247 | Name: gwacl.MakeRandomHostname("endpoint-"), | 247 | Name: gwacl.MakeRandomHostname("endpoint-"), |
1281 | 248 | Port: 1080, | 248 | Port: 1080, |
1282 | 249 | LocalPort: 80, | 249 | LocalPort: 80, |
1283 | 250 | Protocol: "TCP", | 250 | Protocol: "TCP", |
1284 | 251 | } | 251 | } |
1285 | 252 | err = api.AddRoleEndpoints(&gwacl.AddRoleEndpointsRequest{ | 252 | err = api.AddRoleEndpoints(&gwacl.AddRoleEndpointsRequest{ |
1286 | 253 | ServiceName: hostServiceName, | 253 | ServiceName: hostServiceName, |
1287 | 254 | DeploymentName: deployment.Name, | 254 | DeploymentName: deployment.Name, |
1288 | 255 | RoleName: role.RoleName, | 255 | RoleName: role.RoleName, |
1289 | 256 | InputEndpoints: []gwacl.InputEndpoint{endpoint}, | 256 | InputEndpoints: []gwacl.InputEndpoint{endpoint}, |
1290 | 257 | }) | 257 | }) |
1291 | 258 | checkError(err) | 258 | checkError(err) |
1292 | 259 | Info("Added role input endpoint\n") | 259 | Info("Added role input endpoint\n") |
1293 | 260 | 260 | ||
1294 | 261 | defer func() { | 261 | defer func() { |
1295 | 262 | Info("Removing role input endpoint...") | 262 | Info("Removing role input endpoint...") |
1296 | 263 | err := api.RemoveRoleEndpoints(&gwacl.RemoveRoleEndpointsRequest{ | 263 | err := api.RemoveRoleEndpoints(&gwacl.RemoveRoleEndpointsRequest{ |
1297 | 264 | ServiceName: hostServiceName, | 264 | ServiceName: hostServiceName, |
1298 | 265 | DeploymentName: deployment.Name, | 265 | DeploymentName: deployment.Name, |
1299 | 266 | RoleName: role.RoleName, | 266 | RoleName: role.RoleName, |
1300 | 267 | InputEndpoints: []gwacl.InputEndpoint{endpoint}, | 267 | InputEndpoints: []gwacl.InputEndpoint{endpoint}, |
1301 | 268 | }) | 268 | }) |
1302 | 269 | checkError(err) | 269 | checkError(err) |
1303 | 270 | Info("Removed role input endpoint\n") | 270 | Info("Removed role input endpoint\n") |
1304 | 271 | }() | 271 | }() |
1305 | 272 | 272 | ||
1306 | 273 | if pause { | 273 | if pause { |
1307 | 274 | var wait string | 274 | var wait string |
1308 | 275 | fmt.Println("host:", fqdn) | 275 | fmt.Println("host:", fqdn) |
1309 | 276 | fmt.Println("username:", username) | 276 | fmt.Println("username:", username) |
1310 | 277 | fmt.Println("password:", password) | 277 | fmt.Println("password:", password) |
1311 | 278 | fmt.Println("") | 278 | fmt.Println("") |
1312 | 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") |
1313 | 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") |
1314 | 281 | fmt.Scan(&wait) | 281 | fmt.Scan(&wait) |
1315 | 282 | } | 282 | } |
1316 | 283 | } | 283 | } |
1317 | 284 | 284 | ||
1318 | === modified file 'example/storage/run.go' | |||
1319 | --- example/storage/run.go 2013-08-19 06:05:38 +0000 | |||
1320 | +++ example/storage/run.go 2014-12-02 00:40:19 +0000 | |||
1321 | @@ -10,106 +10,106 @@ | |||
1322 | 10 | package main | 10 | package main |
1323 | 11 | 11 | ||
1324 | 12 | import ( | 12 | import ( |
1331 | 13 | "flag" | 13 | "flag" |
1332 | 14 | "fmt" | 14 | "fmt" |
1333 | 15 | "io/ioutil" | 15 | "io/ioutil" |
1334 | 16 | "launchpad.net/gwacl" | 16 | "launchpad.net/gwacl" |
1335 | 17 | "os" | 17 | "os" |
1336 | 18 | "strings" | 18 | "strings" |
1337 | 19 | ) | 19 | ) |
1338 | 20 | 20 | ||
1339 | 21 | func badOperationError() error { | 21 | func badOperationError() error { |
1341 | 22 | return fmt.Errorf("Must specify one of %v", operationNames) | 22 | return fmt.Errorf("Must specify one of %v", operationNames) |
1342 | 23 | } | 23 | } |
1343 | 24 | 24 | ||
1344 | 25 | // operation is something you can instruct this program to do, by specifying | 25 | // operation is something you can instruct this program to do, by specifying |
1345 | 26 | // its name on the command line. | 26 | // its name on the command line. |
1346 | 27 | type operation struct { | 27 | type operation struct { |
1362 | 28 | // name is the operation name as used on the command line. | 28 | // name is the operation name as used on the command line. |
1363 | 29 | name string | 29 | name string |
1364 | 30 | // description holds a description of what the operation does. | 30 | // description holds a description of what the operation does. |
1365 | 31 | description string | 31 | description string |
1366 | 32 | // example illustrates how the operation is used. | 32 | // example illustrates how the operation is used. |
1367 | 33 | example string | 33 | example string |
1368 | 34 | // requiredArgs lists the command-line options that are required for this | 34 | // requiredArgs lists the command-line options that are required for this |
1369 | 35 | // operation. | 35 | // operation. |
1370 | 36 | requiredArgs []string | 36 | requiredArgs []string |
1371 | 37 | // validate is an optional callback to perform more detailed checking on | 37 | // validate is an optional callback to perform more detailed checking on |
1372 | 38 | // the operation's arguments. | 38 | // the operation's arguments. |
1373 | 39 | validate func() error | 39 | validate func() error |
1374 | 40 | // implementation is a function that performs the operation. If it fails, | 40 | // implementation is a function that performs the operation. If it fails, |
1375 | 41 | // it just panics. | 41 | // it just panics. |
1376 | 42 | implementation func(gwacl.StorageContext) | 42 | implementation func(gwacl.StorageContext) |
1377 | 43 | } | 43 | } |
1378 | 44 | 44 | ||
1379 | 45 | // operations defines what operations are available to be invoked from the | 45 | // operations defines what operations are available to be invoked from the |
1380 | 46 | // command line. | 46 | // command line. |
1381 | 47 | var operations = []operation{ | 47 | var operations = []operation{ |
1447 | 48 | { | 48 | { |
1448 | 49 | name: "listcontainers", | 49 | name: "listcontainers", |
1449 | 50 | description: "Show existing storage containers", | 50 | description: "Show existing storage containers", |
1450 | 51 | example: "listcontainers", | 51 | example: "listcontainers", |
1451 | 52 | implementation: listcontainers, | 52 | implementation: listcontainers, |
1452 | 53 | }, | 53 | }, |
1453 | 54 | { | 54 | { |
1454 | 55 | name: "list", | 55 | name: "list", |
1455 | 56 | description: "List files in a container", | 56 | description: "List files in a container", |
1456 | 57 | example: "-container=<container> list", | 57 | example: "-container=<container> list", |
1457 | 58 | requiredArgs: []string{"container"}, | 58 | requiredArgs: []string{"container"}, |
1458 | 59 | implementation: list, | 59 | implementation: list, |
1459 | 60 | }, | 60 | }, |
1460 | 61 | { | 61 | { |
1461 | 62 | name: "containeracl", | 62 | name: "containeracl", |
1462 | 63 | description: "Set access on a container", | 63 | description: "Set access on a container", |
1463 | 64 | example: "-container=<container> -acl <container|blob|private> containeracl", | 64 | example: "-container=<container> -acl <container|blob|private> containeracl", |
1464 | 65 | requiredArgs: []string{"container", "key", "acl"}, | 65 | requiredArgs: []string{"container", "key", "acl"}, |
1465 | 66 | validate: func() error { | 66 | validate: func() error { |
1466 | 67 | if acl != "container" && acl != "blob" && acl != "private" { | 67 | if acl != "container" && acl != "blob" && acl != "private" { |
1467 | 68 | return fmt.Errorf( | 68 | return fmt.Errorf( |
1468 | 69 | "Usage: containeracl -container=<container> <container|blob|private>") | 69 | "Usage: containeracl -container=<container> <container|blob|private>") |
1469 | 70 | } | 70 | } |
1470 | 71 | return nil | 71 | return nil |
1471 | 72 | }, | 72 | }, |
1472 | 73 | implementation: containeracl, | 73 | implementation: containeracl, |
1473 | 74 | }, | 74 | }, |
1474 | 75 | { | 75 | { |
1475 | 76 | name: "getblob", | 76 | name: "getblob", |
1476 | 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)", |
1477 | 78 | example: "-container=<container> -filename=<filename> getblob", | 78 | example: "-container=<container> -filename=<filename> getblob", |
1478 | 79 | requiredArgs: []string{"container", "filename"}, | 79 | requiredArgs: []string{"container", "filename"}, |
1479 | 80 | implementation: getblob, | 80 | implementation: getblob, |
1480 | 81 | }, | 81 | }, |
1481 | 82 | { | 82 | { |
1482 | 83 | name: "addblock", | 83 | name: "addblock", |
1483 | 84 | description: "Upload a file to a block blob", | 84 | description: "Upload a file to a block blob", |
1484 | 85 | example: "-container=<container> -filename=<filename> addblock", | 85 | example: "-container=<container> -filename=<filename> addblock", |
1485 | 86 | requiredArgs: []string{"key", "container", "filename"}, | 86 | requiredArgs: []string{"key", "container", "filename"}, |
1486 | 87 | implementation: addblock, | 87 | implementation: addblock, |
1487 | 88 | }, | 88 | }, |
1488 | 89 | { | 89 | { |
1489 | 90 | name: "deleteblob", | 90 | name: "deleteblob", |
1490 | 91 | description: "Delete a blob", | 91 | description: "Delete a blob", |
1491 | 92 | example: "-container=<container> -filename=<filename> deleteblob", | 92 | example: "-container=<container> -filename=<filename> deleteblob", |
1492 | 93 | requiredArgs: []string{"key", "container", "filename"}, | 93 | requiredArgs: []string{"key", "container", "filename"}, |
1493 | 94 | implementation: deleteblob, | 94 | implementation: deleteblob, |
1494 | 95 | }, | 95 | }, |
1495 | 96 | { | 96 | { |
1496 | 97 | name: "putblob", | 97 | name: "putblob", |
1497 | 98 | description: "Create an empty page blob", | 98 | description: "Create an empty page blob", |
1498 | 99 | example: "-container=<container> -blobname=<blobname> -size=<bytes> " + | 99 | example: "-container=<container> -blobname=<blobname> -size=<bytes> " + |
1499 | 100 | "-blobtype=\"page\" putblob", | 100 | "-blobtype=\"page\" putblob", |
1500 | 101 | requiredArgs: []string{"key", "blobname", "blobtype", "container", "size"}, | 101 | requiredArgs: []string{"key", "blobname", "blobtype", "container", "size"}, |
1501 | 102 | implementation: putblob, | 102 | implementation: putblob, |
1502 | 103 | }, | 103 | }, |
1503 | 104 | { | 104 | { |
1504 | 105 | name: "putpage", | 105 | name: "putpage", |
1505 | 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 " + |
1506 | 107 | "be (modulo 512)-(modulo 512 -1), eg: -pagerange=0-511", | 107 | "be (modulo 512)-(modulo 512 -1), eg: -pagerange=0-511", |
1507 | 108 | example: "-container=<container> -blobname=<blobname> -pagerange=<N-N> " + | 108 | example: "-container=<container> -blobname=<blobname> -pagerange=<N-N> " + |
1508 | 109 | "-filename=<local file> putpage", | 109 | "-filename=<local file> putpage", |
1509 | 110 | requiredArgs: []string{"key", "blobname", "container", "pagerange", "filename"}, | 110 | requiredArgs: []string{"key", "blobname", "container", "pagerange", "filename"}, |
1510 | 111 | implementation: putpage, | 111 | implementation: putpage, |
1511 | 112 | }, | 112 | }, |
1512 | 113 | } | 113 | } |
1513 | 114 | 114 | ||
1514 | 115 | // operationsByName maps each opeation name to an operation struct that | 115 | // operationsByName maps each opeation name to an operation struct that |
1515 | @@ -121,130 +121,130 @@ | |||
1516 | 121 | var operationNames []string | 121 | var operationNames []string |
1517 | 122 | 122 | ||
1518 | 123 | func init() { | 123 | func init() { |
1523 | 124 | operationsByName = make(map[string]operation, len(operations)) | 124 | operationsByName = make(map[string]operation, len(operations)) |
1524 | 125 | for _, op := range operations { | 125 | for _, op := range operations { |
1525 | 126 | operationsByName[op.name] = op | 126 | operationsByName[op.name] = op |
1526 | 127 | } | 127 | } |
1527 | 128 | 128 | ||
1532 | 129 | operationNames = make([]string, len(operations)) | 129 | operationNames = make([]string, len(operations)) |
1533 | 130 | for index, op := range operations { | 130 | for index, op := range operations { |
1534 | 131 | operationNames[index] = op.name | 131 | operationNames[index] = op.name |
1535 | 132 | } | 132 | } |
1536 | 133 | } | 133 | } |
1537 | 134 | 134 | ||
1538 | 135 | // Variables set by command-line options. | 135 | // Variables set by command-line options. |
1539 | 136 | var ( | 136 | var ( |
1552 | 137 | help bool | 137 | help bool |
1553 | 138 | account string | 138 | account string |
1554 | 139 | location string | 139 | location string |
1555 | 140 | key string | 140 | key string |
1556 | 141 | filename string | 141 | filename string |
1557 | 142 | container string | 142 | container string |
1558 | 143 | prefix string | 143 | prefix string |
1559 | 144 | blobname string | 144 | blobname string |
1560 | 145 | blobtype string | 145 | blobtype string |
1561 | 146 | size int | 146 | size int |
1562 | 147 | pagerange string | 147 | pagerange string |
1563 | 148 | acl string | 148 | acl string |
1564 | 149 | ) | 149 | ) |
1565 | 150 | 150 | ||
1566 | 151 | // argumentGiven returns whether the named argument was specified on the | 151 | // argumentGiven returns whether the named argument was specified on the |
1567 | 152 | // command line. | 152 | // command line. |
1568 | 153 | func argumentGiven(name string) bool { | 153 | func argumentGiven(name string) bool { |
1595 | 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! |
1596 | 155 | switch name { | 155 | switch name { |
1597 | 156 | case "account": | 156 | case "account": |
1598 | 157 | return account != "" | 157 | return account != "" |
1599 | 158 | case "location": | 158 | case "location": |
1600 | 159 | return location != "" | 159 | return location != "" |
1601 | 160 | case "key": | 160 | case "key": |
1602 | 161 | return key != "" | 161 | return key != "" |
1603 | 162 | case "container": | 162 | case "container": |
1604 | 163 | return container != "" | 163 | return container != "" |
1605 | 164 | case "filename": | 164 | case "filename": |
1606 | 165 | return filename != "" | 165 | return filename != "" |
1607 | 166 | case "prefix": | 166 | case "prefix": |
1608 | 167 | return prefix != "" | 167 | return prefix != "" |
1609 | 168 | case "blobname": | 168 | case "blobname": |
1610 | 169 | return blobname != "" | 169 | return blobname != "" |
1611 | 170 | case "blobtype": | 170 | case "blobtype": |
1612 | 171 | return blobtype != "" | 171 | return blobtype != "" |
1613 | 172 | case "size": | 172 | case "size": |
1614 | 173 | return size != 0 | 173 | return size != 0 |
1615 | 174 | case "pagerange": | 174 | case "pagerange": |
1616 | 175 | return pagerange != "" | 175 | return pagerange != "" |
1617 | 176 | case "acl": | 176 | case "acl": |
1618 | 177 | return acl != "" | 177 | return acl != "" |
1619 | 178 | } | 178 | } |
1620 | 179 | panic(fmt.Errorf("internal error: unknown command-line option: %s", name)) | 179 | panic(fmt.Errorf("internal error: unknown command-line option: %s", name)) |
1621 | 180 | } | 180 | } |
1622 | 181 | 181 | ||
1623 | 182 | func getParams() (string, error) { | 182 | func getParams() (string, error) { |
1678 | 183 | flag.BoolVar(&help, "h", false, "Show usage and exit") | 183 | flag.BoolVar(&help, "h", false, "Show usage and exit") |
1679 | 184 | 184 | ||
1680 | 185 | flag.StringVar(&account, "account", "", "Storage account name") | 185 | flag.StringVar(&account, "account", "", "Storage account name") |
1681 | 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\"") |
1682 | 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)") |
1683 | 188 | flag.StringVar(&container, "container", "", "Name of the container to use") | 188 | flag.StringVar(&container, "container", "", "Name of the container to use") |
1684 | 189 | flag.StringVar(&filename, "filename", "", "File containing blob/page to upload/download") | 189 | flag.StringVar(&filename, "filename", "", "File containing blob/page to upload/download") |
1685 | 190 | flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs") | 190 | flag.StringVar(&prefix, "prefix", "", "Prefix to match when listing blobs") |
1686 | 191 | flag.StringVar(&blobname, "blobname", "", "Name of blob in container") | 191 | flag.StringVar(&blobname, "blobname", "", "Name of blob in container") |
1687 | 192 | flag.StringVar(&blobtype, "blobtype", "", "Type of blob, 'page' or 'block'") | 192 | flag.StringVar(&blobtype, "blobtype", "", "Type of blob, 'page' or 'block'") |
1688 | 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'") |
1689 | 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") |
1690 | 195 | flag.StringVar(&acl, "acl", "", "When using 'containeracl', specify an ACL type") | 195 | flag.StringVar(&acl, "acl", "", "When using 'containeracl', specify an ACL type") |
1691 | 196 | flag.Parse() | 196 | flag.Parse() |
1692 | 197 | 197 | ||
1693 | 198 | if help { | 198 | if help { |
1694 | 199 | return "", nil | 199 | return "", nil |
1695 | 200 | } | 200 | } |
1696 | 201 | 201 | ||
1697 | 202 | opName := flag.Arg(0) | 202 | opName := flag.Arg(0) |
1698 | 203 | if opName == "" { | 203 | if opName == "" { |
1699 | 204 | return "", fmt.Errorf("No operation specified") | 204 | return "", fmt.Errorf("No operation specified") |
1700 | 205 | } | 205 | } |
1701 | 206 | 206 | ||
1702 | 207 | requiredArgs := []string{"account", "location"} | 207 | requiredArgs := []string{"account", "location"} |
1703 | 208 | for _, arg := range requiredArgs { | 208 | for _, arg := range requiredArgs { |
1704 | 209 | if !argumentGiven(arg) { | 209 | if !argumentGiven(arg) { |
1705 | 210 | return "", fmt.Errorf("Must supply %q parameter.", arg) | 210 | return "", fmt.Errorf("Must supply %q parameter.", arg) |
1706 | 211 | } | 211 | } |
1707 | 212 | } | 212 | } |
1708 | 213 | 213 | ||
1709 | 214 | if len(flag.Args()) != 1 { | 214 | if len(flag.Args()) != 1 { |
1710 | 215 | return "", badOperationError() | 215 | return "", badOperationError() |
1711 | 216 | } | 216 | } |
1712 | 217 | 217 | ||
1713 | 218 | op, isDefined := operationsByName[opName] | 218 | op, isDefined := operationsByName[opName] |
1714 | 219 | if !isDefined { | 219 | if !isDefined { |
1715 | 220 | return "", badOperationError() | 220 | return "", badOperationError() |
1716 | 221 | } | 221 | } |
1717 | 222 | 222 | ||
1718 | 223 | for _, arg := range op.requiredArgs { | 223 | for _, arg := range op.requiredArgs { |
1719 | 224 | if !argumentGiven(arg) { | 224 | if !argumentGiven(arg) { |
1720 | 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) |
1721 | 226 | } | 226 | } |
1722 | 227 | } | 227 | } |
1723 | 228 | 228 | ||
1724 | 229 | if op.validate != nil { | 229 | if op.validate != nil { |
1725 | 230 | err := op.validate() | 230 | err := op.validate() |
1726 | 231 | if err != nil { | 231 | if err != nil { |
1727 | 232 | return "", err | 232 | return "", err |
1728 | 233 | } | 233 | } |
1729 | 234 | } | 234 | } |
1730 | 235 | 235 | ||
1731 | 236 | return op.name, nil | 236 | return op.name, nil |
1732 | 237 | } | 237 | } |
1733 | 238 | 238 | ||
1734 | 239 | func Usage() { | 239 | func Usage() { |
1741 | 240 | fmt.Fprintf( | 240 | fmt.Fprintf( |
1742 | 241 | os.Stderr, | 241 | os.Stderr, |
1743 | 242 | "Usage:\n %s [args] <%s>\n", | 242 | "Usage:\n %s [args] <%s>\n", |
1744 | 243 | os.Args[0], | 243 | os.Args[0], |
1745 | 244 | strings.Join(operationNames, "|")) | 244 | strings.Join(operationNames, "|")) |
1746 | 245 | flag.PrintDefaults() | 245 | flag.PrintDefaults() |
1747 | 246 | 246 | ||
1749 | 247 | fmt.Fprintf(os.Stderr, ` | 247 | fmt.Fprintf(os.Stderr, ` |
1750 | 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. |
1751 | 249 | It is not a complete example but it does give a useful way to do do some | 249 | It is not a complete example but it does give a useful way to do do some |
1752 | 250 | basic operations. | 250 | basic operations. |
1753 | @@ -255,142 +255,142 @@ | |||
1754 | 255 | invocation parameters: | 255 | invocation parameters: |
1755 | 256 | `) | 256 | `) |
1756 | 257 | 257 | ||
1760 | 258 | for _, op := range operations { | 258 | for _, op := range operations { |
1761 | 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) |
1762 | 260 | } | 260 | } |
1763 | 261 | } | 261 | } |
1764 | 262 | 262 | ||
1765 | 263 | func dumpError(err error) { | 263 | func dumpError(err error) { |
1770 | 264 | if err != nil { | 264 | if err != nil { |
1771 | 265 | fmt.Fprintf(os.Stderr, "ERROR:") | 265 | fmt.Fprintf(os.Stderr, "ERROR:") |
1772 | 266 | fmt.Fprintf(os.Stderr, "%s\n", err) | 266 | fmt.Fprintf(os.Stderr, "%s\n", err) |
1773 | 267 | } | 267 | } |
1774 | 268 | } | 268 | } |
1775 | 269 | 269 | ||
1776 | 270 | func listcontainers(storage gwacl.StorageContext) { | 270 | func listcontainers(storage gwacl.StorageContext) { |
1786 | 271 | res, e := storage.ListAllContainers() | 271 | res, e := storage.ListAllContainers() |
1787 | 272 | if e != nil { | 272 | if e != nil { |
1788 | 273 | dumpError(e) | 273 | dumpError(e) |
1789 | 274 | return | 274 | return |
1790 | 275 | } | 275 | } |
1791 | 276 | for _, c := range res.Containers { | 276 | for _, c := range res.Containers { |
1792 | 277 | // TODO: embellish with the other container data | 277 | // TODO: embellish with the other container data |
1793 | 278 | fmt.Println(c.Name) | 278 | fmt.Println(c.Name) |
1794 | 279 | } | 279 | } |
1795 | 280 | } | 280 | } |
1796 | 281 | 281 | ||
1797 | 282 | func containeracl(storage gwacl.StorageContext) { | 282 | func containeracl(storage gwacl.StorageContext) { |
1803 | 283 | err := storage.SetContainerACL(&gwacl.SetContainerACLRequest{ | 283 | err := storage.SetContainerACL(&gwacl.SetContainerACLRequest{ |
1804 | 284 | Container: container, | 284 | Container: container, |
1805 | 285 | Access: acl, | 285 | Access: acl, |
1806 | 286 | }) | 286 | }) |
1807 | 287 | dumpError(err) | 287 | dumpError(err) |
1808 | 288 | } | 288 | } |
1809 | 289 | 289 | ||
1810 | 290 | func list(storage gwacl.StorageContext) { | 290 | func list(storage gwacl.StorageContext) { |
1821 | 291 | request := &gwacl.ListBlobsRequest{ | 291 | request := &gwacl.ListBlobsRequest{ |
1822 | 292 | Container: container, Prefix: prefix} | 292 | Container: container, Prefix: prefix} |
1823 | 293 | res, err := storage.ListAllBlobs(request) | 293 | res, err := storage.ListAllBlobs(request) |
1824 | 294 | if err != nil { | 294 | if err != nil { |
1825 | 295 | dumpError(err) | 295 | dumpError(err) |
1826 | 296 | return | 296 | return |
1827 | 297 | } | 297 | } |
1828 | 298 | for _, b := range res.Blobs { | 298 | for _, b := range res.Blobs { |
1829 | 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) |
1830 | 300 | } | 300 | } |
1831 | 301 | } | 301 | } |
1832 | 302 | 302 | ||
1833 | 303 | func addblock(storage gwacl.StorageContext) { | 303 | func addblock(storage gwacl.StorageContext) { |
1841 | 304 | var err error | 304 | var err error |
1842 | 305 | file, err := os.Open(filename) | 305 | file, err := os.Open(filename) |
1843 | 306 | if err != nil { | 306 | if err != nil { |
1844 | 307 | dumpError(err) | 307 | dumpError(err) |
1845 | 308 | return | 308 | return |
1846 | 309 | } | 309 | } |
1847 | 310 | defer file.Close() | 310 | defer file.Close() |
1848 | 311 | 311 | ||
1854 | 312 | err = storage.UploadBlockBlob(container, filename, file) | 312 | err = storage.UploadBlockBlob(container, filename, file) |
1855 | 313 | if err != nil { | 313 | if err != nil { |
1856 | 314 | dumpError(err) | 314 | dumpError(err) |
1857 | 315 | return | 315 | return |
1858 | 316 | } | 316 | } |
1859 | 317 | } | 317 | } |
1860 | 318 | 318 | ||
1861 | 319 | func deleteblob(storage gwacl.StorageContext) { | 319 | func deleteblob(storage gwacl.StorageContext) { |
1864 | 320 | err := storage.DeleteBlob(container, filename) | 320 | err := storage.DeleteBlob(container, filename) |
1865 | 321 | dumpError(err) | 321 | dumpError(err) |
1866 | 322 | } | 322 | } |
1867 | 323 | 323 | ||
1868 | 324 | func getblob(storage gwacl.StorageContext) { | 324 | func getblob(storage gwacl.StorageContext) { |
1881 | 325 | var err error | 325 | var err error |
1882 | 326 | file, err := storage.GetBlob(container, filename) | 326 | file, err := storage.GetBlob(container, filename) |
1883 | 327 | if err != nil { | 327 | if err != nil { |
1884 | 328 | dumpError(err) | 328 | dumpError(err) |
1885 | 329 | return | 329 | return |
1886 | 330 | } | 330 | } |
1887 | 331 | data, err := ioutil.ReadAll(file) | 331 | data, err := ioutil.ReadAll(file) |
1888 | 332 | if err != nil { | 332 | if err != nil { |
1889 | 333 | dumpError(err) | 333 | dumpError(err) |
1890 | 334 | return | 334 | return |
1891 | 335 | } | 335 | } |
1892 | 336 | os.Stdout.Write(data) | 336 | os.Stdout.Write(data) |
1893 | 337 | } | 337 | } |
1894 | 338 | 338 | ||
1895 | 339 | func putblob(storage gwacl.StorageContext) { | 339 | func putblob(storage gwacl.StorageContext) { |
1903 | 340 | err := storage.PutBlob(&gwacl.PutBlobRequest{ | 340 | err := storage.PutBlob(&gwacl.PutBlobRequest{ |
1904 | 341 | Container: container, | 341 | Container: container, |
1905 | 342 | BlobType: blobtype, | 342 | BlobType: blobtype, |
1906 | 343 | Filename: blobname, | 343 | Filename: blobname, |
1907 | 344 | Size: size, | 344 | Size: size, |
1908 | 345 | }) | 345 | }) |
1909 | 346 | dumpError(err) | 346 | dumpError(err) |
1910 | 347 | } | 347 | } |
1911 | 348 | 348 | ||
1912 | 349 | func putpage(storage gwacl.StorageContext) { | 349 | func putpage(storage gwacl.StorageContext) { |
1935 | 350 | var err error | 350 | var err error |
1936 | 351 | file, err := os.Open(filename) | 351 | file, err := os.Open(filename) |
1937 | 352 | if err != nil { | 352 | if err != nil { |
1938 | 353 | dumpError(err) | 353 | dumpError(err) |
1939 | 354 | return | 354 | return |
1940 | 355 | } | 355 | } |
1941 | 356 | defer file.Close() | 356 | defer file.Close() |
1942 | 357 | 357 | ||
1943 | 358 | var start, end int | 358 | var start, end int |
1944 | 359 | fmt.Sscanf(pagerange, "%d-%d", &start, &end) | 359 | fmt.Sscanf(pagerange, "%d-%d", &start, &end) |
1945 | 360 | 360 | ||
1946 | 361 | err = storage.PutPage(&gwacl.PutPageRequest{ | 361 | err = storage.PutPage(&gwacl.PutPageRequest{ |
1947 | 362 | Container: container, | 362 | Container: container, |
1948 | 363 | Filename: blobname, | 363 | Filename: blobname, |
1949 | 364 | StartRange: start, | 364 | StartRange: start, |
1950 | 365 | EndRange: end, | 365 | EndRange: end, |
1951 | 366 | Data: file, | 366 | Data: file, |
1952 | 367 | }) | 367 | }) |
1953 | 368 | if err != nil { | 368 | if err != nil { |
1954 | 369 | dumpError(err) | 369 | dumpError(err) |
1955 | 370 | return | 370 | return |
1956 | 371 | } | 371 | } |
1957 | 372 | } | 372 | } |
1958 | 373 | 373 | ||
1959 | 374 | func main() { | 374 | func main() { |
1981 | 375 | flag.Usage = Usage | 375 | flag.Usage = Usage |
1982 | 376 | var err error | 376 | var err error |
1983 | 377 | op, err := getParams() | 377 | op, err := getParams() |
1984 | 378 | if err != nil { | 378 | if err != nil { |
1985 | 379 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) | 379 | fmt.Fprintf(os.Stderr, "%s\n", err.Error()) |
1986 | 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") |
1987 | 381 | os.Exit(1) | 381 | os.Exit(1) |
1988 | 382 | } | 382 | } |
1989 | 383 | if help { | 383 | if help { |
1990 | 384 | Usage() | 384 | Usage() |
1991 | 385 | os.Exit(0) | 385 | os.Exit(0) |
1992 | 386 | } | 386 | } |
1993 | 387 | 387 | ||
1994 | 388 | storage := gwacl.StorageContext{ | 388 | storage := gwacl.StorageContext{ |
1995 | 389 | Account: account, | 389 | Account: account, |
1996 | 390 | Key: key, | 390 | Key: key, |
1997 | 391 | AzureEndpoint: gwacl.GetEndpoint(location), | 391 | AzureEndpoint: gwacl.GetEndpoint(location), |
1998 | 392 | } | 392 | } |
1999 | 393 | 393 | ||
2000 | 394 | perform := operationsByName[op].implementation | 394 | perform := operationsByName[op].implementation |
2001 | 395 | perform(storage) | 395 | perform(storage) |
2002 | 396 | } | 396 | } |
2003 | 397 | 397 | ||
2004 | === modified file 'fork/http/chunked.go' | |||
2005 | --- fork/http/chunked.go 2013-07-22 12:53:27 +0000 | |||
2006 | +++ fork/http/chunked.go 2014-12-02 00:40:19 +0000 | |||
2007 | @@ -10,11 +10,11 @@ | |||
2008 | 10 | package http | 10 | package http |
2009 | 11 | 11 | ||
2010 | 12 | import ( | 12 | import ( |
2016 | 13 | "bufio" | 13 | "bufio" |
2017 | 14 | "bytes" | 14 | "bytes" |
2018 | 15 | "errors" | 15 | "errors" |
2019 | 16 | "io" | 16 | "io" |
2020 | 17 | "strconv" | 17 | "strconv" |
2021 | 18 | ) | 18 | ) |
2022 | 19 | 19 | ||
2023 | 20 | const maxLineLength = 4096 // assumed <= bufio.defaultBufSize | 20 | const maxLineLength = 4096 // assumed <= bufio.defaultBufSize |
2024 | @@ -28,60 +28,60 @@ | |||
2025 | 28 | // newChunkedReader is not needed by normal applications. The http package | 28 | // newChunkedReader is not needed by normal applications. The http package |
2026 | 29 | // automatically decodes chunking when reading response bodies. | 29 | // automatically decodes chunking when reading response bodies. |
2027 | 30 | func newChunkedReader(r io.Reader) io.Reader { | 30 | func newChunkedReader(r io.Reader) io.Reader { |
2033 | 31 | br, ok := r.(*bufio.Reader) | 31 | br, ok := r.(*bufio.Reader) |
2034 | 32 | if !ok { | 32 | if !ok { |
2035 | 33 | br = bufio.NewReader(r) | 33 | br = bufio.NewReader(r) |
2036 | 34 | } | 34 | } |
2037 | 35 | return &chunkedReader{r: br} | 35 | return &chunkedReader{r: br} |
2038 | 36 | } | 36 | } |
2039 | 37 | 37 | ||
2040 | 38 | type chunkedReader struct { | 38 | type chunkedReader struct { |
2044 | 39 | r *bufio.Reader | 39 | r *bufio.Reader |
2045 | 40 | n uint64 // unread bytes in chunk | 40 | n uint64 // unread bytes in chunk |
2046 | 41 | err error | 41 | err error |
2047 | 42 | } | 42 | } |
2048 | 43 | 43 | ||
2049 | 44 | func (cr *chunkedReader) beginChunk() { | 44 | func (cr *chunkedReader) beginChunk() { |
2063 | 45 | // chunk-size CRLF | 45 | // chunk-size CRLF |
2064 | 46 | var line string | 46 | var line string |
2065 | 47 | line, cr.err = readLine(cr.r) | 47 | line, cr.err = readLine(cr.r) |
2066 | 48 | if cr.err != nil { | 48 | if cr.err != nil { |
2067 | 49 | return | 49 | return |
2068 | 50 | } | 50 | } |
2069 | 51 | cr.n, cr.err = strconv.ParseUint(line, 16, 64) | 51 | cr.n, cr.err = strconv.ParseUint(line, 16, 64) |
2070 | 52 | if cr.err != nil { | 52 | if cr.err != nil { |
2071 | 53 | return | 53 | return |
2072 | 54 | } | 54 | } |
2073 | 55 | if cr.n == 0 { | 55 | if cr.n == 0 { |
2074 | 56 | cr.err = io.EOF | 56 | cr.err = io.EOF |
2075 | 57 | } | 57 | } |
2076 | 58 | } | 58 | } |
2077 | 59 | 59 | ||
2078 | 60 | func (cr *chunkedReader) Read(b []uint8) (n int, err error) { | 60 | func (cr *chunkedReader) Read(b []uint8) (n int, err error) { |
2103 | 61 | if cr.err != nil { | 61 | if cr.err != nil { |
2104 | 62 | return 0, cr.err | 62 | return 0, cr.err |
2105 | 63 | } | 63 | } |
2106 | 64 | if cr.n == 0 { | 64 | if cr.n == 0 { |
2107 | 65 | cr.beginChunk() | 65 | cr.beginChunk() |
2108 | 66 | if cr.err != nil { | 66 | if cr.err != nil { |
2109 | 67 | return 0, cr.err | 67 | return 0, cr.err |
2110 | 68 | } | 68 | } |
2111 | 69 | } | 69 | } |
2112 | 70 | if uint64(len(b)) > cr.n { | 70 | if uint64(len(b)) > cr.n { |
2113 | 71 | b = b[0:cr.n] | 71 | b = b[0:cr.n] |
2114 | 72 | } | 72 | } |
2115 | 73 | n, cr.err = cr.r.Read(b) | 73 | n, cr.err = cr.r.Read(b) |
2116 | 74 | cr.n -= uint64(n) | 74 | cr.n -= uint64(n) |
2117 | 75 | if cr.n == 0 && cr.err == nil { | 75 | if cr.n == 0 && cr.err == nil { |
2118 | 76 | // end of chunk (CRLF) | 76 | // end of chunk (CRLF) |
2119 | 77 | b := make([]byte, 2) | 77 | b := make([]byte, 2) |
2120 | 78 | if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { | 78 | if _, cr.err = io.ReadFull(cr.r, b); cr.err == nil { |
2121 | 79 | if b[0] != '\r' || b[1] != '\n' { | 79 | if b[0] != '\r' || b[1] != '\n' { |
2122 | 80 | cr.err = errors.New("malformed chunked encoding") | 80 | cr.err = errors.New("malformed chunked encoding") |
2123 | 81 | } | 81 | } |
2124 | 82 | } | 82 | } |
2125 | 83 | } | 83 | } |
2126 | 84 | return n, cr.err | 84 | return n, cr.err |
2127 | 85 | } | 85 | } |
2128 | 86 | 86 | ||
2129 | 87 | // Read a line of bytes (up to \n) from b. | 87 | // Read a line of bytes (up to \n) from b. |
2130 | @@ -89,33 +89,33 @@ | |||
2131 | 89 | // The returned bytes are a pointer into storage in | 89 | // The returned bytes are a pointer into storage in |
2132 | 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. |
2133 | 91 | func readLineBytes(b *bufio.Reader) (p []byte, err error) { | 91 | func readLineBytes(b *bufio.Reader) (p []byte, err error) { |
2152 | 92 | if p, err = b.ReadSlice('\n'); err != nil { | 92 | if p, err = b.ReadSlice('\n'); err != nil { |
2153 | 93 | // We always know when EOF is coming. | 93 | // We always know when EOF is coming. |
2154 | 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. |
2155 | 95 | if err == io.EOF { | 95 | if err == io.EOF { |
2156 | 96 | err = io.ErrUnexpectedEOF | 96 | err = io.ErrUnexpectedEOF |
2157 | 97 | } else if err == bufio.ErrBufferFull { | 97 | } else if err == bufio.ErrBufferFull { |
2158 | 98 | err = ErrLineTooLong | 98 | err = ErrLineTooLong |
2159 | 99 | } | 99 | } |
2160 | 100 | return nil, err | 100 | return nil, err |
2161 | 101 | } | 101 | } |
2162 | 102 | if len(p) >= maxLineLength { | 102 | if len(p) >= maxLineLength { |
2163 | 103 | return nil, ErrLineTooLong | 103 | return nil, ErrLineTooLong |
2164 | 104 | } | 104 | } |
2165 | 105 | 105 | ||
2166 | 106 | // Chop off trailing white space. | 106 | // Chop off trailing white space. |
2167 | 107 | p = bytes.TrimRight(p, " \r\t\n") | 107 | p = bytes.TrimRight(p, " \r\t\n") |
2168 | 108 | 108 | ||
2169 | 109 | return p, nil | 109 | return p, nil |
2170 | 110 | } | 110 | } |
2171 | 111 | 111 | ||
2172 | 112 | // readLineBytes, but convert the bytes into a string. | 112 | // readLineBytes, but convert the bytes into a string. |
2173 | 113 | func readLine(b *bufio.Reader) (s string, err error) { | 113 | func readLine(b *bufio.Reader) (s string, err error) { |
2179 | 114 | p, e := readLineBytes(b) | 114 | p, e := readLineBytes(b) |
2180 | 115 | if e != nil { | 115 | if e != nil { |
2181 | 116 | return "", e | 116 | return "", e |
2182 | 117 | } | 117 | } |
2183 | 118 | return string(p), nil | 118 | return string(p), nil |
2184 | 119 | } | 119 | } |
2185 | 120 | 120 | ||
2186 | 121 | // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP | 121 | // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP |
2187 | @@ -128,13 +128,13 @@ | |||
2188 | 128 | // would result in double chunking or chunking with a Content-Length | 128 | // would result in double chunking or chunking with a Content-Length |
2189 | 129 | // length, both of which are wrong. | 129 | // length, both of which are wrong. |
2190 | 130 | func newChunkedWriter(w io.Writer) io.WriteCloser { | 130 | func newChunkedWriter(w io.Writer) io.WriteCloser { |
2192 | 131 | return &chunkedWriter{w} | 131 | return &chunkedWriter{w} |
2193 | 132 | } | 132 | } |
2194 | 133 | 133 | ||
2195 | 134 | // Writing to chunkedWriter translates to writing in HTTP chunked Transfer | 134 | // Writing to chunkedWriter translates to writing in HTTP chunked Transfer |
2196 | 135 | // Encoding wire format to the underlying Wire chunkedWriter. | 135 | // Encoding wire format to the underlying Wire chunkedWriter. |
2197 | 136 | type chunkedWriter struct { | 136 | type chunkedWriter struct { |
2199 | 137 | Wire io.Writer | 137 | Wire io.Writer |
2200 | 138 | } | 138 | } |
2201 | 139 | 139 | ||
2202 | 140 | // Write the contents of data as one chunk to Wire. | 140 | // Write the contents of data as one chunk to Wire. |
2203 | @@ -142,29 +142,29 @@ | |||
2204 | 142 | // a bug since it does not check for success of io.WriteString | 142 | // a bug since it does not check for success of io.WriteString |
2205 | 143 | func (cw *chunkedWriter) Write(data []byte) (n int, err error) { | 143 | func (cw *chunkedWriter) Write(data []byte) (n int, err error) { |
2206 | 144 | 144 | ||
2227 | 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. |
2228 | 146 | if len(data) == 0 { | 146 | if len(data) == 0 { |
2229 | 147 | return 0, nil | 147 | return 0, nil |
2230 | 148 | } | 148 | } |
2231 | 149 | 149 | ||
2232 | 150 | head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" | 150 | head := strconv.FormatInt(int64(len(data)), 16) + "\r\n" |
2233 | 151 | 151 | ||
2234 | 152 | if _, err = io.WriteString(cw.Wire, head); err != nil { | 152 | if _, err = io.WriteString(cw.Wire, head); err != nil { |
2235 | 153 | return 0, err | 153 | return 0, err |
2236 | 154 | } | 154 | } |
2237 | 155 | if n, err = cw.Wire.Write(data); err != nil { | 155 | if n, err = cw.Wire.Write(data); err != nil { |
2238 | 156 | return | 156 | return |
2239 | 157 | } | 157 | } |
2240 | 158 | if n != len(data) { | 158 | if n != len(data) { |
2241 | 159 | err = io.ErrShortWrite | 159 | err = io.ErrShortWrite |
2242 | 160 | return | 160 | return |
2243 | 161 | } | 161 | } |
2244 | 162 | _, err = io.WriteString(cw.Wire, "\r\n") | 162 | _, err = io.WriteString(cw.Wire, "\r\n") |
2245 | 163 | 163 | ||
2246 | 164 | return | 164 | return |
2247 | 165 | } | 165 | } |
2248 | 166 | 166 | ||
2249 | 167 | func (cw *chunkedWriter) Close() error { | 167 | func (cw *chunkedWriter) Close() error { |
2252 | 168 | _, err := io.WriteString(cw.Wire, "0\r\n") | 168 | _, err := io.WriteString(cw.Wire, "0\r\n") |
2253 | 169 | return err | 169 | return err |
2254 | 170 | } | 170 | } |
2255 | 171 | 171 | ||
2256 | === modified file 'fork/http/client.go' | |||
2257 | --- fork/http/client.go 2013-07-22 12:53:27 +0000 | |||
2258 | +++ fork/http/client.go 2014-12-02 00:40:19 +0000 | |||
2259 | @@ -10,12 +10,12 @@ | |||
2260 | 10 | package http | 10 | package http |
2261 | 11 | 11 | ||
2262 | 12 | import ( | 12 | import ( |
2269 | 13 | "encoding/base64" | 13 | "encoding/base64" |
2270 | 14 | "errors" | 14 | "errors" |
2271 | 15 | "fmt" | 15 | "fmt" |
2272 | 16 | "io" | 16 | "io" |
2273 | 17 | "net/url" | 17 | "net/url" |
2274 | 18 | "strings" | 18 | "strings" |
2275 | 19 | ) | 19 | ) |
2276 | 20 | 20 | ||
2277 | 21 | // A Client is an HTTP client. Its zero value (DefaultClient) is a usable client | 21 | // A Client is an HTTP client. Its zero value (DefaultClient) is a usable client |
2278 | @@ -25,26 +25,26 @@ | |||
2279 | 25 | // TCP connections), so Clients should be reused instead of created as | 25 | // TCP connections), so Clients should be reused instead of created as |
2280 | 26 | // needed. Clients are safe for concurrent use by multiple goroutines. | 26 | // needed. Clients are safe for concurrent use by multiple goroutines. |
2281 | 27 | type Client struct { | 27 | type Client struct { |
2302 | 28 | // Transport specifies the mechanism by which individual | 28 | // Transport specifies the mechanism by which individual |
2303 | 29 | // HTTP requests are made. | 29 | // HTTP requests are made. |
2304 | 30 | // If nil, DefaultTransport is used. | 30 | // If nil, DefaultTransport is used. |
2305 | 31 | Transport RoundTripper | 31 | Transport RoundTripper |
2306 | 32 | 32 | ||
2307 | 33 | // CheckRedirect specifies the policy for handling redirects. | 33 | // CheckRedirect specifies the policy for handling redirects. |
2308 | 34 | // If CheckRedirect is not nil, the client calls it before | 34 | // If CheckRedirect is not nil, the client calls it before |
2309 | 35 | // following an HTTP redirect. The arguments req and via | 35 | // following an HTTP redirect. The arguments req and via |
2310 | 36 | // are the upcoming request and the requests made already, | 36 | // are the upcoming request and the requests made already, |
2311 | 37 | // oldest first. If CheckRedirect returns an error, the client | 37 | // oldest first. If CheckRedirect returns an error, the client |
2312 | 38 | // returns that error instead of issue the Request req. | 38 | // returns that error instead of issue the Request req. |
2313 | 39 | // | 39 | // |
2314 | 40 | // If CheckRedirect is nil, the Client uses its default policy, | 40 | // If CheckRedirect is nil, the Client uses its default policy, |
2315 | 41 | // which is to stop after 10 consecutive requests. | 41 | // which is to stop after 10 consecutive requests. |
2316 | 42 | CheckRedirect func(req *Request, via []*Request) error | 42 | CheckRedirect func(req *Request, via []*Request) error |
2317 | 43 | 43 | ||
2318 | 44 | // Jar specifies the cookie jar. | 44 | // Jar specifies the cookie jar. |
2319 | 45 | // If Jar is nil, cookies are not sent in requests and ignored | 45 | // If Jar is nil, cookies are not sent in requests and ignored |
2320 | 46 | // in responses. | 46 | // in responses. |
2321 | 47 | Jar CookieJar | 47 | Jar CookieJar |
2322 | 48 | } | 48 | } |
2323 | 49 | 49 | ||
2324 | 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. |
2325 | @@ -56,20 +56,20 @@ | |||
2326 | 56 | // A RoundTripper must be safe for concurrent use by multiple | 56 | // A RoundTripper must be safe for concurrent use by multiple |
2327 | 57 | // goroutines. | 57 | // goroutines. |
2328 | 58 | type RoundTripper interface { | 58 | type RoundTripper interface { |
2343 | 59 | // RoundTrip executes a single HTTP transaction, returning | 59 | // RoundTrip executes a single HTTP transaction, returning |
2344 | 60 | // the Response for the request req. RoundTrip should not | 60 | // the Response for the request req. RoundTrip should not |
2345 | 61 | // attempt to interpret the response. In particular, | 61 | // attempt to interpret the response. In particular, |
2346 | 62 | // RoundTrip must return err == nil if it obtained a response, | 62 | // RoundTrip must return err == nil if it obtained a response, |
2347 | 63 | // regardless of the response's HTTP status code. A non-nil | 63 | // regardless of the response's HTTP status code. A non-nil |
2348 | 64 | // err should be reserved for failure to obtain a response. | 64 | // err should be reserved for failure to obtain a response. |
2349 | 65 | // Similarly, RoundTrip should not attempt to handle | 65 | // Similarly, RoundTrip should not attempt to handle |
2350 | 66 | // higher-level protocol details such as redirects, | 66 | // higher-level protocol details such as redirects, |
2351 | 67 | // authentication, or cookies. | 67 | // authentication, or cookies. |
2352 | 68 | // | 68 | // |
2353 | 69 | // RoundTrip should not modify the request, except for | 69 | // RoundTrip should not modify the request, except for |
2354 | 70 | // consuming the Body. The request's URL and Header fields | 70 | // consuming the Body. The request's URL and Header fields |
2355 | 71 | // are guaranteed to be initialized. | 71 | // are guaranteed to be initialized. |
2356 | 72 | RoundTrip(*Request) (*Response, error) | 72 | RoundTrip(*Request) (*Response, error) |
2357 | 73 | } | 73 | } |
2358 | 74 | 74 | ||
2359 | 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", |
2360 | @@ -80,8 +80,8 @@ | |||
2361 | 80 | // bufio.Reader through which we read the response, and the underlying | 80 | // bufio.Reader through which we read the response, and the underlying |
2362 | 81 | // network connection. | 81 | // network connection. |
2363 | 82 | type readClose struct { | 82 | type readClose struct { |
2366 | 83 | io.Reader | 83 | io.Reader |
2367 | 84 | io.Closer | 84 | io.Closer |
2368 | 85 | } | 85 | } |
2369 | 86 | 86 | ||
2370 | 87 | // Do sends an HTTP request and returns an HTTP response, following | 87 | // Do sends an HTTP request and returns an HTTP response, following |
2371 | @@ -96,51 +96,51 @@ | |||
2372 | 96 | // | 96 | // |
2373 | 97 | // Generally Get, Post, or PostForm will be used instead of Do. | 97 | // Generally Get, Post, or PostForm will be used instead of Do. |
2374 | 98 | func (c *Client) Do(req *Request) (resp *Response, err error) { | 98 | func (c *Client) Do(req *Request) (resp *Response, err error) { |
2379 | 99 | if req.Method == "GET" || req.Method == "HEAD" { | 99 | if req.Method == "GET" || req.Method == "HEAD" { |
2380 | 100 | return c.doFollowingRedirects(req) | 100 | return c.doFollowingRedirects(req) |
2381 | 101 | } | 101 | } |
2382 | 102 | return send(req, c.Transport) | 102 | return send(req, c.Transport) |
2383 | 103 | } | 103 | } |
2384 | 104 | 104 | ||
2385 | 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. |
2386 | 106 | func send(req *Request, t RoundTripper) (resp *Response, err error) { | 106 | func send(req *Request, t RoundTripper) (resp *Response, err error) { |
2414 | 107 | if t == nil { | 107 | if t == nil { |
2415 | 108 | t = DefaultTransport | 108 | t = DefaultTransport |
2416 | 109 | if t == nil { | 109 | if t == nil { |
2417 | 110 | err = errors.New("http: no Client.Transport or DefaultTransport") | 110 | err = errors.New("http: no Client.Transport or DefaultTransport") |
2418 | 111 | return | 111 | return |
2419 | 112 | } | 112 | } |
2420 | 113 | } | 113 | } |
2421 | 114 | 114 | ||
2422 | 115 | if req.URL == nil { | 115 | if req.URL == nil { |
2423 | 116 | return nil, errors.New("http: nil Request.URL") | 116 | return nil, errors.New("http: nil Request.URL") |
2424 | 117 | } | 117 | } |
2425 | 118 | 118 | ||
2426 | 119 | if req.RequestURI != "" { | 119 | if req.RequestURI != "" { |
2427 | 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.") |
2428 | 121 | } | 121 | } |
2429 | 122 | 122 | ||
2430 | 123 | // Most the callers of send (Get, Post, et al) don't need | 123 | // Most the callers of send (Get, Post, et al) don't need |
2431 | 124 | // Headers, leaving it uninitialized. We guarantee to the | 124 | // Headers, leaving it uninitialized. We guarantee to the |
2432 | 125 | // Transport that this has been initialized, though. | 125 | // Transport that this has been initialized, though. |
2433 | 126 | if req.Header == nil { | 126 | if req.Header == nil { |
2434 | 127 | req.Header = make(Header) | 127 | req.Header = make(Header) |
2435 | 128 | } | 128 | } |
2436 | 129 | 129 | ||
2437 | 130 | if u := req.URL.User; u != nil { | 130 | if u := req.URL.User; u != nil { |
2438 | 131 | req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String()))) | 131 | req.Header.Set("Authorization", "Basic "+base64.URLEncoding.EncodeToString([]byte(u.String()))) |
2439 | 132 | } | 132 | } |
2440 | 133 | return t.RoundTrip(req) | 133 | return t.RoundTrip(req) |
2441 | 134 | } | 134 | } |
2442 | 135 | 135 | ||
2443 | 136 | // True if the specified HTTP status code is one for which the Get utility should | 136 | // True if the specified HTTP status code is one for which the Get utility should |
2444 | 137 | // automatically redirect. | 137 | // automatically redirect. |
2445 | 138 | func shouldRedirect(statusCode int) bool { | 138 | func shouldRedirect(statusCode int) bool { |
2451 | 139 | switch statusCode { | 139 | switch statusCode { |
2452 | 140 | case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: | 140 | case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: |
2453 | 141 | return true | 141 | return true |
2454 | 142 | } | 142 | } |
2455 | 143 | return false | 143 | return false |
2456 | 144 | } | 144 | } |
2457 | 145 | 145 | ||
2458 | 146 | // Get issues a GET to the specified URL. If the response is one of the following | 146 | // Get issues a GET to the specified URL. If the response is one of the following |
2459 | @@ -155,7 +155,7 @@ | |||
2460 | 155 | // | 155 | // |
2461 | 156 | // Get is a wrapper around DefaultClient.Get. | 156 | // Get is a wrapper around DefaultClient.Get. |
2462 | 157 | func Get(url string) (r *Response, err error) { | 157 | func Get(url string) (r *Response, err error) { |
2464 | 158 | return DefaultClient.Get(url) | 158 | return DefaultClient.Get(url) |
2465 | 159 | } | 159 | } |
2466 | 160 | 160 | ||
2467 | 161 | // Get issues a GET to the specified URL. If the response is one of the | 161 | // Get issues a GET to the specified URL. If the response is one of the |
2468 | @@ -169,95 +169,95 @@ | |||
2469 | 169 | // | 169 | // |
2470 | 170 | // Caller should close r.Body when done reading from it. | 170 | // Caller should close r.Body when done reading from it. |
2471 | 171 | func (c *Client) Get(url string) (r *Response, err error) { | 171 | func (c *Client) Get(url string) (r *Response, err error) { |
2477 | 172 | req, err := NewRequest("GET", url, nil) | 172 | req, err := NewRequest("GET", url, nil) |
2478 | 173 | if err != nil { | 173 | if err != nil { |
2479 | 174 | return nil, err | 174 | return nil, err |
2480 | 175 | } | 175 | } |
2481 | 176 | return c.doFollowingRedirects(req) | 176 | return c.doFollowingRedirects(req) |
2482 | 177 | } | 177 | } |
2483 | 178 | 178 | ||
2484 | 179 | func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) { | 179 | func (c *Client) doFollowingRedirects(ireq *Request) (r *Response, err error) { |
2559 | 180 | // TODO: if/when we add cookie support, the redirected request shouldn't | 180 | // TODO: if/when we add cookie support, the redirected request shouldn't |
2560 | 181 | // necessarily supply the same cookies as the original. | 181 | // necessarily supply the same cookies as the original. |
2561 | 182 | var base *url.URL | 182 | var base *url.URL |
2562 | 183 | redirectChecker := c.CheckRedirect | 183 | redirectChecker := c.CheckRedirect |
2563 | 184 | if redirectChecker == nil { | 184 | if redirectChecker == nil { |
2564 | 185 | redirectChecker = defaultCheckRedirect | 185 | redirectChecker = defaultCheckRedirect |
2565 | 186 | } | 186 | } |
2566 | 187 | var via []*Request | 187 | var via []*Request |
2567 | 188 | 188 | ||
2568 | 189 | if ireq.URL == nil { | 189 | if ireq.URL == nil { |
2569 | 190 | return nil, errors.New("http: nil Request.URL") | 190 | return nil, errors.New("http: nil Request.URL") |
2570 | 191 | } | 191 | } |
2571 | 192 | 192 | ||
2572 | 193 | jar := c.Jar | 193 | jar := c.Jar |
2573 | 194 | if jar == nil { | 194 | if jar == nil { |
2574 | 195 | jar = blackHoleJar{} | 195 | jar = blackHoleJar{} |
2575 | 196 | } | 196 | } |
2576 | 197 | 197 | ||
2577 | 198 | req := ireq | 198 | req := ireq |
2578 | 199 | urlStr := "" // next relative or absolute URL to fetch (after first request) | 199 | urlStr := "" // next relative or absolute URL to fetch (after first request) |
2579 | 200 | for redirect := 0; ; redirect++ { | 200 | for redirect := 0; ; redirect++ { |
2580 | 201 | if redirect != 0 { | 201 | if redirect != 0 { |
2581 | 202 | req = new(Request) | 202 | req = new(Request) |
2582 | 203 | req.Method = ireq.Method | 203 | req.Method = ireq.Method |
2583 | 204 | req.Header = make(Header) | 204 | req.Header = make(Header) |
2584 | 205 | req.URL, err = base.Parse(urlStr) | 205 | req.URL, err = base.Parse(urlStr) |
2585 | 206 | if err != nil { | 206 | if err != nil { |
2586 | 207 | break | 207 | break |
2587 | 208 | } | 208 | } |
2588 | 209 | if len(via) > 0 { | 209 | if len(via) > 0 { |
2589 | 210 | // Add the Referer header. | 210 | // Add the Referer header. |
2590 | 211 | lastReq := via[len(via)-1] | 211 | lastReq := via[len(via)-1] |
2591 | 212 | if lastReq.URL.Scheme != "https" { | 212 | if lastReq.URL.Scheme != "https" { |
2592 | 213 | req.Header.Set("Referer", lastReq.URL.String()) | 213 | req.Header.Set("Referer", lastReq.URL.String()) |
2593 | 214 | } | 214 | } |
2594 | 215 | 215 | ||
2595 | 216 | err = redirectChecker(req, via) | 216 | err = redirectChecker(req, via) |
2596 | 217 | if err != nil { | 217 | if err != nil { |
2597 | 218 | break | 218 | break |
2598 | 219 | } | 219 | } |
2599 | 220 | } | 220 | } |
2600 | 221 | } | 221 | } |
2601 | 222 | 222 | ||
2602 | 223 | for _, cookie := range jar.Cookies(req.URL) { | 223 | for _, cookie := range jar.Cookies(req.URL) { |
2603 | 224 | req.AddCookie(cookie) | 224 | req.AddCookie(cookie) |
2604 | 225 | } | 225 | } |
2605 | 226 | urlStr = req.URL.String() | 226 | urlStr = req.URL.String() |
2606 | 227 | if r, err = send(req, c.Transport); err != nil { | 227 | if r, err = send(req, c.Transport); err != nil { |
2607 | 228 | break | 228 | break |
2608 | 229 | } | 229 | } |
2609 | 230 | if c := r.Cookies(); len(c) > 0 { | 230 | if c := r.Cookies(); len(c) > 0 { |
2610 | 231 | jar.SetCookies(req.URL, c) | 231 | jar.SetCookies(req.URL, c) |
2611 | 232 | } | 232 | } |
2612 | 233 | 233 | ||
2613 | 234 | if shouldRedirect(r.StatusCode) { | 234 | if shouldRedirect(r.StatusCode) { |
2614 | 235 | r.Body.Close() | 235 | r.Body.Close() |
2615 | 236 | if urlStr = r.Header.Get("Location"); urlStr == "" { | 236 | if urlStr = r.Header.Get("Location"); urlStr == "" { |
2616 | 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)) |
2617 | 238 | break | 238 | break |
2618 | 239 | } | 239 | } |
2619 | 240 | base = req.URL | 240 | base = req.URL |
2620 | 241 | via = append(via, req) | 241 | via = append(via, req) |
2621 | 242 | continue | 242 | continue |
2622 | 243 | } | 243 | } |
2623 | 244 | return | 244 | return |
2624 | 245 | } | 245 | } |
2625 | 246 | 246 | ||
2626 | 247 | method := ireq.Method | 247 | method := ireq.Method |
2627 | 248 | err = &url.Error{ | 248 | err = &url.Error{ |
2628 | 249 | Op: method[0:1] + strings.ToLower(method[1:]), | 249 | Op: method[0:1] + strings.ToLower(method[1:]), |
2629 | 250 | URL: urlStr, | 250 | URL: urlStr, |
2630 | 251 | Err: err, | 251 | Err: err, |
2631 | 252 | } | 252 | } |
2632 | 253 | return | 253 | return |
2633 | 254 | } | 254 | } |
2634 | 255 | 255 | ||
2635 | 256 | func defaultCheckRedirect(req *Request, via []*Request) error { | 256 | func defaultCheckRedirect(req *Request, via []*Request) error { |
2640 | 257 | if len(via) >= 10 { | 257 | if len(via) >= 10 { |
2641 | 258 | return errors.New("stopped after 10 redirects") | 258 | return errors.New("stopped after 10 redirects") |
2642 | 259 | } | 259 | } |
2643 | 260 | return nil | 260 | return nil |
2644 | 261 | } | 261 | } |
2645 | 262 | 262 | ||
2646 | 263 | // Post issues a POST to the specified URL. | 263 | // Post issues a POST to the specified URL. |
2647 | @@ -266,28 +266,28 @@ | |||
2648 | 266 | // | 266 | // |
2649 | 267 | // Post is a wrapper around DefaultClient.Post | 267 | // Post is a wrapper around DefaultClient.Post |
2650 | 268 | func Post(url string, bodyType string, body io.Reader) (r *Response, err error) { | 268 | func Post(url string, bodyType string, body io.Reader) (r *Response, err error) { |
2652 | 269 | return DefaultClient.Post(url, bodyType, body) | 269 | return DefaultClient.Post(url, bodyType, body) |
2653 | 270 | } | 270 | } |
2654 | 271 | 271 | ||
2655 | 272 | // Post issues a POST to the specified URL. | 272 | // Post issues a POST to the specified URL. |
2656 | 273 | // | 273 | // |
2657 | 274 | // Caller should close r.Body when done reading from it. | 274 | // Caller should close r.Body when done reading from it. |
2658 | 275 | func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) { | 275 | func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error) { |
2674 | 276 | req, err := NewRequest("POST", url, body) | 276 | req, err := NewRequest("POST", url, body) |
2675 | 277 | if err != nil { | 277 | if err != nil { |
2676 | 278 | return nil, err | 278 | return nil, err |
2677 | 279 | } | 279 | } |
2678 | 280 | req.Header.Set("Content-Type", bodyType) | 280 | req.Header.Set("Content-Type", bodyType) |
2679 | 281 | if c.Jar != nil { | 281 | if c.Jar != nil { |
2680 | 282 | for _, cookie := range c.Jar.Cookies(req.URL) { | 282 | for _, cookie := range c.Jar.Cookies(req.URL) { |
2681 | 283 | req.AddCookie(cookie) | 283 | req.AddCookie(cookie) |
2682 | 284 | } | 284 | } |
2683 | 285 | } | 285 | } |
2684 | 286 | r, err = send(req, c.Transport) | 286 | r, err = send(req, c.Transport) |
2685 | 287 | if err == nil && c.Jar != nil { | 287 | if err == nil && c.Jar != nil { |
2686 | 288 | c.Jar.SetCookies(req.URL, r.Cookies()) | 288 | c.Jar.SetCookies(req.URL, r.Cookies()) |
2687 | 289 | } | 289 | } |
2688 | 290 | return r, err | 290 | return r, err |
2689 | 291 | } | 291 | } |
2690 | 292 | 292 | ||
2691 | 293 | // PostForm issues a POST to the specified URL, | 293 | // PostForm issues a POST to the specified URL, |
2692 | @@ -297,7 +297,7 @@ | |||
2693 | 297 | // | 297 | // |
2694 | 298 | // PostForm is a wrapper around DefaultClient.PostForm | 298 | // PostForm is a wrapper around DefaultClient.PostForm |
2695 | 299 | func PostForm(url string, data url.Values) (r *Response, err error) { | 299 | func PostForm(url string, data url.Values) (r *Response, err error) { |
2697 | 300 | return DefaultClient.PostForm(url, data) | 300 | return DefaultClient.PostForm(url, data) |
2698 | 301 | } | 301 | } |
2699 | 302 | 302 | ||
2700 | 303 | // PostForm issues a POST to the specified URL, | 303 | // PostForm issues a POST to the specified URL, |
2701 | @@ -305,7 +305,7 @@ | |||
2702 | 305 | // | 305 | // |
2703 | 306 | // Caller should close r.Body when done reading from it. | 306 | // Caller should close r.Body when done reading from it. |
2704 | 307 | func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) { | 307 | func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) { |
2706 | 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())) |
2707 | 309 | } | 309 | } |
2708 | 310 | 310 | ||
2709 | 311 | // Head issues a HEAD to the specified URL. If the response is one of the | 311 | // Head issues a HEAD to the specified URL. If the response is one of the |
2710 | @@ -319,7 +319,7 @@ | |||
2711 | 319 | // | 319 | // |
2712 | 320 | // Head is a wrapper around DefaultClient.Head | 320 | // Head is a wrapper around DefaultClient.Head |
2713 | 321 | func Head(url string) (r *Response, err error) { | 321 | func Head(url string) (r *Response, err error) { |
2715 | 322 | return DefaultClient.Head(url) | 322 | return DefaultClient.Head(url) |
2716 | 323 | } | 323 | } |
2717 | 324 | 324 | ||
2718 | 325 | // Head issues a HEAD to the specified URL. If the response is one of the | 325 | // Head issues a HEAD to the specified URL. If the response is one of the |
2719 | @@ -331,9 +331,9 @@ | |||
2720 | 331 | // 303 (See Other) | 331 | // 303 (See Other) |
2721 | 332 | // 307 (Temporary Redirect) | 332 | // 307 (Temporary Redirect) |
2722 | 333 | func (c *Client) Head(url string) (r *Response, err error) { | 333 | func (c *Client) Head(url string) (r *Response, err error) { |
2728 | 334 | req, err := NewRequest("HEAD", url, nil) | 334 | req, err := NewRequest("HEAD", url, nil) |
2729 | 335 | if err != nil { | 335 | if err != nil { |
2730 | 336 | return nil, err | 336 | return nil, err |
2731 | 337 | } | 337 | } |
2732 | 338 | return c.doFollowingRedirects(req) | 338 | return c.doFollowingRedirects(req) |
2733 | 339 | } | 339 | } |
2734 | 340 | 340 | ||
2735 | === modified file 'fork/http/cookie.go' | |||
2736 | --- fork/http/cookie.go 2013-07-22 12:53:27 +0000 | |||
2737 | +++ fork/http/cookie.go 2014-12-02 00:40:19 +0000 | |||
2738 | @@ -5,11 +5,11 @@ | |||
2739 | 5 | package http | 5 | package http |
2740 | 6 | 6 | ||
2741 | 7 | import ( | 7 | import ( |
2747 | 8 | "bytes" | 8 | "bytes" |
2748 | 9 | "fmt" | 9 | "fmt" |
2749 | 10 | "strconv" | 10 | "strconv" |
2750 | 11 | "strings" | 11 | "strings" |
2751 | 12 | "time" | 12 | "time" |
2752 | 13 | ) | 13 | ) |
2753 | 14 | 14 | ||
2754 | 15 | // This implementation is done according to RFC 6265: | 15 | // This implementation is done according to RFC 6265: |
2755 | @@ -19,148 +19,148 @@ | |||
2756 | 19 | // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an | 19 | // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an |
2757 | 20 | // HTTP response or the Cookie header of an HTTP request. | 20 | // HTTP response or the Cookie header of an HTTP request. |
2758 | 21 | type Cookie struct { | 21 | type Cookie struct { |
2765 | 22 | Name string | 22 | Name string |
2766 | 23 | Value string | 23 | Value string |
2767 | 24 | Path string | 24 | Path string |
2768 | 25 | Domain string | 25 | Domain string |
2769 | 26 | Expires time.Time | 26 | Expires time.Time |
2770 | 27 | RawExpires string | 27 | RawExpires string |
2771 | 28 | 28 | ||
2780 | 29 | // MaxAge=0 means no 'Max-Age' attribute specified. | 29 | // MaxAge=0 means no 'Max-Age' attribute specified. |
2781 | 30 | // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' | 30 | // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' |
2782 | 31 | // MaxAge>0 means Max-Age attribute present and given in seconds | 31 | // MaxAge>0 means Max-Age attribute present and given in seconds |
2783 | 32 | MaxAge int | 32 | MaxAge int |
2784 | 33 | Secure bool | 33 | Secure bool |
2785 | 34 | HttpOnly bool | 34 | HttpOnly bool |
2786 | 35 | Raw string | 35 | Raw string |
2787 | 36 | Unparsed []string // Raw text of unparsed attribute-value pairs | 36 | Unparsed []string // Raw text of unparsed attribute-value pairs |
2788 | 37 | } | 37 | } |
2789 | 38 | 38 | ||
2790 | 39 | // readSetCookies parses all "Set-Cookie" values from | 39 | // readSetCookies parses all "Set-Cookie" values from |
2791 | 40 | // the header h and returns the successfully parsed Cookies. | 40 | // the header h and returns the successfully parsed Cookies. |
2792 | 41 | func readSetCookies(h Header) []*Cookie { | 41 | func readSetCookies(h Header) []*Cookie { |
2822 | 42 | cookies := []*Cookie{} | 42 | cookies := []*Cookie{} |
2823 | 43 | for _, line := range h["Set-Cookie"] { | 43 | for _, line := range h["Set-Cookie"] { |
2824 | 44 | parts := strings.Split(strings.TrimSpace(line), ";") | 44 | parts := strings.Split(strings.TrimSpace(line), ";") |
2825 | 45 | if len(parts) == 1 && parts[0] == "" { | 45 | if len(parts) == 1 && parts[0] == "" { |
2826 | 46 | continue | 46 | continue |
2827 | 47 | } | 47 | } |
2828 | 48 | parts[0] = strings.TrimSpace(parts[0]) | 48 | parts[0] = strings.TrimSpace(parts[0]) |
2829 | 49 | j := strings.Index(parts[0], "=") | 49 | j := strings.Index(parts[0], "=") |
2830 | 50 | if j < 0 { | 50 | if j < 0 { |
2831 | 51 | continue | 51 | continue |
2832 | 52 | } | 52 | } |
2833 | 53 | name, value := parts[0][:j], parts[0][j+1:] | 53 | name, value := parts[0][:j], parts[0][j+1:] |
2834 | 54 | if !isCookieNameValid(name) { | 54 | if !isCookieNameValid(name) { |
2835 | 55 | continue | 55 | continue |
2836 | 56 | } | 56 | } |
2837 | 57 | value, success := parseCookieValue(value) | 57 | value, success := parseCookieValue(value) |
2838 | 58 | if !success { | 58 | if !success { |
2839 | 59 | continue | 59 | continue |
2840 | 60 | } | 60 | } |
2841 | 61 | c := &Cookie{ | 61 | c := &Cookie{ |
2842 | 62 | Name: name, | 62 | Name: name, |
2843 | 63 | Value: value, | 63 | Value: value, |
2844 | 64 | Raw: line, | 64 | Raw: line, |
2845 | 65 | } | 65 | } |
2846 | 66 | for i := 1; i < len(parts); i++ { | 66 | for i := 1; i < len(parts); i++ { |
2847 | 67 | parts[i] = strings.TrimSpace(parts[i]) | 67 | parts[i] = strings.TrimSpace(parts[i]) |
2848 | 68 | if len(parts[i]) == 0 { | 68 | if len(parts[i]) == 0 { |
2849 | 69 | continue | 69 | continue |
2850 | 70 | } | 70 | } |
2851 | 71 | 71 | ||
2910 | 72 | attr, val := parts[i], "" | 72 | attr, val := parts[i], "" |
2911 | 73 | if j := strings.Index(attr, "="); j >= 0 { | 73 | if j := strings.Index(attr, "="); j >= 0 { |
2912 | 74 | attr, val = attr[:j], attr[j+1:] | 74 | attr, val = attr[:j], attr[j+1:] |
2913 | 75 | } | 75 | } |
2914 | 76 | lowerAttr := strings.ToLower(attr) | 76 | lowerAttr := strings.ToLower(attr) |
2915 | 77 | parseCookieValueFn := parseCookieValue | 77 | parseCookieValueFn := parseCookieValue |
2916 | 78 | if lowerAttr == "expires" { | 78 | if lowerAttr == "expires" { |
2917 | 79 | parseCookieValueFn = parseCookieExpiresValue | 79 | parseCookieValueFn = parseCookieExpiresValue |
2918 | 80 | } | 80 | } |
2919 | 81 | val, success = parseCookieValueFn(val) | 81 | val, success = parseCookieValueFn(val) |
2920 | 82 | if !success { | 82 | if !success { |
2921 | 83 | c.Unparsed = append(c.Unparsed, parts[i]) | 83 | c.Unparsed = append(c.Unparsed, parts[i]) |
2922 | 84 | continue | 84 | continue |
2923 | 85 | } | 85 | } |
2924 | 86 | switch lowerAttr { | 86 | switch lowerAttr { |
2925 | 87 | case "secure": | 87 | case "secure": |
2926 | 88 | c.Secure = true | 88 | c.Secure = true |
2927 | 89 | continue | 89 | continue |
2928 | 90 | case "httponly": | 90 | case "httponly": |
2929 | 91 | c.HttpOnly = true | 91 | c.HttpOnly = true |
2930 | 92 | continue | 92 | continue |
2931 | 93 | case "domain": | 93 | case "domain": |
2932 | 94 | c.Domain = val | 94 | c.Domain = val |
2933 | 95 | // TODO: Add domain parsing | 95 | // TODO: Add domain parsing |
2934 | 96 | continue | 96 | continue |
2935 | 97 | case "max-age": | 97 | case "max-age": |
2936 | 98 | secs, err := strconv.Atoi(val) | 98 | secs, err := strconv.Atoi(val) |
2937 | 99 | if err != nil || secs != 0 && val[0] == '0' { | 99 | if err != nil || secs != 0 && val[0] == '0' { |
2938 | 100 | break | 100 | break |
2939 | 101 | } | 101 | } |
2940 | 102 | if secs <= 0 { | 102 | if secs <= 0 { |
2941 | 103 | c.MaxAge = -1 | 103 | c.MaxAge = -1 |
2942 | 104 | } else { | 104 | } else { |
2943 | 105 | c.MaxAge = secs | 105 | c.MaxAge = secs |
2944 | 106 | } | 106 | } |
2945 | 107 | continue | 107 | continue |
2946 | 108 | case "expires": | 108 | case "expires": |
2947 | 109 | c.RawExpires = val | 109 | c.RawExpires = val |
2948 | 110 | exptime, err := time.Parse(time.RFC1123, val) | 110 | exptime, err := time.Parse(time.RFC1123, val) |
2949 | 111 | if err != nil { | 111 | if err != nil { |
2950 | 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) |
2951 | 113 | if err != nil { | 113 | if err != nil { |
2952 | 114 | c.Expires = time.Time{} | 114 | c.Expires = time.Time{} |
2953 | 115 | break | 115 | break |
2954 | 116 | } | 116 | } |
2955 | 117 | } | 117 | } |
2956 | 118 | c.Expires = exptime.UTC() | 118 | c.Expires = exptime.UTC() |
2957 | 119 | continue | 119 | continue |
2958 | 120 | case "path": | 120 | case "path": |
2959 | 121 | c.Path = val | 121 | c.Path = val |
2960 | 122 | // TODO: Add path parsing | 122 | // TODO: Add path parsing |
2961 | 123 | continue | 123 | continue |
2962 | 124 | } | 124 | } |
2963 | 125 | c.Unparsed = append(c.Unparsed, parts[i]) | 125 | c.Unparsed = append(c.Unparsed, parts[i]) |
2964 | 126 | } | 126 | } |
2965 | 127 | cookies = append(cookies, c) | 127 | cookies = append(cookies, c) |
2966 | 128 | } | 128 | } |
2967 | 129 | return cookies | 129 | return cookies |
2968 | 130 | } | 130 | } |
2969 | 131 | 131 | ||
2970 | 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. |
2971 | 133 | func SetCookie(w ResponseWriter, cookie *Cookie) { | 133 | func SetCookie(w ResponseWriter, cookie *Cookie) { |
2973 | 134 | w.Header().Add("Set-Cookie", cookie.String()) | 134 | w.Header().Add("Set-Cookie", cookie.String()) |
2974 | 135 | } | 135 | } |
2975 | 136 | 136 | ||
2976 | 137 | // String returns the serialization of the cookie for use in a Cookie | 137 | // String returns the serialization of the cookie for use in a Cookie |
2977 | 138 | // header (if only Name and Value are set) or a Set-Cookie response | 138 | // header (if only Name and Value are set) or a Set-Cookie response |
2978 | 139 | // header (if other fields are set). | 139 | // header (if other fields are set). |
2979 | 140 | func (c *Cookie) String() string { | 140 | func (c *Cookie) String() string { |
3003 | 141 | var b bytes.Buffer | 141 | var b bytes.Buffer |
3004 | 142 | fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) | 142 | fmt.Fprintf(&b, "%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) |
3005 | 143 | if len(c.Path) > 0 { | 143 | if len(c.Path) > 0 { |
3006 | 144 | fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path)) | 144 | fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path)) |
3007 | 145 | } | 145 | } |
3008 | 146 | if len(c.Domain) > 0 { | 146 | if len(c.Domain) > 0 { |
3009 | 147 | fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) | 147 | fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) |
3010 | 148 | } | 148 | } |
3011 | 149 | if c.Expires.Unix() > 0 { | 149 | if c.Expires.Unix() > 0 { |
3012 | 150 | fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) | 150 | fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) |
3013 | 151 | } | 151 | } |
3014 | 152 | if c.MaxAge > 0 { | 152 | if c.MaxAge > 0 { |
3015 | 153 | fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) | 153 | fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) |
3016 | 154 | } else if c.MaxAge < 0 { | 154 | } else if c.MaxAge < 0 { |
3017 | 155 | fmt.Fprintf(&b, "; Max-Age=0") | 155 | fmt.Fprintf(&b, "; Max-Age=0") |
3018 | 156 | } | 156 | } |
3019 | 157 | if c.HttpOnly { | 157 | if c.HttpOnly { |
3020 | 158 | fmt.Fprintf(&b, "; HttpOnly") | 158 | fmt.Fprintf(&b, "; HttpOnly") |
3021 | 159 | } | 159 | } |
3022 | 160 | if c.Secure { | 160 | if c.Secure { |
3023 | 161 | fmt.Fprintf(&b, "; Secure") | 161 | fmt.Fprintf(&b, "; Secure") |
3024 | 162 | } | 162 | } |
3025 | 163 | return b.String() | 163 | return b.String() |
3026 | 164 | } | 164 | } |
3027 | 165 | 165 | ||
3028 | 166 | // readCookies parses all "Cookie" values from the header h and | 166 | // readCookies parses all "Cookie" values from the header h and |
3029 | @@ -168,100 +168,100 @@ | |||
3030 | 168 | // | 168 | // |
3031 | 169 | // if filter isn't empty, only cookies of that name are returned | 169 | // if filter isn't empty, only cookies of that name are returned |
3032 | 170 | func readCookies(h Header, filter string) []*Cookie { | 170 | func readCookies(h Header, filter string) []*Cookie { |
3038 | 171 | cookies := []*Cookie{} | 171 | cookies := []*Cookie{} |
3039 | 172 | lines, ok := h["Cookie"] | 172 | lines, ok := h["Cookie"] |
3040 | 173 | if !ok { | 173 | if !ok { |
3041 | 174 | return cookies | 174 | return cookies |
3042 | 175 | } | 175 | } |
3043 | 176 | 176 | ||
3075 | 177 | for _, line := range lines { | 177 | for _, line := range lines { |
3076 | 178 | parts := strings.Split(strings.TrimSpace(line), ";") | 178 | parts := strings.Split(strings.TrimSpace(line), ";") |
3077 | 179 | if len(parts) == 1 && parts[0] == "" { | 179 | if len(parts) == 1 && parts[0] == "" { |
3078 | 180 | continue | 180 | continue |
3079 | 181 | } | 181 | } |
3080 | 182 | // Per-line attributes | 182 | // Per-line attributes |
3081 | 183 | parsedPairs := 0 | 183 | parsedPairs := 0 |
3082 | 184 | for i := 0; i < len(parts); i++ { | 184 | for i := 0; i < len(parts); i++ { |
3083 | 185 | parts[i] = strings.TrimSpace(parts[i]) | 185 | parts[i] = strings.TrimSpace(parts[i]) |
3084 | 186 | if len(parts[i]) == 0 { | 186 | if len(parts[i]) == 0 { |
3085 | 187 | continue | 187 | continue |
3086 | 188 | } | 188 | } |
3087 | 189 | name, val := parts[i], "" | 189 | name, val := parts[i], "" |
3088 | 190 | if j := strings.Index(name, "="); j >= 0 { | 190 | if j := strings.Index(name, "="); j >= 0 { |
3089 | 191 | name, val = name[:j], name[j+1:] | 191 | name, val = name[:j], name[j+1:] |
3090 | 192 | } | 192 | } |
3091 | 193 | if !isCookieNameValid(name) { | 193 | if !isCookieNameValid(name) { |
3092 | 194 | continue | 194 | continue |
3093 | 195 | } | 195 | } |
3094 | 196 | if filter != "" && filter != name { | 196 | if filter != "" && filter != name { |
3095 | 197 | continue | 197 | continue |
3096 | 198 | } | 198 | } |
3097 | 199 | val, success := parseCookieValue(val) | 199 | val, success := parseCookieValue(val) |
3098 | 200 | if !success { | 200 | if !success { |
3099 | 201 | continue | 201 | continue |
3100 | 202 | } | 202 | } |
3101 | 203 | cookies = append(cookies, &Cookie{Name: name, Value: val}) | 203 | cookies = append(cookies, &Cookie{Name: name, Value: val}) |
3102 | 204 | parsedPairs++ | 204 | parsedPairs++ |
3103 | 205 | } | 205 | } |
3104 | 206 | } | 206 | } |
3105 | 207 | return cookies | 207 | return cookies |
3106 | 208 | } | 208 | } |
3107 | 209 | 209 | ||
3108 | 210 | var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") | 210 | var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") |
3109 | 211 | 211 | ||
3110 | 212 | func sanitizeName(n string) string { | 212 | func sanitizeName(n string) string { |
3112 | 213 | return cookieNameSanitizer.Replace(n) | 213 | return cookieNameSanitizer.Replace(n) |
3113 | 214 | } | 214 | } |
3114 | 215 | 215 | ||
3115 | 216 | var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") | 216 | var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") |
3116 | 217 | 217 | ||
3117 | 218 | func sanitizeValue(v string) string { | 218 | func sanitizeValue(v string) string { |
3119 | 219 | return cookieValueSanitizer.Replace(v) | 219 | return cookieValueSanitizer.Replace(v) |
3120 | 220 | } | 220 | } |
3121 | 221 | 221 | ||
3122 | 222 | func unquoteCookieValue(v string) string { | 222 | func unquoteCookieValue(v string) string { |
3127 | 223 | if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { | 223 | if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { |
3128 | 224 | return v[1 : len(v)-1] | 224 | return v[1 : len(v)-1] |
3129 | 225 | } | 225 | } |
3130 | 226 | return v | 226 | return v |
3131 | 227 | } | 227 | } |
3132 | 228 | 228 | ||
3133 | 229 | func isCookieByte(c byte) bool { | 229 | func isCookieByte(c byte) bool { |
3140 | 230 | switch { | 230 | switch { |
3141 | 231 | case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, | 231 | case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, |
3142 | 232 | 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: | 232 | 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: |
3143 | 233 | return true | 233 | return true |
3144 | 234 | } | 234 | } |
3145 | 235 | return false | 235 | return false |
3146 | 236 | } | 236 | } |
3147 | 237 | 237 | ||
3148 | 238 | func isCookieExpiresByte(c byte) (ok bool) { | 238 | func isCookieExpiresByte(c byte) (ok bool) { |
3150 | 239 | return isCookieByte(c) || c == ',' || c == ' ' | 239 | return isCookieByte(c) || c == ',' || c == ' ' |
3151 | 240 | } | 240 | } |
3152 | 241 | 241 | ||
3153 | 242 | func parseCookieValue(raw string) (string, bool) { | 242 | func parseCookieValue(raw string) (string, bool) { |
3155 | 243 | return parseCookieValueUsing(raw, isCookieByte) | 243 | return parseCookieValueUsing(raw, isCookieByte) |
3156 | 244 | } | 244 | } |
3157 | 245 | 245 | ||
3158 | 246 | func parseCookieExpiresValue(raw string) (string, bool) { | 246 | func parseCookieExpiresValue(raw string) (string, bool) { |
3160 | 247 | return parseCookieValueUsing(raw, isCookieExpiresByte) | 247 | return parseCookieValueUsing(raw, isCookieExpiresByte) |
3161 | 248 | } | 248 | } |
3162 | 249 | 249 | ||
3163 | 250 | func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { | 250 | func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { |
3171 | 251 | raw = unquoteCookieValue(raw) | 251 | raw = unquoteCookieValue(raw) |
3172 | 252 | for i := 0; i < len(raw); i++ { | 252 | for i := 0; i < len(raw); i++ { |
3173 | 253 | if !validByte(raw[i]) { | 253 | if !validByte(raw[i]) { |
3174 | 254 | return "", false | 254 | return "", false |
3175 | 255 | } | 255 | } |
3176 | 256 | } | 256 | } |
3177 | 257 | return raw, true | 257 | return raw, true |
3178 | 258 | } | 258 | } |
3179 | 259 | 259 | ||
3180 | 260 | func isCookieNameValid(raw string) bool { | 260 | func isCookieNameValid(raw string) bool { |
3187 | 261 | for _, c := range raw { | 261 | for _, c := range raw { |
3188 | 262 | if !isToken(byte(c)) { | 262 | if !isToken(byte(c)) { |
3189 | 263 | return false | 263 | return false |
3190 | 264 | } | 264 | } |
3191 | 265 | } | 265 | } |
3192 | 266 | return true | 266 | return true |
3193 | 267 | } | 267 | } |
3194 | 268 | 268 | ||
3195 | === modified file 'fork/http/filetransport.go' | |||
3196 | --- fork/http/filetransport.go 2013-07-22 12:53:27 +0000 | |||
3197 | +++ fork/http/filetransport.go 2014-12-02 00:40:19 +0000 | |||
3198 | @@ -5,13 +5,13 @@ | |||
3199 | 5 | package http | 5 | package http |
3200 | 6 | 6 | ||
3201 | 7 | import ( | 7 | import ( |
3204 | 8 | "fmt" | 8 | "fmt" |
3205 | 9 | "io" | 9 | "io" |
3206 | 10 | ) | 10 | ) |
3207 | 11 | 11 | ||
3208 | 12 | // fileTransport implements RoundTripper for the 'file' protocol. | 12 | // fileTransport implements RoundTripper for the 'file' protocol. |
3209 | 13 | type fileTransport struct { | 13 | type fileTransport struct { |
3211 | 14 | fh fileHandler | 14 | fh fileHandler |
3212 | 15 | } | 15 | } |
3213 | 16 | 16 | ||
3214 | 17 | // NewFileTransport returns a new RoundTripper, serving the provided | 17 | // NewFileTransport returns a new RoundTripper, serving the provided |
3215 | @@ -28,38 +28,38 @@ | |||
3216 | 28 | // res, err := c.Get("file:///etc/passwd") | 28 | // res, err := c.Get("file:///etc/passwd") |
3217 | 29 | // ... | 29 | // ... |
3218 | 30 | func NewFileTransport(fs FileSystem) RoundTripper { | 30 | func NewFileTransport(fs FileSystem) RoundTripper { |
3220 | 31 | return fileTransport{fileHandler{fs}} | 31 | return fileTransport{fileHandler{fs}} |
3221 | 32 | } | 32 | } |
3222 | 33 | 33 | ||
3223 | 34 | func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) { | 34 | func (t fileTransport) RoundTrip(req *Request) (resp *Response, err error) { |
3236 | 35 | // We start ServeHTTP in a goroutine, which may take a long | 35 | // We start ServeHTTP in a goroutine, which may take a long |
3237 | 36 | // time if the file is large. The newPopulateResponseWriter | 36 | // time if the file is large. The newPopulateResponseWriter |
3238 | 37 | // call returns a channel which either ServeHTTP or finish() | 37 | // call returns a channel which either ServeHTTP or finish() |
3239 | 38 | // sends our *Response on, once the *Response itself has been | 38 | // sends our *Response on, once the *Response itself has been |
3240 | 39 | // populated (even if the body itself is still being | 39 | // populated (even if the body itself is still being |
3241 | 40 | // written to the res.Body, a pipe) | 40 | // written to the res.Body, a pipe) |
3242 | 41 | rw, resc := newPopulateResponseWriter() | 41 | rw, resc := newPopulateResponseWriter() |
3243 | 42 | go func() { | 42 | go func() { |
3244 | 43 | t.fh.ServeHTTP(rw, req) | 43 | t.fh.ServeHTTP(rw, req) |
3245 | 44 | rw.finish() | 44 | rw.finish() |
3246 | 45 | }() | 45 | }() |
3247 | 46 | return <-resc, nil | 46 | return <-resc, nil |
3248 | 47 | } | 47 | } |
3249 | 48 | 48 | ||
3250 | 49 | func newPopulateResponseWriter() (*populateResponse, <-chan *Response) { | 49 | func newPopulateResponseWriter() (*populateResponse, <-chan *Response) { |
3264 | 50 | pr, pw := io.Pipe() | 50 | pr, pw := io.Pipe() |
3265 | 51 | rw := &populateResponse{ | 51 | rw := &populateResponse{ |
3266 | 52 | ch: make(chan *Response), | 52 | ch: make(chan *Response), |
3267 | 53 | pw: pw, | 53 | pw: pw, |
3268 | 54 | res: &Response{ | 54 | res: &Response{ |
3269 | 55 | Proto: "HTTP/1.0", | 55 | Proto: "HTTP/1.0", |
3270 | 56 | ProtoMajor: 1, | 56 | ProtoMajor: 1, |
3271 | 57 | Header: make(Header), | 57 | Header: make(Header), |
3272 | 58 | Close: true, | 58 | Close: true, |
3273 | 59 | Body: pr, | 59 | Body: pr, |
3274 | 60 | }, | 60 | }, |
3275 | 61 | } | 61 | } |
3276 | 62 | return rw, rw.ch | 62 | return rw, rw.ch |
3277 | 63 | } | 63 | } |
3278 | 64 | 64 | ||
3279 | 65 | // populateResponse is a ResponseWriter that populates the *Response | 65 | // populateResponse is a ResponseWriter that populates the *Response |
3280 | @@ -67,57 +67,57 @@ | |||
3281 | 67 | // body. Once writes begin or finish() is called, the response is sent | 67 | // body. Once writes begin or finish() is called, the response is sent |
3282 | 68 | // on ch. | 68 | // on ch. |
3283 | 69 | type populateResponse struct { | 69 | type populateResponse struct { |
3290 | 70 | res *Response | 70 | res *Response |
3291 | 71 | ch chan *Response | 71 | ch chan *Response |
3292 | 72 | wroteHeader bool | 72 | wroteHeader bool |
3293 | 73 | hasContent bool | 73 | hasContent bool |
3294 | 74 | sentResponse bool | 74 | sentResponse bool |
3295 | 75 | pw *io.PipeWriter | 75 | pw *io.PipeWriter |
3296 | 76 | } | 76 | } |
3297 | 77 | 77 | ||
3298 | 78 | func (pr *populateResponse) finish() { | 78 | func (pr *populateResponse) finish() { |
3306 | 79 | if !pr.wroteHeader { | 79 | if !pr.wroteHeader { |
3307 | 80 | pr.WriteHeader(500) | 80 | pr.WriteHeader(500) |
3308 | 81 | } | 81 | } |
3309 | 82 | if !pr.sentResponse { | 82 | if !pr.sentResponse { |
3310 | 83 | pr.sendResponse() | 83 | pr.sendResponse() |
3311 | 84 | } | 84 | } |
3312 | 85 | pr.pw.Close() | 85 | pr.pw.Close() |
3313 | 86 | } | 86 | } |
3314 | 87 | 87 | ||
3315 | 88 | func (pr *populateResponse) sendResponse() { | 88 | func (pr *populateResponse) sendResponse() { |
3320 | 89 | if pr.sentResponse { | 89 | if pr.sentResponse { |
3321 | 90 | return | 90 | return |
3322 | 91 | } | 91 | } |
3323 | 92 | pr.sentResponse = true | 92 | pr.sentResponse = true |
3324 | 93 | 93 | ||
3329 | 94 | if pr.hasContent { | 94 | if pr.hasContent { |
3330 | 95 | pr.res.ContentLength = -1 | 95 | pr.res.ContentLength = -1 |
3331 | 96 | } | 96 | } |
3332 | 97 | pr.ch <- pr.res | 97 | pr.ch <- pr.res |
3333 | 98 | } | 98 | } |
3334 | 99 | 99 | ||
3335 | 100 | func (pr *populateResponse) Header() Header { | 100 | func (pr *populateResponse) Header() Header { |
3337 | 101 | return pr.res.Header | 101 | return pr.res.Header |
3338 | 102 | } | 102 | } |
3339 | 103 | 103 | ||
3340 | 104 | func (pr *populateResponse) WriteHeader(code int) { | 104 | func (pr *populateResponse) WriteHeader(code int) { |
3345 | 105 | if pr.wroteHeader { | 105 | if pr.wroteHeader { |
3346 | 106 | return | 106 | return |
3347 | 107 | } | 107 | } |
3348 | 108 | pr.wroteHeader = true | 108 | pr.wroteHeader = true |
3349 | 109 | 109 | ||
3352 | 110 | pr.res.StatusCode = code | 110 | pr.res.StatusCode = code |
3353 | 111 | pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code)) | 111 | pr.res.Status = fmt.Sprintf("%d %s", code, StatusText(code)) |
3354 | 112 | } | 112 | } |
3355 | 113 | 113 | ||
3356 | 114 | func (pr *populateResponse) Write(p []byte) (n int, err error) { | 114 | func (pr *populateResponse) Write(p []byte) (n int, err error) { |
3365 | 115 | if !pr.wroteHeader { | 115 | if !pr.wroteHeader { |
3366 | 116 | pr.WriteHeader(StatusOK) | 116 | pr.WriteHeader(StatusOK) |
3367 | 117 | } | 117 | } |
3368 | 118 | pr.hasContent = true | 118 | pr.hasContent = true |
3369 | 119 | if !pr.sentResponse { | 119 | if !pr.sentResponse { |
3370 | 120 | pr.sendResponse() | 120 | pr.sendResponse() |
3371 | 121 | } | 121 | } |
3372 | 122 | return pr.pw.Write(p) | 122 | return pr.pw.Write(p) |
3373 | 123 | } | 123 | } |
3374 | 124 | 124 | ||
3375 | === modified file 'fork/http/fs.go' | |||
3376 | --- fork/http/fs.go 2013-07-22 12:53:27 +0000 | |||
3377 | +++ fork/http/fs.go 2014-12-02 00:40:19 +0000 | |||
3378 | @@ -7,16 +7,16 @@ | |||
3379 | 7 | package http | 7 | package http |
3380 | 8 | 8 | ||
3381 | 9 | import ( | 9 | import ( |
3392 | 10 | "errors" | 10 | "errors" |
3393 | 11 | "fmt" | 11 | "fmt" |
3394 | 12 | "io" | 12 | "io" |
3395 | 13 | "mime" | 13 | "mime" |
3396 | 14 | "os" | 14 | "os" |
3397 | 15 | "path" | 15 | "path" |
3398 | 16 | "path/filepath" | 16 | "path/filepath" |
3399 | 17 | "strconv" | 17 | "strconv" |
3400 | 18 | "strings" | 18 | "strings" |
3401 | 19 | "time" | 19 | "time" |
3402 | 20 | ) | 20 | ) |
3403 | 21 | 21 | ||
3404 | 22 | // A Dir implements http.FileSystem using the native file | 22 | // A Dir implements http.FileSystem using the native file |
3405 | @@ -26,55 +26,55 @@ | |||
3406 | 26 | type Dir string | 26 | type Dir string |
3407 | 27 | 27 | ||
3408 | 28 | func (d Dir) Open(name string) (File, error) { | 28 | func (d Dir) Open(name string) (File, error) { |
3421 | 29 | if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 { | 29 | if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 { |
3422 | 30 | return nil, errors.New("http: invalid character in file path") | 30 | return nil, errors.New("http: invalid character in file path") |
3423 | 31 | } | 31 | } |
3424 | 32 | dir := string(d) | 32 | dir := string(d) |
3425 | 33 | if dir == "" { | 33 | if dir == "" { |
3426 | 34 | dir = "." | 34 | dir = "." |
3427 | 35 | } | 35 | } |
3428 | 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)))) |
3429 | 37 | if err != nil { | 37 | if err != nil { |
3430 | 38 | return nil, err | 38 | return nil, err |
3431 | 39 | } | 39 | } |
3432 | 40 | return f, nil | 40 | return f, nil |
3433 | 41 | } | 41 | } |
3434 | 42 | 42 | ||
3435 | 43 | // A FileSystem implements access to a collection of named files. | 43 | // A FileSystem implements access to a collection of named files. |
3436 | 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) |
3437 | 45 | // characters, regardless of host operating system convention. | 45 | // characters, regardless of host operating system convention. |
3438 | 46 | type FileSystem interface { | 46 | type FileSystem interface { |
3440 | 47 | Open(name string) (File, error) | 47 | Open(name string) (File, error) |
3441 | 48 | } | 48 | } |
3442 | 49 | 49 | ||
3443 | 50 | // A File is returned by a FileSystem's Open method and can be | 50 | // A File is returned by a FileSystem's Open method and can be |
3444 | 51 | // served by the FileServer implementation. | 51 | // served by the FileServer implementation. |
3445 | 52 | type File interface { | 52 | type File interface { |
3451 | 53 | Close() error | 53 | Close() error |
3452 | 54 | Stat() (os.FileInfo, error) | 54 | Stat() (os.FileInfo, error) |
3453 | 55 | Readdir(count int) ([]os.FileInfo, error) | 55 | Readdir(count int) ([]os.FileInfo, error) |
3454 | 56 | Read([]byte) (int, error) | 56 | Read([]byte) (int, error) |
3455 | 57 | Seek(offset int64, whence int) (int64, error) | 57 | Seek(offset int64, whence int) (int64, error) |
3456 | 58 | } | 58 | } |
3457 | 59 | 59 | ||
3458 | 60 | func dirList(w ResponseWriter, f File) { | 60 | func dirList(w ResponseWriter, f File) { |
3476 | 61 | w.Header().Set("Content-Type", "text/html; charset=utf-8") | 61 | w.Header().Set("Content-Type", "text/html; charset=utf-8") |
3477 | 62 | fmt.Fprintf(w, "<pre>\n") | 62 | fmt.Fprintf(w, "<pre>\n") |
3478 | 63 | for { | 63 | for { |
3479 | 64 | dirs, err := f.Readdir(100) | 64 | dirs, err := f.Readdir(100) |
3480 | 65 | if err != nil || len(dirs) == 0 { | 65 | if err != nil || len(dirs) == 0 { |
3481 | 66 | break | 66 | break |
3482 | 67 | } | 67 | } |
3483 | 68 | for _, d := range dirs { | 68 | for _, d := range dirs { |
3484 | 69 | name := d.Name() | 69 | name := d.Name() |
3485 | 70 | if d.IsDir() { | 70 | if d.IsDir() { |
3486 | 71 | name += "/" | 71 | name += "/" |
3487 | 72 | } | 72 | } |
3488 | 73 | // TODO htmlescape | 73 | // TODO htmlescape |
3489 | 74 | fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name) | 74 | fmt.Fprintf(w, "<a href=\"%s\">%s</a>\n", name, name) |
3490 | 75 | } | 75 | } |
3491 | 76 | } | 76 | } |
3492 | 77 | fmt.Fprintf(w, "</pre>\n") | 77 | fmt.Fprintf(w, "</pre>\n") |
3493 | 78 | } | 78 | } |
3494 | 79 | 79 | ||
3495 | 80 | // ServeContent replies to the request using the content in the | 80 | // ServeContent replies to the request using the content in the |
3496 | @@ -99,192 +99,192 @@ | |||
3497 | 99 | // | 99 | // |
3498 | 100 | // Note that *os.File implements the io.ReadSeeker interface. | 100 | // Note that *os.File implements the io.ReadSeeker interface. |
3499 | 101 | func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) { | 101 | func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker) { |
3511 | 102 | size, err := content.Seek(0, os.SEEK_END) | 102 | size, err := content.Seek(0, os.SEEK_END) |
3512 | 103 | if err != nil { | 103 | if err != nil { |
3513 | 104 | Error(w, "seeker can't seek", StatusInternalServerError) | 104 | Error(w, "seeker can't seek", StatusInternalServerError) |
3514 | 105 | return | 105 | return |
3515 | 106 | } | 106 | } |
3516 | 107 | _, err = content.Seek(0, os.SEEK_SET) | 107 | _, err = content.Seek(0, os.SEEK_SET) |
3517 | 108 | if err != nil { | 108 | if err != nil { |
3518 | 109 | Error(w, "seeker can't seek", StatusInternalServerError) | 109 | Error(w, "seeker can't seek", StatusInternalServerError) |
3519 | 110 | return | 110 | return |
3520 | 111 | } | 111 | } |
3521 | 112 | serveContent(w, req, name, modtime, size, content) | 112 | serveContent(w, req, name, modtime, size, content) |
3522 | 113 | } | 113 | } |
3523 | 114 | 114 | ||
3524 | 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) |
3525 | 116 | // if modtime.IsZero(), modtime is unknown. | 116 | // if modtime.IsZero(), modtime is unknown. |
3526 | 117 | // content must be seeked to the beginning of the file. | 117 | // content must be seeked to the beginning of the file. |
3527 | 118 | func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) { | 118 | func serveContent(w ResponseWriter, r *Request, name string, modtime time.Time, size int64, content io.ReadSeeker) { |
3590 | 119 | if checkLastModified(w, r, modtime) { | 119 | if checkLastModified(w, r, modtime) { |
3591 | 120 | return | 120 | return |
3592 | 121 | } | 121 | } |
3593 | 122 | 122 | ||
3594 | 123 | code := StatusOK | 123 | code := StatusOK |
3595 | 124 | 124 | ||
3596 | 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. |
3597 | 126 | if w.Header().Get("Content-Type") == "" { | 126 | if w.Header().Get("Content-Type") == "" { |
3598 | 127 | ctype := mime.TypeByExtension(filepath.Ext(name)) | 127 | ctype := mime.TypeByExtension(filepath.Ext(name)) |
3599 | 128 | if ctype == "" { | 128 | if ctype == "" { |
3600 | 129 | // read a chunk to decide between utf-8 text and binary | 129 | // read a chunk to decide between utf-8 text and binary |
3601 | 130 | var buf [1024]byte | 130 | var buf [1024]byte |
3602 | 131 | n, _ := io.ReadFull(content, buf[:]) | 131 | n, _ := io.ReadFull(content, buf[:]) |
3603 | 132 | b := buf[:n] | 132 | b := buf[:n] |
3604 | 133 | ctype = DetectContentType(b) | 133 | ctype = DetectContentType(b) |
3605 | 134 | _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file | 134 | _, err := content.Seek(0, os.SEEK_SET) // rewind to output whole file |
3606 | 135 | if err != nil { | 135 | if err != nil { |
3607 | 136 | Error(w, "seeker can't seek", StatusInternalServerError) | 136 | Error(w, "seeker can't seek", StatusInternalServerError) |
3608 | 137 | return | 137 | return |
3609 | 138 | } | 138 | } |
3610 | 139 | } | 139 | } |
3611 | 140 | w.Header().Set("Content-Type", ctype) | 140 | w.Header().Set("Content-Type", ctype) |
3612 | 141 | } | 141 | } |
3613 | 142 | 142 | ||
3614 | 143 | // handle Content-Range header. | 143 | // handle Content-Range header. |
3615 | 144 | // TODO(adg): handle multiple ranges | 144 | // TODO(adg): handle multiple ranges |
3616 | 145 | sendSize := size | 145 | sendSize := size |
3617 | 146 | if size >= 0 { | 146 | if size >= 0 { |
3618 | 147 | ranges, err := parseRange(r.Header.Get("Range"), size) | 147 | ranges, err := parseRange(r.Header.Get("Range"), size) |
3619 | 148 | if err == nil && len(ranges) > 1 { | 148 | if err == nil && len(ranges) > 1 { |
3620 | 149 | err = errors.New("multiple ranges not supported") | 149 | err = errors.New("multiple ranges not supported") |
3621 | 150 | } | 150 | } |
3622 | 151 | if err != nil { | 151 | if err != nil { |
3623 | 152 | Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) | 152 | Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) |
3624 | 153 | return | 153 | return |
3625 | 154 | } | 154 | } |
3626 | 155 | if len(ranges) == 1 { | 155 | if len(ranges) == 1 { |
3627 | 156 | ra := ranges[0] | 156 | ra := ranges[0] |
3628 | 157 | if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { | 157 | if _, err := content.Seek(ra.start, os.SEEK_SET); err != nil { |
3629 | 158 | Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) | 158 | Error(w, err.Error(), StatusRequestedRangeNotSatisfiable) |
3630 | 159 | return | 159 | return |
3631 | 160 | } | 160 | } |
3632 | 161 | sendSize = ra.length | 161 | sendSize = ra.length |
3633 | 162 | code = StatusPartialContent | 162 | code = StatusPartialContent |
3634 | 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)) |
3635 | 164 | } | 164 | } |
3636 | 165 | 165 | ||
3637 | 166 | w.Header().Set("Accept-Ranges", "bytes") | 166 | w.Header().Set("Accept-Ranges", "bytes") |
3638 | 167 | if w.Header().Get("Content-Encoding") == "" { | 167 | if w.Header().Get("Content-Encoding") == "" { |
3639 | 168 | w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) | 168 | w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10)) |
3640 | 169 | } | 169 | } |
3641 | 170 | } | 170 | } |
3642 | 171 | 171 | ||
3643 | 172 | w.WriteHeader(code) | 172 | w.WriteHeader(code) |
3644 | 173 | 173 | ||
3645 | 174 | if r.Method != "HEAD" { | 174 | if r.Method != "HEAD" { |
3646 | 175 | if sendSize == -1 { | 175 | if sendSize == -1 { |
3647 | 176 | io.Copy(w, content) | 176 | io.Copy(w, content) |
3648 | 177 | } else { | 177 | } else { |
3649 | 178 | io.CopyN(w, content, sendSize) | 178 | io.CopyN(w, content, sendSize) |
3650 | 179 | } | 179 | } |
3651 | 180 | } | 180 | } |
3652 | 181 | } | 181 | } |
3653 | 182 | 182 | ||
3654 | 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(). |
3655 | 184 | // return value is whether this request is now complete. | 184 | // return value is whether this request is now complete. |
3656 | 185 | func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool { | 185 | func checkLastModified(w ResponseWriter, r *Request, modtime time.Time) bool { |
3660 | 186 | if modtime.IsZero() { | 186 | if modtime.IsZero() { |
3661 | 187 | return false | 187 | return false |
3662 | 188 | } | 188 | } |
3663 | 189 | 189 | ||
3672 | 190 | // The Date-Modified header truncates sub-second precision, so | 190 | // The Date-Modified header truncates sub-second precision, so |
3673 | 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. |
3674 | 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)) { |
3675 | 193 | w.WriteHeader(StatusNotModified) | 193 | w.WriteHeader(StatusNotModified) |
3676 | 194 | return true | 194 | return true |
3677 | 195 | } | 195 | } |
3678 | 196 | w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) | 196 | w.Header().Set("Last-Modified", modtime.UTC().Format(TimeFormat)) |
3679 | 197 | return false | 197 | return false |
3680 | 198 | } | 198 | } |
3681 | 199 | 199 | ||
3682 | 200 | // name is '/'-separated, not filepath.Separator. | 200 | // name is '/'-separated, not filepath.Separator. |
3683 | 201 | func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) { | 201 | func serveFile(w ResponseWriter, r *Request, fs FileSystem, name string, redirect bool) { |
3750 | 202 | const indexPage = "/index.html" | 202 | const indexPage = "/index.html" |
3751 | 203 | 203 | ||
3752 | 204 | // redirect .../index.html to .../ | 204 | // redirect .../index.html to .../ |
3753 | 205 | // can't use Redirect() because that would make the path absolute, | 205 | // can't use Redirect() because that would make the path absolute, |
3754 | 206 | // which would be a problem running under StripPrefix | 206 | // which would be a problem running under StripPrefix |
3755 | 207 | if strings.HasSuffix(r.URL.Path, indexPage) { | 207 | if strings.HasSuffix(r.URL.Path, indexPage) { |
3756 | 208 | localRedirect(w, r, "./") | 208 | localRedirect(w, r, "./") |
3757 | 209 | return | 209 | return |
3758 | 210 | } | 210 | } |
3759 | 211 | 211 | ||
3760 | 212 | f, err := fs.Open(name) | 212 | f, err := fs.Open(name) |
3761 | 213 | if err != nil { | 213 | if err != nil { |
3762 | 214 | // TODO expose actual error? | 214 | // TODO expose actual error? |
3763 | 215 | NotFound(w, r) | 215 | NotFound(w, r) |
3764 | 216 | return | 216 | return |
3765 | 217 | } | 217 | } |
3766 | 218 | defer f.Close() | 218 | defer f.Close() |
3767 | 219 | 219 | ||
3768 | 220 | d, err1 := f.Stat() | 220 | d, err1 := f.Stat() |
3769 | 221 | if err1 != nil { | 221 | if err1 != nil { |
3770 | 222 | // TODO expose actual error? | 222 | // TODO expose actual error? |
3771 | 223 | NotFound(w, r) | 223 | NotFound(w, r) |
3772 | 224 | return | 224 | return |
3773 | 225 | } | 225 | } |
3774 | 226 | 226 | ||
3775 | 227 | if redirect { | 227 | if redirect { |
3776 | 228 | // redirect to canonical path: / at end of directory url | 228 | // redirect to canonical path: / at end of directory url |
3777 | 229 | // r.URL.Path always begins with / | 229 | // r.URL.Path always begins with / |
3778 | 230 | url := r.URL.Path | 230 | url := r.URL.Path |
3779 | 231 | if d.IsDir() { | 231 | if d.IsDir() { |
3780 | 232 | if url[len(url)-1] != '/' { | 232 | if url[len(url)-1] != '/' { |
3781 | 233 | localRedirect(w, r, path.Base(url)+"/") | 233 | localRedirect(w, r, path.Base(url)+"/") |
3782 | 234 | return | 234 | return |
3783 | 235 | } | 235 | } |
3784 | 236 | } else { | 236 | } else { |
3785 | 237 | if url[len(url)-1] == '/' { | 237 | if url[len(url)-1] == '/' { |
3786 | 238 | localRedirect(w, r, "../"+path.Base(url)) | 238 | localRedirect(w, r, "../"+path.Base(url)) |
3787 | 239 | return | 239 | return |
3788 | 240 | } | 240 | } |
3789 | 241 | } | 241 | } |
3790 | 242 | } | 242 | } |
3791 | 243 | 243 | ||
3792 | 244 | // use contents of index.html for directory, if present | 244 | // use contents of index.html for directory, if present |
3793 | 245 | if d.IsDir() { | 245 | if d.IsDir() { |
3794 | 246 | if checkLastModified(w, r, d.ModTime()) { | 246 | if checkLastModified(w, r, d.ModTime()) { |
3795 | 247 | return | 247 | return |
3796 | 248 | } | 248 | } |
3797 | 249 | index := name + indexPage | 249 | index := name + indexPage |
3798 | 250 | ff, err := fs.Open(index) | 250 | ff, err := fs.Open(index) |
3799 | 251 | if err == nil { | 251 | if err == nil { |
3800 | 252 | defer ff.Close() | 252 | defer ff.Close() |
3801 | 253 | dd, err := ff.Stat() | 253 | dd, err := ff.Stat() |
3802 | 254 | if err == nil { | 254 | if err == nil { |
3803 | 255 | name = index | 255 | name = index |
3804 | 256 | d = dd | 256 | d = dd |
3805 | 257 | f = ff | 257 | f = ff |
3806 | 258 | } | 258 | } |
3807 | 259 | } | 259 | } |
3808 | 260 | } | 260 | } |
3809 | 261 | 261 | ||
3810 | 262 | if d.IsDir() { | 262 | if d.IsDir() { |
3811 | 263 | dirList(w, f) | 263 | dirList(w, f) |
3812 | 264 | return | 264 | return |
3813 | 265 | } | 265 | } |
3814 | 266 | 266 | ||
3815 | 267 | serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f) | 267 | serveContent(w, r, d.Name(), d.ModTime(), d.Size(), f) |
3816 | 268 | } | 268 | } |
3817 | 269 | 269 | ||
3818 | 270 | // localRedirect gives a Moved Permanently response. | 270 | // localRedirect gives a Moved Permanently response. |
3819 | 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. |
3820 | 272 | func localRedirect(w ResponseWriter, r *Request, newPath string) { | 272 | func localRedirect(w ResponseWriter, r *Request, newPath string) { |
3826 | 273 | if q := r.URL.RawQuery; q != "" { | 273 | if q := r.URL.RawQuery; q != "" { |
3827 | 274 | newPath += "?" + q | 274 | newPath += "?" + q |
3828 | 275 | } | 275 | } |
3829 | 276 | w.Header().Set("Location", newPath) | 276 | w.Header().Set("Location", newPath) |
3830 | 277 | w.WriteHeader(StatusMovedPermanently) | 277 | w.WriteHeader(StatusMovedPermanently) |
3831 | 278 | } | 278 | } |
3832 | 279 | 279 | ||
3833 | 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. |
3834 | 281 | func ServeFile(w ResponseWriter, r *Request, name string) { | 281 | func ServeFile(w ResponseWriter, r *Request, name string) { |
3837 | 282 | dir, file := filepath.Split(name) | 282 | dir, file := filepath.Split(name) |
3838 | 283 | serveFile(w, r, Dir(dir), file, false) | 283 | serveFile(w, r, Dir(dir), file, false) |
3839 | 284 | } | 284 | } |
3840 | 285 | 285 | ||
3841 | 286 | type fileHandler struct { | 286 | type fileHandler struct { |
3843 | 287 | root FileSystem | 287 | root FileSystem |
3844 | 288 | } | 288 | } |
3845 | 289 | 289 | ||
3846 | 290 | // FileServer returns a handler that serves HTTP requests | 290 | // FileServer returns a handler that serves HTTP requests |
3847 | @@ -295,73 +295,73 @@ | |||
3848 | 295 | // | 295 | // |
3849 | 296 | // http.Handle("/", http.FileServer(http.Dir("/tmp"))) | 296 | // http.Handle("/", http.FileServer(http.Dir("/tmp"))) |
3850 | 297 | func FileServer(root FileSystem) Handler { | 297 | func FileServer(root FileSystem) Handler { |
3852 | 298 | return &fileHandler{root} | 298 | return &fileHandler{root} |
3853 | 299 | } | 299 | } |
3854 | 300 | 300 | ||
3855 | 301 | func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) { | 301 | func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) { |
3862 | 302 | upath := r.URL.Path | 302 | upath := r.URL.Path |
3863 | 303 | if !strings.HasPrefix(upath, "/") { | 303 | if !strings.HasPrefix(upath, "/") { |
3864 | 304 | upath = "/" + upath | 304 | upath = "/" + upath |
3865 | 305 | r.URL.Path = upath | 305 | r.URL.Path = upath |
3866 | 306 | } | 306 | } |
3867 | 307 | serveFile(w, r, f.root, path.Clean(upath), true) | 307 | serveFile(w, r, f.root, path.Clean(upath), true) |
3868 | 308 | } | 308 | } |
3869 | 309 | 309 | ||
3870 | 310 | // httpRange specifies the byte range to be sent to the client. | 310 | // httpRange specifies the byte range to be sent to the client. |
3871 | 311 | type httpRange struct { | 311 | type httpRange struct { |
3873 | 312 | start, length int64 | 312 | start, length int64 |
3874 | 313 | } | 313 | } |
3875 | 314 | 314 | ||
3876 | 315 | // parseRange parses a Range header string as per RFC 2616. | 315 | // parseRange parses a Range header string as per RFC 2616. |
3877 | 316 | func parseRange(s string, size int64) ([]httpRange, error) { | 316 | func parseRange(s string, size int64) ([]httpRange, error) { |
3928 | 317 | if s == "" { | 317 | if s == "" { |
3929 | 318 | return nil, nil // header not present | 318 | return nil, nil // header not present |
3930 | 319 | } | 319 | } |
3931 | 320 | const b = "bytes=" | 320 | const b = "bytes=" |
3932 | 321 | if !strings.HasPrefix(s, b) { | 321 | if !strings.HasPrefix(s, b) { |
3933 | 322 | return nil, errors.New("invalid range") | 322 | return nil, errors.New("invalid range") |
3934 | 323 | } | 323 | } |
3935 | 324 | var ranges []httpRange | 324 | var ranges []httpRange |
3936 | 325 | for _, ra := range strings.Split(s[len(b):], ",") { | 325 | for _, ra := range strings.Split(s[len(b):], ",") { |
3937 | 326 | i := strings.Index(ra, "-") | 326 | i := strings.Index(ra, "-") |
3938 | 327 | if i < 0 { | 327 | if i < 0 { |
3939 | 328 | return nil, errors.New("invalid range") | 328 | return nil, errors.New("invalid range") |
3940 | 329 | } | 329 | } |
3941 | 330 | start, end := ra[:i], ra[i+1:] | 330 | start, end := ra[:i], ra[i+1:] |
3942 | 331 | var r httpRange | 331 | var r httpRange |
3943 | 332 | if start == "" { | 332 | if start == "" { |
3944 | 333 | // If no start is specified, end specifies the | 333 | // If no start is specified, end specifies the |
3945 | 334 | // range start relative to the end of the file. | 334 | // range start relative to the end of the file. |
3946 | 335 | i, err := strconv.ParseInt(end, 10, 64) | 335 | i, err := strconv.ParseInt(end, 10, 64) |
3947 | 336 | if err != nil { | 336 | if err != nil { |
3948 | 337 | return nil, errors.New("invalid range") | 337 | return nil, errors.New("invalid range") |
3949 | 338 | } | 338 | } |
3950 | 339 | if i > size { | 339 | if i > size { |
3951 | 340 | i = size | 340 | i = size |
3952 | 341 | } | 341 | } |
3953 | 342 | r.start = size - i | 342 | r.start = size - i |
3954 | 343 | r.length = size - r.start | 343 | r.length = size - r.start |
3955 | 344 | } else { | 344 | } else { |
3956 | 345 | i, err := strconv.ParseInt(start, 10, 64) | 345 | i, err := strconv.ParseInt(start, 10, 64) |
3957 | 346 | if err != nil || i > size || i < 0 { | 346 | if err != nil || i > size || i < 0 { |
3958 | 347 | return nil, errors.New("invalid range") | 347 | return nil, errors.New("invalid range") |
3959 | 348 | } | 348 | } |
3960 | 349 | r.start = i | 349 | r.start = i |
3961 | 350 | if end == "" { | 350 | if end == "" { |
3962 | 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. |
3963 | 352 | r.length = size - r.start | 352 | r.length = size - r.start |
3964 | 353 | } else { | 353 | } else { |
3965 | 354 | i, err := strconv.ParseInt(end, 10, 64) | 354 | i, err := strconv.ParseInt(end, 10, 64) |
3966 | 355 | if err != nil || r.start > i { | 355 | if err != nil || r.start > i { |
3967 | 356 | return nil, errors.New("invalid range") | 356 | return nil, errors.New("invalid range") |
3968 | 357 | } | 357 | } |
3969 | 358 | if i >= size { | 358 | if i >= size { |
3970 | 359 | i = size - 1 | 359 | i = size - 1 |
3971 | 360 | } | 360 | } |
3972 | 361 | r.length = i - r.start + 1 | 361 | r.length = i - r.start + 1 |
3973 | 362 | } | 362 | } |
3974 | 363 | } | 363 | } |
3975 | 364 | ranges = append(ranges, r) | 364 | ranges = append(ranges, r) |
3976 | 365 | } | 365 | } |
3977 | 366 | return ranges, nil | 366 | return ranges, nil |
3978 | 367 | } | 367 | } |
3979 | 368 | 368 | ||
3980 | === modified file 'fork/http/header.go' | |||
3981 | --- fork/http/header.go 2013-07-22 12:53:27 +0000 | |||
3982 | +++ fork/http/header.go 2014-12-02 00:40:19 +0000 | |||
3983 | @@ -5,11 +5,11 @@ | |||
3984 | 5 | package http | 5 | package http |
3985 | 6 | 6 | ||
3986 | 7 | import ( | 7 | import ( |
3992 | 8 | "fmt" | 8 | "fmt" |
3993 | 9 | "io" | 9 | "io" |
3994 | 10 | "net/textproto" | 10 | "net/textproto" |
3995 | 11 | "sort" | 11 | "sort" |
3996 | 12 | "strings" | 12 | "strings" |
3997 | 13 | ) | 13 | ) |
3998 | 14 | 14 | ||
3999 | 15 | // A Header represents the key-value pairs in an HTTP header. | 15 | // A Header represents the key-value pairs in an HTTP header. |
4000 | @@ -18,14 +18,14 @@ | |||
4001 | 18 | // Add adds the key, value pair to the header. | 18 | // Add adds the key, value pair to the header. |
4002 | 19 | // It appends to any existing values associated with key. | 19 | // It appends to any existing values associated with key. |
4003 | 20 | func (h Header) Add(key, value string) { | 20 | func (h Header) Add(key, value string) { |
4005 | 21 | textproto.MIMEHeader(h).Add(key, value) | 21 | textproto.MIMEHeader(h).Add(key, value) |
4006 | 22 | } | 22 | } |
4007 | 23 | 23 | ||
4008 | 24 | // Set sets the header entries associated with key to | 24 | // Set sets the header entries associated with key to |
4009 | 25 | // the single element value. It replaces any existing | 25 | // the single element value. It replaces any existing |
4010 | 26 | // values associated with key. | 26 | // values associated with key. |
4011 | 27 | func (h Header) Set(key, value string) { | 27 | func (h Header) Set(key, value string) { |
4013 | 28 | textproto.MIMEHeader(h).Set(key, value) | 28 | textproto.MIMEHeader(h).Set(key, value) |
4014 | 29 | } | 29 | } |
4015 | 30 | 30 | ||
4016 | 31 | // Get gets the first value associated with the given key. | 31 | // Get gets the first value associated with the given key. |
4017 | @@ -33,17 +33,17 @@ | |||
4018 | 33 | // To access multiple values of a key, access the map directly | 33 | // To access multiple values of a key, access the map directly |
4019 | 34 | // with CanonicalHeaderKey. | 34 | // with CanonicalHeaderKey. |
4020 | 35 | func (h Header) Get(key string) string { | 35 | func (h Header) Get(key string) string { |
4022 | 36 | return textproto.MIMEHeader(h).Get(key) | 36 | return textproto.MIMEHeader(h).Get(key) |
4023 | 37 | } | 37 | } |
4024 | 38 | 38 | ||
4025 | 39 | // Del deletes the values associated with key. | 39 | // Del deletes the values associated with key. |
4026 | 40 | func (h Header) Del(key string) { | 40 | func (h Header) Del(key string) { |
4028 | 41 | textproto.MIMEHeader(h).Del(key) | 41 | textproto.MIMEHeader(h).Del(key) |
4029 | 42 | } | 42 | } |
4030 | 43 | 43 | ||
4031 | 44 | // Write writes a header in wire format. | 44 | // Write writes a header in wire format. |
4032 | 45 | func (h Header) Write(w io.Writer) error { | 45 | func (h Header) Write(w io.Writer) error { |
4034 | 46 | return h.WriteSubset(w, nil) | 46 | return h.WriteSubset(w, nil) |
4035 | 47 | } | 47 | } |
4036 | 48 | 48 | ||
4037 | 49 | var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") | 49 | var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") |
4038 | @@ -51,23 +51,23 @@ | |||
4039 | 51 | // WriteSubset writes a header in wire format. | 51 | // WriteSubset writes a header in wire format. |
4040 | 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. |
4041 | 53 | func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { | 53 | func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { |
4059 | 54 | keys := make([]string, 0, len(h)) | 54 | keys := make([]string, 0, len(h)) |
4060 | 55 | for k := range h { | 55 | for k := range h { |
4061 | 56 | if exclude == nil || !exclude[k] { | 56 | if exclude == nil || !exclude[k] { |
4062 | 57 | keys = append(keys, k) | 57 | keys = append(keys, k) |
4063 | 58 | } | 58 | } |
4064 | 59 | } | 59 | } |
4065 | 60 | sort.Strings(keys) | 60 | sort.Strings(keys) |
4066 | 61 | for _, k := range keys { | 61 | for _, k := range keys { |
4067 | 62 | for _, v := range h[k] { | 62 | for _, v := range h[k] { |
4068 | 63 | v = headerNewlineToSpace.Replace(v) | 63 | v = headerNewlineToSpace.Replace(v) |
4069 | 64 | v = strings.TrimSpace(v) | 64 | v = strings.TrimSpace(v) |
4070 | 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 { |
4071 | 66 | return err | 66 | return err |
4072 | 67 | } | 67 | } |
4073 | 68 | } | 68 | } |
4074 | 69 | } | 69 | } |
4075 | 70 | return nil | 70 | return nil |
4076 | 71 | } | 71 | } |
4077 | 72 | 72 | ||
4078 | 73 | // CanonicalHeaderKey returns the canonical format of the | 73 | // CanonicalHeaderKey returns the canonical format of the |
4079 | 74 | 74 | ||
4080 | === modified file 'fork/http/jar.go' | |||
4081 | --- fork/http/jar.go 2013-07-22 12:53:27 +0000 | |||
4082 | +++ fork/http/jar.go 2014-12-02 00:40:19 +0000 | |||
4083 | @@ -5,7 +5,7 @@ | |||
4084 | 5 | package http | 5 | package http |
4085 | 6 | 6 | ||
4086 | 7 | import ( | 7 | import ( |
4088 | 8 | "net/url" | 8 | "net/url" |
4089 | 9 | ) | 9 | ) |
4090 | 10 | 10 | ||
4091 | 11 | // A CookieJar manages storage and use of cookies in HTTP requests. | 11 | // A CookieJar manages storage and use of cookies in HTTP requests. |
4092 | @@ -13,15 +13,15 @@ | |||
4093 | 13 | // Implementations of CookieJar must be safe for concurrent use by multiple | 13 | // Implementations of CookieJar must be safe for concurrent use by multiple |
4094 | 14 | // goroutines. | 14 | // goroutines. |
4095 | 15 | type CookieJar interface { | 15 | type CookieJar interface { |
4100 | 16 | // SetCookies handles the receipt of the cookies in a reply for the | 16 | // SetCookies handles the receipt of the cookies in a reply for the |
4101 | 17 | // given URL. It may or may not choose to save the cookies, depending | 17 | // given URL. It may or may not choose to save the cookies, depending |
4102 | 18 | // on the jar's policy and implementation. | 18 | // on the jar's policy and implementation. |
4103 | 19 | SetCookies(u *url.URL, cookies []*Cookie) | 19 | SetCookies(u *url.URL, cookies []*Cookie) |
4104 | 20 | 20 | ||
4109 | 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. |
4110 | 22 | // It is up to the implementation to honor the standard cookie use | 22 | // It is up to the implementation to honor the standard cookie use |
4111 | 23 | // restrictions such as in RFC 6265. | 23 | // restrictions such as in RFC 6265. |
4112 | 24 | Cookies(u *url.URL) []*Cookie | 24 | Cookies(u *url.URL) []*Cookie |
4113 | 25 | } | 25 | } |
4114 | 26 | 26 | ||
4115 | 27 | type blackHoleJar struct{} | 27 | type blackHoleJar struct{} |
4116 | 28 | 28 | ||
4117 | === modified file 'fork/http/lex.go' | |||
4118 | --- fork/http/lex.go 2013-07-22 12:53:27 +0000 | |||
4119 | +++ fork/http/lex.go 2014-12-02 00:40:19 +0000 | |||
4120 | @@ -7,11 +7,11 @@ | |||
4121 | 7 | // This file deals with lexical matters of HTTP | 7 | // This file deals with lexical matters of HTTP |
4122 | 8 | 8 | ||
4123 | 9 | func isSeparator(c byte) bool { | 9 | func isSeparator(c byte) bool { |
4129 | 10 | switch c { | 10 | switch c { |
4130 | 11 | case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t': | 11 | case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t': |
4131 | 12 | return true | 12 | return true |
4132 | 13 | } | 13 | } |
4133 | 14 | return false | 14 | return false |
4134 | 15 | } | 15 | } |
4135 | 16 | 16 | ||
4136 | 17 | func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 } | 17 | func isCtl(c byte) bool { return (0 <= c && c <= 31) || c == 127 } |
4137 | @@ -29,108 +29,108 @@ | |||
4138 | 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) |
4139 | 30 | // parser, so we replace them with the '?' character. | 30 | // parser, so we replace them with the '?' character. |
4140 | 31 | func httpUnquotePair(b byte) byte { | 31 | func httpUnquotePair(b byte) byte { |
4165 | 32 | // skip the first byte, which should always be '\' | 32 | // skip the first byte, which should always be '\' |
4166 | 33 | switch b { | 33 | switch b { |
4167 | 34 | case 'a': | 34 | case 'a': |
4168 | 35 | return '\a' | 35 | return '\a' |
4169 | 36 | case 'b': | 36 | case 'b': |
4170 | 37 | return '\b' | 37 | return '\b' |
4171 | 38 | case 'f': | 38 | case 'f': |
4172 | 39 | return '\f' | 39 | return '\f' |
4173 | 40 | case 'n': | 40 | case 'n': |
4174 | 41 | return '\n' | 41 | return '\n' |
4175 | 42 | case 'r': | 42 | case 'r': |
4176 | 43 | return '\r' | 43 | return '\r' |
4177 | 44 | case 't': | 44 | case 't': |
4178 | 45 | return '\t' | 45 | return '\t' |
4179 | 46 | case 'v': | 46 | case 'v': |
4180 | 47 | return '\v' | 47 | return '\v' |
4181 | 48 | case '\\': | 48 | case '\\': |
4182 | 49 | return '\\' | 49 | return '\\' |
4183 | 50 | case '\'': | 50 | case '\'': |
4184 | 51 | return '\'' | 51 | return '\'' |
4185 | 52 | case '"': | 52 | case '"': |
4186 | 53 | return '"' | 53 | return '"' |
4187 | 54 | } | 54 | } |
4188 | 55 | return '?' | 55 | return '?' |
4189 | 56 | } | 56 | } |
4190 | 57 | 57 | ||
4191 | 58 | // raw must begin with a valid quoted string. Only the first quoted string is | 58 | // raw must begin with a valid quoted string. Only the first quoted string is |
4192 | 59 | // parsed and is unquoted in result. eaten is the number of bytes parsed, or -1 | 59 | // parsed and is unquoted in result. eaten is the number of bytes parsed, or -1 |
4193 | 60 | // upon failure. | 60 | // upon failure. |
4194 | 61 | func httpUnquote(raw []byte) (eaten int, result string) { | 61 | func httpUnquote(raw []byte) (eaten int, result string) { |
4226 | 62 | buf := make([]byte, len(raw)) | 62 | buf := make([]byte, len(raw)) |
4227 | 63 | if raw[0] != '"' { | 63 | if raw[0] != '"' { |
4228 | 64 | return -1, "" | 64 | return -1, "" |
4229 | 65 | } | 65 | } |
4230 | 66 | eaten = 1 | 66 | eaten = 1 |
4231 | 67 | j := 0 // # of bytes written in buf | 67 | j := 0 // # of bytes written in buf |
4232 | 68 | for i := 1; i < len(raw); i++ { | 68 | for i := 1; i < len(raw); i++ { |
4233 | 69 | switch b := raw[i]; b { | 69 | switch b := raw[i]; b { |
4234 | 70 | case '"': | 70 | case '"': |
4235 | 71 | eaten++ | 71 | eaten++ |
4236 | 72 | buf = buf[0:j] | 72 | buf = buf[0:j] |
4237 | 73 | return i + 1, string(buf) | 73 | return i + 1, string(buf) |
4238 | 74 | case '\\': | 74 | case '\\': |
4239 | 75 | if len(raw) < i+2 { | 75 | if len(raw) < i+2 { |
4240 | 76 | return -1, "" | 76 | return -1, "" |
4241 | 77 | } | 77 | } |
4242 | 78 | buf[j] = httpUnquotePair(raw[i+1]) | 78 | buf[j] = httpUnquotePair(raw[i+1]) |
4243 | 79 | eaten += 2 | 79 | eaten += 2 |
4244 | 80 | j++ | 80 | j++ |
4245 | 81 | i++ | 81 | i++ |
4246 | 82 | default: | 82 | default: |
4247 | 83 | if isQdText(b) { | 83 | if isQdText(b) { |
4248 | 84 | buf[j] = b | 84 | buf[j] = b |
4249 | 85 | } else { | 85 | } else { |
4250 | 86 | buf[j] = '?' | 86 | buf[j] = '?' |
4251 | 87 | } | 87 | } |
4252 | 88 | eaten++ | 88 | eaten++ |
4253 | 89 | j++ | 89 | j++ |
4254 | 90 | } | 90 | } |
4255 | 91 | } | 91 | } |
4256 | 92 | return -1, "" | 92 | return -1, "" |
4257 | 93 | } | 93 | } |
4258 | 94 | 94 | ||
4259 | 95 | // This is a best effort parse, so errors are not returned, instead not all of | 95 | // This is a best effort parse, so errors are not returned, instead not all of |
4260 | 96 | // the input string might be parsed. result is always non-nil. | 96 | // the input string might be parsed. result is always non-nil. |
4261 | 97 | func httpSplitFieldValue(fv string) (eaten int, result []string) { | 97 | func httpSplitFieldValue(fv string) (eaten int, result []string) { |
4300 | 98 | result = make([]string, 0, len(fv)) | 98 | result = make([]string, 0, len(fv)) |
4301 | 99 | raw := []byte(fv) | 99 | raw := []byte(fv) |
4302 | 100 | i := 0 | 100 | i := 0 |
4303 | 101 | chunk := "" | 101 | chunk := "" |
4304 | 102 | for i < len(raw) { | 102 | for i < len(raw) { |
4305 | 103 | b := raw[i] | 103 | b := raw[i] |
4306 | 104 | switch { | 104 | switch { |
4307 | 105 | case b == '"': | 105 | case b == '"': |
4308 | 106 | eaten, unq := httpUnquote(raw[i:]) | 106 | eaten, unq := httpUnquote(raw[i:]) |
4309 | 107 | if eaten < 0 { | 107 | if eaten < 0 { |
4310 | 108 | return i, result | 108 | return i, result |
4311 | 109 | } else { | 109 | } else { |
4312 | 110 | i += eaten | 110 | i += eaten |
4313 | 111 | chunk += unq | 111 | chunk += unq |
4314 | 112 | } | 112 | } |
4315 | 113 | case isSeparator(b): | 113 | case isSeparator(b): |
4316 | 114 | if chunk != "" { | 114 | if chunk != "" { |
4317 | 115 | result = result[0 : len(result)+1] | 115 | result = result[0 : len(result)+1] |
4318 | 116 | result[len(result)-1] = chunk | 116 | result[len(result)-1] = chunk |
4319 | 117 | chunk = "" | 117 | chunk = "" |
4320 | 118 | } | 118 | } |
4321 | 119 | i++ | 119 | i++ |
4322 | 120 | case isToken(b): | 120 | case isToken(b): |
4323 | 121 | chunk += string(b) | 121 | chunk += string(b) |
4324 | 122 | i++ | 122 | i++ |
4325 | 123 | case b == '\n' || b == '\r': | 123 | case b == '\n' || b == '\r': |
4326 | 124 | i++ | 124 | i++ |
4327 | 125 | default: | 125 | default: |
4328 | 126 | chunk += "?" | 126 | chunk += "?" |
4329 | 127 | i++ | 127 | i++ |
4330 | 128 | } | 128 | } |
4331 | 129 | } | 129 | } |
4332 | 130 | if chunk != "" { | 130 | if chunk != "" { |
4333 | 131 | result = result[0 : len(result)+1] | 131 | result = result[0 : len(result)+1] |
4334 | 132 | result[len(result)-1] = chunk | 132 | result[len(result)-1] = chunk |
4335 | 133 | chunk = "" | 133 | chunk = "" |
4336 | 134 | } | 134 | } |
4337 | 135 | return i, result | 135 | return i, result |
4338 | 136 | } | 136 | } |
4339 | 137 | 137 | ||
4340 | === modified file 'fork/http/request.go' | |||
4341 | --- fork/http/request.go 2013-07-22 12:53:27 +0000 | |||
4342 | +++ fork/http/request.go 2014-12-02 00:40:19 +0000 | |||
4343 | @@ -7,26 +7,26 @@ | |||
4344 | 7 | package http | 7 | package http |
4345 | 8 | 8 | ||
4346 | 9 | import ( | 9 | import ( |
4360 | 10 | "bufio" | 10 | "bufio" |
4361 | 11 | "bytes" | 11 | "bytes" |
4362 | 12 | "crypto/tls" | 12 | "crypto/tls" |
4363 | 13 | "encoding/base64" | 13 | "encoding/base64" |
4364 | 14 | "errors" | 14 | "errors" |
4365 | 15 | "fmt" | 15 | "fmt" |
4366 | 16 | "io" | 16 | "io" |
4367 | 17 | "io/ioutil" | 17 | "io/ioutil" |
4368 | 18 | "mime" | 18 | "mime" |
4369 | 19 | "mime/multipart" | 19 | "mime/multipart" |
4370 | 20 | "net/textproto" | 20 | "net/textproto" |
4371 | 21 | "net/url" | 21 | "net/url" |
4372 | 22 | "strings" | 22 | "strings" |
4373 | 23 | ) | 23 | ) |
4374 | 24 | 24 | ||
4375 | 25 | const ( | 25 | const ( |
4380 | 26 | maxValueLength = 4096 | 26 | maxValueLength = 4096 |
4381 | 27 | maxHeaderLines = 1024 | 27 | maxHeaderLines = 1024 |
4382 | 28 | chunkSize = 4 << 10 // 4 KB chunks | 28 | chunkSize = 4 << 10 // 4 KB chunks |
4383 | 29 | defaultMaxMemory = 32 << 20 // 32 MB | 29 | defaultMaxMemory = 32 << 20 // 32 MB |
4384 | 30 | ) | 30 | ) |
4385 | 31 | 31 | ||
4386 | 32 | // ErrMissingFile is returned by FormFile when the provided file field name | 32 | // ErrMissingFile is returned by FormFile when the provided file field name |
4387 | @@ -35,155 +35,155 @@ | |||
4388 | 35 | 35 | ||
4389 | 36 | // HTTP request parsing errors. | 36 | // HTTP request parsing errors. |
4390 | 37 | type ProtocolError struct { | 37 | type ProtocolError struct { |
4392 | 38 | ErrorString string | 38 | ErrorString string |
4393 | 39 | } | 39 | } |
4394 | 40 | 40 | ||
4395 | 41 | func (err *ProtocolError) Error() string { return err.ErrorString } | 41 | func (err *ProtocolError) Error() string { return err.ErrorString } |
4396 | 42 | 42 | ||
4397 | 43 | var ( | 43 | var ( |
4405 | 44 | ErrHeaderTooLong = &ProtocolError{"header too long"} | 44 | ErrHeaderTooLong = &ProtocolError{"header too long"} |
4406 | 45 | ErrShortBody = &ProtocolError{"entity body too short"} | 45 | ErrShortBody = &ProtocolError{"entity body too short"} |
4407 | 46 | ErrNotSupported = &ProtocolError{"feature not supported"} | 46 | ErrNotSupported = &ProtocolError{"feature not supported"} |
4408 | 47 | ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} | 47 | ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} |
4409 | 48 | ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} | 48 | ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} |
4410 | 49 | ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} | 49 | ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} |
4411 | 50 | ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"} | 50 | ErrMissingBoundary = &ProtocolError{"no multipart boundary param Content-Type"} |
4412 | 51 | ) | 51 | ) |
4413 | 52 | 52 | ||
4414 | 53 | type badStringError struct { | 53 | type badStringError struct { |
4417 | 54 | what string | 54 | what string |
4418 | 55 | str string | 55 | str string |
4419 | 56 | } | 56 | } |
4420 | 57 | 57 | ||
4421 | 58 | func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } | 58 | func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } |
4422 | 59 | 59 | ||
4423 | 60 | // Headers that Request.Write handles itself and should be skipped. | 60 | // Headers that Request.Write handles itself and should be skipped. |
4424 | 61 | var reqWriteExcludeHeader = map[string]bool{ | 61 | var reqWriteExcludeHeader = map[string]bool{ |
4430 | 62 | "Host": true, // not in Header map anyway | 62 | "Host": true, // not in Header map anyway |
4431 | 63 | "User-Agent": true, | 63 | "User-Agent": true, |
4432 | 64 | "Content-Length": true, | 64 | "Content-Length": true, |
4433 | 65 | "Transfer-Encoding": true, | 65 | "Transfer-Encoding": true, |
4434 | 66 | "Trailer": true, | 66 | "Trailer": true, |
4435 | 67 | } | 67 | } |
4436 | 68 | 68 | ||
4437 | 69 | // A Request represents an HTTP request received by a server | 69 | // A Request represents an HTTP request received by a server |
4438 | 70 | // or to be sent by a client. | 70 | // or to be sent by a client. |
4439 | 71 | type Request struct { | 71 | type Request struct { |
4538 | 72 | Method string // GET, POST, PUT, etc. | 72 | Method string // GET, POST, PUT, etc. |
4539 | 73 | URL *url.URL | 73 | URL *url.URL |
4540 | 74 | 74 | ||
4541 | 75 | // The protocol version for incoming requests. | 75 | // The protocol version for incoming requests. |
4542 | 76 | // Outgoing requests always use HTTP/1.1. | 76 | // Outgoing requests always use HTTP/1.1. |
4543 | 77 | Proto string // "HTTP/1.0" | 77 | Proto string // "HTTP/1.0" |
4544 | 78 | ProtoMajor int // 1 | 78 | ProtoMajor int // 1 |
4545 | 79 | ProtoMinor int // 0 | 79 | ProtoMinor int // 0 |
4546 | 80 | 80 | ||
4547 | 81 | // A header maps request lines to their values. | 81 | // A header maps request lines to their values. |
4548 | 82 | // If the header says | 82 | // If the header says |
4549 | 83 | // | 83 | // |
4550 | 84 | // accept-encoding: gzip, deflate | 84 | // accept-encoding: gzip, deflate |
4551 | 85 | // Accept-Language: en-us | 85 | // Accept-Language: en-us |
4552 | 86 | // Connection: keep-alive | 86 | // Connection: keep-alive |
4553 | 87 | // | 87 | // |
4554 | 88 | // then | 88 | // then |
4555 | 89 | // | 89 | // |
4556 | 90 | // Header = map[string][]string{ | 90 | // Header = map[string][]string{ |
4557 | 91 | // "Accept-Encoding": {"gzip, deflate"}, | 91 | // "Accept-Encoding": {"gzip, deflate"}, |
4558 | 92 | // "Accept-Language": {"en-us"}, | 92 | // "Accept-Language": {"en-us"}, |
4559 | 93 | // "Connection": {"keep-alive"}, | 93 | // "Connection": {"keep-alive"}, |
4560 | 94 | // } | 94 | // } |
4561 | 95 | // | 95 | // |
4562 | 96 | // HTTP defines that header names are case-insensitive. | 96 | // HTTP defines that header names are case-insensitive. |
4563 | 97 | // The request parser implements this by canonicalizing the | 97 | // The request parser implements this by canonicalizing the |
4564 | 98 | // name, making the first character and any characters | 98 | // name, making the first character and any characters |
4565 | 99 | // following a hyphen uppercase and the rest lowercase. | 99 | // following a hyphen uppercase and the rest lowercase. |
4566 | 100 | Header Header | 100 | Header Header |
4567 | 101 | 101 | ||
4568 | 102 | // The message body. | 102 | // The message body. |
4569 | 103 | Body io.ReadCloser | 103 | Body io.ReadCloser |
4570 | 104 | 104 | ||
4571 | 105 | // ContentLength records the length of the associated content. | 105 | // ContentLength records the length of the associated content. |
4572 | 106 | // The value -1 indicates that the length is unknown. | 106 | // The value -1 indicates that the length is unknown. |
4573 | 107 | // Values >= 0 indicate that the given number of bytes may | 107 | // Values >= 0 indicate that the given number of bytes may |
4574 | 108 | // be read from Body. | 108 | // be read from Body. |
4575 | 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. |
4576 | 110 | ContentLength int64 | 110 | ContentLength int64 |
4577 | 111 | 111 | ||
4578 | 112 | // TransferEncoding lists the transfer encodings from outermost to | 112 | // TransferEncoding lists the transfer encodings from outermost to |
4579 | 113 | // innermost. An empty list denotes the "identity" encoding. | 113 | // innermost. An empty list denotes the "identity" encoding. |
4580 | 114 | // TransferEncoding can usually be ignored; chunked encoding is | 114 | // TransferEncoding can usually be ignored; chunked encoding is |
4581 | 115 | // automatically added and removed as necessary when sending and | 115 | // automatically added and removed as necessary when sending and |
4582 | 116 | // receiving requests. | 116 | // receiving requests. |
4583 | 117 | TransferEncoding []string | 117 | TransferEncoding []string |
4584 | 118 | 118 | ||
4585 | 119 | // Close indicates whether to close the connection after | 119 | // Close indicates whether to close the connection after |
4586 | 120 | // replying to this request. | 120 | // replying to this request. |
4587 | 121 | Close bool | 121 | Close bool |
4588 | 122 | 122 | ||
4589 | 123 | // The host on which the URL is sought. | 123 | // The host on which the URL is sought. |
4590 | 124 | // Per RFC 2616, this is either the value of the Host: header | 124 | // Per RFC 2616, this is either the value of the Host: header |
4591 | 125 | // or the host name given in the URL itself. | 125 | // or the host name given in the URL itself. |
4592 | 126 | Host string | 126 | Host string |
4593 | 127 | 127 | ||
4594 | 128 | // Form contains the parsed form data, including both the URL | 128 | // Form contains the parsed form data, including both the URL |
4595 | 129 | // field's query parameters and the POST or PUT form data. | 129 | // field's query parameters and the POST or PUT form data. |
4596 | 130 | // This field is only available after ParseForm is called. | 130 | // This field is only available after ParseForm is called. |
4597 | 131 | // The HTTP client ignores Form and uses Body instead. | 131 | // The HTTP client ignores Form and uses Body instead. |
4598 | 132 | Form url.Values | 132 | Form url.Values |
4599 | 133 | 133 | ||
4600 | 134 | // MultipartForm is the parsed multipart form, including file uploads. | 134 | // MultipartForm is the parsed multipart form, including file uploads. |
4601 | 135 | // This field is only available after ParseMultipartForm is called. | 135 | // This field is only available after ParseMultipartForm is called. |
4602 | 136 | // The HTTP client ignores MultipartForm and uses Body instead. | 136 | // The HTTP client ignores MultipartForm and uses Body instead. |
4603 | 137 | MultipartForm *multipart.Form | 137 | MultipartForm *multipart.Form |
4604 | 138 | 138 | ||
4605 | 139 | // Trailer maps trailer keys to values. Like for Header, if the | 139 | // Trailer maps trailer keys to values. Like for Header, if the |
4606 | 140 | // response has multiple trailer lines with the same key, they will be | 140 | // response has multiple trailer lines with the same key, they will be |
4607 | 141 | // concatenated, delimited by commas. | 141 | // concatenated, delimited by commas. |
4608 | 142 | // For server requests, Trailer is only populated after Body has been | 142 | // For server requests, Trailer is only populated after Body has been |
4609 | 143 | // closed or fully consumed. | 143 | // closed or fully consumed. |
4610 | 144 | // Trailer support is only partially complete. | 144 | // Trailer support is only partially complete. |
4611 | 145 | Trailer Header | 145 | Trailer Header |
4612 | 146 | 146 | ||
4613 | 147 | // RemoteAddr allows HTTP servers and other software to record | 147 | // RemoteAddr allows HTTP servers and other software to record |
4614 | 148 | // the network address that sent the request, usually for | 148 | // the network address that sent the request, usually for |
4615 | 149 | // logging. This field is not filled in by ReadRequest and | 149 | // logging. This field is not filled in by ReadRequest and |
4616 | 150 | // has no defined format. The HTTP server in this package | 150 | // has no defined format. The HTTP server in this package |
4617 | 151 | // sets RemoteAddr to an "IP:port" address before invoking a | 151 | // sets RemoteAddr to an "IP:port" address before invoking a |
4618 | 152 | // handler. | 152 | // handler. |
4619 | 153 | // This field is ignored by the HTTP client. | 153 | // This field is ignored by the HTTP client. |
4620 | 154 | RemoteAddr string | 154 | RemoteAddr string |
4621 | 155 | 155 | ||
4622 | 156 | // RequestURI is the unmodified Request-URI of the | 156 | // RequestURI is the unmodified Request-URI of the |
4623 | 157 | // Request-Line (RFC 2616, Section 5.1) as sent by the client | 157 | // Request-Line (RFC 2616, Section 5.1) as sent by the client |
4624 | 158 | // to a server. Usually the URL field should be used instead. | 158 | // to a server. Usually the URL field should be used instead. |
4625 | 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. |
4626 | 160 | RequestURI string | 160 | RequestURI string |
4627 | 161 | 161 | ||
4628 | 162 | // TLS allows HTTP servers and other software to record | 162 | // TLS allows HTTP servers and other software to record |
4629 | 163 | // information about the TLS connection on which the request | 163 | // information about the TLS connection on which the request |
4630 | 164 | // was received. This field is not filled in by ReadRequest. | 164 | // was received. This field is not filled in by ReadRequest. |
4631 | 165 | // The HTTP server in this package sets the field for | 165 | // The HTTP server in this package sets the field for |
4632 | 166 | // TLS-enabled connections before invoking a handler; | 166 | // TLS-enabled connections before invoking a handler; |
4633 | 167 | // otherwise it leaves the field nil. | 167 | // otherwise it leaves the field nil. |
4634 | 168 | // This field is ignored by the HTTP client. | 168 | // This field is ignored by the HTTP client. |
4635 | 169 | TLS *tls.ConnectionState | 169 | TLS *tls.ConnectionState |
4636 | 170 | } | 170 | } |
4637 | 171 | 171 | ||
4638 | 172 | // ProtoAtLeast returns whether the HTTP protocol used | 172 | // ProtoAtLeast returns whether the HTTP protocol used |
4639 | 173 | // in the request is at least major.minor. | 173 | // in the request is at least major.minor. |
4640 | 174 | func (r *Request) ProtoAtLeast(major, minor int) bool { | 174 | func (r *Request) ProtoAtLeast(major, minor int) bool { |
4643 | 175 | return r.ProtoMajor > major || | 175 | return r.ProtoMajor > major || |
4644 | 176 | r.ProtoMajor == major && r.ProtoMinor >= minor | 176 | r.ProtoMajor == major && r.ProtoMinor >= minor |
4645 | 177 | } | 177 | } |
4646 | 178 | 178 | ||
4647 | 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. |
4648 | 180 | func (r *Request) UserAgent() string { | 180 | func (r *Request) UserAgent() string { |
4650 | 181 | return r.Header.Get("User-Agent") | 181 | return r.Header.Get("User-Agent") |
4651 | 182 | } | 182 | } |
4652 | 183 | 183 | ||
4653 | 184 | // Cookies parses and returns the HTTP cookies sent with the request. | 184 | // Cookies parses and returns the HTTP cookies sent with the request. |
4654 | 185 | func (r *Request) Cookies() []*Cookie { | 185 | func (r *Request) Cookies() []*Cookie { |
4656 | 186 | return readCookies(r.Header, "") | 186 | return readCookies(r.Header, "") |
4657 | 187 | } | 187 | } |
4658 | 188 | 188 | ||
4659 | 189 | var ErrNoCookie = errors.New("http: named cookie not present") | 189 | var ErrNoCookie = errors.New("http: named cookie not present") |
4660 | @@ -191,10 +191,10 @@ | |||
4661 | 191 | // Cookie returns the named cookie provided in the request or | 191 | // Cookie returns the named cookie provided in the request or |
4662 | 192 | // ErrNoCookie if not found. | 192 | // ErrNoCookie if not found. |
4663 | 193 | func (r *Request) Cookie(name string) (*Cookie, error) { | 193 | func (r *Request) Cookie(name string) (*Cookie, error) { |
4668 | 194 | for _, c := range readCookies(r.Header, name) { | 194 | for _, c := range readCookies(r.Header, name) { |
4669 | 195 | return c, nil | 195 | return c, nil |
4670 | 196 | } | 196 | } |
4671 | 197 | return nil, ErrNoCookie | 197 | return nil, ErrNoCookie |
4672 | 198 | } | 198 | } |
4673 | 199 | 199 | ||
4674 | 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, |
4675 | @@ -202,12 +202,12 @@ | |||
4676 | 202 | // means all cookies, if any, are written into the same line, | 202 | // means all cookies, if any, are written into the same line, |
4677 | 203 | // separated by semicolon. | 203 | // separated by semicolon. |
4678 | 204 | func (r *Request) AddCookie(c *Cookie) { | 204 | func (r *Request) AddCookie(c *Cookie) { |
4685 | 205 | s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) | 205 | s := fmt.Sprintf("%s=%s", sanitizeName(c.Name), sanitizeValue(c.Value)) |
4686 | 206 | if c := r.Header.Get("Cookie"); c != "" { | 206 | if c := r.Header.Get("Cookie"); c != "" { |
4687 | 207 | r.Header.Set("Cookie", c+"; "+s) | 207 | r.Header.Set("Cookie", c+"; "+s) |
4688 | 208 | } else { | 208 | } else { |
4689 | 209 | r.Header.Set("Cookie", s) | 209 | r.Header.Set("Cookie", s) |
4690 | 210 | } | 210 | } |
4691 | 211 | } | 211 | } |
4692 | 212 | 212 | ||
4693 | 213 | // Referer returns the referring URL, if sent in the request. | 213 | // Referer returns the referring URL, if sent in the request. |
4694 | @@ -219,15 +219,15 @@ | |||
4695 | 219 | // alternate (correct English) spelling req.Referrer() but cannot | 219 | // alternate (correct English) spelling req.Referrer() but cannot |
4696 | 220 | // diagnose programs that use Header["Referrer"]. | 220 | // diagnose programs that use Header["Referrer"]. |
4697 | 221 | func (r *Request) Referer() string { | 221 | func (r *Request) Referer() string { |
4699 | 222 | return r.Header.Get("Referer") | 222 | return r.Header.Get("Referer") |
4700 | 223 | } | 223 | } |
4701 | 224 | 224 | ||
4702 | 225 | // multipartByReader is a sentinel value. | 225 | // multipartByReader is a sentinel value. |
4703 | 226 | // Its presence in Request.MultipartForm indicates that parsing of the request | 226 | // Its presence in Request.MultipartForm indicates that parsing of the request |
4704 | 227 | // body has been handed off to a MultipartReader instead of ParseMultipartFrom. | 227 | // body has been handed off to a MultipartReader instead of ParseMultipartFrom. |
4705 | 228 | var multipartByReader = &multipart.Form{ | 228 | var multipartByReader = &multipart.Form{ |
4708 | 229 | Value: make(map[string][]string), | 229 | Value: make(map[string][]string), |
4709 | 230 | File: make(map[string][]*multipart.FileHeader), | 230 | File: make(map[string][]*multipart.FileHeader), |
4710 | 231 | } | 231 | } |
4711 | 232 | 232 | ||
4712 | 233 | // MultipartReader returns a MIME multipart reader if this is a | 233 | // MultipartReader returns a MIME multipart reader if this is a |
4713 | @@ -235,38 +235,38 @@ | |||
4714 | 235 | // Use this function instead of ParseMultipartForm to | 235 | // Use this function instead of ParseMultipartForm to |
4715 | 236 | // process the request body as a stream. | 236 | // process the request body as a stream. |
4716 | 237 | func (r *Request) MultipartReader() (*multipart.Reader, error) { | 237 | func (r *Request) MultipartReader() (*multipart.Reader, error) { |
4725 | 238 | if r.MultipartForm == multipartByReader { | 238 | if r.MultipartForm == multipartByReader { |
4726 | 239 | return nil, errors.New("http: MultipartReader called twice") | 239 | return nil, errors.New("http: MultipartReader called twice") |
4727 | 240 | } | 240 | } |
4728 | 241 | if r.MultipartForm != nil { | 241 | if r.MultipartForm != nil { |
4729 | 242 | return nil, errors.New("http: multipart handled by ParseMultipartForm") | 242 | return nil, errors.New("http: multipart handled by ParseMultipartForm") |
4730 | 243 | } | 243 | } |
4731 | 244 | r.MultipartForm = multipartByReader | 244 | r.MultipartForm = multipartByReader |
4732 | 245 | return r.multipartReader() | 245 | return r.multipartReader() |
4733 | 246 | } | 246 | } |
4734 | 247 | 247 | ||
4735 | 248 | func (r *Request) multipartReader() (*multipart.Reader, error) { | 248 | func (r *Request) multipartReader() (*multipart.Reader, error) { |
4749 | 249 | v := r.Header.Get("Content-Type") | 249 | v := r.Header.Get("Content-Type") |
4750 | 250 | if v == "" { | 250 | if v == "" { |
4751 | 251 | return nil, ErrNotMultipart | 251 | return nil, ErrNotMultipart |
4752 | 252 | } | 252 | } |
4753 | 253 | d, params, err := mime.ParseMediaType(v) | 253 | d, params, err := mime.ParseMediaType(v) |
4754 | 254 | if err != nil || d != "multipart/form-data" { | 254 | if err != nil || d != "multipart/form-data" { |
4755 | 255 | return nil, ErrNotMultipart | 255 | return nil, ErrNotMultipart |
4756 | 256 | } | 256 | } |
4757 | 257 | boundary, ok := params["boundary"] | 257 | boundary, ok := params["boundary"] |
4758 | 258 | if !ok { | 258 | if !ok { |
4759 | 259 | return nil, ErrMissingBoundary | 259 | return nil, ErrMissingBoundary |
4760 | 260 | } | 260 | } |
4761 | 261 | return multipart.NewReader(r.Body, boundary), nil | 261 | return multipart.NewReader(r.Body, boundary), nil |
4762 | 262 | } | 262 | } |
4763 | 263 | 263 | ||
4764 | 264 | // Return value if nonempty, def otherwise. | 264 | // Return value if nonempty, def otherwise. |
4765 | 265 | func valueOrDefault(value, def string) string { | 265 | func valueOrDefault(value, def string) string { |
4770 | 266 | if value != "" { | 266 | if value != "" { |
4771 | 267 | return value | 267 | return value |
4772 | 268 | } | 268 | } |
4773 | 269 | return def | 269 | return def |
4774 | 270 | } | 270 | } |
4775 | 271 | 271 | ||
4776 | 272 | const defaultUserAgent = "Go http package" | 272 | const defaultUserAgent = "Go http package" |
4777 | @@ -285,7 +285,7 @@ | |||
4778 | 285 | // hasn't been set to "identity", Write adds "Transfer-Encoding: | 285 | // hasn't been set to "identity", Write adds "Transfer-Encoding: |
4779 | 286 | // chunked" to the header. Body is closed after it is sent. | 286 | // chunked" to the header. Body is closed after it is sent. |
4780 | 287 | func (r *Request) Write(w io.Writer) error { | 287 | func (r *Request) Write(w io.Writer) error { |
4782 | 288 | return r.write(w, false, nil) | 288 | return r.write(w, false, nil) |
4783 | 289 | } | 289 | } |
4784 | 290 | 290 | ||
4785 | 291 | // WriteProxy is like Write but writes the request in the form | 291 | // WriteProxy is like Write but writes the request in the form |
4786 | @@ -295,145 +295,145 @@ | |||
4787 | 295 | // In either case, WriteProxy also writes a Host header, using | 295 | // In either case, WriteProxy also writes a Host header, using |
4788 | 296 | // either r.Host or r.URL.Host. | 296 | // either r.Host or r.URL.Host. |
4789 | 297 | func (r *Request) WriteProxy(w io.Writer) error { | 297 | func (r *Request) WriteProxy(w io.Writer) error { |
4791 | 298 | return r.write(w, true, nil) | 298 | return r.write(w, true, nil) |
4792 | 299 | } | 299 | } |
4793 | 300 | 300 | ||
4794 | 301 | // extraHeaders may be nil | 301 | // extraHeaders may be nil |
4795 | 302 | func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error { | 302 | func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error { |
4863 | 303 | host := req.Host | 303 | host := req.Host |
4864 | 304 | if host == "" { | 304 | if host == "" { |
4865 | 305 | if req.URL == nil { | 305 | if req.URL == nil { |
4866 | 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") |
4867 | 307 | } | 307 | } |
4868 | 308 | host = req.URL.Host | 308 | host = req.URL.Host |
4869 | 309 | } | 309 | } |
4870 | 310 | 310 | ||
4871 | 311 | ruri := req.URL.RequestURI() | 311 | ruri := req.URL.RequestURI() |
4872 | 312 | if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { | 312 | if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { |
4873 | 313 | ruri = req.URL.Scheme + "://" + host + ruri | 313 | ruri = req.URL.Scheme + "://" + host + ruri |
4874 | 314 | } else if req.Method == "CONNECT" && req.URL.Path == "" { | 314 | } else if req.Method == "CONNECT" && req.URL.Path == "" { |
4875 | 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. |
4876 | 316 | ruri = host | 316 | ruri = host |
4877 | 317 | } | 317 | } |
4878 | 318 | // TODO(bradfitz): escape at least newlines in ruri? | 318 | // TODO(bradfitz): escape at least newlines in ruri? |
4879 | 319 | 319 | ||
4880 | 320 | bw := bufio.NewWriter(w) | 320 | bw := bufio.NewWriter(w) |
4881 | 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) |
4882 | 322 | 322 | ||
4883 | 323 | // Header lines | 323 | // Header lines |
4884 | 324 | fmt.Fprintf(bw, "Host: %s\r\n", host) | 324 | fmt.Fprintf(bw, "Host: %s\r\n", host) |
4885 | 325 | 325 | ||
4886 | 326 | // Use the defaultUserAgent unless the Header contains one, which | 326 | // Use the defaultUserAgent unless the Header contains one, which |
4887 | 327 | // may be blank to not send the header. | 327 | // may be blank to not send the header. |
4888 | 328 | userAgent := defaultUserAgent | 328 | userAgent := defaultUserAgent |
4889 | 329 | if req.Header != nil { | 329 | if req.Header != nil { |
4890 | 330 | if ua := req.Header["User-Agent"]; len(ua) > 0 { | 330 | if ua := req.Header["User-Agent"]; len(ua) > 0 { |
4891 | 331 | userAgent = ua[0] | 331 | userAgent = ua[0] |
4892 | 332 | } | 332 | } |
4893 | 333 | } | 333 | } |
4894 | 334 | if userAgent != "" { | 334 | if userAgent != "" { |
4895 | 335 | fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent) | 335 | fmt.Fprintf(bw, "User-Agent: %s\r\n", userAgent) |
4896 | 336 | } | 336 | } |
4897 | 337 | 337 | ||
4898 | 338 | // Process Body,ContentLength,Close,Trailer | 338 | // Process Body,ContentLength,Close,Trailer |
4899 | 339 | tw, err := newTransferWriter(req) | 339 | tw, err := newTransferWriter(req) |
4900 | 340 | if err != nil { | 340 | if err != nil { |
4901 | 341 | return err | 341 | return err |
4902 | 342 | } | 342 | } |
4903 | 343 | err = tw.WriteHeader(bw) | 343 | err = tw.WriteHeader(bw) |
4904 | 344 | if err != nil { | 344 | if err != nil { |
4905 | 345 | return err | 345 | return err |
4906 | 346 | } | 346 | } |
4907 | 347 | 347 | ||
4908 | 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) |
4909 | 349 | err = req.Header.WriteSubset(bw, reqWriteExcludeHeader) | 349 | err = req.Header.WriteSubset(bw, reqWriteExcludeHeader) |
4910 | 350 | if err != nil { | 350 | if err != nil { |
4911 | 351 | return err | 351 | return err |
4912 | 352 | } | 352 | } |
4913 | 353 | 353 | ||
4914 | 354 | if extraHeaders != nil { | 354 | if extraHeaders != nil { |
4915 | 355 | err = extraHeaders.Write(bw) | 355 | err = extraHeaders.Write(bw) |
4916 | 356 | if err != nil { | 356 | if err != nil { |
4917 | 357 | return err | 357 | return err |
4918 | 358 | } | 358 | } |
4919 | 359 | } | 359 | } |
4920 | 360 | 360 | ||
4921 | 361 | io.WriteString(bw, "\r\n") | 361 | io.WriteString(bw, "\r\n") |
4922 | 362 | 362 | ||
4923 | 363 | // Write body and trailer | 363 | // Write body and trailer |
4924 | 364 | err = tw.WriteBody(bw) | 364 | err = tw.WriteBody(bw) |
4925 | 365 | if err != nil { | 365 | if err != nil { |
4926 | 366 | return err | 366 | return err |
4927 | 367 | } | 367 | } |
4928 | 368 | 368 | ||
4929 | 369 | return bw.Flush() | 369 | return bw.Flush() |
4930 | 370 | } | 370 | } |
4931 | 371 | 371 | ||
4932 | 372 | // Convert decimal at s[i:len(s)] to integer, | 372 | // Convert decimal at s[i:len(s)] to integer, |
4933 | 373 | // returning value, string position where the digits stopped, | 373 | // returning value, string position where the digits stopped, |
4934 | 374 | // and whether there was a valid number (digits, not too big). | 374 | // and whether there was a valid number (digits, not too big). |
4935 | 375 | func atoi(s string, i int) (n, i1 int, ok bool) { | 375 | func atoi(s string, i int) (n, i1 int, ok bool) { |
4948 | 376 | const Big = 1000000 | 376 | const Big = 1000000 |
4949 | 377 | if i >= len(s) || s[i] < '0' || s[i] > '9' { | 377 | if i >= len(s) || s[i] < '0' || s[i] > '9' { |
4950 | 378 | return 0, 0, false | 378 | return 0, 0, false |
4951 | 379 | } | 379 | } |
4952 | 380 | n = 0 | 380 | n = 0 |
4953 | 381 | for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { | 381 | for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { |
4954 | 382 | n = n*10 + int(s[i]-'0') | 382 | n = n*10 + int(s[i]-'0') |
4955 | 383 | if n > Big { | 383 | if n > Big { |
4956 | 384 | return 0, 0, false | 384 | return 0, 0, false |
4957 | 385 | } | 385 | } |
4958 | 386 | } | 386 | } |
4959 | 387 | return n, i, true | 387 | return n, i, true |
4960 | 388 | } | 388 | } |
4961 | 389 | 389 | ||
4962 | 390 | // ParseHTTPVersion parses a HTTP version string. | 390 | // ParseHTTPVersion parses a HTTP version string. |
4963 | 391 | // "HTTP/1.0" returns (1, 0, true). | 391 | // "HTTP/1.0" returns (1, 0, true). |
4964 | 392 | func ParseHTTPVersion(vers string) (major, minor int, ok bool) { | 392 | func ParseHTTPVersion(vers string) (major, minor int, ok bool) { |
4977 | 393 | if len(vers) < 5 || vers[0:5] != "HTTP/" { | 393 | if len(vers) < 5 || vers[0:5] != "HTTP/" { |
4978 | 394 | return 0, 0, false | 394 | return 0, 0, false |
4979 | 395 | } | 395 | } |
4980 | 396 | major, i, ok := atoi(vers, 5) | 396 | major, i, ok := atoi(vers, 5) |
4981 | 397 | if !ok || i >= len(vers) || vers[i] != '.' { | 397 | if !ok || i >= len(vers) || vers[i] != '.' { |
4982 | 398 | return 0, 0, false | 398 | return 0, 0, false |
4983 | 399 | } | 399 | } |
4984 | 400 | minor, i, ok = atoi(vers, i+1) | 400 | minor, i, ok = atoi(vers, i+1) |
4985 | 401 | if !ok || i != len(vers) { | 401 | if !ok || i != len(vers) { |
4986 | 402 | return 0, 0, false | 402 | return 0, 0, false |
4987 | 403 | } | 403 | } |
4988 | 404 | return major, minor, true | 404 | return major, minor, true |
4989 | 405 | } | 405 | } |
4990 | 406 | 406 | ||
4991 | 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. |
4992 | 408 | func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { | 408 | func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { |
4993 | 409 | u, err := url.Parse(urlStr) | ||
4994 | 410 | if err != nil { | ||
4995 | 411 | return nil, err | ||
4996 | 412 | } | ||
4997 | 413 | rc, ok := body.(io.ReadCloser) | ||
4998 | 414 | if !ok && body != nil { | ||
4999 | 415 | rc = ioutil.NopCloser(body) | ||
5000 | 416 | } |
The diff has been truncated for viewing.
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 /code.launchpad .net/~wallyworl d/gwacl/ ensure- all-roles- have-costs/ +merge/ 243346
>
>
> --
>
> https:/
> Your team GWACL Hackers is subscribed to branch lp:gwacl.
>